0

I'd like to make the "Dashboard" string to be sliding left until we can see the rest of the word. After that, the string's position would be reseted and the process would start all over again.

Think of it like a translation animation in a text view where in every x seconds it's position moves some pixels to the left...

enter image description here

I have this piece of code in my onDraw method (this is not the only code here. I have the code to draw the icon, for example):

canvas.drawText(
     item.title,
     item.rect.centerX() + itemIconSize / 2 + itemIconMargin,
     item.rect.centerY() - textHeight, paintText
)

The thing is, I would like for that piece of code to be executed infinitely but decreasing always some value i to the x axis so that way I can give the effect of sliding to the left.

How can I achieve this? I cannot just put the code in a while(true) cycle because it would block the main UI and nothing would be drawn.

By the way, the way we check if the string is bigger than the space it has to be written is in onSizeChanged function (we have a flag variable shorten that tells us if we need to shorten the string or not):

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)

        var lastX = barSideMargins
        itemWidth = (width - (barSideMargins * 2)) / items.size

        // reverse items layout order if layout direction is RTL
        val itemsToLayout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
            && layoutDirection == LAYOUT_DIRECTION_RTL
        ) items.reversed() else items

        for (item in itemsToLayout) {
            // Prevent text overflow by shortening the item title
            var shorted = false
            while (paintText.measureText(item.title) > itemWidth - itemIconSize - itemIconMargin - (itemPadding * 2)) {
                item.title = item.title.dropLast(1)
                shorted = true
            }

            // Add ellipsis character to item text if it is shorted
            if (shorted) {
                item.title = item.title.dropLast(1)
                item.title += context.getString(R.string.ellipsis)
            }

            item.rect = RectF(lastX, 0f, itemWidth + lastX, height.toFloat())
            lastX += itemWidth
        }

        // Set initial active item
        applyItemActiveIndex()
    }

I cannot keep calling invalidate() because that way all the canvas would be redrawn, and I want just that piece of code to be redrawn

UPDATE 1

So, what I currently have on my onDraw is this:

drawText()
paintText.alpha = item.alpha
canvas.drawText(
      item.title,
      (item.rect.centerX() + itemIconSize / 2 + itemIconMargin) - value,
      item.rect.centerY() - textHeight, paintText
)

And for the drawText function, which has the value animator callback, I have this:

private fun drawText() {
        animator.duration = 500
        animator.addUpdateListener {
            value = animator.animatedValue as Float
            invalidate()
        }
        animator.start()
    }

Currently, here, I'm not worried about the text going over the icon.

The problem is, I can't seem to make the text translate left in the x axis. Even with that code, the string won't animate. It should by now translate to left, right? Both value and animator are class fields.

André Nogueira
  • 3,363
  • 3
  • 10
  • 16

1 Answers1

0

Android textview already has some attributes which would help to achieve exactly what you need. It's called Marquee. Marquee text in Android

But if you want to implement this logic yourself from scratch, there are multiple ways to do it with canvas.( Translating the canvas itself, applying layer and clip logic OR translating the drawText method ) . Actual implementation is up to you but the main idea is to introduce additional variables to canvas draw calls and manipulating them with an Animator.

I cannot keep calling invalidate() because that way all the canvas would be redrawn, and I want just that piece of code to be redrawn

This is what you are going to do after altering values with Animator. The view example you posted won't cause performance issues with invalidation. But if you think it would, invalidate() has an overload type which takes a region as parameter and only invalidates that region in canvas.

Nezih Yılmaz
  • 626
  • 4
  • 8
  • So what do you suggest me to do? Create a `ValueAnimator` inside my `drawCanvas` method? Something like this? val animator = ValueAnimator.ofInt(10, 100) animator.addUpdateListener { val value : Int = animator.animatedValue as Int canvas.drawText( item.title, (item.rect.centerX() + itemIconSize / 2 + itemIconMargin) - value, item.rect.centerY() - textHeight, paintText ) invalidate() } animator.start() – André Nogueira Oct 03 '20 at 02:05
  • Yes, you are on the right track. But I think the text would be drawn on icon with this new function. So just play around with canvas calls till its right for you. Take a look at canvas documentation. You might need to use clipping here – Nezih Yılmaz Oct 03 '20 at 02:23
  • Just to make sure: valueAnimator and the new variable 'value' should be class fields. – Nezih Yılmaz Oct 03 '20 at 02:26
  • Can you check my updated question? I did what you said (I guess?) but the canvas won't animate... – André Nogueira Oct 03 '20 at 10:24
  • Dont start animator in onDraw. Call that function somewhere else. Also set repeat mode for the animator since you want that. – Nezih Yılmaz Oct 03 '20 at 12:11
  • Thanks! Can you check this new question regarding the clipping of the string? https://stackoverflow.com/questions/64189887/android-cliprect-not-clipping-as-expected – André Nogueira Oct 04 '20 at 12:05