安卓自定义ViewAndroid开发Android知识

自定义View-onDraw篇(1)

2017-03-12  本文已影响138人  b496178cdc84

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,所以我们在这边才能看到。
未完待续......

上一篇下一篇

猜你喜欢

热点阅读