基于TextView左右Drawable居中的自定义View

由于项目需要一个一体的文字和图片一起居中的组建,用外层包装不能达到需要的效果,所以去网上找了一圈,最后发觉基本全是同一个来源转载的,是只支持左侧图标居中的,而且可能是但是作者忘记了,都没有告知不需要设置gravity,所以我拿到手第一次用的时候发觉就是直接简单粗暴整体往右移动半个长度来完成,简单有效,具体方案是这样的:

protected void onDraw(Canvas canvas) {           
            //左图标
            Drawable drawableLeft = drawables[0];
            if (drawableLeft != null) {
                // 得到文本的宽度
                float textWidth = getPaint().measureText(getText().toString());
                // 得到drawable与text之间的间距
                int drawablePadding = getCompoundDrawablePadding();
                // 得到leftDrawable的宽度
                int drawableWidth = drawableLeft.getIntrinsicWidth();
                float bodyWidth = textWidth + drawableWidth + drawablePadding;
                //移动图片
                canvas.save();
                canvas.translate((getWidth() - bodyWidth) / 2, 0);
                //canvas.restore();
            }
}

由于我实际需要的是右侧添加图标的效果,所以我做了扩展:

           //右图标
            Drawable drawableRight = drawables[2];
            if(drawableRight != null){
                // 得到文本的宽度
                float textWidth = getPaint().measureText(getText().toString());
                // 得到drawable与text之间的间距
                int drawablePadding = getCompoundDrawablePadding();
                // 得到leftDrawable的宽度
                int drawableWidth = drawableRight.getIntrinsicWidth();
                //移动图片
                canvas.save();

                float bodyWidth = textWidth + drawableWidth + drawablePadding;
                canvas.translate(0-(getWidth() - bodyWidth) / 2, 0);
            }

