ラベルリスト選択view:ChooseFlowView


ラベルリスト選択view:ChooseFlowView
ソースgithub管理アドレスに移行
主に不規則なITEM TAGラベルのフローLISTレイアウトに対して、規則的であればRecyclerviewで完全に適任であり、メモリ管理も良好であるが、不規則なものは自分で書く必要がある.文章が長いかもしれないので、ここで効果図を置いて、話す前に大体の理解がある.
一、導入使用
本当はもう1つ記事を書いて紹介しようと思っていたのですが、アクセスは比較的簡単で、そのまま
1.1導入
ルートディレクトリbuild.gradle
maven {
            url "https://dl.bintray.com/fanyafeng/ripple"
        }

プロジェクトmodulebuild.gradle
implementation 'com.ripple.component:ui:0.0.6'

1.2使用
1.2.1定義データモデル
プレゼンテーションなので、データモデルを簡単に定義しました
data class ChooseModel(var title: String, var checkable: Boolean, var checked: Boolean) :
    IChooseModel {
    override fun getChooseItemTitle(): String {
        return title
    }

    override fun getChooseItemCheckable(): Boolean {
        return checkable
    }

    override fun getChooseItemChecked(): Boolean {
        return checked
    }

    override fun setChooseItemChecked(isChecked: Boolean) {
        checked = isChecked
    }

}

1.2.2 activityでの使用
次に、adapterにデータを設定する操作を行います.
5.forEach {
            val model = ChooseModel("   $it", it != 3, it == 0)
            list.add(model)
            val itemView = ChooseItemView(this)
            itemView.setInnerTagWrapContent()
            itemView.chooseViewUnselected = R.drawable.choose_view_normal
            chooseItemView.addItemView(itemView, model)
        }

1.2.3コールバック結果の取得
        chooseItemView.onItemClickListener={ first, position, third, fourth, fifth ->
            //      ,         
            Log.d(TAG, "   :" + position)
        }

        chooseItemView.onItemAbleClickListener = { view, position, model ->
            Log.d(TAG, "   :" + position)
            showToast("   :" + position)

        }

        chooseItemView.onItemUnableClickListener = { view, position, model ->
            Log.d(TAG, "          :" + position)
            showToast("          :" + position)
        }

