自定义圆角背景TextView

自定义圆角背景TextView

我尽量不打错别字,用词准确,不造成阅读障碍

有一段时间没写博客了,因为一些个人原因,以后会慢慢找回状态

我在实际开发中喜欢将TextView代替Button使用(感觉不是一个好习惯),需求中经常会出现圆角的点击按钮,这个时候其实有很多方案:

1.最传统的就是写一个drawable(也就是shape)的xml文件,但是你也不可能遇到不同圆角就写一个xml,遇到不同背景就写一个xml,遇到不同stroke就写一个xml, 这也太麻烦了。

2.写CardView,将TextView嵌套其中,但是写布局不是力求减少布局嵌套吗?这样真的合适吗?

3.自定义TextView

其实我一直都在用第三种方法,github上也有很多开源的组件,今天主要是介绍一个我认为扩展性比较强的,因为我个人比较看重扩展性

先看效果:

在这里插入图片描述

基本满足一般需求吧,才发现,好像stroke绘制的不是很好?

一.定义属性

<attr name="rv_backgroundColor" format="reference|color"/>
<attr name="rv_radius" format="dimension|reference"/>
<attr name="rv_strokeWidth" format="dimension|reference"/>
<attr name="rv_strokeColor" format="reference|color"/>
<attr name="rv_textColor" format="reference|color"/>
<attr name="rv_topLeftRadius" format="dimension|reference"/>
<attr name="rv_topRightRadius" format="dimension|reference"/>
<attr name="rv_bottomLeftRadius" format="dimension|reference"/>
<attr name="rv_bottomRightRadius" format="dimension|reference"/>

定义几个简单属性,这里之所以要定义一个backgroundColor,后面再解释吧,要不然解释起来很空洞。

二.创建一个类,继承自TextView

public class RadiusTextView extends TextView {
    private RadiusViewDelegate delegate;

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

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

    public RadiusTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.delegate = new RadiusViewDelegate(this, context, attrs);
    }

    public RadiusViewDelegate getDelegate() {
        return this.delegate;
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        this.delegate.setBgSelector(); //这里是唯一的调用
    }
}

看起来有点简单,我们没有像传统的自定义布局一样,直接在onMeasure、onLayout、onDraw方法里面做很多绘制工作,而是另写了一个类——RadiusViewDelegate,将本类(this)通过构造方法传进RadiusViewDelegate中,借助onLayout(…)方法,调用this.delegate.setBgSelector();从而进行绘制操作。这也是这种写法具有很好扩展性的原因。

三.进行RadiusViewDelegate的编写

首先要先将所有自定义属性接收:

//view 就是外面穿进来的RadiusTextView
public RadiusViewDelegate(View view, Context context, AttributeSet attrs) {
        this.view = view;
        this.context = context;
        this.obtainAttributes(context, attrs);
    }

    private void obtainAttributes(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.radiusTextView);
        this.backgroundColor = ta.getColor(R.styleable.radiusTextView_rv_backgroundColor, 0);
        this.radius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_radius, 0);
        this.strokeWidth = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_strokeWidth, 0);
        this.strokeColor = ta.getColor(R.styleable.radiusTextView_rv_strokeColor, 0);
        this.textColor = ta.getColor(R.styleable.radiusTextView_rv_textColor, 2147483647);
        this.textPressedColor = ta.getColor(R.styleable.radiusTextView_rv_textPressedColor, 2147483647);
        this.textEnabledColor = ta.getColor(R.styleable.radiusTextView_rv_textEnabledColor, 2147483647);
        this.topLeftRadius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_topLeftRadius, 0);
        this.topRightRadius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_topRightRadius, 0);
        this.bottomLeftRadius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_bottomLeftRadius, 0);
        this.bottomRightRadius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_bottomRightRadius, 0);
        this.isRippleEnable = ta.getBoolean(R.styleable.radiusTextView_rv_rippleEnable, false);
        ta.recycle();
    }

其实外面只调用了一个方法——setBgSelector(), 所以我们所有的重点都在这里面

private GradientDrawable gdBackground = new GradientDrawable();
public void setBgSelector() {
        StateListDrawable bg = new StateListDrawable();
        this.setDrawable(this.gdBackground, this.backgroundColor, this.strokeColor);
         if (this.view.isEnabled()) {
              bg.addState(new int[]{-16842919, -16842913}, this.gdBackground);
         }
         if (Build.VERSION.SDK_INT >= 16) {
              this.view.setBackground(bg);
         } else {
              this.view.setBackgroundDrawable(bg);
         }
    }

