By Pheobe.liu

前言

在项目开发过程中,有时候应用涉及多语言,时常会出现内容过长而溢出的问题,例如某些爬虫语言,所以有些地方就考虑用跑马灯来呈现,此外某些特殊应用也会考虑使用跑马灯来增加用户体验。在这里归纳下自己看的一些文章以及google 原生对跑马灯的定义,并谈下使用的一些体会。

原生定义跑马灯

首先说下google自带跑马灯效果,根据google 关于textView 源码可以查看启动自带跑马灯条件:
private void startMarquee() {// Do not ellipsize EditTextif (getKeyListener() != null) return;if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {return;}if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&getLineCount() == 1 && 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 应该单行显示
  • TextView 内容长度要长于textView 设定的Layout宽度,如果更短就没有跑马灯的意义
  • android:ellipsize=”marquee”也可以用代码来设定
  • 对应的TextView 要获取的到焦点或者被选中

对应的可以参考在XML 文件中设定如下

<TextView  android:layout_width="100dip"  android:layout_height="50dip" android:text="我得意的笑我得意的笑我得意地笑"   android:singleLine="true"  android:ellipsize="marquee"  android:focusable="true"  android:focusableInTouchMode="true"  ></TextView>

针对上述给的几个条件可能有些读者会存在误区,我们一一拆解了看:

  1. TextView 应该单行显示
    看标题的话,我们知道单行显示有两种设置都可以达到效果:android:singleLine=”true”和android:maxLines=”1”。但实际上设置maxLine 对跑马灯来说是无效的。在这里简单说明下关于singleLine和maxLine的区别。官方对两者的定义是:
    maxLines:限制TextView的最高高度,就是指通过限制行数来限制最高高度。
    singleLine: 强制设置TextView的文字不换行。
    区别就是:maxLines还是会默认执行自动换行策略,假如一段文字自动换行后有5行,maxLines设置为1行,那么就只显示第一行的内容,其他行不显示。但是,如果设置了singleLine, 那么这段可以有5行的文字将会被强制放在1行里,然后看最多能显示多少字符,剩下的不显示.由此也就明白为什么设置maxline 为1后虽然显示一行,但是跑马灯也失效。
  2. TextView 内容长度要长于textView 设定的Layout宽度,如果更短就没有跑马灯的意义。顾名思义,就是text内容足够长,当前textView layout 装不下。
  3. android:ellipsize=”marquee”,这个条件也可以通过代码手动设置。
  4. 对应的TextView 要获取的到焦点或者被选中(个人建议使用setSelect ,因为TextView 使用setFocusable 设置焦点有时候会失去焦点)。
    实际上在使用过程,这个条件最容易失败导致跑马灯失效,原因则是Google 设定了正规意义上获取到焦点的View 只能有一个,哪怕同时多个View 设置了获取焦点,欺骗系统的(后续会讲述)不包含在内。

针对焦点问题有两种解决思路:
第一种:textView 失去焦点后,可以如下重新设定marquee和select。从源码可以看得到调用setSelect 方法时,只要各条件符合的话,textView 会立马尝试启动跑马灯。

textView.setEllipsize(TextUtils.TruncateAt.MARQUEE);      textView.setSingleLine(true);
textView.setSelected(true);根据源码setSelected() 方法会重启跑马灯
@Overridepublic void setSelected(boolean selected) {boolean wasSelected = isSelected();super.setSelected(selected);if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {if (selected) {startMarquee();} else {stopMarquee();}}}

第二种:自定义textview 类,重写isFocused 方法,欺骗系统认定当前textview 一直有获取焦点。

@Overridepublic boolean isFocused() {return true;}

关于textView 获取到焦点的充分必要条件,大家可以参考文章,这里就不复述:http://www.cnblogs.com/Gaojiecai/archive/2013/06/18/3142783.html


自定义跑马灯样例

有时候原生定义跑马灯不一定能满足应用需求,比如什么时候暂停什么时候重新启动跑马灯等。下面我们说下如何自定义跑马灯,简单来说是自定义textView类,通过Runnable时间控制text的位置,直接上代码:

public class MarqueeTextView extends TextView implements Runnable {private static final String TAG = "TpvMarqueeTextView";private int circleTimes = 3;private int hasCircled = 0;private int currentScrollPos = 0;private int circleSpeed = 6;private int textWidth = 0;private boolean mIsStop = false;private boolean isMeasured = false;private Handler handler;private boolean flag = false;public TpvMarqueeTextView(Context context, AttributeSet attrs) {super(context, attrs);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (!isMeasured) {getTextWidth();isMeasured = true;}}@Overridepublic void setVisibility(int visibility) {flag = false;isMeasured = false;this.hasCircled = 0;super.setVisibility(visibility);}@Overridepublic void run() {scrollTo(currentScrollPos, 0);if (textWidth <= this.getWidth() && textWidth != 0){return;}currentScrollPos += 1;if (currentScrollPos >= textWidth) {currentScrollPos = -this.getWidth();}if (!flag) {postDelayed(this, circleSpeed);}}private void getTextWidth() {Paint paint = this.getPaint();String str = this.getText().toString();Log.i(TAG, str);if (str == null) {textWidth = 0;}textWidth = (int) paint.measureText(str);}public void setCircleTimes(int circleTimes) {this.circleTimes = circleTimes;}public void setSpeed(int speed) {this.circleSpeed = speed;}public void startScrollShow() {currentScrollPos= 0;getTextWidth();removeCallbacks(this);post(this);}public void stopScroll() {removeCallbacks(this);}
}

同样的在xml当中需要如下设定:

<com.android.textTest.MarqueeTextViewandroid:id="@+id/test_text"android:layout_width="100dp"android:layout_height="50dip"android:singleLine="true"android:text="我得意的笑我得意的笑我得意的笑"android:textSize="12sp"/>

而在对应的java 代码中可以控制跑马灯的开始与结束:

private void setTextMarquee(TpvMarqueeTextView textView) {if (textView != null) {textView.setEllipsize(TextUtils.TruncateAt.MARQUEE);textView.startScrollShow();}}

总的来说自定义的跑马灯优势是能够自定义滚动速度,次数和样式,以及适时的控制跑马灯的开始与停止,而原生的就很难界定,当然也有对应的弊端,启动runable 需要开辟新的线程,如果同时自定义并执行太多的跑马灯,对设备和应用的性能也是个考验。


使用跑马灯走过的坑

上述情况基本上说明了跑马灯的使用情况,接下来,叙述一个自己开发当中的苦逼经历,希望对读者有用。
由于应用比较复杂,布局层次较多,要启动跑马灯的textView 被封装在一个fragment 里面。而该fragment 又囊括在自定义PagerAdapter当中。
当切换fragment时,上述的4个条件好像都是符合的(长度够长,被选中,单行显示,ellipsize 被设置为marquee),但是跑马灯就是失效,各种凌乱。
通过源码查询方法,发现在TextView 的makeNewLayout() 方法当中:

if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {if (!compressText(ellipsisWidth)) {final int height = mLayoutParams.height;// If the size of the view does not depend on the size of the text, try to// start the marquee immediatelyif (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {startMarquee();} else {// Defer the start of the marquee until we know our width (see setFrame())mRestartMarquee = true;}}}

原来在初始化layout 的时候会根据textView 的height是否为LayoutParams.WRAP_CONTENT 或者是LayoutParams.MATCH_PARENT来判定是否立刻启动跑马灯。
很遗憾的是,我的textView 的XML 配置为:

<TextViewandroid:id="@+id/textView4"android:layout_width="100dp"android:layout_height="50dp"android:singleLine="true"android:ellipsize="marquee"android:text="我得意地笑得意地笑得意地笑笑笑笑笑"/> 

以为找到原因了,将layoutHeight 改为wrap_content看是否可行,改完之后,确实跑马灯跑起来了,为什么改完后又能跑了呢,继续看。
原来如果设置为wrap_content后,会添加一个mRestartMarquee = true,那什么时候会用呢:

 @Overrideprotected boolean setFrame(int l, int t, int r, int b) {boolean result = super.setFrame(l, t, r, b);if (mEditor != null){mEditor.setFrame();restartMarqueeIfNeeded();}return result;}private void restartMarqueeIfNeeded() {if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {mRestartMarquee = false;startMarquee();}}

原来在setFrame 方法会根据之前获得的mRestartMarquee 对象是否为真重启跑马灯。
现在又多了个疑问,为什么在makeNewLayout 方法中启动跑马灯失效,在setFrame 当中启动跑马灯又能用呢?
继续看源码,对比makeNewLayout 方法和setFrame 方法的注释说明:

makeNewLayout():
/*** The width passed in is now the desired layout width,* not the full view width with padding.* {@hide}*/setFrame():
/*** Assign a size and position to this view.** This is called from layout.** @param left Left position, relative to parent* @param top Top position, relative to parent* @param right Right position, relative to parent* @param bottom Bottom position, relative to parent* @return true if the new size and position are different than the*         previous ones* {@hide}*/

通过对比我们看到两者的区别是在layout 的属性差异。通过日志输出两个方法调用startMarquee 方法时layout 属性。发现在makeNewLayout 方法下启动跑马灯时,
判断条件canMarquee 方法返回始终为false,进一步的原因则是
int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());中各个参数均为0,导致得到的width 为0,最后跑马灯失效。

 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));}

