当前位置:   article > 正文

Android实现文本过长时右边渐隐,聚焦时跑马灯效果_android 文字走马灯 渐隐

android 文字走马灯 渐隐

写在前面:原创不易,请不要吝啬你的大拇指,点个赞再走呗。然而贴代码很容易,但那不一定有帮助。本文试图从问题点出发,逐步分解,直到实现最终效果。

 

目录

Part1

1. 如何实现文本右边渐隐的效果

2.如何判断文本内容的宽度超出了布局宽度

3.如何在不聚焦时显示渐隐效果,聚焦时显示跑马灯效果

4.考虑更好的复用

Part2

1.自定义View代码

2.自定义属性代码

3.Color颜色工具类代码

4.Demo布局代码

5.Demo演示Activity

Part3

特别注意


 

在Android中,我们知道对于控件TextView,在布局时有时候需要单行文本,但可能会存在文本内容的宽度超出了布局宽度,这个时候就需要我们做一定的兼容,不然可能会显示出不太优雅的UI效果。

针对于此情况——文本内容的宽度超出了布局宽度,你应该知道Android通过ellipsize属性,提供了如下几种解决方式:

  • start:点点点省略号显示在文本开头。
  • middle:点点点省略号显示在文本中间。
  • end:点点点省略号显示在文本结尾。
  • marquee:跑马灯效果。
  • none:直接将文本截断,并且不显示省略号。

基于Android默认的实现方式并不难,可能就跑马灯效果时会有点小坑(比如:需要聚焦,或者右边滚动的时候有渐变问题),不过都很好解决。但是我们今天要实现的UI效果,是需要自定义View的,因为目前Android原生并不直接支持——文本过长时右边渐隐,聚焦时跑马灯效果。UI效果如下图所示(静态+动态):

要实现此UI效果,我们需要思考如下问题:

  • 首先,需要思考的是,如何实现文本右边渐隐的效果;
  • 其次,还需要思考,如何判断文本内容的宽度超出了布局宽度;
  • 然后,考虑如何在不聚焦时显示渐隐效果,聚焦时显示跑马灯效果;
  • 最后,或许需要考虑如何做到让其他应用开发者复用,减少造轮子的时间。

Part1

上面提到的问题,主要设计到Android的View绘制流程、画布Canvas、画笔Paint、着色器Shader以及它的子类LinearGradient。如果你和我一样对上面的内容不是特别清楚,也不会影响你继续阅读这篇文章,因为我会很直白的贴出代码供大佬们使用。下面我们一个一个问题展开来说。

1. 如何实现文本右边渐隐的效果

直观来看,这个UI效果感觉上是在文本末尾添加了一层遮罩,颜色越来越浅直到完全透明。所以简单来看,可能很多同学会想到画一层遮罩阴影去实现这个效果。但很遗憾,我会告诉你,是不行的,因为我试过,何况文本背景是透明的,你如何做到阴影遮罩只遮在文本上面,而不至于画一个难看的矩形阴影遮罩?

但这个问题正好引出了正确的解决办法。我们需要的是遮罩效果只作用于文本,那么Android是否有提供一种修改文本的方法,做到实现文本渐隐的效果。答案是,有的,当然除了我提供的这种方式,你可能也会去Google/Baidu看看有没有其他方式。

其实,实现方式是比较简单的,我们只需要改变画笔的着色器就可以做到。即Paint类的setShader(Shader shader)方法:

paint.setShader(gradient);

接下来,我们只需要定义一个Shader就OK:

  1. gradient = new LinearGradient(gradientStart, 0, (float) measuredWidth, 0,
  2. new int[]{ColorUtils.changeAlpha(getCurrentTextColor(), 0xFF), getCurrentTextColor(), Color.TRANSPARENT},
  3. new float[]{0f, gradientRatio, 1f},
  4. Shader.TileMode.CLAMP);

2.如何判断文本内容的宽度超出了布局宽度

这个的实现是比较容易的,获得文本内容的宽度和布局的宽度,比较一下就OK。可以通过Paint类的measureText(String text)测量文本内容宽度,getText()获取文本内容,getMeasuredWidth()获取布局宽度。如下代码所示:

paint.measureText((String) getText()) > getMeasuredWidth()

3.如何在不聚焦时显示渐隐效果,聚焦时显示跑马灯效果

