阅读 328

TextView实现drawable图标大小位置与第一行文本居中

用最少的代码解决问题。

直接上效果

需求分析

 如上图,需求在每条文本前加一个小圆点,有哪些实现方式可以想到?

 组合 view ? pass... 杀鸡岂能用牛刀?

 drawableLeft 来实现肯定是最好了,用原生实现才是王道! drawableLeft 在多行的时候,就随着整个文本的高度垂直居中了,这肯定不是我们想要的。

 那我们就从TextView源码中一看端倪。

源码分析

 首先从构造函数来分析,找下drawableLeft的解析。

//构造函数TextView
case com.android.internal.R.styleable.TextView_drawableLeft:
    drawableLeft = a.getDrawable(attr);
    break;
复制代码

 接下来看在哪里会使用

//构造函数TextView
// This call will save the initial left/right drawables
setCompoundDrawablesWithIntrinsicBounds(
        drawableLeft, drawableTop, drawableRight, drawableBottom);
复制代码

 看看这个方法要干嘛?

@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);
    }
复制代码

 可以看这个方法,发现 setBounds 可以设置的是默认的 drawable 大小。setCompoundDrawables() 方法,做了一些状态保存和重新绘制申请。看到这里就会发现突破点还是很多的。

 首先可以复写 setCompoundDrawablesWithIntrinsicBounds() 进行重新设置 drawableLeft 的 Bounds,但是不可以,因为要实现需求的功能,需要依赖当前行数来动态计算的,这个方法是在构造函数调用的,行数永远未0。通过复写此方法来更改是不可以行的。

onMeasure() 方法和 onLayout() 方法也不太适合进行操作。还有 onDraw(),看一下。

            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;

            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
            // Make sure to update invalidateDrawable() when changing this code.
            if (dr.mShowing[Drawables.LEFT] != null) {
                canvas.save();
                canvas.translate(scrollX + mPaddingLeft + leftOffset,
                        scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
                dr.mShowing[Drawables.LEFT].draw(canvas);
                canvas.restore();
            }
复制代码

 这也没法操作啊~~,但是我们从这里可以看出一些端倪,drawableleft 是如何进行随文本居中的?是通过文本高度减去 drawableleft 的高度,然后除2,刚好就是居中对齐了。

 那就再换条路吧,之前我们说的,setCompoundDrawablesWithIntrinsicBounds() 方法,它可以设置 drawableleft 的 Bounds,但是时机不对,那我们就给它找个合适的时机。onLayout() 是一个不错的时机,当文本变化时,会重新布局,重新布局是,就可以用手术刀了!

解决问题

 这里面最核心的就是如何去根据文本高度,去计算显示到首行位置和显示到居中的 位置差值。因为 TextView 在 onDraw() 里面会进行居中绘制(这个我们改变不了),那我们就在 onLayout() 中,将这个 位置差值 给它补上,就是,我们在 onLayou() 中将 drawableleft 位置上移,在 onDraw() 中位置下移,一加一减,偏移量消除,不管有几行文本,都是第一行文本的效果。

 Get it,上代码:

 xml文件中的布局

//xml文件中的布局
<com.kejiyuanren.SpecialTextView
    android:id="@+id/special_text_view"
    android:drawableLeft="@drawable/icon"
    android:drawablePadding="10dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
复制代码

 实现代码

public class SpecialTextView extends TextView {
    private static final String TAG = "SpecialTextView";

    public SpecialTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setText("科技猿人科技猿人科技猿人科技猿人科技猿人科技猿人科技猿人科技猿人科技猿人" +
                "科技猿人科技猿人科技猿人科技猿人科技猿人科技猿人科技猿人科技猿人科技猿人");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        handleLeftDrawable();
    }

    private void handleLeftDrawable() {
        Drawable leftDrawable = getCompoundDrawables()[0];
        if (leftDrawable == null) {
            return;
        }
        //获取实际行数
        int lineCount = Math.min(getLineCount(), getMaxLines());
        //获取文本高度
        int vsPace = getBottom() - getTop() - getCompoundPaddingBottom() - getCompoundPaddingTop();
        //计算位置差值
        int verticalOffset = (int) (-1 * (vsPace * (1 - 1.0f / lineCount)) / 2);
        //重新设置Bounds
        leftDrawable.setBounds(0, verticalOffset, leftDrawable.getIntrinsicWidth(),
                leftDrawable.getIntrinsicHeight() + verticalOffset);
    }
}
复制代码

小编的博客系列

Android 问题解析笔记

文章分类
Android
文章标签