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();
这样设置之后,跑马灯的效果就出来了
【关键点讲解】
1、android:layout_width
是限制为固定宽度,同时文本的长度大于所设置的宽度,要是设置android:layout_width
为wrap_content
, 那么Textview的宽度会随着文本长度变长而拉宽,这样就不能出现跑马灯效果
2、android:singleLine="true"
设置Textview只能一行显示,要是不设置为true,默认会自动换行,显示为多行,这样的话,也不能出现跑马灯效果
3、android:ellipsize="marquee"
设置要是文本长度超出Textview的宽度时候,文本应该以跑马灯效果显示,这个是设置跑马灯效果最关键的设置,android:ellipsize
还可以取值start
、end
、middle
、none
,分别是开头显示省略号
、结尾显示省略号
、中间显示省略号
、直接截断
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"/>
来看看效果:
三、延伸阅读
假如有这样一个需求:因为显示文本的空间有限,所以只能用跑马灯的效果来给用户展示文本,但是在用户完整地看完一遍文本之后,需要隐藏掉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源码分析可知,跑马灯跑完一轮之后会调用到Marquee
类 mRestartCallback
对象的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跑马灯效果内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!