温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Android中怎么用TextView实现跑马灯效果

发布时间:2022-01-26 14:15:01 来源:亿速云 阅读:187 作者:iii 栏目:开发技术

这篇“Android中怎么用TextView实现跑马灯效果”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Android中怎么用TextView实现跑马灯效果”文章吧。

    【前言】

         在Textview设置的宽度有限,而需要显示的文字又比较多的情况下,往往需要给Textview设置跑马灯效果才能让用户完整地看到所有设置的文字,所以给TextView设置跑马灯效果的需求是很常见的

    一、新手设置跑马灯效果

    1、先在xml中给Textview设置好对应的属性

     <TextView
            android:id="@+id/tv"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/show_float"
            android:singleLine="true"
            android:ellipsize="marquee"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:marqueeRepeatLimit="-1"
            android:layout_marginTop="20dp"
            android:padding="10dp"
            android:text="欢迎来到跑马灯新手村,这是新手示例~"
            android:textColor="@color/white"
            android:background="@drawable/com_live_rounded_rectangle"/>

    2、然后在代码中设置请求获取焦点即可

            TextView tv = findViewById(R.id.tv);
            tv.requestFocus();

    这样设置之后,跑马灯的效果就出来了

    Android中怎么用TextView实现跑马灯效果

    【关键点讲解】

    1、android:layout_width 是限制为固定宽度,同时文本的长度大于所设置的宽度,要是设置android:layout_widthwrap_content, 那么Textview的宽度会随着文本长度变长而拉宽,这样就不能出现跑马灯效果
    2、android:singleLine="true"设置Textview只能一行显示,要是不设置为true,默认会自动换行,显示为多行,这样的话,也不能出现跑马灯效果
    3、android:ellipsize="marquee"设置要是文本长度超出Textview的宽度时候,文本应该以跑马灯效果显示,这个是设置跑马灯效果最关键的设置,android:ellipsize还可以取值startendmiddlenone,分别是开头显示省略号结尾显示省略号中间显示省略号直接截断
    4、android:focusable="true"设置Textview可以获取焦点,跑马灯效果需要获取到焦点时候才生效,Textview默认是不获取焦点的
    5、android:focusableInTouchMode="true"设置在触摸模式下可以获取焦点,目前智能机基本都是自动进入触摸模式,其实目前只要设置android:focusableInTouchMode="true",默认android:focusable也会变为true了
    6、android:marqueeRepeatLimit="-1"设置跑马灯循环的次数,-1表示无限循环,不设置的话,默认是循环3次
    7、 tv.requestFocus();设置获取焦点, 只有当该view的focusable属性为true时候才生效

    【总结】

    1、一定要设置android:focusableInTouchMode="true",若是只设置了android:focusable="true"android:focusableInTouchMode没设置,那么跑马灯效果是不生效的,因为进入触摸模式之后,isFocusable()返回false,下面看看Texivew startMarquee()源码就知道需要满足什么条件才会开始跑马灯特效:

         private void startMarquee() {
            // Do not ellipsize EditText
            if (getKeyListener() != null) return;
    
            if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
                return;
            }
    		
    		// 1、跑马灯控制类没有创建或者跑马灯效果已经停止
            if ((mMarquee == null || mMarquee.isStopped()) && 
            // 2、当前Textview是获取到焦点或者被选中状态
            (isFocused() || isSelected())
            // 3、文本的行数只有一行
             && getLineCount() == 1
             // 4、文本长度大于Textview的宽度 
             && canMarquee()) {
    
                if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
                    mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
                    final Layout tmp = mLayout;
                    mLayout = mSavedMarqueeModeLayout;
                    mSavedMarqueeModeLayout = tmp;
                    setHorizontalFadingEdgeEnabled(true);
                    requestLayout();
                    invalidate();
                }
    
                if (mMarquee == null) mMarquee = new Marquee(this);
                mMarquee.start(mMarqueeRepeatLimit);
            }
        }
        
         private boolean canMarquee() {
            int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
            return width > 0 && (mLayout.getLineWidth(0) > width
                    || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
                            && mSavedMarqueeModeLayout.getLineWidth(0) > width));
        }

    二、高端玩家设置跑马灯效果

         从上面总结的TextView跑马灯源码可以看到,只要isFocusable()或者isSelected()方法返回true,那么就没必要管是否触摸模式,是否可以获取焦点之类的问题了,所以我们可以自定义一个类继承于TextView,然后重写isFocusable()直接返回true即可:

    public class MarqueeTextView extends TextView {
    
        public MarqueeTextView(Context context) {
            super(context);
            initView(context);
        }
    
        public MarqueeTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initView(context);
        }
    
        public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView(context);
        }
    
        private void initView(Context context) {
            this.setEllipsize(TextUtils.TruncateAt.MARQUEE);
            this.setSingleLine(true);
            this.setMarqueeRepeatLimit(-1);
        }
    
        //最关键的部分
        public boolean isFocused() {
            return true;
        }
    }

    1、直接在Xml中使用自定义的MarqueeTextView,那么跑马灯效果就出来了,无需任何额外配置

     <com.example.MarqueeTextView
            android:id="@+id/tv"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/show_float"
            android:layout_marginTop="20dp"
            android:padding="10dp"
            android:text="欢迎来到跑马灯高端玩家局,这是高端玩法示例~"
            android:textColor="@color/white"
            android:background="@drawable/com_live_rounded_rectangle"/>

    来看看效果:

    Android中怎么用TextView实现跑马灯效果

    三、延伸阅读

         假如有这样一个需求:因为显示文本的空间有限,所以只能用跑马灯的效果来给用户展示文本,但是在用户完整地看完一遍文本之后,需要隐藏掉Textview,那么问题来了,我们怎么知道跑马灯效果什么时候跑完一遍呢?先来看看Textview跑马灯部分Marquee类的部分源码:

           void start(int repeatLimit) {
           //重复次数设置0,那就直接停止跑马灯
                if (repeatLimit == 0) {
                    stop();
                    return;
                }
               //...省略掉大部分不相关的代码
                    mChoreographer.postFrameCallback(mStartCallback);
                }
            }
           
          private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    mStatus = MARQUEE_RUNNING;
                    mLastAnimationMs = mChoreographer.getFrameTime();
                    tick();
                }
            };
     
           void tick() {
                if (mStatus != MARQUEE_RUNNING) {
                    return;
                }
    
                if (textView != null && (textView.isFocused() || textView.isSelected())) {
                    long currentMs = mChoreographer.getFrameTime();
                    long deltaMs = currentMs - mLastAnimationMs;
                    mLastAnimationMs = currentMs;
                    float deltaPx = deltaMs * mPixelsPerMs;
                    mScroll += deltaPx;
                    //要是跑马灯滚动的距离大于最大距离,那么回到给mRestartCallback
                    if (mScroll > mMaxScroll) {
                        mScroll = mMaxScroll;
                        mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
                    } else {
                        mChoreographer.postFrameCallback(mTickCallback);
                    }
                    textView.invalidate();
                }
            }
            
             private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    if (mStatus == MARQUEE_RUNNING) {
                        if (mRepeatLimit >= 0) {
                            mRepeatLimit--;
                        }
                        start(mRepeatLimit);
                    }
                }
            }

    从上面对Marquee源码分析可知,跑马灯跑完一轮之后会调用到MarqueemRestartCallback对象的doFrame方法,那么我们来一招“偷龙转凤”,通过反射把mRestartCallback对象替换成我们自己实例化的对象,那么在跑马灯跑完一轮之后就会回调到我们替换的对象中,这样就实现了对跑马灯效果跑完一轮的监听,实现源码如下:

    public class MarqueeTextView extends androidx.appcompat.widget.AppCompatTextView {
        private Choreographer.FrameCallback mRealRestartCallbackObj;
        private Choreographer.FrameCallback mFakeRestartCallback;
        private OnShowTextListener mOnShowTextListener;
    
        public MarqueeTextView(Context context, OnShowTextListener onShowTextListener) {
            super(context);
            initView(context);
            this.mOnShowTextListener = onShowTextListener;
    
        }
    
        public MarqueeTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initView(context);
        }
    
        public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView(context);
        }
    
    
        private void initView(Context context) {
            //绕过隐藏api的限制
            Reflection.unseal(context.getApplicationContext());
    
            //设置跑马灯生效条件
            this.setEllipsize(TextUtils.TruncateAt.MARQUEE);
            this.setSingleLine(true);
            this.setFocusable(true);
    
            //反射设置跑马灯监听
            try {
                //从TextView类中找到定义的字段mMarquee
                Field marqueeField = ReflectUtil.getDeclaredField(TextView.class, "mMarquee");
                //获取Marquee类的构造方法Marquee(TextView v)
                Constructor declaredConstructor = ReflectUtil.getDeclaredConstructor(Class.forName("android.widget.TextView$Marquee"), TextView.class);
                //实例化一个Marquee对象,传入参数是Textview对象
                Object marqueeObj = declaredConstructor.newInstance(this);
                //从Marquee类中找到定义的字段mRestartCallback,重新开始一轮跑马灯时候会回调到这个对象doFrame()方法
                Field restartCallbackField = ReflectUtil.getDeclaredField(Class.forName("android.widget.TextView$Marquee"), "mRestartCallback");
                //从Marquee实例对象中获取到真实的mRestartCallback对象
                mRealRestartCallbackObj = (Choreographer.FrameCallback) restartCallbackField.get(marqueeObj);
                //构造一个假的mRestartCallback对象,用来监听什么时候跑完一轮跑马灯效果
                mFakeRestartCallback = new Choreographer.FrameCallback() {
                    @Override
                    public void doFrame(long frameTimeNanos) {
                        //这里还是执行真实的mRestartCallback对象的代码逻辑
                        mRealRestartCallbackObj.doFrame(frameTimeNanos);
                        Log.i("min77","跑马灯文本显示完毕");
                        //回调通知跑完一轮
                        if(MarqueeTextView.this.mOnShowTextListener != null){
                            MarqueeTextView.this.mOnShowTextListener.onComplete(0);
                        }
    
                    }
                };
                //把假的mRestartCallback对象设置给Marquee对象,其实就是代理模式
                restartCallbackField.set(marqueeObj, mFakeRestartCallback);
                //把自己实例化的Marquee对象设置给Textview
                marqueeField.set(this, marqueeObj);
    
    
            } catch (Exception e) {
                e.printStackTrace();
                Log.e("min77",e.getMessage());
            }
        }
    
    
    
        //最关键的部分
        public boolean isFocused() {
            return true;
        }
    
        /**
         * 是否显示完整文本
         */
        public interface OnShowTextListener{
            void onComplete(int delayMillisecond);
    
        }
    
    }

    效果如下:

    Android中怎么用TextView实现跑马灯效果

    以上就是关于“Android中怎么用TextView实现跑马灯效果”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI