利用 TextView 自定义 View

公司正在开发的项目中有很多这样需求的样式,例图如下:
在这里插入图片描述
在这里插入图片描述
目前的做法是使用 textview 然后设置它的 background , 在 drawable 里面设置 xml 资源来指定 颜色 、圆角 、是否填充、边框线宽等。这样的做法没有问题,常规都是这样做,有点小不爽的是只要我的颜色、圆角角度或者其他参数有任意变化我都需要去新建一个 xml 资源文件,最后资源文件特别多写起来还有一定程度的费时力。

那么开始想办法优化,需求推动技术。文章的主角自定义 View 登场了。

思考

首先看到上面这么对例图,开始思考,我大致将上述图分为两类

  • 空心、半圆边框线
  • 实心、矩形带圆角、左侧支持添加图片引用 (最好支持选中和非选中状态颜色文字图片变化)

选型

此自定义 View 是继承 View 还是继承 TextView ? 分析例图,居中的文字颜色等都是 TextView 具备的属性,如果继承 view 自己需要处理的部分测量绘制的工作量会更大,这里我们尝试继承于 TextView。

先来看个半成品

在这里插入图片描述
最初的设计我是准备将上文思考部分的的两类功能聚合在一起

于是我的自定义属性 和 代码中:

  public enum ShapeMode {
        /**
         * 普通模式
         */
        NORMAL(0),
        /**
         * 圆角模式
         */
        FILLET(1),
        /**
         * 圆形模式
         */
        CIRCULAR(2);

        private int mode;

        ShapeMode(int mode) {
            this.mode = mode;
        }

        public int getMode() {
            return mode;
        }

        public static ShapeMode setValue(int mode) {
            for (ShapeMode s : ShapeMode.values()) {
                if (mode == s.getMode()) {
                    return s;
                }
            }
            return NORMAL;
        }
    }


    <declare-styleable name="JuLiveTextView">
        <attr name="Mode">
            <enum name="Fillet" value="1" /> <!--圆角模式-->
            <enum name="Circular" value="2" /><!--圆形模式-->
        </attr>
    </declare-styleable>

定义了两个模式 Fillet 和 Circular 圆角模式 和 圆形模式的枚举,并且要求必须指定其一。如果没指定两者其一就使用强制抛出异常

 throw new IllegalArgumentException("ShapeMode must be specified as FILLET or CIRCULAR, otherwise use TextView");

嘿,小老弟。既不用圆角模式也不用圆形模式你还用我干嘛,回去用 TextView 去吧。

以为自己这样的设计优良,暗中自得了一把,后来在编码的过程中感觉自定义属性过于复杂,不仅要求使用者理解两个 Mode 的意思,还要求使用者针对不同的 Mode 去设置类似于

    <declare-styleable name="JuLiveTextView">
      ......
        <attr name="filletColor" format="color" /> <!--圆角模式下背景颜色-->
        <attr name="filletRadius" format="dimension" /> <!--圆角模式下圆角半径-->
        <attr name="circularColor" format="color" /> <!--圆形模式下边框线颜色-->
        <attr name="circularLineSize" format="dimension" /> <!--圆形模式下边框线粗细-->
        <attr name="circularRadius" format="dimension" /> <!--圆形模式下圆角半径-->
    </declare-styleable>

不同的 Mode 模式下还需要指定不同 circularXX 或 filletXX 对开发者理解和使用成本过高,自己在写代码中逻辑也不够清晰,遂放弃这种聚合设计。

一分为二我将两个 view 分别命名为

  • CircularTextView 空心、半圆边框线
  • RectTextView 实心、矩形带圆角、左侧支持添加图片引用

CircularTextView

先易后难的原则先来 CircularTextView

来确定一下 CircularTextView 的自定义属性 ,我们来看几个设计稿的例图

在这里插入图片描述

从上面两个例子我们得出:
1 文字的颜色大小字体,我们不需要理会依赖父类 TextView 的实现
2 线框的宽高 width dp 和 height dp 也不理会依赖父类,这里没有 wrap_content 的场景
3 边框线的宽度,目前所看到的设计稿全是 1dp 在 View 中直接指定死
4 边框线的颜色,很简单同父类的 TextView
5 文字默认在 View 中设置居中

好了,综上所述得出。我们唯一需要自定义属性的就是圆角角度


    <declare-styleable name="CircularTextView">
        <attr name="circularRadius" format="dimension" />
    </declare-styleable>


public class CircularTextView extends AppCompatTextView {

    private float mCircularRadius;

    private RectF circularRect;

    private Paint mPaint;

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

    public CircularTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircularTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircularTextView);
        mCircularRadius = typedArray.getDimension(R.styleable.CircularTextView_circularRadius, 0);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(getCurrentTextColor());
        mPaint.setStrokeWidth(2); 
        mPaint.setAntiAlias(true);
        circularRect = new RectF();
        setGravity(Gravity.CENTER);
        typedArray.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        circularRect.left = 0;
        circularRect.right = getWidth();
        circularRect.top = 0;
        circularRect.bottom = getHeight();
        canvas.drawRoundRect(circularRect, mCircularRadius, mCircularRadius, mPaint);
    }
}

上真机效果图

在这里插入图片描述
完全符合我们的预期 ,而且使用超简单。


 <com.custom.view.julivetextview.CircularTextView
        android:layout_width="230dp"
        android:layout_height="44dp"
        android:layout_margin="10dp"
        android:text="我是文案"
        android:textColor="#00C0EB"
        android:textSize="16sp"
        app:circularRadius="22dp" />

唯一根据设计稿指定圆角 dp 即可, 利用 TextView 自身存在的属性,短短不到 40 行代码就实现了我的需求,也达到了易用性 !

注意: 点击事件符合 TextView 的点击事件,无需代码中自定义

RectTextView

来分析下 RectTextView 需要什么自定义属性

在这里插入图片描述
1 圆角角度
2 背景颜色

    <declare-styleable name="RectTextView">
        <attr name="rectRadius" format="dimension" />
        <attr name="rectColor" format="color" />
    </declare-styleable>
public class RectTextView extends AppCompatTextView {

    private Paint mPaint;
    private RectF mRect;
    private float mRectRadius;

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

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

    public RectTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RectTextView);
        mRectRadius = typedArray.getDimension(R.styleable.RectTextView_rectRadius, 0);
        int rectColor = typedArray.getColor(R.styleable.RectTextView_rectColor, 0);
        mPaint = new Paint();
        mPaint.setColor(rectColor);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
        mRect = new RectF();
        setGravity(Gravity.CENTER);
        typedArray.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mRect.left = 0;
        mRect.right = getWidth();
        mRect.top = 0;
        mRect.bottom = getHeight();
        canvas.drawRoundRect(mRect, mRectRadius, mRectRadius, mPaint);
        super.onDraw(canvas);
    }
}


使用很简单


<com.custom.view.julivetextview.RectTextView
        android:layout_width="118dp"
        android:layout_height="42dp"
        android:layout_margin="10dp"
        android:text="我是文案"
        android:textColor="#FFF"
        android:textSize="16sp"
        app:rectColor="#00C0EB"
        app:rectRadius="4dp" />

效果图

在这里插入图片描述
这里提个小插曲,也算一个坑,在 onDraw 方法中一直将自己的绘制逻辑放在 super.onDraw 后导致 TextViev 的字样一直被 RcetF 覆盖。

以为这样就完事了,我们注意到上面例图是存在文字左侧小 icon 的图片,记得 TextView 有个 drawableLeft 的属性来试一把

在这里插入图片描述
惨案,由于 RcetF 是固定的 width height , 这个图片肯定会被撑起来,解决方案是让设计小姐姐切对应大小的 icon ? 不 ,这不是我们的风格

  public RectTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

	......

        Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
        drawable.setBounds(0, 0, (int) getTextSize(), (int) getTextSize());
        setCompoundDrawablePadding(10);
        setCompoundDrawables(drawable, null, null, null);
        typedArray.recycle();
    }


将设置的 drawable 进行边界设置,保持和字体一样宽高 ,来看效果

在这里插入图片描述
图案大小符合我们的预期,只是这个间距是什么鬼?

xml 和 代码中

android:drawablePadding="1dp"


setCompoundDrawablePadding(10);

在这里插入图片描述

都没有效果,搞不动了留待有缘人帮忙解决

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值