Custom TextView -- resolve ViewGroup does not call OnDraw method

First draw TextView inherited from View: paste code directly

public class TextView extends LinearLayout{

    private String mText;
    private int mTextSize = 18;
    private int mTextColor = Color.BLACK;
    private Paint mPaint;//A brush for writing

    // Constructor will be called when new is in the code
    // TextView tv = new TextView(this);
    public TextView(Context context) {
        this(context, null);
    }

    // Use in layout layout (call)

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

    // Use (call) in layout layout, but there will be style
    public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // Get custom properties
        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));

        // Recycling s
        array.recycle();
        //Initialize brush
        mPaint = new Paint();
        mPaint.setAntiAlias(true);//Set anti aliasing
        mPaint.setColor(mTextColor);//Set brush color
        mPaint.setTextSize(mTextSize);

    }


    /**
     * Measurement method of custom View
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // The width and height of the layout are specified by this method
        // Specifies the width and height of the control, which needs to be measured
        // Get the mode of width and height
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //1. The determined value does not need to be calculated at this time. What is the given value
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //2. Uncertainty value is given
        if (widthMode == MeasureSpec.AT_MOST) {
            //The calculated width is related to the length of the font the size of the font is measured with a brush
            Rect rect = new Rect();
            //Get rectangle of text
            mPaint.getTextBounds(mText, 0, mText.length(), rect);
            width = rect.width();
        }

        //2. Uncertainty value is given
        if (heightMode == MeasureSpec.AT_MOST) {
            //The calculated width is related to the length of the font the size of the font is measured with a brush
            Rect rect = new Rect();
            //Get rectangle of text
            mPaint.getTextBounds(mText, 0, mText.length(), rect);
            height = rect.height();
        }
        setMeasuredDimension(width, height);
    }

    /**
     * Used for rendering
     *
     * @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;

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

Source code analysis why ViewGroup does not call onDraw, but after setting the background, it can display the customized TextView

  • First look at the source code for calling onDraw in View.
 // 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);

You can see that OnDraw is called when dirtyOpaque is false

  • dirtyOpaque source code
 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;

You can see that it is controlled by privateFlags, that is, mPrivateFlags

  • view construction method
 computeOpaqueFlags();
  • computeOpaqueFlags(); check the source code
  // 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;
        }
  • View the source code of ViewGroup construction method
 super(context, attrs, defStyleAttr, defStyleRes);
        initViewGroup();
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
  • View initViewGroup source code
  if (!debugDraw()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }

You can see that the OnDraw in viewgrow is not visible because of setFlags.

Why: the background can be displayed after setting in viewgroup

  • Source code of setBackDrawable in view
   computeOpaqueFlags();

        if (background == mBackground) {
            return;
        }

At this time, the discovery recalculated

I think there are three solutions

  • ① Replace onDraw with dispatchDraw
  • ② Set background transparency
  • ③ You can see the source code and set the value of setFlags
public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

Posted by mzshah on Thu, 09 Apr 2020 08:58:12 -0700