自定義 ViewGroup 其實也不復雜,但是要對子 View 的margin屬性支持,就還需要花點經歷,
下面自己寫了一個自定義的 FlowLayout,支持了本身的 padding 屬性的同時,也支持子 View 的 margin 屬性,基本注釋都已盡可能詳盡地寫在代碼中,
先上效果圖


兄弟們,上代碼
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.core.view.children
import kotlin.math.max
/**
*
* @Author: QCoder
* @CreateDate: 2021/12/6
* @Description: 流式布局,當一行放不下后,換行放
* 支持了本身的 Padding 屬性,以及子 View 的 margin 屬性
* @Email: 526416248@qq.com
*/
class QFlowLayout(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs) {
//通過矩陣記錄每個子 View margin 后的具體位置
private val childrenBounds = mutableListOf<Rect>()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//獲取 QFlowLayout 的寬高的期望值 (XSize) 和 測量模式(XMode),其中 X 代表寬或高,下面同義,
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
// QFlowLayout 的實際寬度
var selfWidth = 0
// QFlowLayout 的實際高度
var selfHeight = 0
//當行的寬度(當行已有寬度,由子 View 和 margin,padding 屬性累加起來的)
var currentLineWidth = 0
//當行的高度
var currentLineHeight = 0
//遍歷測量子 View
for (child in children) {
//判斷,若 visibility == GONE ,即不可見又不占位置的時候,跳過測量
if (child.visibility == GONE) continue
//測量子 View,child 當前的子 View;XMeasureSpec 是QFlowLayout 對子 View 的期望
measureChild(child, widthMeasureSpec, heightMeasureSpec)
//獲取到子 View 的 layout屬性,
val lp = child.layoutParams as MarginLayoutParams
val childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
val childHeight = child.measuredHeight + lp.topMargin + lp.bottomMargin
//判斷是否需要換行,true 需要換行;false 不需要換行
if (currentLineWidth + childWidth > widthSize - paddingLeft - paddingRight) {
//將當前寬度和原先的寬度對比后,重置寬度為當前子 View 寬度
selfWidth = max(selfWidth, currentLineWidth)
currentLineWidth = childWidth
selfHeight += currentLineHeight
currentLineHeight = childHeight
childrenBounds.add(
//因為需要換行,所以當前的子 View 是在新的一行,那么
// left 左邊界 = 當前子View 的 leftMargin + QFlowLayout 的 paddingLeft
// top 上邊界 = 累計的高度 selfHeight + 當前子View 的 topMargin + QFlowLayout 的 paddingTop
// right 右邊界 = 當前子View 的寬度 + left
// bottom 下邊界 = top + 當前子View 的高度
Rect(
lp.leftMargin + paddingLeft, //left
selfHeight + lp.topMargin + paddingTop, //top
child.measuredWidth + lp.leftMargin + paddingLeft, //right
selfHeight + lp.topMargin + paddingTop + child.measuredHeight //bottom
)
)
} else {
//因為不需要換行,所以當前的子 View 在當行的接軌上去,那么
// left 左邊界 = 當行寬度 currentLineWidth + 當前子View 的 leftMargin + QFlowLayout 的 paddingLeft
// top 上邊界 = 累計的高度 selfHeight + 當前子View 的 topMargin + QFlowLayout 的 paddingTop
// right 右邊界 = 當行寬度 currentLineWidth + left
// bottom 下邊界 = top + 當前子View 的高度
childrenBounds.add(
Rect(
currentLineWidth + lp.leftMargin + paddingLeft,//left
selfHeight + lp.topMargin + paddingTop,//top
child.measuredWidth + currentLineWidth + lp.leftMargin + paddingLeft, //right
selfHeight + lp.topMargin + paddingTop + child.measuredHeight//bottom
)
)
//不需要換行,所以當前行的寬度 = 原來的寬度 + 當前子 View 的寬度
currentLineWidth += childWidth
//行的高度,我們只需要知道最高的那就行
currentLineHeight = max(currentLineHeight, childHeight)
}
}
selfWidth = max(selfWidth, currentLineWidth) + paddingRight + paddingLeft
selfHeight += currentLineHeight + paddingTop + paddingBottom
setMeasuredDimension(
if (widthMode == MeasureSpec.EXACTLY) widthSize else selfWidth,
if (heightMode == MeasureSpec.EXACTLY) heightSize else selfHeight
)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for ((index, child) in children.withIndex()) {
val childBounds = childrenBounds[index]
child.layout(
childBounds.left,
childBounds.top,
childBounds.right,
childBounds.bottom
)
}
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
override fun generateLayoutParams(p: LayoutParams): LayoutParams {
return MarginLayoutParams(p)
}
override fun generateDefaultLayoutParams(): LayoutParams {
return MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
}
}
最后,附上一個最近我整理的有關自定義 View 的知識網路結構圖
鏈接:https://pan.baidu.com/s/1COTMibrJtANax7cXYL_eeA
提取碼:tbok

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/377142.html
標籤:其他
上一篇:助學弟準備跳槽,狠心復習了這9門Android核心知識,熬夜整理成思維導圖
下一篇:DRouter的底層實作淺析
