0.演示
1.创建一个FlowLayout继承自ViewGroup,重写measure,layout方法
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
class FlowLayout : ViewGroup {
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)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
}
}
2.values文件夹下创建attrs,添加integre类型的变量lines控制行数
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="lines" format="integer"/>
<declare-styleable name="FlowLayout">
<attr name="lines"/>
</declare-styleable>
</resources>
3.在FlowLayout中引入
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
if (context != null) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout)
linesNum = typedArray.getInteger(R.styleable.FlowLayout_lines, 3)
typedArray.recycle()
}
}
private var linesNum = 0 //总行数
4.编写布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".flow.FlowActivity">
<com.zrq.customview.flow.FlowLayout
android:id="@+id/flow_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="50dp"
app:lines="3">
</com.zrq.customview.flow.FlowLayout>
</RelativeLayout>
5.模拟数据,动态添加view
class FlowActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityFlowBinding.inflate(layoutInflater)
setContentView(mBinding.root)
initData()
}
private lateinit var mBinding: ActivityFlowBinding
private val list = mutableListOf<String>()
@SuppressLint("InflateParams")
private fun initData() {
mBinding.apply {
list.add("Java")
list.add("Android")
list.add("JavaEE")
list.add("c#")
list.add("c")
list.add("c++")
list.add("Flutter")
list.add("Vue")
list.add("Html")
list.add("Jsp")
list.add("MySQL")
list.add("Kotlin")
list.add("哼哼啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊")
for (i in 0..20) {
list.add(i.toString())
}
list.forEach {
val view = LayoutInflater.from(this@FlowActivity).inflate(R.layout.item_flow, null)
view.tag = it
view.findViewById<TextView>(R.id.tv_title).text = it
flowLayout.addView(view)
}
}
}
}
6.编写item布局文件
(item_flow.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="2dp"
android:background="@drawable/shape_tv_bg"
android:ellipsize="end"
android:lines="1"
android:padding="6dp"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="12sp" />
</RelativeLayout>
(shape_tv_bg.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="50dp"/>
<solid android:color="@color/green"/>
</shape>
7.创建实体类保存每一行的信息
data class Line(var views: MutableList<View> = mutableListOf(), var width: Int = 0, var height: Int = 0) {
fun addView(view: View) {
views.add(view)
width += view.measuredWidth
if (height < view.measuredHeight)
height = view.measuredHeight
}
}
8.编写measure方法体内容(重点)
private var linesNum = 0 //总行数
private var curLine = Line() //当前操作的行
private val lines = mutableListOf<Line>()
private var usedWidth = 0
private var curLinesNum = 0
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val totalWidth = MeasureSpec.getSize(widthMeasureSpec)
val totalHeight = MeasureSpec.getSize(heightMeasureSpec)
val widthSize = totalWidth - paddingLeft - paddingRight
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightSize = totalHeight - paddingTop - paddingBottom
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
lines.clear()
lines.add(curLine)
usedWidth = 0
curLinesNum = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child.visibility == GONE) break
val childWidth = MeasureSpec.makeMeasureSpec(widthSize, if (widthMode == MeasureSpec.EXACTLY) MeasureSpec.AT_MOST else widthMode)
val childHeight = MeasureSpec.makeMeasureSpec(heightSize, if (heightMode == MeasureSpec.EXACTLY) MeasureSpec.AT_MOST else heightMode)
child.measure(childWidth, childHeight)
if (usedWidth + child.measuredWidth > widthSize) {
//换行
curLine = Line()
lines.add(curLine)
usedWidth = 0
curLinesNum++
}
if (curLinesNum >= linesNum) break
curLine.addView(child)
usedWidth += child.measuredWidth
}
setMeasuredDimension(totalWidth, totalHeight)
}
9.编写layout方法(重点)
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var top = paddingTop
lines.forEach { line ->
var left = paddingLeft
line.views.forEach { view ->
view.layout(left, top, r, b)
left += view.measuredWidth
}
top += line.height
}
}
10.总结:没啥说的还是蛮简单的,核心代码总共不到100行就可以实现,想要实现其他功能也可以在此基础上拓展