现在总结下我的经历:

  • android:layout_height=”50dp” 写死了textView 的height 属性,导致在初始化布局makeNewLayout()方法当中就启动跑马灯,并且mRestartMarquee 设为false,但此时由于整体布局还未完成,启动跑马灯的条件canMarquee()方法返回false,最终跑马灯失效。
  • android:layout_height=”wrap_content”时,在初始化布局makeNewLayout()方法当中会将mRestartMarquee 设为true,当布局完成时调用SetFrame()时,由于mRestartMarquee 为true ,会尝试重新启动跑马灯,这时由于布局已完成,只要textView 的text 足够长,canMarquee()方法会返回true,最终成功启动跑马灯。

但是呢可能有些读者会说有时候即使android:layout_height=”50dp” 写死了高度属性,跑马灯也有用,这原因也简单,因为TextView源码当中分4种情况会调用startMarquee()方法。
第一种:setSelect(true)方法,即一个textView的isSelect()方法从false->true.
第二种:makeNewLayout()方法,如上述。
第三种:restartMarqueeIfNeeded()方法。在setFrame(),onDraw()方法中均会判定是否要重启跑马灯。
第四种:startStopMarquee(),我们详细说下该方法:

    private void startStopMarquee(boolean start) {if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {if (start) {startMarquee();} else {stopMarquee();}}}

