基于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;
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容