二、設計原理
一般的な話では、汎用クラスのものを書くには、修正に対して閉鎖し、拡張に開放する必要があります.また、ユーザーのアクセスコストは小さくなければなりません.デフォルトの実装があるほうがいいですが、ユーザーの詳細ごとの修正をサポートする必要があります.カスタマイズすることができます.
2.1需要概要
まず、gif図は3つの部分に分けられ、第1部分はリスト選択コントロールのように勝手にviewを追加し、第2はここで言うポイントChooseFlowView、第3はレイアウトの修正とコールバックの結果の下で詳しくChooseFLowView、ここは皆さんがよく知っているある宝あるいはある東商品の詳細ページの多規格選択のviewです.需要は彼らの需要に従って来て、このように需要は需要表を確定しました:
  • リストのITEMには、選択、非選択の3つのステータスがあります.
  • は選択できません.
  • リストの最大および最小選択数は、最大数を超えるとFIFOアルゴリズムに従って選択リスト
  • を更新する.
  • の3つの状態のITEMコールバックをクリックするには、リスニング結果
  • が必要である.
  • データの塗りつぶしおよびスタイルのカスタマイズ
  • リスト更新時再利用リスト(イントラチューニングでしょう)
  • 2.2設計思想
    以上の需要分析を通じて、このChooseFlowViewの骨格図を列挙することができて、共通のviewをするので、ここはやはりいつものように**インタフェースと汎用**を採用して具体的な実現を行います
    2.2.1まずITEMの抽象を見る
    /**
     * Author: fanyafeng
     * Data: 2020/6/28 19:24
     * Email: [email protected]
     * Description:     view          
     */
    interface IChooseItemView:Serializable {
    
        /**
         *        
         */
        fun isCheckable(): Boolean
    
        /**
         *           
         */
        fun setCheckable(isCheckable: Boolean)
    
        /**
         *      
         */
        fun isChecked(): Boolean
    
        /**
         *       
         */
        fun setChecked(isChecked: Boolean)
    
        /**
         * Change the checked state of the view to the inverse of its current state
         */
        fun toggle()
    
    }
    

    2.2.2データインタフェースの定義を見る
    /**
     * Author: fanyafeng
     * Data: 2020/6/29 09:31
     * Email: [email protected]
     * Description:     item
     *
     * data model        
     */
    interface IChooseModel : Serializable {
    
        /**
         *   view  
         */
        fun getChooseItemTitle(): String
    
        /**
         *        
         */
        fun getChooseItemCheckable(): Boolean
    
        /**
         *      
         */
        fun getChooseItemChecked(): Boolean
    
        /**
         *               FIFO  data model
         */
        fun setChooseItemChecked(isChecked: Boolean)
    }
    

    2.2.3次はChooseFlowViewが提供する機能です
    /**
     * Author: fanyafeng
     * Data: 2020/6/29 09:53
     * Email: [email protected]
     * Description:     
     *        
     */
    interface IChooseFlowView : Serializable {
    
        /**
         *         
         */
        fun setMaxChooseCount(maxCount: Int)
    
        /**
         *        ,   1
         */
        fun getMaxChooseCount(): Int
    
        /**
         *         
         */
        fun getMinChooseCount(): Int
    
        /**
         *         
         *    0,              ,      
         */
        fun setMinChooseCount(minCount: Int)
    }
    

    三、実現
    当時はChooseFlowViewのカスタマイズについていろいろ考えていましたが、良い実現方法が思いつかず、後に書きながら変更し、最後に比較的良い案を選びました
    3.1 viewカスタマイズ
    まずtag viewです.これはデフォルトの実装がありますが、デフォルトの実装はIChooseItemViewインタフェースを実装しています.動作を統一するため、このインタフェースを実装する必要があります.また、ChooseFlowViewITEMを設定する場合も、このインタフェースを実装する必要があります.さらにdata modelは、データ型の抽象です.ここでは抽象的です.まず、方法の定義を大きく見てみましょう.
    fun  addItemView(
            itemView: T,
            model: M,
            params: LayoutParams? = null
        )
    

    ユーザーがカスタムviewを追加することをサポートしますが、modelIChooseModelを実現します.
    3.2 viewの更新ChooseFlowViewを更新するには簡単で乱暴な方法があります.すべてのITEMをやめてから新しく追加することですが、これはよくありません.ここではRecyclerview ViewHolderの案を真似することができます.ある場合は持ってきてから更新します.役に立たない場合は削除しますが、再利用の問題はやはり前にリセットする必要があります.ここでは覚えておいてください.
    3.3操作
    ここは文章の重点であり、ChooseFlowViewで実現されたコアコードが最も多い場所でもある.
  • ユーザーが初めてデータを埋め込んでユーザーインタフェースを初期化する前にユーザーが選択できる最大数と最小数を確定したのは、なぜかというと、ユーザーはデータの山に来る可能性があるが、中の選択状態が最大値を超える場合、コントロールがFIFO(ユーザーの選択習慣に合致する)に従ってフィルタリングする必要があるからだ.同様にユーザがデータを更新することにおいても同様の問題が発生し、これによりデータの表示は
  • を解決する.
  • 以上のデータのフィルタリングが完了するとページが初期化する、このときページはユーザの要求に従って表示され、このとき取得する結果は信頼性が高く、要求に合致する
  • である.
  • ユーザーのクリック操作に関わる場合、複数選択、単一選択、逆選択が必要な場合は最大数、最小数を設定して**を制御することができる(PS:後に逆選択をサポートするかどうかを追加することができるが、意味が一時的に保留されていないような気がする)**
  • ChooseFlowViewを更新し、ページを更新するときに選択したデータを再フィルタリングします.ここでは3つのケースがあります.新しいデータが大きい、等しい、小さいなら古いデータより小さい、等しいなら一番いい処理です.data listを更新してページを更新するだけでいいです.小さいなら余分なviewremoveと同時にページを更新する必要があります.それより大きい場合はITEMを新築してChooseFlowView
  • に加える必要がある.
    3.4コアコード
    3.4.1初期化データ
    /**
         *     
         *         
         */
        @JvmOverloads
        fun  addItemView(
            itemView: T,
            model: M,
            params: LayoutParams? = null
        ) {
            position++
            allModelList.add(model)
            itemView.initData(model)
            itemView.tag = position
    
            val initCount = selectList.size
    
            if (model.getChooseItemChecked()) {
                if (initCount >= maxCount) {
                    val first = selectList.first
                    (getChildAt(first) as ChooseItemView).toggle()
                    setItemCheckStatus(selectList.first,false)
                    selectList.removeFirst()
                    selectList.addLast(position)
                    setItemCheckStatus(position,true)
                } else {
                    selectList.addLast(position)
                }
            }
    
            itemView.setOnClickListener {
                val pos = it.tag as Int
    
                val isCheckable: Boolean
    
                /**
                 *                 
                 *        ,     
                 */
                var checkRepeat = true
    
                if (itemView.isCheckable()) {
                    isCheckable = true
                    val mCount = selectList.size
                    if (itemView.isChecked()) {
                        //    
                        if (mCount <= minCount) {
                            //                     
                            checkRepeat = false
                        } else {
                            selectList.remove(pos)
                            setItemCheckStatus(pos,false)
                            itemView.toggle()
                        }
                    } else {
                        if (mCount >= maxCount) {
                            //           
                            val first = selectList.first
                            (getChildAt(first) as ChooseItemView).toggle()
                            itemView.toggle()
                            setItemCheckStatus(selectList.first,false)
                            selectList.removeFirst()
                            selectList.addLast(pos)
                            setItemCheckStatus(pos,true)
                        } else {
                            //    
                            itemView.toggle()
                            selectList.addLast(pos)
                            setItemCheckStatus(pos,true)
                        }
                    }
                    onItemAbleClickListener?.invoke(it, pos, model)
                } else {
                    isCheckable = false
                    onItemUnableClickListener?.invoke(it, pos, model)
                }
    
                onItemClickListener?.invoke(it, pos, model, isCheckable, checkRepeat)
    
    
            }
            if (params != null) {
                addView(itemView, params)
            } else {
                addView(itemView)
            }
        }
    

    3.4.2データの更新
    /**
         *      view
         *               view
         *     view   
         */
        fun  updateView(list: List>) {
            selectList.clear()
            val newCount = list.size
            val oldCount = allModelList.size
            if (newCount == oldCount) {
                list.forEachIndexed { index, model ->
                    val chooseModel = model.first
                    updateSelectList(index, chooseModel)
                    //     model  
                    allModelList[index] = chooseModel
                    //     view  
                    (getChildAt(index) as ChooseItemView).initData(chooseModel)
                }
            } else if (newCount > oldCount) {
                list.forEachIndexed { index, model ->
                    val chooseModel = model.first
                    updateSelectList(index, chooseModel)
                    //           
                    if (index < oldCount) {
                        allModelList[index] = chooseModel
                        (getChildAt(index) as ChooseItemView).initData(chooseModel)
                    } else {
                        addItemView(model.second, chooseModel)
                    }
                }
            } else {
                list.forEachIndexed { index, model ->
                    val chooseModel = model.first
                    updateSelectList(index, chooseModel)
                    //     model  
                    allModelList[index] = chooseModel
                    //     view  
                    (getChildAt(index) as ChooseItemView).initData(chooseModel)
                    //     
                    setItemCheckStatus(index,true)
                }
    
                (newCount until oldCount).forEach {
                    allModelList.removeAt(it)
                    removeViewAt(it)
                }
            }
        }
    
        /**
         *       
         *            ,                   
         *             datamodel,                         
         *              
         *              ,   FIFO
         *                     
         */
        private fun updateSelectList(selectPosition: Int, chooseModel: IChooseModel) {
            val initCount = selectList.size
            //      item       
            if (chooseModel.getChooseItemChecked()) {
                //       ,                 
                if (initCount >= maxCount) {
                    //             model
                    setItemCheckStatus(selectList.first,false)
                    //             
                    (getChildAt(selectList.first) as ChooseItemView).toggle()
                    //        item  
                    selectList.removeFirst()
    
                    //        item            
                    //    
                    selectList.addLast(selectPosition)
                    //        item
                    setItemCheckStatus(selectPosition,true)
                } else {
                    selectList.addLast(selectPosition)
                    setItemCheckStatus(selectPosition,true)
                }
            }
        }