[Android]自定义View

1,131 阅读2分钟

自定义view

1. 自定义绘制

  • 方式:重写绘制方法(最常用: onDraw()
  • 绘制的关键: Canvas
    • Canvas的绘制类方法: drawXXX()(关键参数: Paint)
    • Canvas的辅助类方法:范围裁切( clipXXX(()和几何变换( Matrix))
  • 使用不同的绘制方法来控制遮盖关系

1.1 Canvas对绘制的辅助

  • 范围裁切
  • clipRect()和 clipPath()
  • 几何变换
  • Canvas.translate/rotate/scale/skew()
  • Matrix.pre/postTranslate/Rotate/Scae/Skew()及自定义变换
  • Camera.rotate() Camera.setLocation():三维变换

2. 属性动画

2.1 ViewPropertyAnimator

  • View.animate()
  • 配合上translationX() translationY() alpha() rotation() scaleX() 等方法

2.2 ObjectAnimator

使用方式:

  • 给自定义View添加 setter/ getter方法
  • ObjectAnimator.ofXXX()
  • ObjectAnimator.start()
  • ObjectAnimator.ofXXX()创建Animator对象(自定义View别忘了添加setter / getter方法)
  • start()执行动画

2.3 功能

  • setDuration():设置时长
  • setInterpolator():设置速度模型
  • 设置监听器

3. 布局onMeasure()和onLayout()

onMeasure()和onLayout() 1

方式:重写布局过程的相关方法

  • 1.测量过程: onMeasure()
  • 2.布局过程: onLayout()

具体:

  • 1.重写 onMeasure()来修改已有的View的尺寸
  • 2.重写 onMeasure()来全新计算自定义View的尺寸
    1. 重写onMeasure()和onLayout()来全新计算自定义ViewGroup的内部布局

如,自定义一个方形imageview 可以这样写:
重写onMeasure()修改尺寸

  1. 重写onMeasure(),并调用super.onMeasure()触发原先的测量
  2. 用getMeasuredWidth()和getMeasuredHeight()取到之前测得的尺寸, 利用这两个尺寸来计算出最终尺寸
  3. 使用setMeasuredDimension()保存尺寸
2

4. 全薪定义view的尺寸

1.重写onMeasure()把尺寸计算出来
2.把计算的结果用resolveSize()过滤一遍后保存

3

5. 定制layout的内部布局

1.重写onMeasure()来计算内部布局

  1. 计算子View的尺寸
    1. 调用每个子View的measure(),让子View自我测量
    2. 结合开发者的要求(layout_ xxx)和自己的可用空间(自己的尺寸,上限一已用尺寸)
  2. 根据子View给出的尺寸,得出子View的位置,并保存它们的位置和尺寸
  3. 根据子View的位置和尺寸计算出自己的尺寸,并用setMeasuredDimension()保存
4

2.重写onLayout()来摆放子View

5

可用空间的判断方法

根据自己的MeasureSpec中的mode的不同:

  • EXACTLY / AT_MOST:
    可用空间: MeasureSpec 6 size
  • UNSPECIFIED:
    可用空间:无限大
关于保存子View位置的两点说明
  1. 不是所有的Layout都需要保存子View的位置(因为有的Layout可以在布局阶段实时推导出子View的位置,例如LinearLayout)
  2. 有时候对某些子View需要重复测量两次或多次才能得到正确的尺寸和位置

6. 触摸反馈的核心原理及机制

6

自定义触摸反馈

  • 重写onTouchEvent(),写入触摸反馈算法,并返回true;
  • 如果是ViewGroup并且可能与子View产生触摸判别冲突,还需要重写onInterceptTouchEvent(),在合适的时候返回false来拦截事件流;
  • 如果需要临时阻止父View的拦截,可以用requestDisallowInterceptTouchEvent(),这是一个非持久的方法,仅对当前事件流有效。