I have created
new view, which is added to activity.xml
class, which operate this view -> in this class I create overlay for entire screen and cut in one place rounder place, but I do not why it does not want to be cut further than a fragment creates own elements (look at the screen)
class TutorialView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
drawTransparentLayout(canvas)
}
fun drawTransparentLayout(canvas: Canvas?) {
from xml it is color -> <color name="tutorial_background">#BF000000</color>
val mPath = Path()
mPath.fillType = Path.FillType.WINDING
mPath.addRect(0F, 0F, widthView, heightView, Path.Direction.CW)
mPath.addRoundRect(20F, 130F, widthMenu, heightMenu, 50F, 50F, Path.Direction.CCW)
val mPaintHole = Paint(Paint.ANTI_ALIAS_FLAG)
mPaintHole.style = Paint.Style.FILL
mPaintHole.color = backGroundTransparentBlack
if (canvas != null)
canvas!!.drawPath(mPath, mPaintHole)
}
In screen you can see it only fills with half of the rectangle, because tabs is fragment element. A little another the wait space it starts.
Related
How can I create pie chart using onDraw override method in Kotlin on Android? I've tested multiple method Paint.apply parameters but didnt achieve pie chart. My code so far looks like this:
class ChartView(context: Context, attrs: AttributeSet) : View(context, attrs){
val paint = Paint().apply {
color = Color.GREEN
style = Paint.Style.FILL
}
val text = "My Component"
val bounds = Rect().also {
paint.getTextBounds(text, 0, text.length, it)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawCircle(344F, 890F, 160.0F,paint)
}
}
This resulted in regular green circle. Target effect would be:
https://ibb.co/Vw9Lzc0
Here is the implementation of the Default time bar where I have customized to add segments
XML
<com.sampleapp.androidapp.kotlin.util.exoplayer.SampleAppDefaultTimeBar
android:id="#id/exo_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:bar_height="2dp"
app:buffered_color="#color/gb_seek_bar_buffer"
app:layout_constraintStart_toStartOf="parent"
app:played_color="#color/gb_seek_bar_played"
app:scrubber_color="#color/gb_seek_bar_played"
app:unplayed_color="#color/gb_seek_bar_unplayed" />
Code
class SampleAppDefaultTimeBar : DefaultTimeBar {
companion object {
const val DIVIDER_WIDTH = 5F
const val PROGRESS_LEFT_RIGHT_PADDING = 32F
}
// List of points for creating sections
var indicatorPositions: List<Float> = arrayListOf()
var enableSegmentsForPlayer: Boolean = false
//var indicatorPositions: List<Float> = listOf(0.13F, 0.34F, 0.57F, 0.85F, 0.92F)
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context,
attrs, defStyle)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private val paintIndicator = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
color = ContextCompat.getColor(context, R.color.black)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawProgressDivider(canvas)
}
private fun drawProgressDivider(canvas: Canvas) {
if(enableSegmentsForPlayer){
// Only if the segments are enabled - Draw the segments
indicatorPositions.forEach {
val barPositionCenter = it * width()
val barPositionLeft = barPositionCenter - DIVIDER_WIDTH
val barPositionRight = barPositionCenter + DIVIDER_WIDTH
drawOnCanvas(canvas, paintIndicator, barPositionLeft, barPositionRight)
}
}
}
private fun width(): Float {
return measuredWidth.toFloat()
}
private fun drawOnCanvas(canvas: Canvas, paint:Paint, progressLeft: Float, progressRight: Float) {
//val barTop = (measuredHeight - barHeight) / 2
//val barBottom = (measuredHeight + barHeight) / 2
val progressTop = height / 1.8 - minimumHeight / 2
val progressBottom = progressTop / 1.3 + minimumHeight / 2
val barRect = RectF(progressLeft, progressTop.toFloat(),
progressRight, progressBottom.toFloat())
canvas.drawRoundRect(barRect, 50F, 50F, paint)
}
}
Output
As seen in the image above there is padding at the beginning and end of
the bar if the points are found to be at the very begining.
It's the space for the thumb
How to remove that Padding
I am extending Chip class to perform some drawing over it for my lib , my use case is more complex but for simplicity let's say i am just drawing a diagonal line
my code
class MyChip (context: Context,attributeSet: AttributeSet) : Chip(context,attributeSet){
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//just want to draw a diagonal line
canvas.drawLine(0f,0f,width/1f,height/1f,paint)
}
}
xml
<com.abhinav.chouhan.loaderchipdemo.MyChip
android:layout_width="200dp"
android:layout_height="50dp"
android:textAlignment="center"
android:text="SOME TEXT"/>
when i don't have attribute android:textAlignment="center" everything works fine , but with that attribute we can not draw anything on chip.
I tried everything but couldn't figure out why is it happening.
Please Help
The Chip widget adheres strongly to the Material Design guidelines and is not (IMO) amenable to change. I suggest that you look at other widgets - maybe a MaterialButton to see if something else may suit your needs.
The attribute you reference, textAlignment, is available in TextView which is a class that Chip is based on. Chips expects wrap_content and also expect for text to be aligned to the start. Somewhere in the mix, your over-draw is lost. I doubt that something like that has ever been tested as it does not adhere to the Material guidelines.
However, if you must use a Chip, here is a way to do so with a custom view:
class MyChip #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr) {
private val mLinePaint = Paint().apply {
color = Color.BLUE
strokeWidth = 10f
}
init {
doOnNextLayout {
// Turn off center alignment if specified. We will handle centering ourselves.
if (isTextAlignmentCenter()) {
textAlignment = View.TEXT_ALIGNMENT_GRAVITY
// Get the length of all the stuff before the text.
val lengthBeforeText =
if (isChipIconVisible) iconStartPadding + chipIconSize + iconEndPadding else 0f
val chipCenter = width / 2
// Chips have only one line, so we can get away with this.
val textWidth = layout.getLineWidth(0)
val newTextStartPadding = chipCenter - (textWidth / 2) - lengthBeforeText
textStartPadding = max(0f, newTextStartPadding)
textEndPadding = 0f
}
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//just want to draw a diagonal line
canvas.drawLine(0f, 0f, width.toFloat(), height.toFloat(), mLinePaint)
}
private fun isTextAlignmentCenter() = textAlignment == View.TEXT_ALIGNMENT_CENTER
}
A sample layout:
activity_main.xml
<com.example.myapplication.MyChip
android:id="#+id/chip"
android:layout_width="300dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:text="Some Chip"
android:textAlignment="center"
app:chipIcon="#drawable/ic_baseline_cloud_download_24"
app:chipIconVisible="true"
app:closeIcon="#drawable/ic_baseline_clear_24"
app:closeIconVisible="true" />
</LinearLayout>
And the result:
I looked into the issue a little more deeply. For some reason, canvas operations such a drawLine(), drawRect(), etc. do not function. However, I can draw text on the canvas and fill it with a paint or a color.
Update: So, I tracked the problem down to the bringTextIntoView() method of TextView. For a reason that is not clear to me, the view is scrolled quite a few places (large, like 10's of thousands) in the positive direction. It is in this scrolled position that the text is written. To draw on the Chip, we must also scroll to this position. This scroll explains why I could fill the canvas with a color but could not draw on it so it would be visible.
The following will capture the "bad" scroll position and scroll to that position before drawing on the Chip. The screen capture looks the same as the above image.
MyChip.kt
class MyChip #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr)/*, View.OnScrollChangeListener*/ {
private val mLinePaint = Paint().apply {
color = Color.BLUE
strokeWidth = 10f
}
private var mBadScroll = 0f
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//just want to draw a diagonal line
canvas.withTranslation(x = mBadScroll) {
canvas.drawLine(0f, 0f, this#MyChip.width.toFloat(), height.toFloat(), mLinePaint)
}
}
private fun isTextAlignmentCenter() = textAlignment == View.TEXT_ALIGNMENT_CENTER
override fun scrollTo(x: Int, y: Int) {
super.scrollTo(x, y)
if (isTextAlignmentCenter()) {
mBadScroll = scrollX.toFloat()
}
}
}
Update: Horizontal scrolling is still the issue. In onMeasure() method of TextView, the wanted width is set to VERY_WIDE if horizontal scrolling is enabled for the view. (And it is for Chip.) VERY_WIDE is 1024*1024 or one megabyte. This is the source of the large scroll that we see later on. This problem with Chip can be replicated directly with TextView if we call setHorizontallyScrolling(true) in the TextView.
Chips have only one line and, probably, shouldn't scroll. Calling setHorizontallyScrolling(true) doesn't make the Chip or TextView scrollable anyway. The above code now just becomes:
MyChip.kt
class MyChip #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr) {
private val mLinePaint = Paint().apply {
color = Color.BLUE
strokeWidth = 10f
}
init {
setHorizontallyScrolling(false)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//just want to draw a diagonal line
canvas.drawLine(0f, 0f, this#MyChip.width.toFloat(), height.toFloat(), mLinePaint)
}
}
Horizontal scrolling is set to "true" in the constructor for Chip.
Suppose I have a single TextView like this
As you can see, the text is broken into three lines.
Is there any way that I can get the text areas in Rects? As this text is broken into three lines, I would need three Rects.
It is important to highlight that the left of a Rect is the left of the first character of the line, and the right is the right of the last character of the line.
Onik has the right idea, but the results will all be relative to zero. You will have to do a little more computation if you want to know where in the canvas the text of your TextView lies.
Here is a custom TextView that will outline the text on the screen.
class CustomTextView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.textViewStyle
) : AppCompatTextView(context, attrs, defStyleAttr) {
private val mPaint = Paint().apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.RED
}
private val mLineOutline = Rect()
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.save()
// This is the view's padding which we want to ignore.
canvas.translate(totalPaddingLeft.toFloat(), totalPaddingTop.toFloat())
for (line in 0 until lineCount) {
// This gets the outline of the text on a line but it is all relative to zero.
paint.getTextBounds(
text.toString(), layout.getLineStart(line), layout.getLineEnd(line), mLineOutline
)
canvas.save()
// We have the outline relative to zero, shift it so it outlines the text.
canvas.translate(layout.getLineLeft(line), layout.getLineBaseline(line).toFloat())
canvas.drawRect(mLineOutline, mPaint)
canvas.restore()
}
canvas.restore()
}
}
This is what is displayed:
You might not need this TextView, but you can grab its computations.
I find this posting very helpful when thinking about Android typography.
I'd do it as follows (in Kotlin):
var lineStart = 0
var lineEnd = 0
var lineText = ""
val paint = textView.paint
val rectList = arrayListOf<Rect>()
for (i in 0 until textView.lineCount) {
lineStart = textView.layout.getLineStart(i)
lineEnd = textView.layout.getLineEnd(i)
lineText = textView.text.substring(lineStart, lineEnd)
val rect = Rect()
paint.getTextBounds(lineText, 0, lineText.length - 1, rect)
rectList.add(rect)
}
I'm trying to get the distance between the text and the left side of a TextView. It uses the property android:gravity="center".
I want to get the distance of the red bar (this red bar is not a part of the layout) to center the blue button. What should I do?
The dark area represents the bounds of the TextView.
I don't want to use a compoundDrawable because this view will change the color of the button randomly.
The code of the view (written in Kotlin):
class BallTextView: TextView {
private lateinit var ballPaint : Paint
private var ballRadius : Float = 10f
private var ballColor : Int = Color.BLACK
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
initializeAttributes(attrs)
configBall()
}
override fun onDraw(canvas: Canvas) {
canvas.drawCircle(ballRadius, height.toFloat()/2, ballRadius, ballPaint)
super.onDraw(canvas)
}
fun configBall() {
ballPaint = Paint()
ballPaint.isAntiAlias = true
ballPaint.color = ballColor
}
fun initializeAttributes(attrs: AttributeSet) {
val attributes = context.obtainStyledAttributes(attrs, R.styleable.ball_textview)
ballRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
attributes.getFloat(R.styleable.ball_textview_ball_radius, ballRadius),
context.resources.displayMetrics)
ballColor = attributes.getColor(R.styleable.ball_textview_ball_color, ballColor)
}
}
Thanks.
The TextView#getLineBounds(int, Rect) method is what you want.
The first parameter is the zero-based line number, and the second is a Rect object that will hold the bounds values of the given line after the call. The left field of the Rect will have the horizontal inset of the line, which you can use with the radius of your drawn circle to figure its center's x coordinate.
My another solution was:
override fun onDraw(canvas: Canvas) {
if (xPosition == 0f) {
xPosition = (width - paint.measureText(text.toString())) / 3
}
canvas.drawCircle(xPosition, height.toFloat()/2, ballRadius, ballPaint)
super.onDraw(canvas)
}
#MikeM. What do you think about this approach?