以上基本能满足基础需求了!
但实际上,如果整个TextView长度有超长出现尾部省略的情况或者设置了drawablePadding等属性以后,对达到使用要求还是有些差距,最后决定自己重写一下处理过程,使该组件能跟正常的TextView一样的使用,通过查看源码,切入点依然在onDraw里面:

            //源码里onDraw前面的无关代码忽略,有兴趣的可以自己看一下,以下是关键源码部分:

            /*
             * Compound, not extended, because the icon is not clipped
             * if the text height is smaller.
             */

            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();
            }

            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
            // Make sure to update invalidateDrawable() when changing this code.
            if (dr.mShowing[Drawables.RIGHT] != null) {
                canvas.save();
                canvas.translate(scrollX + right - left - mPaddingRight
                        - dr.mDrawableSizeRight - rightOffset,
                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
                dr.mShowing[Drawables.RIGHT].draw(canvas);
                canvas.restore();
            }

通过查看这部分源码,我们可以发现,完全可以把这部分代码复制下来,修改一下绘制位置,由我们自己来绘制一次图标,就可以完成我们的需求。
第一步:dr.mShowing[Drawables.LEFT] 以及dr.mShowing[Drawables.RIGHT]设置为空,就可以阻止系统执行绘制(此处有坑:后面描述):

 //移除左侧图片,防止再次绘制
 setCompoundDrawables(null, cacheDrawable[1], cacheDrawable[2], cacheDrawable[3]);
 //移除右侧图片,防止再次绘制
setCompoundDrawables(cacheDrawable[0],cacheDrawable[1],null,cacheDrawable[3]);

第二步:复制源码的绘制过程,对canvas.translate内的x,y坐标进行重新计算绘制,完成需求:

            //-------------------左侧图片处理过程(右侧的类似)-----------------------
            // 得到文本的宽度
            float textWidth = getPaint().measureText(getText().toString());
            // 得到drawable与text之间的间距
            int drawablePadding = getCompoundDrawablePadding();
            // 得到leftDrawable的宽度
            int drawableWidth = drawableLeft.getIntrinsicWidth();
            //计算宽度
            float bodyWidth = textWidth/2 + drawableWidth + drawablePadding + getPaddingLeft();
            //偏移图片
            Rect mCompoundRect = new Rect();
            drawableLeft.copyBounds(mCompoundRect);
            int vSpace = getHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
            //计算X位标
            int dx = getScrollX() + getPaddingLeft();
            //计算偏移量
            float moveDx = getWidth()/2 - bodyWidth;
            //如果实际占用距离已满,则直接调用系统绘制
            if(moveDx >= 0) {
                //保留画布场景便于恢复
                canvas.save();
                //更新偏移
                canvas.translate(dx + moveDx,
                        getScrollY() + getCompoundPaddingTop() + (vSpace - mCompoundRect.height()) / 2);
               //源码对比
               //canvas.translate(scrollX + right - left - mPaddingRight
               //         - dr.mDrawableSizeRight - rightOffset,
               //          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
                //绘制
                drawableLeft.draw(canvas);
                //移除左侧图片,防止再次绘制
                setCompoundDrawables(null, cacheDrawable[1], cacheDrawable[2], cacheDrawable[3]);
                //恢复场景
                canvas.restore();
            }

第三步:优化移动条件,仅当用户设置为文字居中center的时候进行该偏移计算,其余情况使用默认绘制过程(即与普通TextView无差别):

        //不是居中文字,跳过
        if(Gravity.CENTER_HORIZONTAL != getGravity() && Gravity.CENTER != getGravity()){
            super.onDraw(canvas);
        }else{
            //缓存
            Drawable[] cacheDrawable = getCompoundDrawables();
            //绘制拦截
            drawInject(canvas,cacheDrawable);
            //调用父类绘制
            super.onDraw(canvas);
            //恢复场景
            setCompoundDrawables(cacheDrawable[0],cacheDrawable[1],cacheDrawable[2],cacheDrawable[3]);
        }

最后献上完整源码,直接复制就可以使用了:

public class DrawableCenterTextView extends androidx.appcompat.widget.AppCompatTextView {

    public DrawableCenterTextView(Context context) {
        super(context);
    }

    public DrawableCenterTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public DrawableCenterTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //不是居中文字,跳过
        if(Gravity.CENTER_HORIZONTAL != getGravity() && Gravity.CENTER != getGravity()){
            super.onDraw(canvas);
        }else{
            //缓存
            Drawable[] cacheDrawable = getCompoundDrawables();
            //绘制拦截
            drawInject(canvas,cacheDrawable);
            //调用父类绘制
            super.onDraw(canvas);
            //恢复场景
            setCompoundDrawables(cacheDrawable[0],cacheDrawable[1],cacheDrawable[2],cacheDrawable[3]);
        }
    }

    //已废弃,网络上通用的偏移绘制方式
    private void drawTransOld(Canvas canvas){
        Drawable[] drawables = getCompoundDrawables();
        if (drawables != null) {
            //左图标
            Drawable drawableLeft = drawables[0];
            if (drawableLeft != null) {
                // 得到文本的宽度
                float textWidth = getPaint().measureText(getText().toString());
                // 得到drawable与text之间的间距
                int drawablePadding = getCompoundDrawablePadding();
                // 得到leftDrawable的宽度
                int drawableWidth = drawableLeft.getIntrinsicWidth();
                float bodyWidth = textWidth + drawableWidth + drawablePadding;
                //移动图片
                canvas.save();
                canvas.translate((getWidth() - bodyWidth) / 2, 0);
                //canvas.restore();
            }
            //右图标
            Drawable drawableRight = drawables[2];
            if(drawableRight != null){
                // 得到文本的宽度
                float textWidth = getPaint().measureText(getText().toString());
                // 得到drawable与text之间的间距
                int drawablePadding = getCompoundDrawablePadding();
                // 得到leftDrawable的宽度
                int drawableWidth = drawableRight.getIntrinsicWidth();
                //移动图片
                canvas.save();

                float bodyWidth = textWidth + drawableWidth + drawablePadding;
                canvas.translate(0-(getWidth() - bodyWidth) / 2, 0);
            }
        }
    }

    private void drawInject(Canvas canvas,Drawable[] cacheDrawable){
        //左图标
        Drawable drawableLeft = cacheDrawable[0];
        if (drawableLeft != null) {
            // 得到文本的宽度
            float textWidth = getPaint().measureText(getText().toString());
            // 得到drawable与text之间的间距
            int drawablePadding = getCompoundDrawablePadding();
            // 得到leftDrawable的宽度
            int drawableWidth = drawableLeft.getIntrinsicWidth();
            //计算宽度
            float bodyWidth = textWidth/2 + drawableWidth + drawablePadding + getPaddingLeft();
            //偏移图片
            Rect mCompoundRect = new Rect();
            drawableLeft.copyBounds(mCompoundRect);
            int vSpace = getHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
            //计算X位标
            int dx = getScrollX() + getPaddingLeft();
            //计算偏移量
            float moveDx = getWidth()/2 - bodyWidth;
            //如果实际占用距离已满,则直接调用系统绘制
            if(moveDx >= 0) {
                //保留画布场景便于恢复
                canvas.save();
                //更新偏移
                canvas.translate(dx + moveDx,
                        getScrollY() + getCompoundPaddingTop() + (vSpace - mCompoundRect.height()) / 2);
                //绘制
                drawableLeft.draw(canvas);
                //移除左侧图片,防止再次绘制
                setCompoundDrawables(null, cacheDrawable[1], cacheDrawable[2], cacheDrawable[3]);
                //恢复场景
                canvas.restore();
            }
        }
        //右图标
        Drawable drawableRight = cacheDrawable[2];
        if(drawableRight != null){
            // 得到文本的宽度
            float textWidth = getPaint().measureText(getText().toString());
            // 得到drawable与text之间的间距
            int drawablePadding = getCompoundDrawablePadding();
            // 得到leftDrawable的宽度
            int drawableWidth = drawableRight.getIntrinsicWidth();
            //计算移动距离
            float bodyWidth = textWidth/2 + drawableWidth + drawablePadding + getPaddingRight();
            //偏移图片
            Rect mCompoundRect = new Rect();
            drawableRight.copyBounds(mCompoundRect);
            int vSpace = getHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
            //计算X位标
            int dx = getScrollX() + getWidth() - getPaddingRight() - mCompoundRect.width();
            //计算偏移量
            float moveDx = getWidth()/2 - bodyWidth;
            //如果实际占用距离已满,则直接调用系统绘制
            if(moveDx >= 0){
                //保留画布场景便于恢复
                canvas.save();
                //更新偏移
                canvas.translate(dx - (moveDx >= 0 ? moveDx:0),
                        getScrollY()+getCompoundPaddingTop()+(vSpace-mCompoundRect.height())/2);
                //绘制
                drawableRight.draw(canvas);
                //移除左侧图片,防止再次绘制
                setCompoundDrawables(cacheDrawable[0],cacheDrawable[1],null,cacheDrawable[3]);
                //恢复场景
                canvas.restore();
            }
        }
    }
}

