基础
1. 分类
自定义View的实现方式有以下几种
类型 | 定义 |
---|---|
自定义组合控件 | 多个控件组合成为一个新的控件,方便多处复用 |
继承系统View控件 | 继承自TextView等系统控件,在系统控件的基础功能上进行扩展 |
继承View | 不复用系统控件逻辑,继承View进行功能定义 |
继承系统ViewGroup | 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展 |
继承ViewViewGroup | 不复用系统控件逻辑,继承ViewGroup进行功能定义 |
2. View绘制流程
View的绘制基本由measure()、layout()、draw()这个三个函数完成
函数 | 作用 | 相关方法 |
---|---|---|
measure() | 测量View的宽高 | measure(),setMeasuredDimension(),onMeasure() |
layout() | 计算当前View以及子View的位置 | layout(),onLayout(),setFrame() |
draw() | 视图的绘制工作 | draw(),onDraw() |
3. 坐标系
在Android坐标系中,以屏幕左上角作为原点,这个原点向右是X轴的正轴,向下是Y轴正轴。如下所示:
Android坐标系.png
除了Android坐标系,还存在View坐标系,View坐标系内部关系如图所示。
视图坐标系
View获取自身高度
由上图可算出View的高度:
width = getRight() - getLeft();
height = getBottom() - getTop();
View的源码当中提供了getWidth()和getHeight()方法用来获取View的宽度和高度,其内部方法和上文所示是相同的,我们可以直接调用来获取View得宽高。
View自身的坐标
通过如下方法可以获取View到其父控件的距离。
getTop();获取View到其父布局顶边的距离。
getLeft();获取View到其父布局左边的距离。
getBottom();获取View到其父布局底边的距离。
getRight();获取View到其父布局右边的距离。
自定义View
1. 构造函数
构造函数有多个,必须至少继承几个
public class MyView extends View {
/**
* 在java代码里new的时候会用到
* @param context
*/
public MyView(Context context) {
super(context);
}
/**
* 在xml布局文件中使用时自动调用
* @param context
* @param attrs
*/
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
/**
* 不会自动调用,如果有默认style时,在第二个构造函数中调用
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 只有在API版本>21时才会用到
* 不会自动调用,如果有默认style时,在第二个构造函数中调用
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
2. onMeasure
在学习Android的时候,我们就知道,在xml布局文件中,我们的layout_width和layout_height参数可以不用写具体的尺寸,而是wrap_content或者是match_parent。其意思我们都知道,就是将尺寸设置为“包住内容”和“填充父布局给我们的所有空间”。这两个设置并没有指定真正的大小,可是我们绘制到屏幕上的View必须是要有具体的宽高的,正是因为这个原因,我们必须自己去处理和设置尺寸。当然了,View类给了默认的处理,但是如果View类的默认处理不满足我们的要求,我们就得重写onMeasure函数啦。这里举个例子,比如我们希望我们的View是个正方形,如果在xml中指定宽高为
wrap_content
,如果使用View类提供的measure处理方式,显然无法满足我们的需求。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
MeasureSpec
MeasureSpec是View的内部类,它封装了一个View的尺寸,在onMeasure()当中会根据这个MeasureSpec的值来确定View的宽高。
MeasureSpec的值保存在一个int值当中。一个int值有32位,前两位表示模式mode后30位表示大小size。即MeasureSpec = mode + size。
在MeasureSpec当中一共存在三种mode:UNSPECIFIED、EXACTLY 和AT_MOST。
对于View来说,MeasureSpec的mode和Size有如下意义
模式 | 意义 | 对应 |
---|---|---|
EXACTLY | 精准模式,View需要一个精确值,这个值即为MeasureSpec当中的Size | match_parent,固定尺寸(如100dp) |
AT_MOST | 最大模式,View的尺寸有一个最大值,View不可以超过MeasureSpec当中的Size值 | wrap_content |
UNSPECIFIED | 无限制,View对尺寸没有任何限制,View设置为多大就应当为多大 | 一般系统内部使用 |
setMeasuredDimension(int measuredWidth, int measuredHeight) :该方法用来设置View的宽高,在我们自定义View时也会经常用到。
重写onMeasure(),画个正方形
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getSize(200,widthMeasureSpec);
int height = getSize(200,heightMeasureSpec);
if (width<height){
height = width;
}else{
width = height;
}
setMeasuredDimension(width,height);
}
private int getSize(int defaultSize, int measureSpec) {
int resultSize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.EXACTLY://如果是固定的大小,那就不要去改变它
resultSize = size;
break;
case MeasureSpec.AT_MOST://如果测量模式是最大取值为size
//我们将大小取最大值,你也可以取其他值
resultSize = size;
break;
case MeasureSpec.UNSPECIFIED://如果没有指定大小,就设置为默认大小
resultSize = defaultSize;
break;
default:
break;
}
return resultSize;
}
自定义view.png
3. 重写onDraw
@Override
protected void onDraw(Canvas canvas) {
//调用父View的onDraw函数,因为View这个类帮我们实现了一些
// 基本的而绘制功能,比如绘制背景颜色、背景图片等
super.onDraw(canvas);
int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
//圆心的横坐标为当前的View的左边起始位置+半径
int centerX = getLeft() + r;
//圆心的纵坐标为当前的View的顶部起始位置+半径
int centerY = getTop() + r;
Paint paint = new Paint();
paint.setColor(Color.GREEN);
//开始绘制
canvas.drawCircle(centerX, centerY, r, paint);
}
圆
4.自定义布局属性
4.1 在res/values/styles.xml声明自定义的属性
<!--name为声明的"属性集合"名,可以随便取,但是最好是设置为跟我们的View一样的名称-->
<declare-styleable name="MyView">
<!--声明我们的属性,名称为default_size,取值类型为尺寸类型(dp,px等)-->
<attr name="defalut_size" format="dimension"/>
<!--声明我们的属性,defalut_color,取值类型为颜色值或者参考某一资源ID-->
<attr name="defalut_color" format="color|reference"/>
</declare-styleable>
4.2 在xml文件中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:hqd = "http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
tools:context=".ViewActivity">
<com.study.hdq.componentdemo.MyView
android:layout_width="match_parent"
hqd:defalut_color="#f34567"
hqd:defalut_size="300dp"
android:layout_height="100dp"/>
</LinearLayout>
注意:需要在根标签(LinearLayout)里面设定命名空间,命名空间名称可以随便取,比如hc,命名空间后面取得值是固定的:"http://schemas.android.com/apk/res-auto"
4.3 获取值
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//第二个参数就是我们在styles.xml文件中的<declare-styleable>标签
//即属性集合的标签,在R文件中名称为R.styleable+name
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
//第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称
//第二个参数为,如果没有设置这个属性,则设置的默认的值
defalutSize = a.getDimensionPixelSize(R.styleable.MyView_defalut_size, 100);
defalutColor = a.getColor(R.styleable.MyView_defalut_color, Color.RED);
//最后记得将TypedArray对象回收
a.recycle();
}
可以设置颜色的圆.png
网友评论