安卓实现textview跑马灯效果

1.使用textview自身跑马灯效果

XML 复制代码
 <TextView
       android:id="@+id/tv_message_content"
       android:layout_width="100dp"
       android:layout_height="wrap_content"
       android:ellipsize="marquee"
       android:focusable="true"
       android:focusableInTouchMode="true"
       android:marqueeRepeatLimit="marquee_forever"
       android:singleLine="true"
       android:text="我是跑马灯~~~~~~~~~~啦啦啦"
       />

其中设置了几个属性:

ellipsize是文本超出控件之后截断方式(默认是在末尾显示省略号),取值marquee表示文本长度超出Textview的宽度时候,文本应该以跑马灯效果显示,这个是设置跑马灯效果最关键的设置,ellipsize还可以取值start、end、middle、none,分别是开头显示省略号、结尾显示省略号、中间显示省略号、直接截断

focusable:设置Textview可以获取焦点,跑马灯效果需要获取到焦点时候才生效,Textview默认是不获取焦点的

focusableInTouchMode:设置在触摸模式下可以获取焦点,目前只要设置android:focusableInTouchMode="true",默认android:focusable也会变为true了

marqueeRepeatLimit: 跑马灯循环次数,marquee_forever表示无限循环

singleLine:设置文本只显示一行,文本长度超过tetxview宽度时不换行

因为此种方式实现tetxview跑马灯,需要获取到焦点,所以需要在代码中调用setSelected或者requestFocus获取焦点,之后才能有跑马灯效果;针对这个问题可以扩展textview将其isFocused设置为true,这样就不需要手动获取焦点了。

java 复制代码
public class MarqueeTextView extends androidx.appcompat.widget.AppCompatTextView {

    public MarqueeTextView(Context context) {
        super(context);
    }

    public MarqueeTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MarqueeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean isFocused() {
        return true;
    }
}

另外有两个问题,导致这种实现方式有些缺陷,可能无法实现某些情况下的需求。

首先第一个是点击事件问题,如果textview需要点击之后触发某些业务逻辑,那么会出现第一次点击没有触发onClicked的情况,第二次点击才可以触发,解决方法是使用onTouchListener,在其中对MotionEvent.ACTION_DOWN进行处理,代替使用onClickListener;

第二个是某些情况下页面失去焦点导致跑马灯"回滚",例如页面触发显示半透明dialog,导致失去焦点,虽然textview还是可见,但是跑马灯停止了,或者页面重新布局了,导致焦点重新获取,目前暂未有解决方法,对于这种情况需要使用动画手动实现跑马灯。

2.使用属性动画实现跑马灯

java 复制代码
 /**
     * 启动跑马灯动画
     * @param textView
     * @param duration  一次跑马灯动画持续时间(单位ms),从文字开始出现到完全消失计一次
     */
    private void startPropertyMarquee(TextView textView, int duration) {
        Log.d(TAG, "startPropertyMarquee call with textView = [" + textView + "], duration = [" + duration + "]");
        if (textView == null) return;

        textView.setSingleLine(true);  //设置单行显示
        textView.setEllipsize(null);   //设置超出textview宽度时截断,不显示省略号,如果需要的话可以自行调整

        textView.post(() -> {
            String text = textView.getText().toString();
            float textWidth = textView.getPaint().measureText(text);
            float containerWidth = textView.getWidth();

            //文本长度大于textView宽度
            if (textWidth > containerWidth) {
                // 使用属性动画
                ValueAnimator animator = ValueAnimator.ofFloat(textWidth, -textWidth);
                animator.setDuration(duration);
                animator.setInterpolator(new LinearInterpolator());
                animator.setRepeatCount(ValueAnimator.INFINITE);
                animator.setRepeatMode(ValueAnimator.RESTART);

                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float value = (float) animation.getAnimatedValue();
                        textView.setPadding((int) value, 0, 0, 0);
                    }
                });

                animator.setCurrentFraction(0.5f); //设置起始位置为中间(动画是从右侧开始出现到左侧完全消失,中间则是文字全部显示,并开始向左移动)
                animator.start();
                textView.setTag(animator);  //保存动画对象的引用,方便停止
            }
        });
    }

    private void stopPropertyMarquee(TextView textView) {
        LogUtil.d(TAG, "stopPropertyMarquee call with textView = [" + textView + "]");
        if (textView != null) {
            ValueAnimator marqueeAnimator = (ValueAnimator) textView.getTag();
            if (marqueeAnimator != null) {
                marqueeAnimator.cancel();  
                //可以在这里复原tetxview
                //textView.setPadding(0, 0, 0, 0);
            }
        }
    }

    //还可以stop的时候选择调用marqueeAnimator.pause,后续可以继续播放

使用属性动画会更灵活,也不会有焦点问题就是写起来代码比较多,可以扩展tetxview做成自定义控件方便复用。

这里要使用属性动画而不能使用补间动画(平移动画),因为补间动画只是改变绘制,无法将textview剩余文本显示出来。

相关推荐
AC赳赳老秦15 分钟前
供应链专员提效:OpenClaw自动跟踪物流信息、更新库存数据,异常自动提醒
java·大数据·服务器·数据库·人工智能·自动化·openclaw
迈巴赫车主21 分钟前
Java基础:list、set、map一遍过
java·开发语言
程序员陆业聪23 分钟前
两次Flutter全屏白踩坑复盘:Layout的静默失败,以及AI结对编程的认知盲区
android
灵犀学长1 小时前
基于 Spring ThreadPoolTaskScheduler + CronTrigger 实现的动态定时任务调度系统
java·数据库·spring
程序员陆业聪2 小时前
Compose Strong Skipping Mode 的真相:它并不会让你的类型变 Stable
android
好家伙VCC2 小时前
【无标题】
java
小碗羊肉3 小时前
【JavaWeb | 第十一篇】文件上传(本地&阿里云OSS)
java·阿里云·servlet
吾疾唯君医3 小时前
Java SpringBoot集成积木报表实操记录
java·spring boot·spring·导出excel·积木报表·数据文件下载
Byron Loong3 小时前
【c++】为什么有了dll和.h,还需要包含lib
java·开发语言·c++
hexu_blog4 小时前
vue+java实现图片批量压缩
java·前端·vue.js