那就需要设置焦点监听,进而改变文本的UI效果,如下代码所示:

  1. TCLTextView tvHide = findViewById(R.id.tv_element_text_view_long_hide);
  2. tvHide.setEllipsize(null);
  3. tvHide.setOnFocusChangeListener((view, focus) -> {
  4. if (focus) {
  5. tvHide.setEllipsize(TextUtils.TruncateAt.MARQUEE);
  6. tvHide.setTextGradient(false);
  7. } else {
  8. tvHide.setEllipsize(null);
  9. tvHide.setTextGradient(true);
  10. }
  11. });

4.考虑更好的复用

  • 通过自定义属性提供是否显示渐隐效果的功能;
  • 可以将获取焦点部分放在自定义View中,不用其他应用开发者在Java代码中去控制是否显示渐隐效果。

Part2

下面我们一起来回顾下,如何实现此UI效果:

首先,我们需要自定义一个View——TCLTextView,并继承TextView。

然后,需要重写onSizeChanged和onDraw方法。在onSizeChanged方法里面初始化LinearGradient,并拿到默认的文本画笔TextPaint和默认的着色器Shader。在onDraw方法里面判断是否显示渐隐效果——通过一个自定义属性提供出来,并判断文本内容的宽度是否超出了布局宽度,以及获取焦点时设置自定义的Shader、未聚焦时还原Shader。

最后,对于是否显示渐隐效果,还需要提供API出来使用。我们定义了set和get方法,并在set时调用postInvalidate方法,从而重新走onDraw回调。下面贴上代码供大佬们使用。

1.自定义View代码

  1. public class TCLTextView extends TextView {
  2. private TextPaint paint; // 文本画笔
  3. private Shader shader; // 默认Shader
  4. private LinearGradient gradient; // 文本过长渐隐效果
  5. private boolean isTextGradient = false; // 默认没有渐隐效果
  6. public TCLTextView(Context context) {
  7. this(context, null);
  8. }
  9. public TCLTextView(Context context, AttributeSet attrs) {
  10. this(context, attrs, 0);
  11. }
  12. public TCLTextView(Context context, AttributeSet attrs, int defStyle) {
  13. super(context, attrs, defStyle);
  14. initTextGradient(context, attrs);
  15. // ...
  16. }
  17. @SuppressLint("NewApi")
  18. public TCLTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  19. super(context, attrs, defStyleAttr, defStyleRes);
  20. initTextGradient(context, attrs);
  21. // ...
  22. }
  23. @Override
  24. protected void onSizeChanged(int w, int h, int oldW, int oldH) {
  25. super.onSizeChanged(w, h, oldW, oldH);
  26. int measuredWidth = getMeasuredWidth();
  27. if (measuredWidth > 0) {
  28. float gradientRatio = 2 / 3f; // 渐隐比例,默认从2/3位置开始,可修改
  29. float gradientStart = measuredWidth * gradientRatio; // 渐隐开始位置
  30. gradient = new LinearGradient(gradientStart, 0, (float) measuredWidth, 0,
  31. new int[]{ColorUtils.changeAlpha(getCurrentTextColor(), 0xFF), getCurrentTextColor(), Color.TRANSPARENT},
  32. new float[]{0f, gradientRatio, 1f},
  33. Shader.TileMode.CLAMP); // LinearGradient效果是受文本颜色透明度影响的,于是做出兼容措施。从左到右按比例:无透明文本颜色@0,文本颜色@gradientRatio,完全透明文本颜色@1。
  34. paint = getPaint();
  35. shader = paint.getShader();
  36. }
  37. }
  38. @Override
  39. protected void onDraw(Canvas canvas) {
  40. if (paint != null && gradient != null) {
  41. if (isTextGradient && paint.measureText((String) getText()) > getMeasuredWidth()) {
  42. paint.setShader(gradient);
  43. } else {
  44. paint.setShader(shader);
  45. }
  46. }
  47. super.onDraw(canvas);
  48. }
  49. private void initTextGradient(Context context, AttributeSet attrs) {
  50. TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TCLTextView);
  51. isTextGradient = typedArray.getBoolean(R.styleable.TCLTextView_ElementNeedTextGradient, false);
  52. typedArray.recycle();
  53. }
  54. public boolean isTextGradient() {
  55. return isTextGradient;
  56. }
  57. public void setTextGradient(boolean textGradient) {
  58. if (textGradient != isTextGradient) {
  59. isTextGradient = textGradient;
  60. postInvalidate();
  61. }
  62. }
  63. }

2.自定义属性代码

  1. <declare-styleable name="TCLTextView">
  2. <!--其他属性-->
  3. <attr name="ElementNeedTextGradient" format="boolean" />
  4. </declare-styleable>

多说一句,害怕和我一样的小白不知道在哪自定义属性——存放在res--values的attrs.xml文件中,如果没有此文件就创建一个。

