自定义View学习总结
自定义View一般有三种方式
- 继承View自定实现所有的逻辑
- 继承现有的View或者ViewGroup,在这些基础上加一些逻辑
- 组合现有的控件,添加一些额外的逻辑
这里我们就只看第一种,也是比较基础的。自定义View一般重写三个方法onMeasure
,onLayout
和onDraw
- onMeasure:测量View来确定最终的宽高
- onLayout:布局确定子View的位置,自定义ViewGroup才需要重写这个方法
- onDraw:可以在画布上画任何你想画的东西
首先定义一个CustomizeView
,看看怎么调用
class CustomizeView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
init { }
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
XLogUtils.d("onLayout")
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
XLogUtils.d("onMeasure")
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
XLogUtils.d("onDraw")
}
}
Activity和XML代码很简单
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@android:color/holo_blue_light">
<com.example.cb.test.ui.custom_view.CustomizeView
android:layout_width="100dp"
android:background="@android:color/holo_red_light"
android:layout_height="100dp" />
</FrameLayout>
</LinearLayout>
class CustomViewActivity : BaseActivity() {
override fun getLayoutId()= R.layout.activity_custom_view
override fun initUI() {}
override fun initEvent() {}
}
运行之后可以看到
onMeasure
执行了两次,onLayout
和onDraw
各执行一次;onMeasure
不一定全是执行两次,这里不讨论这个了,有兴趣的可以去搜下。
onMeasure探究
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {}
widthMeasureSpec宽的测量模式,heightMeasureSpec高度测量模式,测量模式??什么东西?
我们看下google的解释
大概的意思是 父View对其的要求,也就是这里的参数是父View传来的MeasureSpec
,MeasureSpec
是一个32位的int值,高2位表示SpecMode
,低30位表示SpecSize
也就是测量的大小,先记着后面会讲,MeasureSpec
也提供的方法可以直接获取SpecMode
和SpecSize
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
从getMode
可以看出来 测量模式一共有三种
- UNSPECIFIED:父容器不对子View做任何限制,子View要多大给多大,例如ScrollView
- AT_MOST:父容器限定子View不能超过它,例如wrap_content
- EXACTLY:父容器固定值或者match_parent
下面代码演示两个,第一行第二个 和 第三行第一个
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
XLogUtils.d("onMeasure")
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
when (widthMode) {
MeasureSpec.AT_MOST -> XLogUtils.i("MeasureSpec.AT_MOST")
MeasureSpec.UNSPECIFIED -> XLogUtils.i("MeasureSpec.UNSPECIFIED")
MeasureSpec.EXACTLY -> XLogUtils.i("MeasureSpec.EXACTLY")
else -> XLogUtils.i("")
}
}
都和上面的图对上了,小伙伴们可以自己去试试,这么说还不明显 我用一张图来表示,
两张图一对照就明白了了。加入我们要自定义一个宽高相等的view利用上面所讲的内容代码如下
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val screenWidth = 500//假设屏幕宽度
var width = when (widthMode) {
MeasureSpec.AT_MOST -> min(screenWidth, widthSize)
MeasureSpec.UNSPECIFIED -> screenWidth
MeasureSpec.EXACTLY -> widthSize
else -> widthSize //不写要报错
}
//存储测量测的值
setMeasuredDimension(width, width)
}
基本上onMeasure
差不多就上面的这些东西,具体还要看业务中的需求。
onLayout
只会执行一次,用来决定子View的位置
onTouchEvent
@Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(ev);
}
int action = ev.getAction(); 常用的有下面几种
- MotionEvent.ACTION_DOWN 手指按下操作
- MotionEvent.ACTION_MOVE 手指移动操作
- MotionEvent.ACTION_UP 手指抬起操作
- MotionEvent.ACTION_CANCEL 事件结束操作
还有需要注意的一点就是
ev.getX()和ev.getRawX()区别
前者是获取在相对父View的x轴位置,后者是获取相对屏幕的x轴位置
自定义view与viewgroup的区别
- onDraw(Canvas canvas):两者都有,可以绘制自己的各种样式。
- onLayout() :viewgroup中必须重写,是个抽象方法
- dispatchDraw():控制子View绘制分发,可以控制子View绘制顺序
invalidate()、postInvalidate()、requestLayout()
- invalidate:重绘,会执行onDraw方法
- postInvalidate:跟invalidate功能一样,可以在子线程操作
- requestLayout:当View的宽高,发生了变化时调用,会重新执行一遍 onMease onlayout