左侧图片处理的时候会发觉文字会往左偏移很多,来来回回看了很多遍绘制源码并做对比,百撕不得骑姐啊!通过对比发觉如果不移除左侧图标,就不会除这个问题,但是就出现2个左侧图标了,所以考虑应该是左侧的宽度影响了text的位置计算,于是乎往下找(还是在TextView源码onDraw里面):

        if (mEditor != null) {
            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
        } else {
            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
        }

注意这个mEditor.onDraw,文字绘制原来会根据这个做边界计算,在我们清空drawableLeft 之前,这个layout已经计算过值了,而里面会受到getCompoundPaddingLeft的影响,而getCompoundPaddingLeft会根据有没有drawableLeft 返回不同的值:

    /**
     * Returns the left padding of the view, plus space for the left
     * Drawable if any.
     */
    public int getCompoundPaddingLeft() {
        final Drawables dr = mDrawables;
        if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
            return mPaddingLeft;
        } else {
            return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
        }
    }

所以我们需要在绘制文字的时候把drawableLeft 恢复回去,否则文字将左偏一个drawableLeft宽度的位置,添加重写代码如下:

    public int getCompoundPaddingLeft() {
        if(cacheDrawable == null){
            return super.getCompoundPaddingLeft();
        }else{
            //缓存处理场景
            Drawable[] temp = getCompoundDrawables();
            //恢复初始场景
            setCompoundDrawables(cacheDrawable[0],cacheDrawable[1],cacheDrawable[2],cacheDrawable[3]);
            //计算数据
            int cpl = super.getCompoundPaddingLeft();
            //恢复处理场景
            setCompoundDrawables(temp[0],temp[1],temp[2],temp[3]);
            //返回计算数据
            return cpl;
        }
    }

你可能感兴趣的:(基于TextView左右Drawable居中的自定义View)