3.Color颜色工具类代码

  1. public class ColorUtils {
  2. /**
  3. * 修改颜色透明度
  4. * @param color 颜色
  5. * @param alpha 需要修改成的透明橙
  6. * @return 修改透明度后的颜色
  7. */
  8. public static int changeAlpha(int color, int alpha) {
  9. int red = Color.red(color);
  10. int green = Color.green(color);
  11. int blue = Color.blue(color);
  12. return Color.argb(alpha, red, green, blue);
  13. }
  14. }

4.Demo布局代码

a、activity_demo_text_view_long_hide.xml文件。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:background="?attr/element_background_picture"
  8. android:gravity="center"
  9. android:orientation="vertical">
  10. <com.tcl.uicompat.TCLTextView
  11. style="@style/TextViewEllipsize"
  12. android:ellipsize="end"
  13. android:text="TextView字符过长时,显示省略号,无跑马灯效果。"
  14. tools:ignore="HardcodedText" />
  15. <com.tcl.uicompat.TCLTextView
  16. android:id="@+id/tv_element_text_view_long_hide_no"
  17. style="@style/TextViewEllipsize"
  18. android:ellipsize="marquee"
  19. android:text="TextView字符过长时,不显示省略号,有跑马灯效果。"
  20. tools:ignore="HardcodedText" />
  21. <com.tcl.uicompat.TCLTextView
  22. android:id="@+id/tv_element_text_view_long_hide"
  23. style="@style/TextViewEllipsize"
  24. android:ellipsize="marquee"
  25. android:text="TextView字符过长时,把省略号效果改为渐隐效果。"
  26. app:ElementNeedTextGradient="true"
  27. tools:ignore="HardcodedText" />
  28. </LinearLayout>

b、styles.xml文件。

  1. <style name="TextViewEllipsize">
  2. <item name="android:layout_width">600px</item>
  3. <item name="android:layout_height">wrap_content</item>
  4. <item name="android:layout_marginTop">80px</item>
  5. <item name="android:focusable">true</item>
  6. <item name="android:marqueeRepeatLimit">marquee_forever</item>
  7. <item name="android:singleLine">true</item>
  8. <item name="android:textColor">#B3FFFFFF</item>
  9. <item name="android:textSize">28px</item>
  10. </style>

5.Demo演示Activity

  1. public class TextViewLongHideDemoActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_demo_text_view_long_hide);
  6. TCLTextView tvHideNo = findViewById(R.id.tv_element_text_view_long_hide_no);
  7. tvHideNo.setEllipsize(null);
  8. tvHideNo.setOnFocusChangeListener((view, focus) -> {
  9. if (focus) {
  10. tvHideNo.setEllipsize(TextUtils.TruncateAt.MARQUEE);
  11. } else {
  12. tvHideNo.setEllipsize(null);
  13. }
  14. });
  15. TCLTextView tvHide = findViewById(R.id.tv_element_text_view_long_hide);
  16. tvHide.setEllipsize(null);
  17. tvHide.setOnFocusChangeListener((view, focus) -> {
  18. if (focus) {
  19. tvHide.setEllipsize(TextUtils.TruncateAt.MARQUEE);
  20. tvHide.setTextGradient(false);
  21. } else {
  22. tvHide.setEllipsize(null);
  23. tvHide.setTextGradient(true);
  24. }
  25. });
  26. }
  27. }

Part3

其实这个UI效果的实现比较简单的,核心在setShader这个方法,当你不知道Android提供了此API你可能会一筹莫展,但一旦你获取到了这方面的知识,那应该可以触类旁通,利用LinearGradient去实现更多绚丽的UI,关于LinearGradient的使用可以参考这篇文章的介绍——关于着色器LinearGradient的使用

特别注意

这其中其实有个坑,TextView的color如果带有透明度,未聚焦时文本的透明度会降低,聚焦时正常,LinearGradient的构造函数就需要使用文中介绍的带数组的方式;如果不带透明度,那么直接使用startColor和endColor的那个构造函数即可。即如下方式:

  1. gradient = new LinearGradient(0, 0, (float) measuredWidth, 0,
  2. new int[]{getCurrentTextColor(), Color.TRANSPARENT},
  3. new float[]{gradientRatio, 1f},
  4. Shader.TileMode.CLAMP);

这个坑,让我啃了一天,印象深刻,篇幅原因就不在此详述。

 

——书山有路勤为径,学海无涯苦作舟。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/420720
推荐阅读
相关标签
  

闽ICP备14008679号