Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TextView Marquee speed extension #183

Open
1 of 3 tasks
Ribesg opened this issue Mar 27, 2019 · 4 comments
Open
1 of 3 tasks

TextView Marquee speed extension #183

Ribesg opened this issue Mar 27, 2019 · 4 comments
Assignees

Comments

@Ribesg
Copy link

Ribesg commented Mar 27, 2019

As discussed on Slack, here's a proposal for an extension which uses reflection to change the marquee ellipsis animation speed.

import android.os.Build.VERSION.SDK_INT
import android.text.Editable
import android.text.TextWatcher
import android.widget.TextView
import androidx.core.view.doOnNextLayout
import splitties.dimensions.dp
import java.lang.reflect.Field
import java.util.*

/**
 * The marquee speed in pixels per second.
 *
 * The default value is 30dp.
 */
var TextView.marqueeSpeed: Float
    get() = marquee?.speed ?: marqueeSpeeds[this]?.first ?: dp(defaultMarqueeSpeed)
    set(speed) {
        removeTextChangedListener(marqueeSpeeds.remove(this)?.second)
        marquee?.speed = speed
        // TODO: This should probably be defined cleaner in a class or at least outside of this setter
        val listener = object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun afterTextChanged(s: Editable?) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                doOnNextLayout { 
                    marquee?.speed = speed
                }
            }
        }
        addTextChangedListener(listener)
        marqueeSpeeds[this] = speed to listener
    }

// Internals of Marquee speed edition below

private val marqueeSpeeds = WeakHashMap<TextView, Pair<Float, TextWatcher>>()

private inline val defaultMarqueeSpeed: Int
    get() = TextView::class.java.declaredClasses.single { it.simpleName == "Marquee" }.run {
        getDeclaredField("MARQUEE_DP_PER_SECOND").accessible.getInt(this)
    }

private typealias Marquee = Any

private inline val TextView.marquee: Marquee?
    get() = TextView::class.java
        .getDeclaredField("mMarquee")
        .accessible
        .get(this)

private inline val Marquee.speedField: Field
    get() = javaClass.getDeclaredField(
        when {
            SDK_INT > 21 -> "mPixelsPerSecond"
            else         -> "mScrollUnit"
        }
    ).accessible

private inline var Marquee.speed: Float
    get() = speedField.getFloat(this)
    set(speed) {
        speedField.setFloat(this, speed)
    }

// TODO: Maybe this shouldn't be here, or shouldn't be at all
private inline val Field.accessible: Field
    get() = apply { isAccessible = true }

Todo list:

  • Test it on all supported API levels (and maybe check that the default value has always been 30dp in the process?)
  • Cleanup code, add some try-catch to future-proof it
  • Open an issue to ask for a public API for this use case
@LouisCAD LouisCAD self-assigned this Mar 27, 2019
@Ribesg
Copy link
Author

Ribesg commented Apr 5, 2019

The code I provided doesn't work on Android 9, I'm trying to fix it.
aosp-mirror/platform_frameworks_base@fa83834

@LouisCAD
Copy link
Owner

LouisCAD commented Apr 5, 2019

That's the first Android version I'd have tried to use it on 😅
Could you start by adding the try catch blocks so it becomes no-op instead of crashing? That'd allow to ensure it doesn't fail, with a mean to validate it.

@LouisCAD
Copy link
Owner

LouisCAD commented Apr 5, 2019

I just submitted a new public API request on Android's issue tracker: https://issuetracker.google.com/issues/129999621

@Ribesg
Copy link
Author

Ribesg commented Apr 29, 2019

FYI I tried to make a version working from API 19 to 28 and didn't find a good way to do it. But I didn't have enough time to invest in this for now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants