自定义View 流程
一、绘制基础
1、onDraw()
override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) canvas.drawXX(x,x,paint) }
负责自身内容主体绘制。super.onDraw()为空实现,写与不写都没影响
2、Paint 画笔
可实现多种效果,如渐变。参考:https://juejin.cn/post/6844903487570968584
drawText,为什么TextView的宽度比实际要宽?
参考:https://juejin.cn/post/6844903488460177416#heading-23
二、绘制顺序
1、super.onDraw() 前或后编写绘制代码?
写在下面,类似
override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) canvas.drawXX(x,x,paint) }
把绘制代码写在 super.onDraw()
的下面,由于绘制代码会在原有内容绘制结束之后才执行,所以绘制内容就会盖住控件原来的内容。
相反,写在上面,会先绘制代码内容,然后在绘制原有内容
2、dispatchDraw():绘制子View的方法
public class SpottedLinearLayout extends LinearLayout { ... protected void onDraw(Canvas canvas) { super.onDraw(canvas); ... // 绘制斑点 } }
添加子view后,发现绘制斑点代码无效
<SpottedLinearLayout android:orientation="vertical" ... > <ImageView ... /> <TextView ... /> </SpottedLinearLayout>
在绘制过程中,每一个ViewGroup会先绘制onDraw()主体内容,然后绘制子view,dispatchDraw()。上面的例子就是先绘制斑点,后绘制子view,导致斑点被覆盖。
怎样让主体绘制内容不被子view覆盖呢?方法是在子view绘制完后,再绘制斑点
public class SpottedLinearLayout extends LinearLayout { ... // 把 onDraw() 换成了 dispatchDraw() protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); ... // 绘制斑点 } }
3、draw()方法
此方法是绘制过程中的总方法,执行顺序
View --- > darw(Canvas canvas)
- Draw the background 绘制背景
- Draw view's content 绘制自己的内容
- Draw chidren 绘制子View的内容
- Draw decorations(scrollbars for instance) 给View添加自己的装饰
View.java
draw(){
······
drawBackgroud()
onDraw()
dispathDraw()
······
}
在View中,dispatchDraw()为空实现,在ViewGroup中dispatchDraw()方法如下:
ViewGroup.java ····· dispathchDraw(){ ······ drawChild(canvas, child, drawingTime); } protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
重新调用view的draw方法,进行绘制。
ViewGroup的绘制过程,draw():- ······
- drawBackgroup()
- onDraw()
- dispatchDraw()
- ``````
ViewGroup绘制方法执行顺序
onFinishInflate、onAttachedToWindow、onMeasure、onLayout、draw、onDraw、dispatchDraw
View绘制顺序:
onFinishInflate、onAttachedToWindow、onMeasure、onLayout、draw、onDraw、dispatchDraw
log发现,两者调用顺序一致。如果viewgroup中嵌套view,那么调用顺序如何?
子onFinishInflate、父onFinishInflate、父onMeasure、子onMeasure、父onLayout,子onLayout,父draw、ondraw、diapatchDraw,子draw、ondraw。
父布局测量、子布局测量;父布局定位、子布局定位;父布局绘制、子布局绘制
可在activity里onWindowFocusChanged方法里获取View的宽高:
override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) Log.e("====", " onWindowFocusChanged:" + binding.sdownView.width) }
E/====: onWindowFocusChanged:1080
结合Activity的生命周期就是
onCreate -> View onFlinshInflate
-> onStart -> onResume->View onMeasure->View onLayout -> View onDraw
View.invalidate : 请求重绘View树,会调用onDraw()和computeScroll(),在UI线程执行
说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”
视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图
View.postInvalidate:在非UI线程执行
View.requestLayout:依次调用onMeasure、onLayout、不会调用onDraw
三、onMeasure参数含义
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { Log.e("====", "==onMeasure:$widthMeasureSpec") super.onMeasure(widthMeasureSpec, heightMeasureSpec) }
这两个参数,每一个都包含了两方面的信息,一个是测量模式(mode)信息,另一个尺寸大小信息。我们通过重写onMeasure方法决定视图的宽和高。一般,自定义View都应该要重写这个方法。重写了这个方法onMeasure后,记得调用setMeasuredDimension(int, int)保存该View、ViewGroup宽和高。
参考:
Android 自定义View之onMeasure、onLayout、onDraw
HenCoder Android 自定义 View 1-5: 绘制顺序
onSizeChanged、onDraw、onMeasure、onLayout 执行顺序和作用