最后都会走到this.view.setBackground(bg); 所以最重要是这个bg,在这之前首先是创建了一个新的对象StateListDrawable———即bg; 其实这个drawable类对应的是xml里面的selector标签,shap标签对应的是GradientDrawable类,但是在这里,在简单情况下,两者的显示效果是一样的,作者的其实是为了其它功能的原因使用了StateListDrawable,这里先不管。然后调用bg.setDrawable(…)方法,这个方法是设置圆角的。这里的gdBackground是类初始化的时候新new的一个GradientDrawable对象,后面全部代码贴出时会看到,具体看代码:

private void setDrawable(GradientDrawable gd, int color, int strokeColor) {
        gd.setColor(color);
        if (this.topLeftRadius <= 0 && this.topRightRadius <= 0 && this.bottomRightRadius <= 0 && this.bottomLeftRadius <= 0) {
            gd.setCornerRadius((float)this.radius);
        } else {
            this.radiusArr[0] = (float)this.topLeftRadius;
            this.radiusArr[1] = (float)this.topLeftRadius;
            this.radiusArr[2] = (float)this.topRightRadius;
            this.radiusArr[3] = (float)this.topRightRadius;
            this.radiusArr[4] = (float)this.bottomRightRadius;
            this.radiusArr[5] = (float)this.bottomRightRadius;
            this.radiusArr[6] = (float)this.bottomLeftRadius;
            this.radiusArr[7] = (float)this.bottomLeftRadius;
            gd.setCornerRadii(this.radiusArr);
        }
        gd.setStroke(this.strokeWidth, strokeColor);
    }

代码很清晰,没啥好解释的,就是设置各种属性;可以看到第一个参数是GradientDrawable,也就是shape,回到上一个代码,接下来是bg.addState(…),这个方法的调用就很重要了,直白的说就是把我们的shap标签加入到selector标签中,就像这样:

<selector>
  <item>
     <shape>
     </shape>
  </item>
</selector>

有经验的同学应该知道这样其实效果和只写一个shape标前是一样的,因为简单嘛~,所以最后的this.view.setBackground(bg);和我们平常用的设置背景的方法是一样的。这样整个逻辑就很简单了,其实就是我们代码完成了drawable的XML文件编写,这也解释了为什么定义了一个rv_backgroundColor属性,因为属性顶掉了,我们就是在xml设置background啊,因为view的background属性就被我们顶掉了,如果我们不设置background,那么view就没有颜色了,笑哭~

RadiusViewDelegate完整代码如下:(我删了一部分代码)

package com.study.longl.myselfviewdemo.Views.radiusview;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import com.study.longl.myselfviewdemo.R;

public class RadiusViewDelegate {
    private View view;
    private Context context;
    private GradientDrawable gdBackground = new GradientDrawable();
    private int backgroundColor;
    private int radius;
    private int topLeftRadius;
    private int topRightRadius;
    private int bottomLeftRadius;
    private int bottomRightRadius;
    private int strokeWidth;
    private int strokeColor;
    private int textColor;
    private int textPressedColor;
    private int textEnabledColor;
    private boolean isRadiusHalfHeight;
    private boolean isWidthHeightEqual;
    private boolean isRippleEnable;
    private float[] radiusArr = new float[8];

    public RadiusViewDelegate(View view, Context context, AttributeSet attrs) {
        this.view = view;
        this.context = context;
        this.obtainAttributes(context, attrs);
    }

