自定义View示例
构造方法
- 创建一个 java 类,继承 View 父类。
- 重写3个构造方法
【示例】
public class MyView extends View {
public MyView(Context context) {
this(context, null); //调用本类第二个构造方法
}
public MyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0); //调用本类第3个构造方法
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
(注:修改前两个构造方法的目的是方便调用,无论调用哪个构造方法,其实最终调用的都是第3个构造方法。)
构造方法调用时机
第一个构造方法
// 构造函数会在代码里面 new 的时候调用
// MyView mv = new MyView(this);
public MyView(Context context) {
this(context, null);
}
第二个构造方法
// 在布局文件中添加控件时调用
public MyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
<!-- 路径:layout/activity_main.xml -->
<com.example.viewproject.MyView
app:text="ZZFan"
app:textColor="@color/white"
android:layout_width="match_parent"
android:layout_height="match_parent" />
第三个构造方法=
//布局文件中添加自定义属性,当使用 style 属性时调用该构造方法
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
<com.example.viewproject.MyView
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/default" />
自定义属性
自定义控件需要使用自己定义的属性
一般在 res/values/ 新建 attrs.xml 文件添加自定义属性。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--自定义控件的属性也需要自定义-->
<!--name:自定义控件的名字-->
<declare-styleable name="MyView">
<!--
name:自定义属性的名称;
format:格式 string 字符串
color 颜色
dimension 宽高,字体大小
integer 数字
reference 资源(drawable)
-->
<attr name="text" format="string" />
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
<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>
然后在第三个构造方法中获取属性值
public class MyView extends View {
private String mText;
private int mTextSize = 15;
private int mTextColor = Color.BLACK;
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
//获取文字
mText = typedArray.getString(R.styleable.MyView_text);
//获取颜色
mTextColor = typedArray.getColor(R.styleable.MyView_textColor, mTextColor);
//获取文字大小
mTextSize = typedArray.getDimensionPixelSize(R.styleable.MyView_textSize, mTextSize);
//回收
typedArray.recycle();
}
}
onMeasure()
/**
* 自定义View的测量方法
* 布局的宽高由这个方法指定
* @param widthMeasureSpec 宽
* @param heightMeasureSpec 高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//指定控件的宽高,需要测量
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
/*
* View测量模式
* MeasureSpec.AT_MOST:在布局中指定为 wrap_content
* MeasureSpec.EXACTLY:在布局中指定为确切的值(100dp)或 match_parent fill_parent
* MeasureSpec.UNSPECIFIED:在布局中尽可能大,很少用到。
* ScrollView嵌套ListView使用时会有显示不全的问题,在测量子布局大小时会用到UNSPECIFIED
*/
if (widthMode == MeasureSpec.AT_MOST) {
//TODO
//具体实现逻辑可以参考 ListView 中的 onMeasure() 方法
}
}
onDraw()
/**
* 用于绘制图像,包括画图,画文本
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//一般不在 onDraw() 中初始化 paint 对象
Paint paint = new Paint();
paint.setTextSize(100);
//画文本
//注意:x y 的值并不是文字的左上角,二十基于文字的基准线画的。
canvas.drawText("HelloWorld", 200, 200, paint);
//定义圆心位置,以及圆的大小
float x = (getWidth() - getHeight() / 2) / 2;
float y = getHeight() / 4;
RectF oval = new RectF( x, y,
getWidth() - x, getHeight() - y);
//画弧
/*
@param oval 定义圆弧的形状和大小范围
@param startAngle 设置圆弧是从哪个角度(哪个起始点)顺时针画的
@param sweepAngle 圆弧扫过的角度
@param useCenter If true, include the center of the oval in the arc, and close it if it is
being stroked. This will draw a wedge
(如果是 true 则图像为扇形,即绘制的图形是经过圆心的;如果是 false 则图像是半圆形。
@param paint The paint used to draw the arc (画笔对象属性)
*/
canvas.drawArc(oval, 360, 140, false, paint);
//画圆
/*
@param cx 圆心 x 坐标
@param cy 圆心 y 坐标
@param radius 圆的半径
@param paint 绘制时所使用的画笔
*/
canvas.drawCircle(50, 50, 10, paint);
}
onTouchEvent()
/**
* 处理跟用户交互的 Touch 事件,滑动滚动
* @param event touch 事件分发、拦截
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//手指按下
Log.d("onTouchEvent", "action down");
break;
case MotionEvent.ACTION_MOVE:
//手指拖动
Log.d("onTouchEvent", "action move");
break;
case MotionEvent.ACTION_UP:
//手指抬起
Log.d("onTouchEvent", "action up");
break;
}
return super.onTouchEvent(event);
/*
return true; 这么写涉及到责任链模式,后续研究。
主要是会调用一个 dispatchTouchEvent() 方法中的 while 循环,不断监听、分发 touch 事件。
如果不设置成 true,那么响应一次 MotionEvent.ACTION_DOWN 之后就结束了,就不再回调 onTouchEvent 方法
*/
}