求助:Android中用Kotlin实现从下到上滚动文本动画
Android文本垂直滚动(从下到上)实现方案
我已通过Android内置属性实现文本水平滚动:
android:marqueeRepeatLimit="marquee_forever" android:padding="10dp" android:scrollHorizontally="true"
但需要实现从下到上的垂直滚动,尝试过AI生成的自定义VerticalMarqueeTextView类、对应布局配置,以及修改原生AppCompatTextView的XML属性,均未成功实现效果,因对Kotlin了解有限,不清楚如何正确启动动画,寻求可行方案。
原自定义View的核心问题
之前的VerticalMarqueeTextView存在几个关键缺陷:
init块中设置isSingleLine = true和setHorizontallyScrolling(true),限制了文本为单行水平滚动,与垂直滚动需求冲突- 在
init阶段调用startMarquee()时,View未完成测量,height值为0,动画位移参数无效 - 动画起始/结束位置设置不合理,未考虑文本实际高度与容器的适配关系
可行实现方案
方案1:改进自定义VerticalMarqueeTextView
重写自定义View,解决测量问题并调整动画逻辑:
package com.brainintelcorp.brainintel import android.content.Context import android.util.AttributeSet import android.view.animation.Animation import android.view.animation.TranslateAnimation import androidx.appcompat.widget.AppCompatTextView class VerticalMarqueeTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatTextView(context, attrs, defStyleAttr) { private var scrollAnimation: TranslateAnimation? = null init { // 取消单行限制,允许文本换行 isSingleLine = false // 关闭水平滚动 setHorizontallyScrolling(false) } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) // 测量完成后启动动画,确保获取正确高度值 startVerticalMarquee() } private fun startVerticalMarquee() { // 仅当文本总高度超过View容器高度时启动滚动 if (lineCount * lineHeight <= measuredHeight) { scrollAnimation?.cancel() return } scrollAnimation = TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, -1f ).apply { duration = 10000 // 滚动时长,值越小速度越快 repeatCount = Animation.INFINITE repeatMode = Animation.RESTART } startAnimation(scrollAnimation) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() scrollAnimation?.cancel() clearAnimation() } }
对应布局配置
需为View设置固定高度(或限制高度),确保文本高度超过容器时触发滚动:
<com.brainintelcorp.brainintel.VerticalMarqueeTextView android:id="@+id/tvQuestionContent" android:layout_width="match_parent" android:layout_height="120dp" <!-- 固定高度是触发滚动的关键 --> android:layout_marginTop="20dp" android:autoSizeMaxTextSize="100sp" android:autoSizeMinTextSize="14sp" android:autoSizeStepGranularity="1sp" android:autoSizeTextType="uniform" android:padding="10dp" android:text="@string/question_content" android:textColor="@color/primaryColor" android:textSize="24sp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/imageLogo" />
方案2:ScrollView+Handler实现精准平滑滚动
若需要更细腻的滚动控制或自定义逻辑,可使用ScrollView嵌套TextView,通过Handler定时触发滚动:
布局文件
<ScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="120dp" android:scrollbars="none" <!-- 隐藏默认滚动条 --> android:layout_marginTop="20dp" app:layout_constraintTop_toBottomOf="@id/imageLogo" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tvQuestionContent" android:layout_width="match_parent" android:layout_height="wrap_content" android:autoSizeMaxTextSize="100sp" android:autoSizeMinTextSize="14sp" android:autoSizeStepGranularity="1sp" android:autoSizeTextType="uniform" android:padding="10dp" android:text="@string/question_content" android:textColor="@color/primaryColor" android:textSize="24sp" android:textStyle="bold" /> </ScrollView>
Activity逻辑实现
import android.os.Bundle import android.os.Handler import android.os.Looper import android.widget.ScrollView import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { private lateinit var scrollView: ScrollView private val handler = Handler(Looper.getMainLooper()) private val scrollRunnable = object : Runnable { override fun run() { // 每次滚动1px,数值越大滚动速度越快 scrollView.smoothScrollBy(0, 1) // 延迟10ms再次触发滚动,数值越小速度越快 handler.postDelayed(this, 10) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) scrollView = findViewById(R.id.scrollView) // 延迟1秒启动滚动,可根据需求调整 handler.postDelayed(scrollRunnable, 1000) } override fun onPause() { super.onPause() handler.removeCallbacks(scrollRunnable) } override fun onResume() { super.onResume() handler.post(scrollRunnable) } override fun onDestroy() { super.onDestroy() handler.removeCallbacks(scrollRunnable) } }
内容的提问来源于stack exchange,提问作者Akash Gupta
相关产品推荐
相关产品推荐

