本文主要是记载一下承继子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)

效果如图所示:

写一个自定义View你都需要注意什么

第二个结构办法

适用与在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之类才能够运用此办法,进行团体界说,由于由于textViewStyleAndroid 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制作流程

这儿只是简述一个View的制作流程,不牵扯其他的,这儿加上代码

写一个自定义View你都需要注意什么
打印结果如图所示:
写一个自定义View你都需要注意什么
这儿来解说以下这三个函数的含义:

  1. onMeasure:在丈量阶段调用,用于确定 View 的巨细。在该办法中,您能够经过设置 View 的 MeasuredWidthMeasuredHeight 来指定视图的丈量尺寸。这个办法通常被重写以确保 View 在布局中取得恰当的巨细。
  2. onLayout:在布局阶段调用,用于确定 View 在父容器中的方位。在该办法中,您能够经过设置 View 的 lefttoprightbottom 来指定视图在布局中的方位。这个办法通常在自界说 ViewGroup 中重写,用于摆放子视图的方位。
  3. 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,有以下几种丈量模式

  1. MeasureSpec.EXACTLY:表明视图的巨细现已确定,要么是经过固定的宽度和高度值,要么是经过 match_parent 或指定的具体巨细。在这种状况下,您能够直接运用
  2. MeasureSpec.AT_MOST:表明视图的巨细能够是一个约束范围内的值,例如父容器供给的可用空间巨细。在这种状况下,您需求依据丈量标准和视图的内容来核算一个合适的丈量尺寸。
  3. MeasureSpec.UNSPECIFIED:表明视图的巨细没有约束,能够依据需求自由扩展。 核算完结后设置这个View的真正巨细,调用setMeasuredDimension(measuredWidth, measuredHeight)来设置

onLayout

当 Android 体系需求组织视图在父容器中的方位时,会调用视图的 onLayout 办法。在 onLayout 办法中,您能够指定视图的左上角 (lefttop) 和右下角 (rightbottom) 的方位,然后布局视图在父容器中的方位,先看一下办法:

    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
  • lefttoprightbottom:这四个参数表明视图的边界框(矩形),指定了视图在父容器中的方位。 也便是是依据ViewGroup传来的lefttoprightbottom来设置这个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字型规划,其间主要有三个办法dispatchTouchEventonInterceptTouchEvent,onTouchEvent,别离效果于事情分发,事情拦截,事情消费,在此之前先看一般的流程图:

写一个自定义View你都需要注意什么
这个图是我最初学习事情分发copy下来的,这儿也记载一下 能够看到三个办法的不同效果,其间onInterceptTouchEventViewGroup特有的,

事情类型

事情的类型主要有三种:

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自界说view

# 自界说View结构函数,知多少?