赞
踩
写在前面:原创不易,请不要吝啬你的大拇指,点个赞再走呗。然而贴代码很容易,但那不一定有帮助。本文试图从问题点出发,逐步分解,直到实现最终效果。
目录
在Android中,我们知道对于控件TextView,在布局时有时候需要单行文本,但可能会存在文本内容的宽度超出了布局宽度,这个时候就需要我们做一定的兼容,不然可能会显示出不太优雅的UI效果。
针对于此情况——文本内容的宽度超出了布局宽度,你应该知道Android通过ellipsize属性,提供了如下几种解决方式:
基于Android默认的实现方式并不难,可能就跑马灯效果时会有点小坑(比如:需要聚焦,或者右边滚动的时候有渐变问题),不过都很好解决。但是我们今天要实现的UI效果,是需要自定义View的,因为目前Android原生并不直接支持——文本过长时右边渐隐,聚焦时跑马灯效果。UI效果如下图所示(静态+动态):
要实现此UI效果,我们需要思考如下问题:
上面提到的问题,主要设计到Android的View绘制流程、画布Canvas、画笔Paint、着色器Shader以及它的子类LinearGradient。如果你和我一样对上面的内容不是特别清楚,也不会影响你继续阅读这篇文章,因为我会很直白的贴出代码供大佬们使用。下面我们一个一个问题展开来说。
直观来看,这个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);
这个的实现是比较容易的,获得文本内容的宽度和布局的宽度,比较一下就OK。可以通过Paint类的measureText(String text)测量文本内容宽度,getText()获取文本内容,getMeasuredWidth()获取布局宽度。如下代码所示:
paint.measureText((String) getText()) > getMeasuredWidth()
那就需要设置焦点监听,进而改变文本的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);
- }
- });
下面我们一起来回顾下,如何实现此UI效果:
首先,我们需要自定义一个View——TCLTextView,并继承TextView。
然后,需要重写onSizeChanged和onDraw方法。在onSizeChanged方法里面初始化LinearGradient,并拿到默认的文本画笔TextPaint和默认的着色器Shader。在onDraw方法里面判断是否显示渐隐效果——通过一个自定义属性提供出来,并判断文本内容的宽度是否超出了布局宽度,以及获取焦点时设置自定义的Shader、未聚焦时还原Shader。
最后,对于是否显示渐隐效果,还需要提供API出来使用。我们定义了set和get方法,并在set时调用postInvalidate方法,从而重新走onDraw回调。下面贴上代码供大佬们使用。
- 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();
- }
- }
-
- }
- <declare-styleable name="TCLTextView">
- <!--其他属性-->
- <attr name="ElementNeedTextGradient" format="boolean" />
- </declare-styleable>
多说一句,害怕和我一样的小白不知道在哪自定义属性——存放在res--values的attrs.xml文件中,如果没有此文件就创建一个。
- 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);
- }
-
- }
a、activity_demo_text_view_long_hide.xml文件。
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="?attr/element_background_picture"
- android:gravity="center"
- android:orientation="vertical">
-
- <com.tcl.uicompat.TCLTextView
- style="@style/TextViewEllipsize"
- android:ellipsize="end"
- android:text="TextView字符过长时,显示省略号,无跑马灯效果。"
- tools:ignore="HardcodedText" />
-
- <com.tcl.uicompat.TCLTextView
- android:id="@+id/tv_element_text_view_long_hide_no"
- style="@style/TextViewEllipsize"
- android:ellipsize="marquee"
- android:text="TextView字符过长时,不显示省略号,有跑马灯效果。"
- tools:ignore="HardcodedText" />
-
- <com.tcl.uicompat.TCLTextView
- android:id="@+id/tv_element_text_view_long_hide"
- style="@style/TextViewEllipsize"
- android:ellipsize="marquee"
- android:text="TextView字符过长时,把省略号效果改为渐隐效果。"
- app:ElementNeedTextGradient="true"
- tools:ignore="HardcodedText" />
- </LinearLayout>
b、styles.xml文件。
- <style name="TextViewEllipsize">
- <item name="android:layout_width">600px</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_marginTop">80px</item>
- <item name="android:focusable">true</item>
- <item name="android:marqueeRepeatLimit">marquee_forever</item>
- <item name="android:singleLine">true</item>
- <item name="android:textColor">#B3FFFFFF</item>
- <item name="android:textSize">28px</item>
- </style>
- 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);
- }
- });
- }
-
- }
其实这个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);
这个坑,让我啃了一天,印象深刻,篇幅原因就不在此详述。
——书山有路勤为径,学海无涯苦作舟。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。