自定义View可以分为两大类,一种是自定义View,一种是自定义ViewGroup;其中自定义View又分为继承View和继承系统控件两种。
1.继承系统控件的自定义View
这种自定义View在系统控件的基础上进行拓展,一般是添加新的功能或者修改显示的效果,一般情况下我们在onDraw()方法中进行处理
自定义view时只需要重写onMeasure()和onDraw()
自定义viewGroup时只需要重写onMeasure()和onLyaout()
public class MyTextView extends View {
//当在代码中创建一个 View 的时候使用
//这个构造函数就是在代码中直接 new view 的时候使用,这样出来的 View 默认是没有任何的属性值,需要后面自己手动 set。
public MyTextView(@NonNull Context context) {
super(context);
}
//当我们在 xml 中定义了 View 然后在代码中使用这个 View 的时候,这个 View 就是利用这个构造方法生成的。
//View 的属性值来自 AttributeSet 的值
//这个构造函数是在代码中生成对应 xml 中定义的 View 使用的。这个时候在 xml 中定义的属性值会通过 AttributeSet 传递,这样生成的 View 对象是有默认的属性值的
public MyTextView(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
//这个构造方法就是提供了默认的 defStyleAttr 用于指定基本的属性
//这个构造函数就是相对于第二个构造函数,多提供了一种给 View 添加默认属性的方式,通过 deftStyleAttr 如果没有默认的值,就用 0 。
public MyTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//第四个构造函数相对第三个构造函数就多了一个 defStyleRes ,说白了就是多了一种提供 View 默认属性的一种方式。这种方式更加的简单,直接在代码中传入 R.style.XX 就可以了。如果没有默认值的话就为 0 。这个参数只有 defStyleAttr 为 0 的时候才会生效。
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
自定义属性
- AttributeSet与自定义属性
系统自带的View可以在xml中配置属性,对于写的好的自定义View同样可以在xml中配置属性,为了使自定义的View的属性可以在xml中配置,需要以下4个步骤:
- 1、通过<declare-styleable> 为自定义View添加属性
- 2、在xml中为相应的属性声明属性值
- 3、在运行时(一般为构造函数)获取属性值
- 4、将获取到的属性值应用到View
android系统的控件以android开头的比如android:layout_width,这些都是系统自带的属性,为了方便配置MyTextView的属性,我们也可以自定义属性,首先在values目录下创建 attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
<attr name="textcolor" format="color"/>
</declare-styleable>
</resources>
在构造方法中解析自定义view的属性
public MyTextView(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
typedArray.getColor(R.styleable.MyTextView_textcolor,1);
//获取资源后要及时回收
typedArray.recycle();
}
在xml里使用自定义属性
<com.example.myview.view.MyTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:textcolor="@color/black"/>
三个过程
三个动作对应的意义
测量:onMeasure()决定View的大小;
布局:onLayout()决定View在ViewGroup中的位置;
绘制:onDraw()决定绘制这个View。
MeasureSpec
1.MeasureSpec的Model可以分为以下三种模式:
UNSPECIFIED
:父视图不对View大小做限制,这种情况一般用于系统内部, 表示一种测量状态。(这个模式主要用于系统内部多次Measure的情形,并不是真的说你想要多大最后就真有多大)。EXACTLY
:父控件已经知道你所需的精确大小,你的最终大小应该就是这么大,如:100dpAT_MOST
:子视图的大小不能大于父控件给你指定的size,但具体是多少,得看你自己的实现。如:matchParent, 最大不能超父视图。
2、getMeasureWidth与getWidth的区别
getMeasuredWidth
在measure()过程结束后就可以获取到对应的值;
通过setMeasuredDimension()方法来进行设置的。getWidth
在layout()过程结束后才能获取到;
通过视图右边的坐标减去左边的坐标计算出来的。
父视图与子视图的关系
- 当view采用固定宽高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小;
- 当view的宽高是match_parent时,这个时候如果父容器的模式是精准模式,那么view也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且其大小不会超过父容器的剩余空间;
- 当view的宽高是wrap_content时,不管父容器的模式是精准还是最大化,view的模式总是最大化并且大小不能超过父容器的剩余空间。
- Unspecified模式,这个模式主要用于系统内部多次measure的情况下,一般来说,我们不需要关注此模式(这里注意自定义View放到ScrollView的情况 需要 处理)。