自定义View之继承View

View的工作原理:
        测量流程:measure确定View的测量宽/高
            对于顶级View,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec有其父容器和自身的LayoutParams来共同决定
        布局流程:layout确定View的最终宽/高和四个顶点的位置
        绘制流程: draw将View绘制到屏幕上
    自定义View之继承View:
        直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent
        需要自己处理wrap_content和padding,处理wrap_content重写onMeasure()方法,处理padding在onDraw()方法中处理。

        例如:首先一个简单的实现代码如下所示

public class MeasureView extends View {
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    public MeasureView(Context context) {
        super(context);
        initPaint();
    }

    public MeasureView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public MeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initPaint() {
        paint.setColor(Color.parseColor("#ff0000"));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(width/2,height/2,radius,paint);
    }

}
在activity的布局文件中引用自定义控件如下所示

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.example.sun.customerview.MeasureView
        android:id="@+id/custom_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#3F51B5"/>
</LinearLayout>

运行结果如下图所示


从图中可以看出,尽管layout_width和layout_height设置为wrap_content,但View的显示是match_parent;向xml布局中为自定义View增添margin属性,如下图所示:

<com.example.sun.customerview.MeasureView
    android:id="@+id/custom_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="20dp"
    android:background="#3F51B5"/>

运行结果显示margin属性生效;

为View增添padding属性,如下图:

<com.example.sun.customerview.MeasureView
    android:id="@+id/custom_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="20dp"
    android:padding="20dp"
    android:background="#3F51B5"/>

运行结果显示padding并没有起作用,如下图所示


这也验证了上面所说的:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent,并且需要自己实现padding。改进方式如下:

public class MeasureView extends View {
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 

    public MeasureView(Context context) {
        super(context);
        initPaint();
    }

    public MeasureView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public MeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initPaint() {
        paint.setColor(Color.parseColor("#ff0000"));
      
    }

  
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //处理padding
        final int paddingRight = getPaddingRight();
        final int paddingLeft = getPaddingLeft();
        final int paddingBottom = getPaddingBottom();
        final int paddingTop = getPaddingTop();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingBottom - paddingTop;
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(paddingLeft +width/2,paddingTop +height/2,radius,paint);
    }

  //方式二 处理wrap_content
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measuredHeight(heightMeasureSpec));
    }

    /**
     * 测量宽
     * @param widthMeasureSpec
     */
    private int measureWidth(int widthMeasureSpec) {
        int result ;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY){
            result = specSize;
        }else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST){
                result = Math.min(result,specSize);
            }
        }
        return result;
    }

    /**
     * 测量高
     * @param heightMeasureSpec
     */
    private int measuredHeight(int heightMeasureSpec) {
        int result ;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY){
            result = specSize;
        }else{
            result = 200;
            if(specMode == MeasureSpec.AT_MOST){
                result = Math.min(result,specSize);
            }
        }
        return  result;
    }

}
 
 

运行结果为:

这样才算完全实现了一个自定义View


猜你喜欢

转载自blog.csdn.net/yao_94/article/details/79099732