在Android Kotlin中实现逐字显示文本效果(也称打字机效果)

3

这可能是一个基本问题,或者我可能让我的项目超出了我的能力范围,但我确实需要帮助,因为这让我花了几个小时搜索和尝试他人的代码。

我的活动包括使用ViewPager2的三个片段,我将称它们为Fragment A、B和C。其中,Fragment B将加载另外数量不确定的片段。我将其称为Fragment X1、X2……可能是X99等。这些片段是使用打字机效果的地方,所以我将文本动画函数放置在Fragment B上,因为我认为它最好是在加载片段的地方而不是每个Fragment Xn都有它。

fun TextView.textDisplay(text: Int) {
    val targetstring: Int = text
    if(textanim) {
            for(i in 1..targetstring){
                println(targetstring.toString()[i])
                Thread.sleep(textanimspeed.toLong())
            } // Typewriter effect
    } else {
        println(targetstring)
    } // display texts at once
}

Fragment B中还有一些值,我计划用它们来设置文本的大小、动画速度以及是否要进行动画。

var textsize:Byte = 10
var textanimspeed:Byte = 100
var textanim: Boolean = true

然后尝试加载 X1 片段。

(activity as FragmentB).TextView1.textDisplay(R.string.stringX1)

原来这行代码是无法到达的,"textDisplay" 是一个"未解决的引用"。
如果我移除 textDisplay(text: Int) 的接收器参数 "TextView" 并且执行代码,Android Studio 就不会显示它是未解决的了,但它仍然是无法到达的。最重要的是,我相信文本必须在 TextView 中进行动画处理。运行应用程序将不会显示任何文本。
第二件我尝试的事情是复制打字机效果的 Java 代码并将其粘贴,以便 Android Studio 自动将其转换为 Kotlin。结果有一些红色的东西,但我摆脱了它。我创建了一个新的类 "TypeWriterView.kt",其中包含以下代码:
class TypeWriterView: AppCompatTextView {
  private var mText: CharSequence? = null
  private var mIndex = 0
  private var mDelay: Long = 150 // in ms

  constructor(context: Context) : super(context)
  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

  private val mHandler: Handler = Handler()
  private val characterAdder: Runnable = object : Runnable {
    override fun run() {
        text = mText!!.subSequence(0, mIndex++)
        if (mIndex <= mText!!.length) {
            mHandler.postDelayed(this, mDelay)
        }
    }
  }

  fun animateText(txt: CharSequence?) {
    mText = txt
    mIndex = 0
    text = ""
    mHandler.removeCallbacks(characterAdder)
    mHandler.postDelayed(characterAdder, mDelay)
  }

  fun setCharacterDelay(m: Long) {
    mDelay = m
  }
}

并在 Fragment X1 中尝试了它。

    Sc_X1_tv01.setText(R.string.stringX1)
    Sc_X1_tv01.setCharacterDelay(150)
    Sc_X1_tv01.animateText("${R.string.stringX1}")

这段代码没有红色错误提示,但仍然无法运行并且没有显示任何文本。

我现在卡在这一步,需要帮助。感谢阅读和任何帮助。

2个回答

5
使用协程可以很容易地在单个函数中实现打字机效果,然后您可以将其用于任何TextView而不创建任何属性。
fun TextView.typeWrite(lifecycleOwner: LifecycleOwner, text: String, intervalMs: Long) {
    this@typeWrite.text = ""
    lifecycleOwner.lifecycleScope.launch {
        repeat(text.length) {
            delay(intervalMs)
            this@typeWrite.text = text.take(it + 1)
        }
    }
}

使用方法:

// In an Activity:
myTextView.typeWrite(this, "Hello, world!", 33L)

// In a Fragment:
myTextView.typeWrite(viewLifecycleOwner, "Hello, world!", 33L)

对于您自己的代码,第一种策略不起作用,因为您不能在UI线程上睡觉。您的策略与我的类似,只是协程中的delay()不会使您从启动它的线程上睡眠,而是暂停该协程。我不知道您的第二尝试有什么问题,但可能是您没有发布的代码。不确定您所谓的“无法访问”是什么意思。


谢谢你的帮助!无法访问的代码是因为我把它们放错了位置,应该放在onCreateView中,所以我把它们移到了onCreate中,问题解决了。现在我的第一次尝试和你的代码还是出现了"未解析的引用"和"此转换永远不会成功"的问题,而且我的第二次尝试(新的类)甚至出现了NPE。似乎更大的问题出在其他地方。幸好我学会了使用delay(),我想知道在调用函数时,我需要为lifecycleOwner参数传入什么? - undefined
LifecycleOwner可以是你的Fragment或者Activity。你还需要在构建文件中添加fragment ktx依赖:implementation "androidx.fragment:fragment-ktx:1.2.5" - undefined

0
如果还有人在寻找的话,你可以使用ValueAnimator来实现,而无需使用lifecycleOwner。

fun TextView.animateCharacterByCharacter(text: String, delay: Long = 33L) {
    // Make sure the text is not empty
    if (text.isEmpty()) return

    // Initialize a value animator to iterate over characters
    val charAnimation = ValueAnimator.ofInt(0, text.length)

    charAnimation.apply {
        this.duration = delay * text.length.toLong()
        this.repeatCount = 0
        addUpdateListener {
            val charCount = it.animatedValue as Int
            val animatedText = text.substring(0, charCount)
            this@animateCharacterByCharacter.text = animatedText
        }
    }

    charAnimation.start()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接