本文主要是记录一下继承子View,所需要实现的方法,以及对自己的知识做一下梳理和记录,其中不少内容觉得自己应该是会的,但是实际写起来,还是遇到不少阻碍
构造方法
首先构造先了解一下构造方法,一般来说,继承自View,需要实现四个构造方法,如下列代码:
public SVGView(Context context) {
this(context, null);
}
public SVGView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SVGView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public SVGView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){
super(context, attrs, defStyleAttr, defStyleRes);
}
第一个构造方法
只有在Java中去new一个新的控件的时候才会使用,比如去new一个TextView
val textView = TextView(this)
val text = "Hello Word"
val layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT)
textView.layoutParams = layoutParams
textView.text = text
layoutBox.addView(textView)
效果如图所示:
第二个构造方法
适用与在XML中去加入这个控件,其中attrs这个参数,就是存储在XML中,效果和之前是一样的
<TextView
android:text="Hello Word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
第三个构造方法
defStyleAttr是主题,一个Theme中的属性,如果在主题中定制了文字的颜色,大小,可以使用此函数,现在我们去Theme去添加一些主题的属性
<style name="Base.Theme.SVGDemo" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:textViewStyle">@style/MyTextViewStyle</item>
</style>
<style name="MyTextViewStyle" parent="Widget.AppCompat.TextView">
<item name="android:textColor">@color/red</item>
</style>
这里需要注意只有系统的View,比如TextView,ImageView之类才可以使用此方式,进行集体定义,因为因为textViewStyle
是Android Framework中定义的特定样式项,用于设置TextView的默认样式,如果是继承自TextView的一个自定义View,也是不能直接使用的,但是可以这样:
constructor(context: Context) : super(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, android.R.attr.textViewStyle)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
对于自定义View,只会走两个构造方法,第一个和第二个,如果你设置了类似android:textViewStyle
进行了集体修改,并且希望自定义View也按照此规则进行变化,则需要单独设置
第四个构造方法
第四个,只有在SDK21及以上才可以使用,作用是什么呢,就比如在Theme设置了主要文字颜色为黑色,但是这个View很特殊,它得是红色,所以,你可以额外给它单独设置一个主题
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr,R.style.MyTextViewStyle)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
如图所示,没有做修改,在第四个构造方法内传入了一个defStyleRes的Int类型参数,就实现了变化
View绘制流程
这里只是简述一个View的绘制流程,不牵扯其他的,这里加上代码
打印结果如图所示:
这里来解释以下这三个函数的意义:
onMeasure
:在测量阶段调用,用于确定 View 的大小。在该方法中,您可以通过设置 View 的MeasuredWidth
和MeasuredHeight
来指定视图的测量尺寸。这个方法通常被重写以确保 View 在布局中获得适当的大小。onLayout
:在布局阶段调用,用于确定 View 在父容器中的位置。在该方法中,您可以通过设置 View 的left
、top
、right
和bottom
来指定视图在布局中的位置。这个方法通常在自定义 ViewGroup 中重写,用于摆放子视图的位置。onDraw
:在绘制阶段调用,用于绘制 View 的内容。在该方法中,您可以使用绘图工具(Canvas)绘制各种形状、文本、图像等内容。这个方法通常在自定义 View 中重写,用于自定义视图的外观和样式。
onMeasure
这里主要确定View的大小,通过调用 MeasureSpec.getMode(widthMeasureSpec)
可以获取测量模式,通过调用 MeasureSpec.getSize(widthMeasureSpec)
可以获取测量大小。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
Log.e("MyView","onMeasure")
}
然后通过这个View本身需要加载的内容来计算出内容大小,通过在new View
的设置的wrap_content
或者match_parent
,或者多少DP配合来设置View的实际显示大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getSize(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
其中widthMode
,heightMode
,有以下几种测量模式
MeasureSpec.EXACTLY
:表示视图的大小已经确定,要么是通过固定的宽度和高度值,要么是通过match_parent
或指定的具体大小。在这种情况下,您可以直接使用MeasureSpec.AT_MOST
:表示视图的大小可以是一个限制范围内的值,例如父容器提供的可用空间大小。在这种情况下,您需要根据测量规格和视图的内容来计算一个合适的测量尺寸。MeasureSpec.UNSPECIFIED
:表示视图的大小没有限制,可以根据需要自由扩展。 计算完成后设置这个View的真正大小,调用setMeasuredDimension(measuredWidth, measuredHeight)
来设置
onLayout
当 Android 系统需要安排视图在父容器中的位置时,会调用视图的 onLayout
方法。在 onLayout
方法中,您可以指定视图的左上角 (left
和 top
) 和右下角 (right
和 bottom
) 的位置,从而布局视图在父容器中的位置,先看一下方法:
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
Log.e("MyView","onLayout")
}
changed
:一个布尔值,表示视图的布局是否发生了变化。当视图的布局发生变化时,该参数值为true
;否则为false
。left
、top
、right
、bottom
:这四个参数表示视图的边界框(矩形),指定了视图在父容器中的位置。 也就是是根据ViewGroup传来的left
、top
、right
、bottom
来设置这个View的位置大小 可以直接通 = 的方法 比如this.left = left
onDraw
这个是自定义View中最重要的方法,你的View显示什么内容全靠它来设置,那么如何设置,通过画布和画笔
- 在
onDraw
方法中,您可以使用传入的Canvas
对象进行绘制操作。Canvas
是一个画布对象,它提供了一系列的绘制方法,如绘制线条、矩形、文本、位图等。 - 在
onDraw
方法中,您可以使用Canvas
对象绘制视图的各个部分,例如背景、文本、图标等。可以根据需要使用drawRect()
、drawText()
、drawBitmap()
等方法进行绘制。
比如画一个矩形:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
paint.color = Color.RED
val rect = Rect(0, 0, width, height)
canvas?.drawRect(rect, paint)
}
或者去画一下其他比较复杂的东西。这里记录一下sin,cos,tan,为什么记录,在计算比较复杂的图形中,这些是必不可少,还有贝塞尔曲线什么之类的,总的来说,嗯,数学很重要
- sin 对边与斜边的比值
- cos 邻边与斜边的比值
- tan 对边与邻边的比值
页面刷新
如果你的自定义View是一个会变化的页面
- invalidate 会进行onDraw的重新绘制,一般用于View大小不变,内容有了变化,需要更新视图
- requestLayout 会重新进行布局 ,调用
measure()
->onLayout
requestLayout是不会自己主动调用onDraw,如果需要布局发生变化后立即触发onDraw的话,需要自己手动调用invalidate
XML增加参数
如果希望你的View可以像系统的View可以在Xml中添加比较多的定义,这里需要在theme或者attr中去定义方法名字和接受的参数类型,比如:
<declare-styleable name="MyView">
<attr name="MyViewColor" format="color"/>
</declare-styleable>
这里定义了一个MyViewColor的,然后使用需要添加一个Color,那么我如何把它拿出来使用呢
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, R.style.MyTextViewStyle) {
var typedArray: TypedArray? = null
try {
typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView)
val color = typedArray.getColor(R.styleable.MyView_MyViewColor,Color.BLACK)
} catch (e: Exception) {
e.printStackTrace()
} finally {
typedArray?.recycle()
}
}
这样就完成就可以了
事件分发
事件分发流程
AndroidView的事件分发是一个U字型设计,其中主要有三个方法dispatchTouchEvent
、onInterceptTouchEvent
,onTouchEvent
,分别作用于事件分发,事件拦截,事件消费,在此之前先看一般的流程图:
这个图是我当初学习事件分发copy下来的,这里也记录一下 可以看到三个方法的不同作用,其中onInterceptTouchEvent
是ViewGroup
特有的,
事件类型
事件的类型主要有三种:
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
//按下
}
MotionEvent.ACTION_MOVE ->{
//移动
}
MotionEvent.ACTION_UP ->{
//抬起
}
}
return super.onTouchEvent(event)
}
可以根据不同的情况来处理,最后如果此事件消费了,不想让他继续往下面传递就return true,如果想让他继续往下面船体就return false
Android 学习笔录
Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap