流式布局-源码来自于https://github.com/androidneter/TaobaoHistorySearch
flowlayout-lib中的主要核心代码集合成该类(简化依赖),基本用法不变,去掉其自定义属性,若要使用,需代码动态设置(limitLineCount,isLimit,isOverFlow,mGravity,mSelectedMax)
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.text.TextUtils
import android.util.AttributeSet
import android.util.LayoutDirection
import android.util.Log
import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import android.view.ViewGroup
import android.widget.Checkable
import android.widget.FrameLayout
import androidx.core.text.TextUtilsCompat
import java.util.*
/**
* @author: fangyichao
* @date: 2021/6/1
*
* @desc 流式布局-源码来自于https://github.com/androidneter/TaobaoHistorySearch
* flowlayout-lib中的主要核心代码集合成该类(简化依赖),基本用法不变
* 去掉其自定义属性,若要使用,需代码动态设置(limitLineCount,isLimit,isOverFlow,mGravity,mSelectedMax)
*/
open class FlowLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : ViewGroup(context, attrs, defStyle) {
private var limitLineCount: Int = 3 //默认显示3行 断词条显示3行,长词条显示2行
private var isLimit: Boolean = true//是否有行限制
private var isOverFlow = false //是否溢出2行
private var mGravity: Int = LEFT
private var mSelectedMax = 1 //-1为不限制数量
private var mAllViews: MutableList<MutableList<View?>> = ArrayList()
private var mLineHeight: MutableList<Int> = ArrayList()
private var mLineWidth: MutableList<Int> = ArrayList()
private var lineViews: MutableList<View?> = ArrayList()
private var mTagAdapter: TagAdapter<*>? = null
private val mSelectedView: MutableSet<Int> = HashSet()
private var mOnSelectListener: OnSelectListener? = null
private var mOnTagClickListener: OnTagClickListener? = null
private var mOnLongClickListener: OnLongClickListener? = null
open fun isOverFlow(): Boolean {
return isOverFlow
}
open fun setOverFlow(overFlow: Boolean) {
isOverFlow = overFlow
}
fun isLimit(): Boolean {
return isLimit
}
open fun setLimit(limit: Boolean) {
if (!limit) {
isOverFlow = false
}
isLimit = limit
}
open fun getLimitLineCount(): Int {
return limitLineCount
}
open fun setLimitLineCount(count: Int) {
limitLineCount = count
}
open fun getGravity(): Int {
return mGravity
}
open fun setGravity(gravity: Int) {
mGravity = gravity
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val sizeWidth = MeasureSpec.getSize(widthMeasureSpec)
val modeWidth = MeasureSpec.getMode(widthMeasureSpec)
val sizeHeight = MeasureSpec.getSize(heightMeasureSpec)
val modeHeight = MeasureSpec.getMode(heightMeasureSpec)
// wrap_content
var width = 0
var height = 0
var lineWidth = 0
var lineHeight = 0
//在每一次换行之后记录,是否超过了行数
var lineCount = 0 //记录当前的行数
val cCount = childCount
for (i in 0 until cCount) {
val child = getChildAt(i)
if (child.visibility == GONE) {
if (i == cCount - 1) { //最后一个
if (isLimit) {
if (lineCount == limitLineCount) {
isOverFlow = true
break
} else {
isOverFlow = false
}
}
width = Math.max(lineWidth, width)
height += lineHeight
lineCount++
}
continue
}
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
val lp = child.layoutParams as MarginLayoutParams
val childWidth = (child.measuredWidth + lp.leftMargin + lp.rightMargin)
val childHeight = (child.measuredHeight + lp.topMargin + lp.bottomMargin)
if (lineWidth + childWidth > sizeWidth - paddingLeft - paddingRight) {
if (isLimit) {
if (lineCount == limitLineCount) {
isOverFlow = true
break
} else {
isOverFlow = false
}
}
width = Math.max(width, lineWidth)
lineWidth = childWidth
height += lineHeight
lineHeight = childHeight
lineCount++
} else {
lineWidth += childWidth
lineHeight = Math.max(lineHeight, childHeight)
}
if (i == cCount - 1) {
if (isLimit) {
if (lineCount == limitLineCount) {
isOverFlow = true
break
} else {
isOverFlow = false
}
}
width = Math.max(lineWidth, width)
height += lineHeight
lineCount++
}
}
setMeasuredDimension(
if (modeWidth == MeasureSpec.EXACTLY) sizeWidth else width + paddingLeft + paddingRight,
if (modeHeight == MeasureSpec.EXACTLY) sizeHeight else height + paddingTop + paddingBottom
)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
mAllViews.clear()
mLineHeight.clear()
mLineWidth.clear()
lineViews.clear()
val width = width
var lineWidth = 0
var lineHeight = 0
//如果超过规定的行数则不进行绘制
var lineCount = 0 //记录当前的行数
val cCount = childCount
for (i in 0 until cCount) {
val child = getChildAt(i)
if (child.visibility == GONE) continue
val lp = child
.layoutParams as MarginLayoutParams
val childWidth = child.measuredWidth
val childHeight = child.measuredHeight
if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - paddingLeft - paddingRight) {
if (isLimit) {
if (lineCount == limitLineCount) {
break
}
}
mLineHeight.add(lineHeight)
mAllViews.add(lineViews)
mLineWidth.add(lineWidth)
lineWidth = 0
lineHeight = childHeight + lp.topMargin + lp.bottomMargin
lineViews = ArrayList()
lineCount++
}
lineWidth += childWidth + lp.leftMargin + lp.rightMargin
lineHeight = Math.max(
lineHeight, childHeight + lp.topMargin
+ lp.bottomMargin
)
lineViews.add(child)
}
mLineHeight.add(lineHeight)
mLineWidth.add(lineWidth)
mAllViews.add(lineViews)
var left = paddingLeft
var top = paddingTop
val lineNum = mAllViews.size
for (i in 0 until lineNum) {
lineViews = mAllViews[i]
lineHeight = mLineHeight[i]
// set gravity
val currentLineWidth = mLineWidth[i]
when (mGravity) {
LEFT -> left = paddingLeft
CENTER -> left = (width - currentLineWidth) / 2 + paddingLeft
RIGHT -> {
// 适配了rtl,需要补偿一个padding值
left = width - (currentLineWidth + paddingLeft) - paddingRight
// 适配了rtl,需要把lineViews里面的数组倒序排
Collections.reverse(lineViews)
}
}
for (j in lineViews.indices) {
val child = lineViews[j]
if (child!!.visibility == GONE) {
continue
}
val lp = child
.layoutParams as MarginLayoutParams
val lc = left + lp.leftMargin
val tc = top + lp.topMargin
val rc = lc + child.measuredWidth
val bc = tc + child.measuredHeight
child.layout(lc, tc, rc, bc)
left += (child.measuredWidth + lp.leftMargin
+ lp.rightMargin)
}
top += lineHeight
}
}
override fun generateLayoutParams(attrs: AttributeSet): LayoutParams {
return MarginLayoutParams(context, attrs)
}
override fun generateDefaultLayoutParams(): LayoutParams {
return MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
}
override fun generateLayoutParams(p: LayoutParams): LayoutParams {
return MarginLayoutParams(p)
}
companion object {
private const val TAG = "FlowLayout"
private const val LEFT = -1
private const val CENTER = 0
private const val RIGHT = 1
}
init {
// val ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout)
// mGravity = ta.getInt(R.styleable.TagFlowLayout_tag_gravity, LEFT)
// limitLineCount = ta.getInt(R.styleable.TagFlowLayout_limit_line_count, 3)
// isLimit = ta.getBoolean(R.styleable.TagFlowLayout_is_limit, false)
// mSelectedMax = ta.getInt(R.styleable.TagFlowLayout_max_select, -1)
val layoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
if (layoutDirection == LayoutDirection.RTL) {
mGravity = if (mGravity == LEFT) {
RIGHT
} else {
LEFT
}
}
// ta.recycle()
}
open fun setOnSelectListener(onSelectListener: OnSelectListener) {
mOnSelectListener = onSelectListener
}
open fun setOnTagClickListener(onTagClickListener: OnTagClickListener) {
mOnTagClickListener = onTagClickListener
}
open fun setOnLongClickListener(onLongClickListener: OnLongClickListener) {
mOnLongClickListener = onLongClickListener
}
open fun <T> setAdapter(adapter: TagAdapter<T>) {
mTagAdapter = adapter as TagAdapter<*>?
// mTagAdapter.setOnDataChangedListener(this)
mTagAdapter!!.setOnDataChangedListener(object : TagAdapter.OnDataChangedListener {
override fun onChanged() {
mSelectedView.clear()
changeAdapter<T>(true)
}
override fun onReChanged() {
changeAdapter<T>(false)
}
})
mSelectedView.clear()
changeAdapter<T>(true)
}
private fun <T> changeAdapter(isChange: Boolean) {
removeAllViews()
val adapter = mTagAdapter!! as TagAdapter<T>
var tagViewContainer: TagView? = null
val preCheckedList: HashSet<Int>
if (isChange) {
preCheckedList = mTagAdapter!!.preCheckedList
} else {
preCheckedList = mSelectedView as HashSet<Int>
}
for (i in 0 until adapter.count) {
val tagView = adapter.getView(this, i, adapter.getItem(i))
tagViewContainer = TagView(context)
tagView!!.isDuplicateParentStateEnabled = true
if (tagView.layoutParams != null) {
tagViewContainer.layoutParams = tagView.layoutParams
} else {
val lp = MarginLayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
)
lp.setMargins(
dip2px(context, 5f),
dip2px(context, 5f),
dip2px(context, 5f),
dip2px(context, 5f)
)
tagViewContainer.layoutParams = lp
}
val lp = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
tagView.layoutParams = lp
tagViewContainer.addView(tagView)
addView(tagViewContainer)
val disableList: HashSet<Int> = mTagAdapter!!.getDisableList()
if (disableList.contains(i)) {
tagViewContainer.isEnabled = false
} else {
if (preCheckedList.contains(i)) {
setChildChecked(i, tagViewContainer)
}
}
if (mTagAdapter!!.setSelected(i, adapter.getItem(i))) {
setChildChecked(i, tagViewContainer)
}
tagView.isClickable = false
val finalTagViewContainer: TagView = tagViewContainer
tagViewContainer.setOnClickListener(OnClickListener {
doSelect(finalTagViewContainer, i)
mOnTagClickListener?.onTagClick(
finalTagViewContainer, i
)
})
tagViewContainer.setOnLongClickListener(OnLongClickListener {
if (mOnLongClickListener != null) {
mOnLongClickListener!!.onLongClick(finalTagViewContainer, i)
//消费事件,不让事件继续下去
return@OnLongClickListener true
}
false
})
}
mSelectedView.addAll(preCheckedList)
}
open fun setMaxSelectCount(count: Int) {
if (mSelectedView.size > count) {
Log.w(TAG, "you has already select more than $count views , so it will be clear .")
mSelectedView.clear()
}
mSelectedMax = count
}
open fun getSelectedList(): Set<Int?>? {
return HashSet(mSelectedView)
}
open fun setChildChecked(position: Int, view: TagView) {
view.isChecked = true
mTagAdapter!!.onSelected(position, view.tagView)
}
open fun setChildUnChecked(position: Int, view: TagView) {
view.isChecked = false
mTagAdapter!!.unSelected(position, view.tagView)
}
open fun doSelect(child: TagView, position: Int) {
if (!child.isChecked) {
//处理max_select=1的情况
if (mSelectedMax == 1 && mSelectedView.size == 1) {
val iterator = mSelectedView.iterator()
val preIndex = iterator.next()
val pre = getChildAt(preIndex) as TagView
setChildUnChecked(preIndex, pre)
setChildChecked(position, child)
mSelectedView.remove(preIndex)
mSelectedView.add(position)
} else {
if (mSelectedMax > 0 && mSelectedView.size >= mSelectedMax) {
return
}
setChildChecked(position, child)
mSelectedView.add(position)
}
} else {
setChildUnChecked(position, child)
mSelectedView.remove(position)
}
mOnSelectListener?.onSelected(HashSet(mSelectedView))
}
open fun getAdapter(): TagAdapter<*>? {
return mTagAdapter
}
private val KEY_CHOOSE_POS = "key_choose_pos"
private val KEY_DEFAULT = "key_default"
override fun onSaveInstanceState(): Parcelable? {
val bundle = Bundle()
bundle.putParcelable(KEY_DEFAULT, super.onSaveInstanceState())
var selectPos = ""
if (mSelectedView.size > 0) {
for (key in mSelectedView) {
selectPos += "$key|"
}
selectPos = selectPos.substring(0, selectPos.length - 1)
}
bundle.putString(KEY_CHOOSE_POS, selectPos)
return bundle
}
override fun onRestoreInstanceState(state: Parcelable?) {
if (state is Bundle) {
val bundle = state
val mSelectPos = bundle.getString(KEY_CHOOSE_POS)
if (!TextUtils.isEmpty(mSelectPos)) {
val split = mSelectPos!!.split("\\|").toTypedArray()
for (pos in split) {
val index = pos.toInt()
mSelectedView.add(index)
val tagView = getChildAt(index) as TagView
tagView.let { setChildChecked(index, it) }
}
}
super.onRestoreInstanceState(bundle.getParcelable(KEY_DEFAULT))
return
}
super.onRestoreInstanceState(state)
}
open fun dip2px(context: Context, dpValue: Float): Int {
val scale = context.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
interface OnSelectListener {
fun onSelected(selectPosSet: Set<Int?>?)
}
interface OnTagClickListener {
fun onTagClick(view: View?, position: Int)
}
interface OnLongClickListener {
fun onLongClick(view: View?, position: Int)
}
}
abstract class TagAdapter<T> {
private var mTagDatas: List<T>?
private var mOnDataChangedListener: OnDataChangedListener? = null
@get:Deprecated("")
@Deprecated("")
val preCheckedList = HashSet<Int>()
private val mDisablePosList = HashSet<Int>()
constructor(datas: List<T>?) {
mTagDatas = datas
}
fun setData(datas: List<T>?) {
mTagDatas = datas
}
fun getData(): List<T>?{
return mTagDatas
}
@Deprecated("")
constructor(datas: Array<T>) {
mTagDatas = ArrayList(Arrays.asList(*datas))
}
fun setOnDataChangedListener(listener: OnDataChangedListener?) {
mOnDataChangedListener = listener
}
@Deprecated("")
fun setSelectedList(vararg poses: Int) {
val set: MutableSet<Int> = HashSet()
for (pos in poses) {
set.add(pos)
}
setSelectedList(set)
}
@Deprecated("")
fun setSelectedList(set: Set<Int>?) {
preCheckedList.clear()
if (set != null) {
preCheckedList.addAll(set)
}
notifyDataChanged()
}
/*public void setEnableList(int... poses) {
Set<Integer> set = new HashSet<>();
for (int pos : poses) {
set.add(pos);
}
setEnableList(set);
}
public void setEnableList(Set<Integer> set) {
mEnablePosList.clear();
if (set != null) {
mEnablePosList.addAll(set);
}
notifyDataChanged();
}
HashSet<Integer> getEnableList() {
return mEnablePosList;
}*/
open fun setDisableList(vararg poses: Int) {
val set: MutableSet<Int> = HashSet()
for (pos in poses) {
set.add(pos)
}
setDisableList(set)
}
open fun setDisableList(set: Set<Int>?) {
mDisablePosList.clear()
if (set != null) {
mDisablePosList.addAll(set)
}
if (mOnDataChangedListener != null) mOnDataChangedListener?.onReChanged()
}
open fun getDisableList(): HashSet<Int> {
return mDisablePosList
}
val count: Int
get() = if (mTagDatas == null) 0 else mTagDatas!!.size
fun notifyDataChanged() {
if (mOnDataChangedListener != null) mOnDataChangedListener!!.onChanged()
}
fun getItem(position: Int): T {
return mTagDatas!![position]
}
abstract fun getView(parent: FlowLayout?, position: Int, t: T): View?
fun onSelected(position: Int, view: View?) {
Log.d("zhy", "onSelected $position")
}
fun unSelected(position: Int, view: View?) {
Log.d("zhy", "unSelected $position")
}
fun setSelected(position: Int, t: Any?): Boolean {
return false
}
interface OnDataChangedListener {
fun onChanged()
fun onReChanged()
}
}
class TagView(context: Context) : FrameLayout(context),
Checkable {
private var isChecked = false
val tagView: View
get() = getChildAt(0)
public override fun onCreateDrawableState(extraSpace: Int): IntArray {
val states = super.onCreateDrawableState(extraSpace + 1)
if (isChecked()) {
mergeDrawableStates(states, CHECK_STATE)
}
return states
}
/**
* Change the checked state of the view
*
* @param checked The new checked state
*/
override fun setChecked(checked: Boolean) {
if (isChecked != checked) {
isChecked = checked
refreshDrawableState()
}
}
/**
* @return The current checked state of the view
*/
override fun isChecked(): Boolean {
return isChecked
}
/**
* Change the checked state of the view to the inverse of its current state
*/
override fun toggle() {
setChecked(!isChecked)
}
companion object {
private val CHECK_STATE = intArrayOf(android.R.attr.state_checked)
}
}
网友评论