什么时候会调用这个方法呢?

  • onFocusChanged(),onWindowFocusChanged()两个方法。也就是说去更改textView 的焦点或者是更改当前窗口的焦点即可。

为什么设置了android:layout_height=”50dp” 之后没做其他特殊操作,跑马灯也能成功呢,原因就在onWindowFocusChanged()身上,通过日志可以查询的到一个页面加载过程中,涉及跑马灯的几个方法先后调用顺序为

  • makeNewLayout()->setFrame()->onWindowFocusChanged().

也就是说onWindowFocusChanged()方法在setFrame()方法之后,此时布局已完成,textView 的各个layout属性正常,只要符合其他跑马灯启动条件,跑马灯就能成功启动。

而我的苦逼经历就在于通过PagerAdapter 切换ViewPage 的时候onWindowFocusChanged()方法不会重新触发,又设置了android:layout_height=”50dp” ,导致在未完成布局的时候就启动跑马灯。

总的来说我碰到的问题是由于还未布局完成,启动跑马灯失效,等布局完成后,又没有重启跑马灯。在这里提供两种解决思路供读者参考:

  1. 简单点,直接将android:layout_height=”50dp” 修改为android:layout_height=”wrap_content” ,目的是让布局完成后调用setFrame()方法重启跑马灯。
  2. 等布局完成后通过代码重启跑马灯,这类方法比较多,这里举一种方法:监听textView 的GlobalLayout(读者自行百度),该监听方法会多次调用,当监听到对应的textView width 不为0 时,通过setSelect()方法重启跑马灯,并移除GlobalLayout监听。

总结

上述主要描述了原生跑马灯以及自定义跑马灯相关的使用情况,两者各有优劣,读者可以根据自己需求选择不同的方法来实现。文章的末段也说明了我的使用经历,仅仅只是我们无意识的一丁点错误,最后表现上可能会天差地别,就像上面的经历一样,仅仅是android:layout_height=”50dp” 和android:layout_height=”wrap_content” 的区别。最后希望大家在开发过程中,多看下google 源码,它们才是最好的样例,最好的解释,文章当中有些内容还不够详细准确,欢迎读者能够补充并提出指正。

