为什么要自定义控件?

1.特定的显示风格

2.处理特有的用户交互

例:比如原本TextView不能滑动里面的文字,通过自定义控件实现

3.优化我们的布局

例:通过嵌套实现复杂的布局,但是绘制和测量的效率慢,通过自定义控件实现,提升效率

4.封装等

例:app内很多控件可以复用,比如首页底部的tab按钮,封装成自定义控件,方便后续使用

如何自定义控件?

1.自定义属性的声明与获取

提取自定义控件的属性,去声明,然后在构造方法里去获取。

  • 分析需要的自定义属性:颜色,文字大小,文字,图标等
  • 在res/values/attrs.xml定义声明
  • 在layout.xml文件中进行使用
  • 在View的构造方法中进行获取
    通过TypedArray a=context.obtainStyledAttributes()进行获取,返回来一个TypedArray对象,通过这个对象可以获取到对应的自定义属性的值,获取完成要记得recycle();
    中间获取的过程可以使用for循环,直接获取也可以

2.测量onMeasure

测量自身需要多大的范围,
两个数字决定,一个测量的模式,一个测量的值
测量的模式分三种:

  • EXACTLY:设置了明确的值,那result就等于我们设置的值
  • AT_MOST:最多不能超过某个值,这个模式一般出现在设置了wrap_content的情况下,尺寸是根据自身内容决定的,但是不能超过父控件高度和宽度
  • UNSPECIFIED:没有限制高度和宽度,适用于Listview,ScrollView等
    使用MeasureSpec对象获取自定义控件的测试模式和数值设置了大小,然后来设置自定义控件的高宽,最后要记得用setMeasuredDimension方法来把得到的result值传进去
    需要重新测量的话要调用requestLayout(),用来提供给外部进行重新测量,不包括绘制

3.布局onLayout(ViewGroup)

单纯的View不需要考虑这个,
ViewGroup需要复写这个方法

  • 决定子View的位置
  • 尽可能将onMeasure中一些操作移动到此方法中(耗时操作移动到这里,onLayout只触发一次)
  • 计算好位置,通过requestLayout()这个方法进行布局

4.绘制onDrow

  • 绘制内容区域:使用Canvas相关的API绘制你想要的效果
  • invalidate(在UI线城中调用),postInvalidate(在子线程中调用)
  • Cancas.drawXXX 熟练使用这里面的一些方法
  • translate,rotate,scale,skew 巧妙使用这些变换的方法
  • save(),restore() 结束要注意这个状态
    一般情况下,与用户没有交互的情况下,理论下只考虑onMeasure和onDrow就可以了,如果是自定义的ViewGroup还需要额外考虑onLayout

5.onTouchEvent

如果有交互的地方,需要在这里进行处理

  • ACTION_DOWN :
    放下手指
  • ACTION_MOVE :
    移动手指
  • ACTION_UP :
    抬起手指,如果有加入到速度检测等,在这里判断
  • ACTION_POINTER_DOWN:
    考虑多点触控时,需要考虑的类型
  • ACTION_POINTER_UP:
    考虑多点触控时,需要考虑的类型
  • getParent().requestDisallowInterceptTouchEvent(true)
    如果子view已经拿到事件了,可以调用这个方法,可以告诉父控件,不要拦截我的事件
  • VelocityTracker

6.onInterceptTouchEvent(ViewGroup)

事件转发过程中,事件是由交给子控件处理的,但是转发的过程中,父控件有权去拦截子控件的事件,就是通过这个方法,如果返回时true了,那就表明事件被拦截了,事件要交给ViewGroup自己去处理

  • ACTION_DOWN :
    放下手指
  • ACTION_MOVE :
    移动手指,进行一些移动距离的计算
  • ACTION_UP :
    抬起手指,如果有加入到速度检测等,在这里判断
  • ACTION_POINTER_DOWN:
    考虑多点触控时,需要考虑的类型
  • ACTION_POINTER_UP:
    考虑多点触控时,需要考虑的类型
  • getParent().requestDisallowInterceptTouchEvent(true)
    如果子view已经拿到事件了,可以调用这个方法,可以告诉父控件,不要拦截我的事件
    决定是否拦截该手势
    如果自定义的是ViewGroup,想拦截子View里面的方法,还需要写这个方法。