自定义FlowLayout

围巾🧣 2021年10月08日 391次浏览

继承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进行测量,分配到具体的行。

  1. 看当前的行的剩余的宽度是否可以容纳下一个子View, 如果放不下,换行 保存当前行的所有子View,累加行高,当前行的宽度、高度置零
  2. 每一次都把childView信息添加到当前行上
  3. 过滤掉 match_parent 的情况
  4. 处理最后不够一行的情况

遍历结束后,重新测量一次,处理 lp.height = match_parent 的情况

onLayout 布局子 view

外层循环针对 行布局,内层循环针对 每一行的 view 布局

  1. 取出所有这一行的 view 还有高度
  2. 对每一个 view 设置 left、top、right、bottom child.layout(left, top, right, bottom)
  3. 设置下一个 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 了

链接:https://gitlab.xlxs.top/xlxsui/jinflowlayout