本篇主要是讲解自定义View的基本操作,暂时无运行效果,主要涉及自定义View构造函数、onMeasure、onDraw、监听与时事件分发、自定义View属性等。最终效果需要结合《自定义View(2)onMeasure、onDraw》一齐食用。
1. 什么是自定义View?
自定义View可以理解为,除了安卓提供的原生继承View的API之外的,自定义的继承View或者ViewGroup的控件。
2. 自定义TextView
自定义TextView继承自View,并重写里面的三个构造方法
布局为什么会显示加载到我们的Activity,LayoutInflate解析的时候(实例化View是通过反射)。
public class TextView extends View {
// 该构造函数在代码里面new的时候调用。TextView textView = new TextView(this);
public TextView(Context context) {
super(context);
}
// 该构造函数在布局layout中使用。<com.example.view1.TextView/>
public TextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
/***
* 该构造函数在布局layout中调用,但是是在使用style时。
* <com.example.view1.TextView
* style="@style/Test_View_Test"
* android:text="Hello World!" />
*/
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
2.2 自定义View的测量方法onMeasure
/**
* 自定义View的测量方法,布局、控件的宽高由这个方法指定,需要测量。
*
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取宽高的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// MeasureSpec.AT_MOST : 在布局中指定为wrap_content
// MeasureSpec.EXACTLY : 在布局中指定为确切的值 xxdp、match_content、fill_content
// MeasureSpec.UNSPECIFIED : 在布局尽可能的大 很少用到,一般ScrollView、ListView在测量子布局的时候用到
if (widthMode == MeasureSpec.AT_MOST) {
}
}
ScrollView嵌套ListView会显示不全(只显示一项)
是因为ScrollView传给ListView的heightMode为UNSPECIFIED,走如下代码:
// /Android/Sdk/sources/android-33/android/widget/ListView.java
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
要解决这个问题,那么就需要heightMode为AT_MOST,我们自定义一个MyListView继承ListView。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Integer.MAX_VALUE是防止进入 if (returnedHeight >= maxHeight) 而不是返回return returnedHeight;
// >> 2 是因为MAX_VALUE是2^31,>> 2就是30位。(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST)就是32位,
// 满足makeMeasureSpec的需要。
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
---------------------
final int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 获取前2位
int heightSize = MeasureSpec.getSize(heightMeasureSpec); // 获取后30位
2.3 自定义View的绘制方法onDraw
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 画文本
canvas.drawText();
// 画弧
canvas.drawArc();
//画圆
canvas.drawCircle();
}
2.4 自定义View的事件分发处理
手指触摸,处理事件分发和拦截,用于用户交互
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}
2.5 自定义属性
2.5.1 在布局中自定义属性
<!-- 新建 /CustomView/View1/app/src/main/res/values/attrs.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- name最好是自定义View的名字 -->
<declare-styleable name="TextView">
<!-- name是属性名称;format是格式:dimension表示宽高、字体大小 -->
<attr name="text" format="string"/>
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="android:maxLength" format="integer"/>
<attr name="android:background" format="reference|color"/>
<!-- 枚举 -->
<attr name="android:inputType">
<enum name="number" value="1"/>
<enum name="text" value="2"/>
<enum name="password" value="3"/>
</attr>
</declare-styleable>
</resources>
2.5.2 在布局中使用自定义属性
申明命名空间,然后在自定义View布局中使用。
<!-- /CustomView/View1/app/src/main/res/layout/activity_main.xml -->
<!-- 这个是必须要添加的,app也可以改成其他名字,不然Android不认识,如下的android,在编译时就会报错。-->
xmlns:app="http://schemas.android.com/apk/res-auto"
<com.example.view1.TextView
app:text="你好"
app:textColor="@color/yellow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
2.5.3 在自定义View中获取这些属性
// /CustomView/View1/app/src/main/java/com/example/view1/TextView.java
public TextView(Context context) {
// 第一个构造函数可以直接调用第二个构造函数
this(context, null);
}
// 该构造函数在布局layout中使用。<com.example.view1.TextView/>
public TextView(Context context, @Nullable AttributeSet attrs) {
// 让第二个构造函数调用第三个构造函数
this(context, attrs, 0);
}
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
// 保证不管使用哪个购置函数,都是用的第三个构造函数
super(context, attrs, defStyleAttr);
// 获取自定义属性,包括刚刚自定义的Text、TextColor、TextSize
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);
mText = array.getString(R.styleable.TextView_text);
mTextColor = array.getColor(R.styleable.TextView_textColor, mTextColor);
mTextSize = array.getDimensionPixelSize(R.styleable.TextView_textSize, mTextSize);
// 回收
array.recycle();
}
做完这些,此时还是什么都不会显示的,因为没有指定宽高和进行绘制。