项目中大部分场景的按钮是使用的TextView,然后设置shape作为背景,如果需要有点击效果反馈,则在drawable中使用selector。
这一套标准用法没啥毛病,唯一大缺点的就是繁琐。当然,如果项目的UI比较规范,定义几个常用的,复用起来也会很顺手。
但对于UI设计不规范的项目,写一堆的selector+shape还是很恶心的。
想法:shape的几个属性比较固定,就圆角和背景色等几个。能不能在xml写TextView时直接把这几个属性也传进去,这样就不用再去添加shape.xml了?
如果只是传几个属性,还是比较容易的,关键是如何把设置的参数使用起来。
第一思路:ShapeDrawable
float radius = DensityUtil.dip2px(this, 10);
float[] outerR = new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
RoundRectShape rr = new RoundRectShape(outerR, null, null);
ShapeDrawable drawable = new ShapeDrawable(rr);
drawable.getPaint().setColor(0xff333333);
drawable.getPaint().setStyle(Paint.Style.STROKE);
drawable.getPaint().setStrokeWidth(2);
code.setBackgroundDrawable(drawable);
看看drawable.xml加载的源码,发现shape加载出来的不是ShapeDrawable,而是GradientDrawable。
float radius = DensityUtil.dip2px(this, 10);
GradientDrawable drawable = new GradientDrawable();
drawable.setCornerRadius(radius);
drawable.setStroke(2, 0xff333333);
code.setBackgroundDrawable(drawable);
方案有了,就开干:
<declare-styleable name="CompatTextView">
<attr name="backgroundType">
<enum name="solid" value="0" />
<enum name="stroke" value="1" />
<!--<enum name="gradient" value="2" />-->
</attr>
<attr name="solidColor" format="color|reference"/>
<attr name="strokeColor" format="color|reference"/>
<attr name="strokeWidth" format="dimension|reference"/>
<attr name="radius" format="dimension|reference"/>
</declare-styleable>
public class CompatTextView extends AppCompatTextView {
public static final int TYPE_SOLID = 0;
public static final int TYPE_STROKE = 1;
public static final int TYPE_GRADIENT = 2;
public CompatTextView(Context context) {
this(context, null, 0);
}
public CompatTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (attrs != null) {
initAttrs(context, attrs);
}
}
private void initAttrs(Context context, AttributeSet attrs) {
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);
int bgType = typedArray.getInt(R.styleable.CompatTextView_backgroundType, -1);
if (0 <= bgType && bgType <= 2) {
float radius = typedArray.getDimension(R.styleable.CompatTextView_radius, 0);
GradientDrawable drawable = new GradientDrawable();
if (bgType == TYPE_SOLID) {
int solidColor = typedArray.getInt(R.styleable.CompatTextView_solidColor, 0);
drawable.setColor(solidColor);
} else if (bgType == TYPE_STROKE) {
int strokeWidth = (int) typedArray.getDimension(R.styleable.CompatTextView_strokeWidth, 0);
int strokeColor = typedArray.getInt(R.styleable.CompatTextView_strokeColor, 0);
drawable.setStroke(strokeWidth, strokeColor);
}
drawable.setCornerRadius(radius);
setBackgroundDrawable(drawable);
}
typedArray.recycle();
}
}
使用
<com.brian.views.CompatTextView
android:id="@+id/btn_logout"
android:layout_width="288dp"
android:layout_height="48dp"
android:text="退出登录"
android:textColor="#ffffffff"
android:textSize="14sp"
android:textStyle="bold"
android:gravity="center"
app:backgroundType="solid"
app:solidColor="@color/popi_dark_btn_bg"
app:radius="24dp"
/>
可以看到,使用时只添加了三个自定义属性,这样就省去了定义一个shape,效率提升,性能也稍微好一点(不需要加载drawable.xml);
如果需要添加点击态,也很简单,加一个属性,然后使用ColorStateList搞定:
int[][] stateList = new int[][]{
new int[]{android.R.attr.state_pressed},
new int[]{}
};
int[] stateColorList = new int[]{
pressedColor,
typedArray.getInt(R.styleable.CompatTextView_solidColor, 0),
};
ColorStateList colorStateList = new ColorStateList(stateList, stateColorList);
drawable.setColor(colorStateList);
现在Android版本也更新较快,目前Android4.x及以下的设备持有量较少,是时候普及 Android5.0引入的Ripple点击态了。
第一想法就是看看有没有RippleDrawable,搜索源码发现还真有这个类。
有就好办了,看api,然后CV一下就差不多完工了:
public class CompatTextView extends AppCompatTextView {
public static final int TYPE_SOLID = 0;
public static final int TYPE_STROKE = 1;
public static final int TYPE_GRADIENT = 2;
public CompatTextView(Context context) {
this(context, null, 0);
}
public CompatTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (attrs != null) {
initAttrs(context, attrs);
}
}
private void initAttrs(Context context, AttributeSet attrs) {
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);
int bgType = typedArray.getInt(R.styleable.CompatTextView_backgroundType, -1);
if (0 <= bgType && bgType <= 2) {
float radius = typedArray.getDimension(R.styleable.CompatTextView_radius, 0);
if (bgType == TYPE_SOLID) {
int solidColor = typedArray.getInt(R.styleable.CompatTextView_solidColor, 0);
GradientDrawable drawable = new GradientDrawable();
drawable.setColor(solidColor);
drawable.setCornerRadius(radius);
int rippleColor = typedArray.getInt(R.styleable.CompatTextView_rippleColor, 0);
if (rippleColor != 0) {
int[][] stateList = new int[][]{
new int[]{android.R.attr.state_pressed},
new int[]{}
};
int[] stateColorList = new int[]{
rippleColor,
solidColor,
};
ColorStateList colorStateList = new ColorStateList(stateList, stateColorList);
RippleDrawable rippleDrawable = new RippleDrawable(colorStateList, drawable, null);
setBackgroundDrawable(rippleDrawable);
} else {
setBackgroundDrawable(drawable);
}
} else if (bgType == TYPE_STROKE) {
int strokeWidth = (int) typedArray.getDimension(R.styleable.CompatTextView_strokeWidth, 0);
int strokeColor = typedArray.getInt(R.styleable.CompatTextView_strokeColor, 0);
GradientDrawable drawable = new GradientDrawable();
drawable.setStroke(strokeWidth, strokeColor);
drawable.setCornerRadius(radius);
setBackgroundDrawable(drawable);
}
}
typedArray.recycle();
}
}
<declare-styleable name="CompatTextView">
<attr name="backgroundType">
<enum name="solid" value="0" />
<enum name="stroke" value="1" />
<!--<enum name="gradient" value="2" />-->
</attr>
<attr name="rippleColor" format="color|reference"/>
<attr name="solidColor" format="color|reference"/>
<attr name="strokeColor" format="color|reference"/>
<attr name="strokeWidth" format="dimension|reference"/>
<attr name="radius" format="dimension|reference"/>
</declare-styleable>
至此,思路和方案都比较清楚了,根据个人喜好再封装一下就很顺手了