自定义TextView——解决ViewGroup不调用OnDraw方法

本文详细介绍了如何从LinearLayout派生自定义TextView的过程,并深入探讨了TextView的构造方法、测量方法及绘制过程。此外,还分析了ViewGroup不调用onDraw方法的原因及解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先绘制TextView继承于View:直接贴代码

public class TextView extends LinearLayout{

    private String mText;
    private int mTextSize = 18;
    private int mTextColor = Color.BLACK;
    private Paint mPaint;//文字的画笔

    // 构造函数会在代码里面new的时候调用
    // TextView tv = new TextView(this);
    public TextView(Context context) {
        this(context, null);
    }

    // 在布局layout中使用(调用)

    public TextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // 在布局layout中使用(调用),但是会有style
    public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // 获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);

        mText = array.getString(R.styleable.TextView_yijiaText);
        mTextColor = array.getColor(R.styleable.TextView_yijiaTextColor, mTextColor);
        // 18 18px 18sp
        mTextSize = array.getDimensionPixelSize(R.styleable.TextView_yijiaTextSize,
                sp2px(mTextSize));

        // 回收s
        array.recycle();
        //初始化画笔
        mPaint = new Paint();
        mPaint.setAntiAlias(true);//设置抗锯齿
        mPaint.setColor(mTextColor);//设置画笔颜色
        mPaint.setTextSize(mTextSize);

    }


    /**
     * 自定义View的测量方法
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 布局的宽高都是由这个方法指定
        // 指定控件的宽高,需要测量
        // 获取宽高的模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //1.确定的值,这个时候不需要计算,给多少是多少
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //2.给的是不确定值
        if (widthMode == MeasureSpec.AT_MOST) {
            //计算的宽度 与字体的长度有关 与字体的大小 用画笔来测量
            Rect rect = new Rect();
            //获取文本的矩形
            mPaint.getTextBounds(mText, 0, mText.length(), rect);
            width = rect.width();
        }

        //2.给的是不确定值
        if (heightMode == MeasureSpec.AT_MOST) {
            //计算的宽度 与字体的长度有关 与字体的大小 用画笔来测量
            Rect rect = new Rect();
            //获取文本的矩形
            mPaint.getTextBounds(mText, 0, mText.length(), rect);
            height = rect.height();
        }
        setMeasuredDimension(width, height);
    }

    /**
     * 用于绘制
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Rect rect = new Rect();
        mPaint.getTextBounds(mText, 0, mText.length(), rect);
        int dx = getWidth() / 2 - rect.width() / 2;

        //获取中心(fontMetrics.bottom - fontMetrics.top) / 2
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();

        int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        int baseLine = getHeight() / 2 + dy;
        canvas.drawText(mText, dx, baseLine, mPaint);
        Log.e("TAG", "dy==" + dy + ",centerY==" + getHeight() / 2+",baseLine=="+baseLine);
    }


    private int sp2px(int spValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue,
                getResources().getDisplayMetrics());
    }
}

源码分析为什么ViewGroup不调用onDraw,而设置背景后又可以显示自定义的TextView

  • 首先看View中调用onDraw的源码
 // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

可以看到当dirtyOpaque为false时调用OnDraw

  • dirtyOpaque源码
 final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

可以看到是由privateFlags控制,即mPrivateFlags

  • 看view的构造方法
 computeOpaqueFlags();
  • computeOpaqueFlags();查看源码
  // Opaque if:
        //   - Has a background
        //   - Background is opaque
        //   - Doesn't have scrollbars or scrollbars overlay

        if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
            mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
        } else {
            mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
        }

        final int flags = mViewFlags;
        if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
            mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
        } else {
            mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
        }
  • 查看ViewGroup构造方法源码
 super(context, attrs, defStyleAttr, defStyleRes);
        initViewGroup();
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
  • 查看initViewGroup源码
  if (!debugDraw()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }

可以看到最后因为setFlags的原因导致viewGrop中的OnDraw不可见。

为什么:viewgroup中设置背景后又可以显示

  • view中setBackDrawable的源码
   computeOpaqueFlags();

        if (background == mBackground) {
            return;
        }

此时发现又重新计算了

个人觉得解决方法可以有三

  • ①onDraw换成dispatchDraw
  • ②设置背景透明
  • ③看源码可以知道设置setFlags的值即可
public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值