流式布局FlowLayout.kt

作者: 颤抖的闪电 | 来源:发表于2021-07-09 16:20 被阅读0次

    流式布局-源码来自于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)
        }
    }
    

    相关文章

      网友评论

        本文标题:流式布局FlowLayout.kt

        本文链接:https://www.haomeiwen.com/subject/ejvfpltx.html