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:

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

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

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

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

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

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

TCLTextView tvHide = findViewById(R.id.tv_element_text_view_long_hide);
tvHide.setEllipsize(null);
tvHide.setOnFocusChangeListener((view, focus) -> {
            if (focus) {
                tvHide.setEllipsize(TextUtils.TruncateAt.MARQUEE);
                tvHide.setTextGradient(false);
            } else {
                tvHide.setEllipsize(null);
                tvHide.setTextGradient(true);
            }
        });

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代码

public class TCLTextView extends TextView {

    private TextPaint paint; // 文本画笔
    private Shader shader; // 默认Shader
    private LinearGradient gradient; // 文本过长渐隐效果
    private boolean isTextGradient = false; // 默认没有渐隐效果

    public TCLTextView(Context context) {
        this(context, null);
    }

    public TCLTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TCLTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initTextGradient(context, attrs);
        // ...
    }

    @SuppressLint("NewApi")
    public TCLTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initTextGradient(context, attrs);
        // ...
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        super.onSizeChanged(w, h, oldW, oldH);
        int measuredWidth = getMeasuredWidth();
        if (measuredWidth > 0) {
            float gradientRatio = 2 / 3f; // 渐隐比例,默认从2/3位置开始,可修改
            float gradientStart = measuredWidth * gradientRatio; // 渐隐开始位置
            gradient = new LinearGradient(gradientStart, 0, (float) measuredWidth, 0,
                    new int[]{ColorUtils.changeAlpha(getCurrentTextColor(), 0xFF), getCurrentTextColor(), Color.TRANSPARENT},
                    new float[]{0f, gradientRatio, 1f},
                    Shader.TileMode.CLAMP); // LinearGradient效果是受文本颜色透明度影响的,于是做出兼容措施。从左到右按比例:无透明文本颜色@0,文本颜色@gradientRatio,完全透明文本颜色@1。
            paint = getPaint();
            shader = paint.getShader();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (paint != null && gradient != null) {
            if (isTextGradient && paint.measureText((String) getText()) > getMeasuredWidth()) {
                paint.setShader(gradient);
            } else {
                paint.setShader(shader);
            }
        }
        super.onDraw(canvas);
    }

    private void initTextGradient(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TCLTextView);
        isTextGradient = typedArray.getBoolean(R.styleable.TCLTextView_ElementNeedTextGradient, false);
        typedArray.recycle();
    }

    public boolean isTextGradient() {
        return isTextGradient;
    }

    public void setTextGradient(boolean textGradient) {
        if (textGradient != isTextGradient) {
            isTextGradient = textGradient;
            postInvalidate();
        }
    }

}

2.自定义属性代码

    
        
        
    

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

3.Color颜色工具类代码

public class ColorUtils {

    /**
     * 修改颜色透明度
     * @param color 颜色
     * @param alpha 需要修改成的透明橙
     * @return 修改透明度后的颜色
     */
    public static int changeAlpha(int color, int alpha) {
        int red = Color.red(color);
        int green = Color.green(color);
        int blue = Color.blue(color);
        return Color.argb(alpha, red, green, blue);
    }

}

4.Demo布局代码

a、activity_demo_text_view_long_hide.xml文件。




    

    

    

b、styles.xml文件。

5.Demo演示Activity

public class TextViewLongHideDemoActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo_text_view_long_hide);
        TCLTextView tvHideNo = findViewById(R.id.tv_element_text_view_long_hide_no);
        tvHideNo.setEllipsize(null);
        tvHideNo.setOnFocusChangeListener((view, focus) -> {
            if (focus) {
                tvHideNo.setEllipsize(TextUtils.TruncateAt.MARQUEE);
            } else {
                tvHideNo.setEllipsize(null);
            }
        });

        TCLTextView tvHide = findViewById(R.id.tv_element_text_view_long_hide);
        tvHide.setEllipsize(null);
        tvHide.setOnFocusChangeListener((view, focus) -> {
            if (focus) {
                tvHide.setEllipsize(TextUtils.TruncateAt.MARQUEE);
                tvHide.setTextGradient(false);
            } else {
                tvHide.setEllipsize(null);
                tvHide.setTextGradient(true);
            }
        });
    }

}

Part3

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

特别注意

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

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

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

 

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

 

你可能感兴趣的:(Android:开发实践,TextView,跑马灯,阴影渐变,文本过长)