自定义View

239 阅读4分钟

1 View生命周期

2.自定义View分为两类

  1. 继承与View

    在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View。

    自定义View主要是实现 onMeasure + onDraw

  2. 继承与ViewGroup(现有布局)

    利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout。

    自定义ViewGroup主要是实现:onMeasure + onLayout

3 自定义View绘制流程

  1. 创建View

    • 继承View/ViewGroup等,实现构造函数。

      需要实现包含Context和AttributeSet参数的构造,该构造允许我们在xml文件中创建和编辑自定义控件实例。

    • attrs.xml文件中声明自定义属性,xml文件中使用自定义属性。

    • 在构造函数中通过AttributeSet获取TypedArray,根据TypedArray获取自定义属性,其中TypedArray对象为共享资源,获取完属性后需要回收。

    • 添加设置自定义属性的Java接口

      在xml中指定的自定义属性只有在view被初始化的时候能够获取到,动态设置自定义属性后需要调用invalidate(),重新绘制View。

  2. 测量View:onMeasure()

    MeasureSpec

    onMeasure有两个参数widthMeasureSpec, heightMeasureSpec,MeasureSpec包含父容器传给View的测量模式和大小。

    MeasureSpec一个32位的int值,高2位代表SpecMode是指测量模式,低30位代表SpecSize是指在某种测量模式下的View大小。

    SpecMode有三类:

    1. UNSPECIFIED

      父容器对View没有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态,ScrollView/listview测量子View时用的就是这个。

    2. EXACTLY

      确切的大小,父容器已检测出View所需大小,View的最终大小就是SpaecSize,对应LayoutParams中的matche_parent和具体的数值(dp)两种模式。

    3. AT_MOST

      指定View的最大值,View的大小不能大于这个值,对应LayoutParams中的wrap_content。

    两类自定义View的测量流程:

    • 自定义ViewGroup

      遍历所有子View,通过getChildMeasureSpec获取子View的MeasureSpec,调用子View的measure,并计算自身的大小,调用setMeasureDimension,保存控件的大小,否则会因为自身大小为0,不显示。

      getChildMeasureSpec算法:

      childLayoutParams/
      parentSpecMode
      EXACTLYAT_MOSTUNSPECIFIED
      dp/pxEXACTLY
      childSize
      EXACTLY
      childSize
      EXACTLY
      childSize
      match_parentEXACTLY
      parentSize
      AT_MOST
      parentSize
      UNSPECIFIED
      0
      wrap_contentAT_MOST
      parentSize
      AT_MOST
      parentSize
      UNSPECIFIED
      0
    • 自定义View

      直接计算自身的大小,调用setMeasureDimension,保存控件的大小,否则会因为自身大小为0,不显示。

      View大小算法:

      • EXACTLY:父容器传递的大小measureSize

      • UNSPECIFIED:希望View的大小desireSize

      • AT_MOST:min(desireSize,specSize)

  3. 布局View:onLayout()(自定义ViewGroup需要实现)

    根据onMeasure中记录的View大小,布局子View。

  4. 绘制View:onDraw()(自定义Viewp需要实现)

    自定义控件被创建,并且测量、布局之后,接下来就需要实现onDraw()来绘制View了。

    onDraw方法包含了比较重要的两个参数::

    • Canvas(画布):决定要去画什么
    • Paint(画笔):决定怎么画 比如,Canvas提供了画线方法,Paint就来决定线的颜色。Canvas提供了画矩形,Paint又可以决定让矩形是空心还是实心。

    在onDraw方法中开始绘制之前,你应该让画笔Paint对象的信息初始化完毕。这是因为View的重新绘制是比较频繁的,这就可能多次调用onDraw,所以初始化的代码不应该放在onDraw方法里。

  5. 与用户进行交互

    自定义控件需支持点击、拖拽等操作,可在onTouchEvent中处理触摸事件,并对外提供接口。

  6. 优化已定义的View

    自定义控件需运行流畅,为了避免你的控件看得来迟缓,确保动画始终保持每秒60帧。

    需注意以下几点:

    1. 避免不必要的代码
    2. 在onDraw()方法中不应该有会导致垃圾回收的代码。
    3. 尽可能少让onDraw()方法调用,大多数onDraw()方法调用都是手动调用了invalidate()的结果,所以如果不是必须,不要调用invalidate()方法,比如动态设置自定义属性,需要调用invalidate才能生效,所以应该设置完所有需要设置的属性后,在调用invalidate()。

4 补充知识点

  1. 为什么要measure

    onMeasure方法就是测量View/子View自身的大小并且存储测量的大小的。

  2. getMeasureWidth与getWidth的区别

    • getMeasureWidth:在measure()过程结束后就可以获取到对应的值;是通过setMeasuredDimension()方法来进行设置的。

    • getWidth:在layout()过程结束后才能获取到;是通过视图右边的坐标减去左边的坐标计算出来的。