    private void obtainAttributes(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.radiusTextView);
        this.backgroundColor = ta.getColor(R.styleable.radiusTextView_rv_backgroundColor, 0);
        this.radius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_radius, 0);
        this.strokeWidth = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_strokeWidth, 0);
        this.strokeColor = ta.getColor(R.styleable.radiusTextView_rv_strokeColor, 0);
        this.textColor = ta.getColor(R.styleable.radiusTextView_rv_textColor, 2147483647);
        this.textPressedColor = ta.getColor(R.styleable.radiusTextView_rv_textPressedColor, 2147483647);
        this.textEnabledColor = ta.getColor(R.styleable.radiusTextView_rv_textEnabledColor, 2147483647);
        this.topLeftRadius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_topLeftRadius, 0);
        this.topRightRadius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_topRightRadius, 0);
        this.bottomLeftRadius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_bottomLeftRadius, 0);
        this.bottomRightRadius = ta.getDimensionPixelSize(R.styleable.radiusTextView_rv_bottomRightRadius, 0);
        this.isRippleEnable = ta.getBoolean(R.styleable.radiusTextView_rv_rippleEnable, false);
        ta.recycle();
    }

    public void setBackgroundColor(int backgroundColor) {
        this.backgroundColor = backgroundColor;
        this.setBgSelector();
    }

    public void setRadius(int radius) {
        this.radius = this.dp2px((float)radius);
        this.setBgSelector();
    }

    public void setStrokeWidth(int strokeWidth) {
        this.strokeWidth = this.dp2px((float)strokeWidth);
        this.setBgSelector();
    }

    public void setStrokeColor(int strokeColor) {
        this.strokeColor = strokeColor;
        this.setBgSelector();
    }

    public void setTextColor(int textColor) {
        this.textColor = textColor;
        this.setBgSelector();
    }

    public void setEadiusHalfHeightEnable(boolean isRadiusHalfHeight) {
        this.isRadiusHalfHeight = isRadiusHalfHeight;
        this.setBgSelector();
    }

    public void setWidthHeightEqualEnable(boolean isWidthHeightEqual) {
        this.isWidthHeightEqual = isWidthHeightEqual;
        this.setBgSelector();
    }

    public void setTopLeftRadius(int topLeftRadius) {
        this.topLeftRadius = topLeftRadius;
        this.setBgSelector();
    }

    public void setTopRightRadius(int topRightRadius) {
        this.topRightRadius = topRightRadius;
        this.setBgSelector();
    }

    public void setBottomLeftRadius(int bottomLeftRadius) {
        this.bottomLeftRadius = bottomLeftRadius;
        this.setBgSelector();
    }

    public void setBottomRightRadius(int bottomRightRadius) {
        this.bottomRightRadius = bottomRightRadius;
        this.setBgSelector();
    }

    public int getBackgroundColor() {
        return this.backgroundColor;
    }

    public int getRadius() {
        return this.radius;
    }

    public int getStrokeWidth() {
        return this.strokeWidth;
    }

    public int getStrokeColor() {
        return this.strokeColor;
    }

    public boolean getRadiusHalfHeightEnable() {
        return this.isRadiusHalfHeight;
    }

    public boolean getWidthHeightEqualEnable() {
        return this.isWidthHeightEqual;
    }

    public int gettopLeftRadius() {
        return this.topLeftRadius;
    }

    public int gettopRightRadius() {
        return this.topRightRadius;
    }

    public int getbottomLeftRadius() {
        return this.bottomLeftRadius;
    }

    public int getbottomRightRadius() {
        return this.bottomRightRadius;
    }

    protected int dp2px(float dp) {
        float scale = this.context.getResources().getDisplayMetrics().density;
        return (int)(dp * scale + 0.5F);
    }

    protected int sp2px(float sp) {
        float scale = this.context.getResources().getDisplayMetrics().scaledDensity;
        return (int)(sp * scale + 0.5F);
    }

    private void setDrawable(GradientDrawable gd, int color, int strokeColor) {
        gd.setColor(color);
        if (this.topLeftRadius <= 0 && this.topRightRadius <= 0 && this.bottomRightRadius <= 0 && this.bottomLeftRadius <= 0) {
            gd.setCornerRadius((float)this.radius);
        } else {
            this.radiusArr[0] = (float)this.topLeftRadius;
            this.radiusArr[1] = (float)this.topLeftRadius;
            this.radiusArr[2] = (float)this.topRightRadius;
            this.radiusArr[3] = (float)this.topRightRadius;
            this.radiusArr[4] = (float)this.bottomRightRadius;
            this.radiusArr[5] = (float)this.bottomRightRadius;
            this.radiusArr[6] = (float)this.bottomLeftRadius;
            this.radiusArr[7] = (float)this.bottomLeftRadius;
            gd.setCornerRadii(this.radiusArr);
        }
        gd.setStroke(this.strokeWidth, strokeColor);
    }

    public void setBgSelector() {
        StateListDrawable bg = new StateListDrawable();
        this.setDrawable(this.gdBackground, this.backgroundColor, this.strokeColor);
        if (Build.VERSION.SDK_INT >= 21 && this.isRippleEnable && this.view.isEnabled()) {
            RippleDrawable rippleDrawable = new RippleDrawable(this.getColorSelector(this.backgroundColor, this.backgroundColor, this.backgroundColor == 2147483647 ? this.backgroundColor : this.backgroundColor), this.gdBackground, null);
            this.view.setBackground(rippleDrawable);
        } else {
            if (this.view.isEnabled()) {
                bg.addState(new int[]{-16842919, -16842913}, this.gdBackground);
            }
            if (Build.VERSION.SDK_INT >= 16) {
                this.view.setBackground(bg);
            } else {
                this.view.setBackgroundDrawable(bg);
            }
        }
    }

    @TargetApi(11)
    private ColorStateList getColorSelector(int normalColor, int pressedColor, int enabledColor) {
        return new ColorStateList(new int[][]{{16842908}, {16843518}, {16842919}, {-16842910}, new int[0]}, new int[]{pressedColor, pressedColor, pressedColor, enabledColor, normalColor});
    }
}

逻辑非常简单,代码量也小,完全没必要引入三方依赖,而且这种写法最闪亮的点在于扩展方便,TextView换成ImageView也可以,换成ReleativeLayout也可以,尤其是kotlin代码,复制粘贴不要太爽。

代码地址:
https://github.com/longlong-2l/MySelfViewDemo

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值