自定义View学习笔记(一)-简单的自定义View

Android实际开发过程中,不可避免的需要实现一些不规则的效果,这种效果不方便使用控件组合方式实现,往往需要静态或者动态地显示一些不规则的图形。这种效果需要通过绘制的方式来实现,即重写onDraw方法。
我们先使用用一种最简单的方式来创建我们的第一个自定义VIew。打开Android Studio,先新建一个测试工程,然后使用模板自动生成一个自定义View,关键步骤如图


自定义View学习笔记(一)-简单的自定义View_第1张图片
bfa76b64.png


这里面有两个重要的知识点:

1.如何添加自定义属性?

参考自动生成的自定义View,可以看到添加自定义属性的步骤如下:
1.在values目录下创建自定义属性的xml,比如:attrs_custom_view.xml,这个名字无限制。本例自动生成的代码如下:



    
        
        
        
        
    

这里的格式(format)除了包含基本数据类型string,integer,boolean等,还有格式color是指颜色,reference是指资源id,dimension是指尺寸等。
2.在View的构造方法中解析自定义属性的值并做相应处理。

      // Load attributes
        final TypedArray a = getContext().obtainStyledAttributes(
                attrs, R.styleable.CustomView, defStyle, 0);

        mExampleString = a.getString(
                R.styleable.CustomView_exampleString);
        mExampleColor = a.getColor(
                R.styleable.CustomView_exampleColor,
                mExampleColor);
        // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
        // values that should fall on pixel boundaries.
        mExampleDimension = a.getDimension(
                R.styleable.CustomView_exampleDimension,
                mExampleDimension);

        if (a.hasValue(R.styleable.CustomView_exampleDrawable)) {
            mExampleDrawable = a.getDrawable(
                    R.styleable.CustomView_exampleDrawable);
            mExampleDrawable.setCallback(this);
        }

        a.recycle();

解析完后,通过recycle方法来释放资源。
3.在布局文件中使用自定义属性,如下所示。



    


为了使用自定义属性,需要在布局文件中添加schemas声明: xmlns:app="http://schemas.android.com/apk/res-auto
在这个声明中,app是自定义属性的前缀,当然可以使用其他名字,但是CustomView中自定义属性的前缀必须和这里一致,然后就可以在CustomView中使用自定义属性了,如: app:exampleDimension="24sp"。自动生成的代码采用的是:xmlns:app="http://schemas.android.com/apk/res/com.example.my.customview.view"这种方式命名,也就是会在apk/res/+包名。这两种方式没有本质的区别,但是我倾向于使用res-auto这种声明方式。

2. 自定义View为什么需要重写onDraw方法?

我们需要先学习下非ViewGroup的View的绘制流程。
自定义View学习笔记(一)-简单的自定义View_第2张图片
View绘制流程图 .png

View的draw()源码,在该阶段真正地开始对视图进行绘制。

/**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    @CallSuper
    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);
        }

        // 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
           // 重载的onDraw在这里被调用
            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;
        }

看完了draw()的源码,我们就要把注意力集中在onDraw()了。

  /**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }

官方文档中介绍
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
在绘图时需要明确的4个基本要素:
1.一个Bitmap保存了所绘图像的各个像素(pixel)
2.一个源,即是你想画的东西(比如矩形,路径,文本,抑或另一个位图)
3.一个canvas执行绘图操作。 比如,canvas.drawCircle(),canvas.drawLine()。
4.一支画笔(Paint)来绘图。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // TODO: consider storing these as member variables to reduce
        // allocations per draw cycle.
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();

        int contentWidth = getWidth() - paddingLeft - paddingRight;
        int contentHeight = getHeight() - paddingTop - paddingBottom;

        // Draw the text.
        canvas.drawText(mExampleString,
                paddingLeft + (contentWidth - mTextWidth) / 2,
                paddingTop + (contentHeight + mTextHeight) / 2,
                mTextPaint);

        // Draw the example drawable on top of the text.
        if (mExampleDrawable != null) {
            mExampleDrawable.setBounds(paddingLeft, paddingTop,
                    paddingLeft + contentWidth, paddingTop + contentHeight);
            mExampleDrawable.draw(canvas);
        }
    }

总结

本文介绍一种非常简单的自定义View方式,讲述了可能遇到的疑惑,后续会有更多文章来记录我重新学习自定义View的过程。

你可能感兴趣的:(自定义View学习笔记(一)-简单的自定义View)