自定义一个ViewGroup的首要任务就是要定义测量逻辑,让ViewGroup知道自己的大小,才能在屏幕上展示出来。 根据上面的分析得出:
当图片只有一张的时候,整个ViewGroup的大小和负责显示图片的ImageView是一样大的。这个大小可以根据图片的宽高比乘以一个预设的宽度或高度得到。这个预设的宽度取决于xml文件里设定或根据UI需求自己定义。
而当有多张图片的时候,宽度有两种情况需要考虑:
Wrap_Content
模式,宽度根据实际展示的列数乘以每列的宽度Match_Parent
模式,宽度直接设定为系统测量到的值但其实很少有使用Wrap_Content
模式的场景,所以这里不考虑。
除了需要确定自身的大小以为,还需要确定每个子View的大小。子View大小逻辑在分析中已经可以得出。
理清逻辑后,则编码的工作就简单了。代码如下:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (data.isEmpty())
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY))
else {
val groupWidth: Float
val groupHeight: Float
val size = data.size
if (size == 1) {
//当图片只有1张的时候,最大宽为当前ViewGroup的宽80%,最大高定义为200dp
val maxWidth = MeasureSpec.getSize(widthMeasureSpec) * 0.8f
val maxHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
200f,
r
esources.displayMetrics
)
//可自由定制
val minWidth = maxWidth * 0.8f
val minHeight = maxHeight * 0.8f
val image = data.first()
val ratio = image.getWidth() / image.getHeight().toFloat()
val childWidth: Float
val childHeight: Float
if (ratio > 1) {
childWidth = min(maxWidth, max(minWidth, image.getWidth().toFloat()))
childHeight = childWidth / ratio
} else {
childHeight = min(maxHeight, max(minHeight, image.getHeight().toFloat()))
childWidth = childHeight * ratio
}
measureChild(childWidth.toInt(), childHeight.toInt())
groupWidth = childWidth
groupHeight = childHeight
} else {
//如果是大于两个,则child宽高为当前ViewGroup宽度的1/3
val childWidth =
(MeasureSpec.getSize(widthMeasureSpec) -
(space * (maxRowCount - 1))) / maxRowCount.toFloat()
measureChild(childWidth.toInt(), childWidth.toInt())
groupWidth = MeasureSpec.getSize(widthMeasureSpec).toFloat()
groupHeight = (childWidth * this.lineCount) + (space * (this.lineCount - 1))
}
setMeasuredDimension(
MeasureSpec.makeMeasureSpec(groupWidth.toInt(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(groupHeight.toInt(), MeasureSpec.EXACTLY)
)
}
}
private fun measureChild(childWidth: Int, childHeight: Int) {
for (i in 0 until data.size) {
val child = getChildAt(i) ?: continue
child.measure(
MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
)
}
}
测量完成后,知道了自身和子View的大小,那么就需要确定子View该怎么排列的问题。九宫格的布局比较规律,是比较好实现的,每列最多3个view,最多3排,咱们使用一个for循环就搞定了。
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
if (data.isEmpty())
return
for (i in 0 until data.size) {
val child = getChildAt(i)
val childWidth = child.measuredWidth
val childHeight = child.measuredHeight
val currentRowIndex = i % maxRowCount
val currentLineIndex = i / maxRowCount
val marginLeft = if (currentRowIndex == 0) 0 else this.space
val marginTop = if (currentLineIndex == 0) 0 else this.space
val left = currentRowIndex * childWidth + marginLeft * currentRowIndex
val top = currentLineIndex * childHeight + marginTop * currentLineIndex
child.layout(left, top, left + childWidth, top + childHeight)
}
}
上面两个方法写完后,就已经完成了90%了。但是咱们现在还没有真正往里添加ImageView
,现在暴露一个方法,设置数据并添加ImageView
//loadCallback 是加载图片的回调,由调用者实现加载图片的功能。
fun setData(
data: List,
loadCallback: (index: Int, view: ImageView, image: GridImage) -> Unit
) {
n setData(
data: List,
loadCallback: (index: Int, view: ImageView, image: GridImage) -> Unit
) {