Skip to content

Instantly share code, notes, and snippets.

@xckevin
Last active March 3, 2023 07:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xckevin/acca07539c5577ab9d69350068a805ee to your computer and use it in GitHub Desktop.
Save xckevin/acca07539c5577ab9d69350068a805ee to your computer and use it in GitHub Desktop.
支持View跟随Text的布局组件。如果TextView最后一行足够放下,则跟随TextView最后一行;放不下则换行。支持margin padding调整跟随位置。
class TextFlowLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {
private lateinit var baseTextView: TextView
fun attachTextView(tv: TextView) {
if (tv.parent != this) {
throw IllegalStateException("attached view's parent is not FlowLayout")
}
baseTextView = tv
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val w = MeasureSpec.getSize(widthMeasureSpec)
val h = MeasureSpec.getSize(heightMeasureSpec)
measureChildren(widthMeasureSpec, heightMeasureSpec)
val finalWidth = w
var finalHeight = 0
var state = 0
var currentLineHeight = 0
var remainW = w
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child == baseTextView) {
state = 1
val size = remainW - if (baseTextView.layoutParams is MarginLayoutParams) {
((baseTextView.layoutParams as MarginLayoutParams).leftMargin + (baseTextView.layoutParams as MarginLayoutParams).rightMargin)
} else {
0
}
measureChild(child, MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST), heightMeasureSpec)
}
if (state == 1) {
remainW -= getLastLineWidth(baseTextView)
state = 2
currentLineHeight = max(child.heightWithMargin(), currentLineHeight)
} else if (state == 2) {
if (remainW >= child.widthWithMargin()) {
remainW -= child.widthWithMargin()
val mayHeight =
baseTextView.heightWithMargin() - (baseTextView.layoutParams as MarginLayoutParams).bottomMargin - baseTextView.lineHeight + child.heightWithMargin()
currentLineHeight = max(mayHeight, currentLineHeight)
} else {
state = 3
remainW = w
finalHeight += currentLineHeight
currentLineHeight = child.heightWithMargin()
}
} else {
if (remainW >= child.widthWithMargin()) {
remainW -= child.widthWithMargin()
currentLineHeight = max(child.heightWithMargin(), currentLineHeight)
} else {
remainW = w
finalHeight += currentLineHeight
currentLineHeight = child.heightWithMargin()
}
}
}
finalHeight += currentLineHeight
setMeasuredDimension(finalWidth, finalHeight)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val width = r - l
var height = 0
var currentLineHeight = 0
var costWidth = 0
var state = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child == baseTextView) {
state = 1
}
var top: Int
var left = costWidth
if (state == 1) {
costWidth += getLastLineWidth(baseTextView)
top = height
currentLineHeight = max(child.heightWithMargin(), currentLineHeight)
state = 2
} else if (state == 2) {
if (costWidth + child.widthWithMargin() > width) {
left = 0
height += currentLineHeight
top = height
currentLineHeight = child.heightWithMargin()
costWidth = child.widthWithMargin()
state = 3
} else {
costWidth += child.widthWithMargin()
top =
height + baseTextView.heightWithMargin() - (baseTextView.layoutParams as MarginLayoutParams).bottomMargin - baseTextView.lineHeight - baseTextView.paddingBottom
currentLineHeight = max(
currentLineHeight,
top + child.heightWithMargin()
)
}
} else {
if (costWidth + child.widthWithMargin() > width) {
costWidth = 0
height += currentLineHeight
currentLineHeight = child.heightWithMargin()
top = height
} else {
costWidth += child.widthWithMargin()
top = height
currentLineHeight = max(child.heightWithMargin(), currentLineHeight)
}
}
val lp = child.layoutParams
child.layout(
left + if (lp is MarginLayoutParams) lp.leftMargin else 0,
top + if (lp is MarginLayoutParams) lp.topMargin else 0,
left + child.widthWithMargin() - if (lp is MarginLayoutParams) lp.rightMargin else 0,
top + child.heightWithMargin() - if (lp is MarginLayoutParams) lp.bottomMargin else 0
)
}
}
private fun View.widthWithMargin(): Int {
val lp = this.layoutParams
return if (lp is MarginLayoutParams) {
this.measuredWidth + lp.leftMargin + lp.rightMargin
} else {
this.measuredWidth
}
}
private fun View.heightWithMargin(): Int {
val lp = this.layoutParams
return if (lp is MarginLayoutParams) {
this.measuredHeight + lp.topMargin + lp.bottomMargin
} else {
this.measuredHeight
}
}
override fun generateLayoutParams(attrs: AttributeSet?): ViewGroup.LayoutParams {
return LayoutParams(context, attrs)
}
override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams {
return LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
return LayoutParams(p)
}
private fun getLastLineWidth(tv: TextView): Int {
return tv.paddingLeft + (
if (tv.lineCount <= 1) tv.widthWithMargin()
else if (tv.layoutParams is MarginLayoutParams) tv.layout.getLineWidth(tv.lineCount - 1).toInt()
+ (tv.layoutParams as MarginLayoutParams).leftMargin
else tv.layout.getLineWidth(tv.lineCount - 1).toInt()
)
}
open class LayoutParams : MarginLayoutParams {
constructor(c: Context?, attrs: AttributeSet?) : super(c, attrs)
constructor(width: Int, height: Int) : super(width, height)
constructor(source: MarginLayoutParams?) : super(source)
constructor(source: ViewGroup.LayoutParams?) : super(source)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment