一. 业务背景
TextView的setCompoundDrawables()可以在其四周设置图片,但是有个众所周知的问题,即无法设置drawable大小。这就导致在实际的使用中有很大的局限性,必须用代码去控制,就略显麻烦了。
这个时候我们就需要自定义 TextView 了,这个自定义控件虽然简单,也非常不起眼,但是用处还真不少:
解决了主要矛盾,无法在布局里设置 TextView 图片大小问题,使用更加简单。
除了设置图片大小,其它的 TextView 可以的事情这个一样也都可以
图片加文字的简单组合非常见,原本为了适配图片大小不得不用一个 xxxLayout+ImageView+TextView 才能搞定的事,现在用一个控件即可搞定。
在方便、高效使用的同时,也有效的减少了布局层。千万不要瞧不上这点苍蝇肉,这可能是你的app卡顿罪魁祸首
二. 扩展TextView原理
通过Drawable的setBound()设置显示区域,也就是图片大小
通过TextView的setCompoundDrawables()设置要显示的图片
三. 扩展TextView实现
3.1 定义一个MkDrawableTextView,继承AppCompatTextView,重写三个构造方法
public MkDrawableTextView(Context context) {
super(context);
}
public MkDrawableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MkDrawableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
3.2 通过自定义属性定义Drawable宽高,顶点坐标;drawable左上右下的图标引用
<declare-styleable name="DrawableTextView">
<attr name="left_drawable" format="reference" />
<attr name="right_drawable" format="reference" />
<attr name="top_drawable" format="reference" />
<attr name="bottom_drawable" format="reference" />
<attr name="drawable_width" format="dimension" />
<attr name="drawable_height" format="dimension" />
<attr name="left_drawable_width" format="dimension" />
<attr name="left_drawable_height" format="dimension" />
<attr name="right_drawable_width" format="dimension" />
<attr name="right_drawable_height" format="dimension" />
<attr name="top_drawable_width" format="dimension" />
<attr name="top_drawable_height" format="dimension" />
<attr name="bottom_drawable_width" format="dimension" />
<attr name="bottom_drawable_height" format="dimension" />
</declare-styleable>
3.3 获取Drawable真实宽高
局部的大小设置均正常的情况,我们获取局部设置的宽高,局部大小没设置时,看全局的大小是否正确设置,如果正确获取全局大小宽高
public static class SizeWrap {
int width;
int height;
/**
* 检查Drawable的宽高是否符合要求
* @param globalWidth xml定义的Drawable获取真实的宽度
* @param globalHeight xml定义的Drawable获取真实的高度
* @param localWidth Drawable实际图标局部设置的宽度
* @param localHeight Drawable实际图标局部设置的高度
* @return 是否符合要求
*/
public boolean checkWidthAndHeight(int globalWidth, int globalHeight, int localWidth, int localHeight) {
width = 0;
height = 0;
// 局部的大小设置均正常的情况
if (localWidth > 0 && localHeight > 0) {
width = localWidth;
height = localHeight;
return true;
}
// 局部大小没设置时,看全局的大小是否正确设置
if (localWidth == -1 && localHeight == -1) {
if (globalWidth > 0 && globalHeight > 0) {
width = globalWidth;
height = globalHeight;
return true;
}
}
return false;
}
}
}
3.4 将顶角图标Drawable设置到我们的TextView上
private void init(Context context, AttributeSet attrs) {
// 1. 通过style.xml文件拿到所有的样式文件
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DrawableTextView);
int width = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_drawable_width, -1);
int height = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_drawable_height, -1);
SizeWrap sizeWrap = new SizeWrap();
Drawable leftDrawable = ta.getDrawable(R.styleable.DrawableTextView_left_drawable);
if (leftDrawable != null) {
int lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_left_drawable_width, -1);
int lheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_left_drawable_height, -1);
if (sizeWrap.checkWidthAndHeight(width, height, lwidth, lheight)) {
leftDrawable.setBounds(0, 0, sizeWrap.width, sizeWrap.height);
} else {
throw new IllegalArgumentException("error left drawable size setting");
}
}
Drawable rightDrawable = ta.getDrawable(R.styleable.DrawableTextView_right_drawable);
if (rightDrawable != null) {
int rwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_right_drawable_width, -1);
int rheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_right_drawable_height, -1);
if (sizeWrap.checkWidthAndHeight(width, height, rwidth, rheight)) {
rightDrawable.setBounds(0, 0, sizeWrap.width, sizeWrap.height);
} else {
throw new IllegalArgumentException("error right drawable size setting");
}
}
Drawable topDrawable = ta.getDrawable(R.styleable.DrawableTextView_top_drawable);
if (topDrawable != null) {
int twidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_top_drawable_width, -1);
int theight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_top_drawable_height, -1);
if (sizeWrap.checkWidthAndHeight(width, height, twidth, theight)) {
topDrawable.setBounds(0, 0, sizeWrap.width, sizeWrap.height);
} else {
throw new IllegalArgumentException("error top drawable size setting");
}
}
Drawable bottomDrawable = ta.getDrawable(R.styleable.DrawableTextView_bottom_drawable);
if (bottomDrawable != null) {
int bwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_bottom_drawable_width, -1);
int bheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_bottom_drawable_height, -1);
if (sizeWrap.checkWidthAndHeight(width, height, bwidth, bheight)) {
bottomDrawable.setBounds(0, 0, sizeWrap.width, sizeWrap.height);
} else {
throw new IllegalArgumentException("error bottom drawable size setting");
}
}
this.setCompoundDrawables(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
ta.recycle();
ta = null;
}
四. 使用指南
<com.github.microkibaco.view.MkDrawableTextView
android:id="@+id/mk_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_toEndOf="@+id/top_text"
android:background="@drawable/mk_circle_rect_10_white_bg"
android:drawablePadding="4dp"
android:gravity="center"
android:paddingStart="10dp"
android:paddingTop="3dp"
android:paddingEnd="10dp"
android:paddingBottom="3dp"
android:textColor="@color/white"
android:textSize="@dimen/text_size_11"
android:visibility="gone"
app:right_drawable="@drawable/mk_arrow"
app:right_drawable_height="10dp"
app:right_drawable_width="10dp"
tools:text="@string/mk_rank_no" />
五. 扩展TextView注意事项
在网上看到有一个版本,在控件的onMeasure()设置Drawable.setBound(), 在onDraw()里设置: setCompoundDrawables()。看setCompoundDrawables()源码可以知道,这个方法最终会调用invalide()和requestLayout(),会导致严重的后果就是,onMeasure()和onDraw()会无限循环的互调下去,有点浪费
改不完的 Bug,写不完的矫情。公众号 杨正友 现在专注移动基础开发 ,涵盖音视频和 APM,信息安全等各个知识领域;只做全网最 Geek 的公众号,欢迎您的关注!