1. 定义
自定义View可以认为是继承自View,系统没有提供给我们的效果,需要我们自己需要手动去写,比如TextView、ImageView、Button不是自定义View,因为都是系统提供好的。
2. 继承
继承关系有2种
1>:extends View
2>:extends ViewGroup
3. 构造方法
在这里,我们就以一个自定义TextView作为自定义View的一个入门小示例,在自定义TextView中,会有3个构造方法,而这3个构造方法的具体调用场景是不一样的,调用场景如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/3/17 8:17
* Version 1.0
* Params:
* Description: 自定义TextView
*/
public class TextView extends View {
/*在MainActivity中new TextView时调用这个构造方法
TextView tv = new TextView(this) ;*/
public TextView(Context context) {
super(context);
}
// 在布局文件中引用的 自定义的TextView时调用这个构造方法
/*<com.jackchen.view_day01.TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="jack"
/>*/
public TextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
// 在布局文件layout中会调用,但是是用style把属性引进来
/* <!-- 注意这里的name不能为系统关键字-->
<style name="defualt">
<!-- Customize your theme here. -->
<item name="layout_width">wrap_content</item>
<item name="layout_height">wrap_content</item>
<item name="textColor">@color/colorAccent</item>
</item>
</style>
<com.jackchen.view_day01.TextView
style="@style/defualt"
android:text="jack"
/>
*/
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
由以上代码可知:
1>:如果是直接在MainActivity中直接 new TextView()的,那么会调用带有一个参数的构造方法;
2>:如果是在布局中引用的自定义的TextView,那么会调用带有2个参数的构造方法;
3>:如果是先在style中定义好所有属性,然后把该style属性引入到layout布局文件中,那么会调用带有3个参数的构造方法;
4. onMeasure()方法
用于测量并指定我们所有自定义View控件的宽高,获取宽高的模式、宽高的大小方法如下:
/**
* 自定义View 的测量方法
* layout布局文件中的所有自定义View控件的宽高,都是由这个方法指定的
* 指定控件的宽高,需要测量
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取宽高的模式 前2位
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 获取宽高的大小 后面30位
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 如果layout布局文件中自定义View控件的宽高设置为wrap_content ,那么获取到的模式就是 MeasureSpec.AT_MOST
// 如果layout布局文件中自定义View控件的宽高设置为确切的值,如100dp、match_parent、fill_parent,那么获取到的模式就是 MeasureSpec.EXACTLY
// 如果layout布局文件中自定义View控件的宽高值尽可能的大 ,那么获取到的模式就是 MeasureSpec.UNSPECIFIED,这个很少用到,一般是系统中才会用的
// ScrollView源码、ListView源码在测量子布局的时候会用到UNSPECIFIED
if (widthMode ==MeasureSpec.UNSPECIFIED){
}
}
注意 1:
MeasureSpec.AT_MOST ——> 代表layout布局文件中自定义View控件的宽高设置为wrap_content;
MeasureSpec.EXACTLY ——> 代表layout布局文件中自定义View控件的宽高设置为确切的值,如100dp、match_parent、fill_parent;
MeasureSpec.UNSPECIFIED ——> 代表layout布局文件中自定义View控件的宽高值尽可能的大 ,这个很少用到,一般是系统中才会用的,比如ScrollView源码、ListView源码在测量子布局的时候会用到UNSPECIFIED;
注意 2:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
注意onMeasure()方法中的widthMeasureSpec或者heightMeasureSpec,它代表2个意思:
比如说heightMeasureSpec,包含了两个信息,它是一个32位的值,前边30位是一个Integer.MAX_VALUE的值,后边2位是MeasureSpec.AT_MOST的模式
在这里会有一个面试题,ScrollView嵌套ListView显示不全,什么原因?如何解决?
5. onDraw()方法
onDraw()方法用于绘制,绘制文本、绘制圆弧、绘制圆形
/**
* 用于绘制
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// // 画文本
// canvas.drawText();
// // 画弧
// canvas.drawArc();
// // 画圆
// canvas.drawCircle();
}
6. onTouchEvent()方法
onTouchEvent()方法用于处理和用户手指触摸、交互事件的。
/**
* 事件分发使用的设计模式是 —— 责任链设计模式
* 处理和用户手指触摸、交互的
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 手指按下
Log.e("TAG" , "手指按下") ;
break;
case MotionEvent.ACTION_MOVE:
// 手指移动
Log.e("TAG" , "手指移动") ;
break;
case MotionEvent.ACTION_UP:
// 手指抬起
Log.e("TAG" , "手指抬起") ;
break;
}
// return super.onTouchEvent(event);
return true ;
}
在事件分发中采用的是责任链设计模式,因为ViewGroup源码中里边有一个 while(true)循环,代码如下:
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
如果你在onTouchEvent()方法中return true,那么它会不断的调用 onTouchEvent()方法中的ACTION_DOWN、ACTION_MOVE、ACTION_UP方法;
如果不返回true,而直接返回的是super.onTouchEvent(event),那么onTouchEvent()方法就只会调用ACTION_DOWN方法,并且不会进到ACTION_MOVE和ACTION_UP方法中,也就是说它就不会响应 这两个事件。
而且一般onTouchEvent()方法都是return true,而不会super.onTouchEvent(event),因为return true表示事件是交给自己处理,而return super的话是交给父类处理的。
7. 自定义属性
定义
自定义属性就是用来配置的
7.1:系统的自定义属性
比如下边的 android:text="JackChen",就是系统的一个自定义属性
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JackChen"
/>
7.2:自己自定义View的自定义属性
1>:在res下的values下面新建一个attrs.xml文件,名字可以随便起,但是一般都是叫做attrs.xml,别人一看就知道;
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 这里的name最好就是自己自定义View的名字就可以-->
<declare-styleable name="TextView">
<!-- name是属性名称,format是格式
string 表示文字
color 表示文字颜色
dimension 表示宽高、字体大小
integer 表示数字,相当于int
reference 表示资源 (drawable)
-->
<attr name="text" format="string"/>
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="maxLength" format="integer"/>
<attr name="background" format="reference|color"/>
<!-- 枚举 -->
<attr name="inputType">
<enum name="number" value="1"/>
<enum name="text" value="2"/>
<enum name="password" value="3"/>
</attr>
</declare-styleable>
</resources>
2>:在布局中使用
首先声明命名空间:
xmlns:app="http://schemas.android.com/apk/res-auto"
然后在自己自定义View的布局文件中去使用
<com.jackchen.view_day01.TextView
app:text="1111111"
app:textColor="@color/colorAccent"
app:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
3>:在自定义View的第三个构造方法中获取自定义属性
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);
// 获取文字
mText = array.getString(R.styleable.TextView_text) ;
// 获取文字颜色
mTextColor = array.getColor(R.styleable.TextView_textColor , mTextColor) ; // mTextColor表示上边定义的默认黑色
// 获取文字大小
mTextSize = array.getDimensionPixelSize(R.styleable.TextView_textSize , mTextSize) ;
// 回收
array.recycle();
}
上边这3步都是自定义View的套路,在我们接下来写的所有的自定义View的效果中,都是按照这些套路中写的,而且都离不开这3步,也会围绕着这3步去扩展一些新的东西,而这3步就是最基础的自定义View的入门的套路。