自定义View
自定义View三步走:
- mesure: View的测量流程
- layout: 布局流程
- draw: 绘制流程
mesure:用来测量View的宽和高
layout:确定View在父容器中放置位置
draw:负责将View绘制在屏幕上
理解MeasureSpec
MeasureSpec字面意思:“测量规格”
测量过程中,系统会将View的LayoutParams**根据父容器所施加的规则**转化成对应的MeasureSpec,在根据这个MeasureSpec来测量View的宽和高。
- 这里面说的根据父容器施加的规则到底是什么?
MeasureSpec代表32位int,高两位代表SpecMode,低30位代表SpecSize。
SpecMode有三种:
UNSPECIFIED
不对View任何限制要多大有多大(一般不常用)EXACTLY
这种模式称为精确模式,父容器已经检测出View所需要的精确大小,它对应的就是LayouParams中的match_parent和具体数值这两种模式。(View最终大小就是SpecSize)AT_MOST
这种模式称为最大模式,父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体什么值看不同View的具体实现。它对应LayoutParams中的wrap_content。
普通View来说,MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams来共同决定,此外还和View的margin及padding有关
普通View的MeasureSpec的创建规则
子元素的MeasureSpec是由父容器的MeasureSpec和自身LayoutParams共同决定!
父容器不同,View的LayoutParams不同,View九游多种MeasureSpec。
表格说明:
当View采用固定宽/高(dp/px),不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式,大小遵循LayoutParams中的大小。
当View的宽/高是match_parent时:
- 父容器是EXACTLY(精确模式),这时候View也是精确模式,大小是父容器剩余空间。
- 父容器是AT_MOST(最大模式),这时候View也是最大模式,大小不会超过父容器剩余空间。
当View的宽/高是wrap_content时:不管父容器是模式是精确还是最大化,View的模式总是最大化,大小不能超过父容器剩余空间。
View的工作流程
measure过程
- measure确定View的测量过程
- layout确定View的最终宽高,和四个顶点位置
- draw将View绘制到屏幕上
View的measure过程
结论:直接继承View的自定义控件需要重写onMeasure,并且还要设置是wrap_content时的自身大小(就是布局中如果是wrap_content时,需要设置View的大小)否则在布局中使用wrap_content就相当于match_parent.
原因:在布局中使用wrap_content,那么它的specMode是AT_MOST模式,它的宽/高都是父布局中可使用的大小,这时候的specSize是parentSize(父布局当前剩余的空间大小)。效果和在布局中使用match_parent一样!
怎样解决:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widrhSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(200, 200); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(200, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widrhSpecSize, 200); } }
这里给View指定一个默认的内部宽/高(自己设置大小,没有固定依据,自己去灵活处理),并在wrap_content时设置设置此宽高即可。对于非wrap_content沿用系统测量的即可。
继承View重写onDraw()方法
实现过程必须考虑到wrap_content模式以及padding。还可以对外提供自定义属性。
margin属性有父容器控制,不用自定义View特殊处理。
例如:布局文件设置了padding属性,onDraw里面解决方式:
<com.example.pakar.viewdemo.CustomView
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_margin="20dp"
android:background="#000000"
android:padding="20dp"
my:my_color="@color/colorAccent" />
布局中设置了padding。
CustomView里面onDraw需要注意:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); int width = getWidth() - paddingLeft - paddingRight; int height = getHeight() - paddingTop - paddingBottom; int radius = Math.min(width, height) / 2; canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint); }
这时候padding效果才会显示出来。
当然这里的布局文件也使用了wrap_content,onMeasure里面也需要处理。(onMeasure方法里面上面代码有),不然和match_parent一样了。
自定义属性提供给外界
这是套路都这样写:
在values目录下面创建自定义属性XML,比如attrs.xml,也可以类似于attrs_circle_view.xml等,这种attrs_开头的,可以随意取名。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomView">
<attr name="my_color" format="color" />
</declare-styleable>
</resources></pre>
在View构造方法解析自定义属性
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mColor = array.getColor(R.styleable.CustomView_my_color, Color.RED); array.recycle(); }
第三步:在布局文件中使用自定义属性
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:my="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.pakar.viewdemo.CustomView
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_margin="20dp"
android:background="#000000"
android:padding="20dp"
my:my_color="@color/colorAccent" />
</LinearLayout>
* xmlns:my=”http://schemas.android.com/apk/res-auto”*
这里的my是自定义属性的前缀,随便。
apk/res后面可以附加应用的包名,不过/apk/res-auto更方便。