美文网首页Android 问题解析
TextView实现drawable图标大小位置与第一行文本居中

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

作者: 科技猿人 | 来源:发表于2020-11-30 20:05 被阅读0次

用最少的代码解决问题。

直接上效果

需求分析

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

 组合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);
    }
}
齐活儿 不要吝啬你手中的小心心(~点赞吧~)

相关文章

网友评论

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

    本文链接:https://www.haomeiwen.com/subject/vrddwktx.html