当前位置: 首页 > >

自定义圆角背景TextView

发布时间:

自定义圆角背景TextView


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



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


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


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


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


3.自定义TextView


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


先看效果:



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


一.定义属性











定义几个简单属性,这里之所以要定义一个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标签中,就像这样:









有经验的同学应该知道这样其实效果和只写一个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



友情链接: