TextView的drawable 无法控制大小,经常会导致效果不如意,不得不采用布局嵌套多个View。效果不好。
因此,扩展TextView功能,使其能自由控制大小。
TextView 已经有实现drawableLeft ,drawablePadding 等,因此,我们只要继承并扩展TextView即可。
为了兼容性更好,直接 extends AppCompatTextView 。
TextView上下左右都可以设置drawable,有可能会有需要单独设置宽高大小的情况。因此,我们需要设置上下左右的宽高,总共8个属性。也有可能需要统一大小,那就再添加一对设置整体 drawable 的宽高设置。因此总共10个属性。如下:
attrs.xml
自定义属性
<resources>
<declare-styleable name="DrawableTextView">
<attr name="drawableWidth" format="dimension" />
<attr name="drawableHeight" format="dimension" />
<attr name="drawableLeftWidth" format="dimension" />
<attr name="drawableLeftHeight" format="dimension" />
<attr name="drawableRightWidth" format="dimension" />
<attr name="drawableRightHeight" format="dimension" />
<attr name="drawableTopWidth" format="dimension" />
<attr name="drawableTopHeight" format="dimension" />
<attr name="drawableBottomWidth" format="dimension" />
<attr name="drawableBottomHeight" format="dimension" />
declare-styleable>
resources>
step 1 . drawable的获取
在构造方法里
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
// ...省略其它
Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
drawableBottom = null, drawableStart = null, drawableEnd = null;
// ...
switch (attr) {
case com.android.internal.R.styleable.TextView_drawableLeft:
drawableLeft = a.getDrawable(attr);
break;
case com.android.internal.R.styleable.TextView_drawableTop:
drawableTop = a.getDrawable(attr);
break;
case com.android.internal.R.styleable.TextView_drawableRight:
drawableRight = a.getDrawable(attr);
break;
case com.android.internal.R.styleable.TextView_drawableBottom:
drawableBottom = a.getDrawable(attr);
break;
// ...
}
step 2. drawable的设置 (重点)
从这里可以看到,setCompoundDrawablesWithIntrinsicBounds 设置了drawable的尺寸。因此我们只需要修改这部分就可以。
@android.view.RemotableViewMethod
public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
@Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
if (left != null) {
left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
}
if (right != null) {
right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
}
if (top != null) {
top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
}
if (bottom != null) {
bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
}
setCompoundDrawables(left, top, right, bottom);
}
step 1 . 变量的设计与存放
考虑到除了自定义属性外,我们还有可能在java代码中进行调用,每次调用有可能设置某一个drawable的宽高。如果我们都采用单独变量的话,上下左右4个drawable + 4个drawable的宽和高 + 全局设置 ,
那么总共需要:4*2 + 2 + 4 个变量,以及对应的set方法。工作量不可谓不大,而且也不利于查看。
因此,我们可以采用两个数组来表示。一个数组存 drawable,另外一个存其尺寸。代码如下:
public final static int DRAWABLE_POSITION_LEFT = 0;
public final static int DRAWABLE_POSITION_TOP = 1;
public final static int DRAWABLE_POSITION_RIGHT = 2;
public final static int DRAWABLE_POSITION_BOTTOM = 3;
public final static int SIZE_WIDTH = 0;
public final static int SIZE_HEIGHT = 1;
private Drawable[] drawableList;
private int[][] drawableSizeList = new int[4][2];
step 2 . 在我们的自定义View中获取到drawable
从父类方法中可以看到,TextView是在构造方法中用一个本地变量进行的获取。那么,在自定义View中难以直接使用或以类似方法取值。
当然,也不是没有办法。可以如下获取:
int identifier = Resources.getSystem().getIdentifier("drawableLeft", "attr", "android");
leftDrawable = ta.getDrawable(identifier);
除此之外,我们可以看到 调用 setCompoundDrawablesWithIntrinsicBounds 方法时,会传递过来4个drawable,那么子类可以通过重写此处,来实现drawable的获取。
@Override
public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
if (drawableList == null) {
drawableList = new Drawable[4];
}
drawableList[DRAWABLE_POSITION_LEFT] = left;
drawableList[DRAWABLE_POSITION_TOP] = top;
drawableList[DRAWABLE_POSITION_RIGHT] = right;
drawableList[DRAWABLE_POSITION_BOTTOM] = bottom;
}
需要注意的是,这个方法的调用在父类的构造方法,此时子类还没构造完成,因此无法在子类中给drawableList赋值。
step 3. 设置drawable尺寸
我们只需要参考TextView中的 setCompoundDrawablesWithIntrinsicBounds 写法就可以了,照葫芦画瓢。
如下:
public void setDrawableSize() {
if (drawableList == null) {
drawableList = new Drawable[4];
}
if (drawableList[DRAWABLE_POSITION_LEFT] != null) {
drawableList[DRAWABLE_POSITION_LEFT].setBounds(0, 0, drawableSizeList[DRAWABLE_POSITION_LEFT][SIZE_WIDTH], drawableSizeList[DRAWABLE_POSITION_LEFT][SIZE_HEIGHT]);
}
if (drawableList[DRAWABLE_POSITION_TOP] != null) {
drawableList[DRAWABLE_POSITION_TOP].setBounds(0, 0, drawableSizeList[DRAWABLE_POSITION_TOP][SIZE_WIDTH], drawableSizeList[DRAWABLE_POSITION_TOP][SIZE_HEIGHT]);
}
if (drawableList[DRAWABLE_POSITION_RIGHT] != null) {
drawableList[DRAWABLE_POSITION_RIGHT].setBounds(0, 0, drawableSizeList[DRAWABLE_POSITION_LEFT][SIZE_WIDTH], drawableSizeList[DRAWABLE_POSITION_RIGHT][SIZE_HEIGHT]);
}
if (drawableList[DRAWABLE_POSITION_BOTTOM] != null) {
drawableList[DRAWABLE_POSITION_BOTTOM].setBounds(0, 0, drawableSizeList[DRAWABLE_POSITION_BOTTOM][SIZE_WIDTH], drawableSizeList[DRAWABLE_POSITION_BOTTOM][SIZE_HEIGHT]);
}
setCompoundDrawables(drawableList[DRAWABLE_POSITION_LEFT], drawableList[DRAWABLE_POSITION_TOP],
drawableList[DRAWABLE_POSITION_RIGHT], drawableList[DRAWABLE_POSITION_BOTTOM]);
}
step 4. 代码中动态设置
在java代码中动态设置。因为大多数情况每次知识控制一个drawable的尺寸,那么,我们指定其每次传递一个标识position与尺寸,然后进行相关设置即可。
代码如下:
public void setDrawableSize(int position, int width, int height) {
// 如果数值未发生变化,直接返回
if (drawableSizeList[position][SIZE_WIDTH] == width && drawableSizeList[position][SIZE_HEIGHT] == height) {
return;
}
drawableSizeList[position][SIZE_WIDTH] = width;
drawableSizeList[position][SIZE_HEIGHT] = height;
setDrawableSize();
}
但是如果,position 传递的并非我们设置的4个值,程序就会报错。那么想到的是可以替换为枚举类型。除此之外,给position加一个注解的限定,是一个更好的解决方案。
这里就要用到java中提供的注解 @IntDef ,自定义注解如下:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
@IntDef({DRAWABLE_POSITION_LEFT, DRAWABLE_POSITION_TOP, DRAWABLE_POSITION_RIGHT, DRAWABLE_POSITION_BOTTOM})
@interface Position {
}
然后,用我们的自定义注解去注解参数int position 即可。
Github: DrawableTextView.java
另外,自荐一下我整理的View库,https://github.com/zhengweichao/view ,收集了一些常用的轻量级自定义View。如感兴趣,尽可查看。