继承ViewGroup
重写三个构造方法、定义存储数据结构
// 每一行的View
private lateinit var lineViews: MutableList<View>
// 所有的行 一行一行的存
private lateinit var views: MutableList<MutableList<View>>
// 每一行的高度
private lateinit var heights: MutableList<Int>
constructor(context: Context?) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
自定义参数和使用
在 attrs.xml 文件定义
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlowLayout">
<attr name="android:gravity" />
<attr name="horizonSpacing" format="dimension|reference" />
</declare-styleable>
<declare-styleable name="FlowLayout_Layout">
<attr name="android:layout_gravity" />
</declare-styleable>
</resources>
方法一:
直接在自定义 ViewGroup 里面调用 getXXXX
方法
try {
mProgressDrawable = getDrawable(R.styleable.QingheSeekBarPreference_seekBarProgressDrawable)
mThumbDrawable = getDrawable(R.styleable.QingheSeekBarPreference_seekBarThumbDrawable)
mProgressColor = getColor(R.styleable.QingheSeekBarPreference_progressColor, Color.BLUE)
} finally {
a.recycle()
}
方法二:
在自定义的 View 里定义一个继承 LayoutParams 内部类
class MyLayoutParams : MarginLayoutParams {
private var gravity = -1
constructor(c: Context?, attrs: AttributeSet?) : super(c, attrs) {
// 取出属性集合
val a = c!!.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout)
try {
// 拿到自定义的属性
gravity = a.getInt(R.styleable.FlowLayout_android_gravity, -1)
} finally {
a.recycle()
}
}
constructor(width: Int, height: Int) : super(width, height)
constructor(source: LayoutParams?) : super(source)
}
自定义 ViewGroup 要使用自定义属性类 得重写几个方法
// 生成布局参数
override fun generateLayoutParams(p: LayoutParams?): MyLayoutParams {
return MyLayoutParams(p)
}
override fun generateLayoutParams(attrs: AttributeSet?): MyLayoutParams {
return MyLayoutParams(context, attrs)
}
override fun generateDefaultLayoutParams(): MyLayoutParams {
return MyLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
}
// 检查布局参数
override fun checkLayoutParams(p: LayoutParams?): Boolean {
return super.checkLayoutParams(p) && p is LayoutParams
}
使用,取出来,转换,然后从 lp 里面获取
// 自定义参数在这里可以用
val lp = child.layoutParams as MyLayoutParams
onMeasure 测量所占的宽高
遍历所有子view,对子view进行测量,分配到具体的行。
- 看当前的行的剩余的宽度是否可以容纳下一个子View, 如果放不下,换行 保存当前行的所有子View,累加行高,当前行的宽度、高度置零
- 每一次都把childView信息添加到当前行上
- 过滤掉 match_parent 的情况
- 处理最后不够一行的情况
遍历结束后,重新测量一次,处理 lp.height = match_parent 的情况
onLayout 布局子 view
外层循环针对 行布局,内层循环针对 每一行的 view 布局
- 取出所有这一行的 view 还有高度
- 对每一个 view 设置 left、top、right、bottom
child.layout(left, top, right, bottom)
- 设置下一个 view 布局所要的参数
滑动实现
事件拦截
关键方法 onInterceptTouchEvent
ACTION_DOWN
时记录点击的位置
ACTION_MOVE
时跟 DOWN 时记录的信息比较,y 的变化大时,认为上下滑动,记录位置,返回 true
处理事件
ACTION_DOWN
记录当前 y 的位置
ACTION_MOVE
时比较得出 dy,处理滑动,记录位置
ACTION_UP
处理边界回弹,根据滑动速度继续滑动列表
使用 OverScroller
fling
根据手指抬起时的速度继续滑动列表
startScroll
把滑动分段,触发 ViewGroup 的 computeScroll
真正滑动
springBack
处理边界回弹
ACTION_MOVE 拦截不到
点击子view 外的区域,发现没有响应,move事件的日志也没有打印。
可能是 onTouchEvent 返回 return super.onTouchEvent(event)
原因是拦截到后 onTouchEvent
没有返回 true,也没有子 View 返回 true,后续事件就不发送到该 ViewGroup 了