跑马灯的一些使用心得相关推荐

  1. c语言跑马灯实验报告,单片机跑马灯实验

    一.实验目的 1. 熟悉HNIST-2型单片机系统相关硬件电路,程序下载方法: 2. 掌握采用汇编语言与C语言开发单片机系统的程序结构: 3. 掌握51系列单片机通用I/O口的使用. 二.实验前准备 ...

  2. 基于8255A接口芯片的跑马灯程序

    目前这段时间,微机原理这门课上到了接口部分:开始接触到基于接口芯片的汇编编程,这个星期五上机实验完成后.考虑了一下老师说的跑马灯程序,感觉逻辑上实现不是很复杂 于是去实现了一下,但是没跑起来:于是最后 ...

  3. 第一个ARM实验(跑马灯)

    要成功完成基于ARM板的跑马灯实验首先需要:硬件(ARM板),软件(ADS1_2,JLINK,GIVEIO) 现在我们准备好了软件,先对软件进行安装! 一.对ADS1_2的安装: 1.点击进行安装, ...

  4. HarmonyOS 实现跑马灯效果

    跑马灯效果实现效果图 注意事项: HarmonyOS里面Text由于multiple_lines默认为false 所以我们可以直接来设置跑马灯效果,当想设置Text换行的时候设置multiple_li ...

  5. Android 使用 ellipsize 实现文字横向移动效果(跑马灯效果)

    实现的效果图如下 ellipsize 可以设置跑马灯效果 具体代码设置如下 <TextViewandroid:layout_width="match_parent"andro ...

  6. linux arm中断跑马灯,S3C2410 MDK实验---ARM汇编语言实现跑马灯

    具体思路: 由于开发板是将GPF4-7与灯相连,所以通过设置GPFCON控制寄存器将将LED灯相连的引脚4,5,6,7设置为输出功能,将他们的上拉电阻设置为禁用(设置GPFUP为0),通过修改GPFD ...

  7. 微信小程序 跑马灯效果完整代码附效果图

    微信小程序开发交流qq群   173683895    承接微信小程序开发.扫码加微信. 正文: 一:功能介绍及讲解 实现的跑马灯(跑马灯里面显示文章的title)的效果,并在右侧有个查看文章的按钮, ...

  8. iOS - 支持水平 / 垂直显示自动滚动的跑马灯控件 --- SKAutoScrollLabel 的使用和实现

    原文链接:http://www.jianshu.com/p/7221bc08f26a SKAutoScrollLabel 是一个同时支持水平 / 垂直两种类型的 "跑马灯" 效果的 ...

  9. iOS 跑马灯封装(带点击事件)

    1.WAdvertScrollView.h #import <UIKit/UIKit.h> @class WAdvertScrollView; typedef enum : NSUInte ...

  10. TextView实现跑马灯效果

    经常使用TextView会出现这样的情况,有限的空间内只能写一行,然后导致好多文字被"..."表示了,如图: 而且有可能是重要信息被隐藏了,于是就有了跑马灯效果. 实现方式很简单: ...

最新文章

  1. 弯曲评论上关于SOC的一段文章【整理】
  2. 交叉熵损失函数、修正Huber损失、极大似然估计、负对数似然、似然与交叉熵、KL散度
  3. git 只merge部分_[Skill]俩小时掌握多人开发中git的主要用法
  4. 入手一个windows ce系统的可以打电话的HPC,测试在上面发表博客
  5. Linux时间同步+国内常用的NTP服务器地址
  6. FreeMarker 一二事 - 静态模板的使用与生成
  7. 吴恩达 coursera ML 第九课总结+作业答案
  8. java enum.parse_java 枚举的简单使用(enum)
  9. 在laravel5.8中集成swoole组件----用协程实现的服务端和客户端(一)
  10. 数据结构之图的存储结构:邻接矩阵法
  11. 机器学习中的数学(一)--基础数学与基本微分学
  12. 图像patch feature源码
  13. Matlab调用系统命令行利用Internet Download Manager(IDM)实现批量下载
  14. 经纬度WGS84地理坐标系转换成CGCS2000坐标系步骤
  15. scrapy 爬取酷狗热门歌手音乐
  16. 一盘多用,Linux安装盘+window PE启动盘
  17. 如何将平板设置为笔记本的扩展屏
  18. ubuntu批量解压分卷文件
  19. C语言实现光栅化实现过程,为什么要光栅化?怎么实现光栅化方法?
  20. 青海省谷歌地球高程DEM等高线下载

热门文章

  1. Word 打印 PrintOut 方法
  2. 解决isilon网络配置界面无配置显示
  3. 【视频插帧】XVFI: eXtreme Video Frame Interpolation
  4. (QACNN)自然语言处理:智能问答 IBM 保险QA QACNN 实现笔记
  5. C++Builder的基本功能
  6. Laravel文档梳理7、视图
  7. ios 程序中安装 描述文件
  8. python 检查域名是否可以访问_利用Python实现DGA域名检测
  9. 一个非常好用的批量图片压缩工具
  10. 爬虫使用代理socks