自定义View-onDraw篇(1)

作者: b496178cdc84 | 来源:发表于2017-03-12 23:07 被阅读138次

    1、<a href='http://www.jianshu.com/p/f6623f00b4a0'>写给新人看的自定义View-onMeasure篇(1)</a>
    2、<a href='http://www.jianshu.com/p/fb687e55b0d1'>写给新人看的自定义View-onMeasure篇(2)</a>
    3、<a href='http://www.jianshu.com/p/a5b1e778744f'>写给新人看的自定义View-onLayout篇</a>

    onDraw我觉得是最繁琐,最复杂的一个步骤,因为我每次写坐标,都很费事,可能我对坐标本身并不敏感,虽然很费事,但是绘制成功之后,还是成就感大大滴。


    Paste_Image.png

    最后待我们分析完后,一起来使用onDraw方法,画上图中的效果。
    Path和属性动画一起使用,大家可以先思考一下.

    最近有人问我,知道这些自定义View的流程,就是像onMeasure , onLayout ,onDraw 有什么用呢?项目中不太常用到,而且,就算用到了,也可以直接去github上找对应的库。而且平时自定义View都是直接继承LinearLayout,和Relativelayout,然后直接LayoutInfalte就可以了,根本不需要重写这些流程。

    我觉得呢,这些步骤还是非常有用的,应该是非常非常有用,不知道大家有没有遇到,在繁琐的Xml布局中,有时候给控件wrap_content,会出现自己不想要的结果,或者嵌套自定义View,宽高也不是自己想要的,这种情况发生的时候,大多数情况下,都是去反复的调试,可能真的能调试出自己的想要的样子,但是却不知道为什么,下次出现了,又不会了。所以,知道这些流程,能帮助我们快速定位到布局中出现问题的地方,并且以后不会再触碰这些坑。。
    OK,废了一些话,步入正题
    国际惯例,还是先看一下源码

     public void draw(Canvas canvas) {
            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;
    
            /*
             * Draw traversal performs several drawing steps which must be executed
             * in the appropriate order:
             *
             *      1. Draw the background
             *      2. If necessary, save the canvas' layers to prepare for fading
             *      3. Draw view's content
             *      4. Draw children
             *      5. If necessary, draw the fading edges and restore layers
             *      6. Draw decorations (scrollbars for instance)
             */
    
            // Step 1, draw the background, if needed
            int saveCount;
    
            if (!dirtyOpaque) {
                drawBackground(canvas);
            .......
            }
    

    代码实在忒多了,一点一点的来。
    直接挑选重点看。

            /*
             * Draw traversal performs several drawing steps which must be executed
             * in the appropriate order:
             *
             *      1. Draw the background
             *      2. If necessary, save the canvas' layers to prepare for fading
             *      3. Draw view's content
             *      4. Draw children
             *      5. If necessary, draw the fading edges and restore layers
             *      6. Draw decorations (scrollbars for instance)
             */
    

    谷歌大大已经为我们标注好了大概的流程,我们来看一下。
    第一步:画View的背景,也就是为这个View搞一个背景,我们在Xml文件中background属性。
    第二步:这步我们暂时用不到(做了几个项目,也写了很多自定义View还没涉及到这个),源码也上也是说,如果有必要的话,在去分析。我们为了新人也能懂(其实,我也不知道),这边先不分析。先放一下。
    第三步:画View的内容。
    第四部:如果有子View,就画子View
    第五步:如果必要。。。。 同第二步,现在对我们来说不是很必要。放一下。
    第六步:画decorations(装饰品) ,啥是装饰品,也就类似于,我们View上面的滑动条啊,滚动条之类的,给我们View做个装饰。
    第七步:没有第七步了。。。哈哈。没错,我是个智障。

            if (!dirtyOpaque) {
                drawBackground(canvas);
    

    上面的代码已经可以看到第一步了,drawBackground。

        private void drawBackground(Canvas canvas) {
            final Drawable background = mBackground;
            if (background == null) {
                return;
            }
    
            setBackgroundBounds();
    
            // Attempt to use a display list if requested.
            if (canvas.isHardwareAccelerated() && mAttachInfo != null
                    && mAttachInfo.mHardwareRenderer != null) {
                mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
    
                final RenderNode renderNode = mBackgroundRenderNode;
                if (renderNode != null && renderNode.isValid()) {
                    setBackgroundRenderNodeProperties(renderNode);
                    ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                    return;
                }
            }
    ....
    }
    

    代码不多,mBackground是通过我们传递的resId生成的drawable,然后这个步骤,将Drawable画在View的背景上

    第三步

         // skip step 2 & 5 if possible (common case)
            final int viewFlags = mViewFlags;
            boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
            boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
            if (!verticalEdges && !horizontalEdges) {
                // 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);
    
                // we're done...
                return;
            }
    
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);
    

    哎呀,很快就看到今天的主角了。View绘制自己的内容,调用了onDraw方法。传了一个canvas进去。OK,这个onDraw是留给子类自己实现的,我们已经都知道了。继续看下面.

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

    绘制子View,其实这个方法也是由子View(ViewGroup)实现的,我们可以看看ViewGourp是怎么实现的这个方法

        protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            return child.draw(canvas, this, drawingTime);
        }
    

    是滴,很一针见血,直接调用了子View的onDraw方法。其实看一下LinearLayout的dispatchDraw方法就是知道,遍历子View,然后做了一些动画操作,之后调用了子View的onDraw方法。对对对,就是这样。这里不贴源码了,有兴趣的同学可以自行查看LinearLayout的dispatchDraw方法。

    第五步:
    和第二步一样,我们先不考虑,不是很有必要考虑他。

    第六步:

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

    画装饰

     public void onDrawForeground(Canvas canvas) {
            onDrawScrollIndicators(canvas);
            onDrawScrollBars(canvas);
    
            final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
            if (foreground != null) {
                if (mForegroundInfo.mBoundsChanged) {
                    mForegroundInfo.mBoundsChanged = false;
                    final Rect selfBounds = mForegroundInfo.mSelfBounds;
                    final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
    
                    if (mForegroundInfo.mInsidePadding) {
                        selfBounds.set(0, 0, getWidth(), getHeight());
                    } else {
                        selfBounds.set(getPaddingLeft(), getPaddingTop(),
                                getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
                    }
    
                    final int ld = getLayoutDirection();
                    Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                            foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
                    foreground.setBounds(overlayBounds);
                }
    
                foreground.draw(canvas);
            }
        }
    

    这里面主要是画一些滚动条,滑动指示器之类的。像我们的ListView,GridVIew。其实我们像我们的TextView,Button最基本的控件,也是有这些装饰品,只不过我们没有让他们显示出来。
    OK,暂且先分析这几个基本的步骤。下面来看onDraw。

    先来看几个东西

    Canvas : 画什么; 比如我想画矩形,圆形,不规则图形
    Bitmap : 载体;我画的东西,展示在这个上面
    Paint : 画笔,怎么画;比如:我要画的东西要是黑色的,并且有边框,边框为5px,抗锯齿。

    这三个东西是不可分开的,我们要将图形展示在界面上,这三个缺一不可。

    好,现在看一个demo

    public class MyView extends View {
        Paint mPaint;
    
        public MyView(Context context) {
            this(context, null);
            initView();
        }
    
        public MyView(Context context, AttributeSet attrs) {
            super(context, attrs, 0);
            initView();
        }
    
        public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView();
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawRect(0,0,100,100,mPaint);
        }
    
        private void initView() {
            mPaint = new Paint();
            mPaint.setColor(Color.BLUE);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            //只测试一下宽度就好
            switch (widthMode) {
                //从ViewGroup的源码中可以看出,
                case MeasureSpec.AT_MOST:
                    //我们这里写死了200
                    widthSize = 200;
                    break;
                case MeasureSpec.EXACTLY:
    
                    break;
                case MeasureSpec.UNSPECIFIED:
                    break;
            }
            setMeasuredDimension(widthSize, heightSize);
        }
    }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawRect(0,0,100,100,mPaint);
        }
    
    

    OK ,我们画了一个矩形,是个蓝色的矩形。
    有些人可能有疑问了,不是说好Canvas Paint Bitmap是牢牢不可分的吗?Canvas Paint 有了,Bitmap呢? 其实 这个Canvas传来的时候,已经绑定好了Bitmap,所以我们在这边才能看到。
    未完待续......

    相关文章

      网友评论

        本文标题:自定义View-onDraw篇(1)

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