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()方法中处理。
测量流程: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