【干貨】Android 實現 ScrollingTable上下左右滑動的列表 使用Kotlin

GitHub源碼地址

說明:本項目使用了多種實現方式,根據不同的業務需求去選定;

Type1(使用兩個列表):(左側)RecycleView + (右側)【HorizontalScrollView + RecycleView(使用GridLayoutManager)】

Type2(使用一個列表):RecycleView + Item布局{(左邊)TextView+(右邊)RecycleView} 【沒有完成列表中聯動】

Type5(使用兩個列表):(左側)RecycleView + (右側)【HorizontalScrollView + RecycleView(使用LinearLayoutManager)】

Type4(本項目的最佳實現【推薦】):ListView + Item布局{(左邊)TextView+(右邊)HorizontalScrollView}

下載APK

下載鏈接二維碼.png

要求:可以上下左右移動的表格布局,仿同花順自選列表,老虎證券財報列表

同花順效果 老虎證券效果 最終實現效果如圖
Gif_20180202_001640.gif
Gif_20180201_225941.gif
Gif_20180202_001417.gif

實現思路分析

深度截圖_選擇區域_20180201223904.png

視圖分析

1、主視圖分為: 頭部控件(HeadView)+下面的ListView

2、頭部控件(HeadView):左邊為 TextView,右邊為 HorizontalScrollView

3、ListView 條目視圖:左邊為 TextView,右邊為 HorizontalScrollView

視圖聯動分析

1、頭部 HorizontalScrollView 滑動事件廣播通知 ListView 條目中的 HorizontalScrollView 從而實現聯動效果

2、攔截 ListView 單個條目中的 HorizontalScrollView 滑動事件,防止 ListView 的觸摸事件和 HorizontalScrollView 觸摸事件沖突

3、統一處理 ListView 和 頭部控件(HeadView)觸摸事件,統一將觸摸事件傳遞給 頭部控件(HeadView)右邊的 HorizontalScrollView ,從而實現(1)中的效果

 /**
  * Created by xiaoyulaoshi on 2018/1/31.
  *
  * 自定義的 滾動控件
  * 重載了 [SyncHScrollView.onScrollChanged](滾動條變化),監聽每次的變化通知給觀察(此變化的)觀察者
  * 可使用 [SyncHScrollView.AddOnScrollChangedListener] 來訂閱本控件的 滾動條變化
  */
 class SyncHScrollView : HorizontalScrollView {
     internal var mScrollViewObserver: ScrollViewObserver? = ScrollViewObserver()
 
     constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}
 
     constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
 
     constructor(context: Context) : super(context) {}
 
     override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
         /*
          * 當滾動條移動后,引發 滾動事件。通知給觀察者,觀察者會傳達給其他的條目中的滾動視圖。
         */
         if (mScrollViewObserver != null) {
             mScrollViewObserver!!.NotifyOnScrollChanged(l, t, oldl, oldt)
         }
         super.onScrollChanged(l, t, oldl, oldt)
     }
 
     /*
      * 訂閱 本控件 的 滾動條變化事件
      * */
     fun AddOnScrollChangedListener(listener: OnScrollChangedListener) {
         mScrollViewObserver!!.AddOnScrollChangedListener(listener)
     }
 
     /*
      * 取消 訂閱 本控件 的 滾動條變化事件
      * */
     fun RemoveOnScrollChangedListener(listener: OnScrollChangedListener) {
         mScrollViewObserver!!.RemoveOnScrollChangedListener(listener)
     }
 
     /*
      * 當發生了滾動事件時
      */
     interface OnScrollChangedListener {
         fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int)
     }
 
     /**
      * 觀察者
      */
     class ScrollViewObserver {
         internal var mList: MutableList<OnScrollChangedListener>? = null
 
         init {
             mList = ArrayList()
         }
 
         fun AddOnScrollChangedListener(listener: OnScrollChangedListener) {
             mList!!.add(listener)
         }
 
         fun RemoveOnScrollChangedListener(
                 listener: OnScrollChangedListener) {
             mList!!.remove(listener)
         }
 
         fun NotifyOnScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
             if (mList == null || mList!!.size == 0) {
                 return
             }
             for (i in mList!!.indices) {
                 if (mList!![i] != null) {
                     mList!![i].onScrollChanged(l, t, oldl, oldt)
                 }
             }
         }
     }
 }
/**
* ListView使用的數據適配器,實現數據填充以及列表右側的 HorizontalScrollView 與 頁面中的 頭部控件(HeadView) 右側 HorizontalScrollView 的綁定
* 
* Created by xiaoyulaoshi on 2018/1/31.
 */
class Type4Adapter(context: Context,
                   /**
                    * layout ID
                    */
                   private val id_row_layout: Int,
                   /**
                    * List中的數據
                    */
                   private val currentData: MutableList<Data>,
                   /**
                    * ListView頭部
                    */
                   private val mHead: RelativeLayout) : BaseAdapter() {
    private val mInflater: LayoutInflater


    init {
        Log.v(TAG + ".Type4Adapter", " 初始化")
        this.mInflater = LayoutInflater.from(context)

    }

    override fun getCount(): Int {
        return this.currentData.size
    }

    override fun getItem(position: Int): Any? {
        return null
    }

    override fun getItemId(position: Int): Long {
        return 0
    }

    /**
     * 向List中添加數據
     *
     * @param items
     */
    fun addItem(items: List<Data>) {
        for (item in items) {
            currentData.add(item)
        }
    }

    /**
     * 清空當List中的數據
     */
    fun cleanAll() {
        this.currentData.clear()
    }

    @SuppressLint("SetTextI18n")
    override fun getView(position: Int, convertView: View?, parentView: ViewGroup): View {
        var convertView = convertView
        var holder: ViewHolder? = null
        if (convertView == null) {
            convertView = mInflater.inflate(id_row_layout, null)
            holder = ViewHolder()

            //獲取當前條目中的右側滑動控件
            val scrollView1 = convertView!!.findViewById<SyncHScrollView>(R.id.horizontalScrollView1)

            //TODO 劃重點:這里需要從傳入的列表頭拿到里面的右側滑動控件
            val headScrollView = mHead.findViewById<SyncHScrollView>(R.id.horizontalScrollView1)
            //將當前條目的右側滑動控件添加到頭部滑動控件的滑動觀察者集合中
            headScrollView.AddOnScrollChangedListener(OnScrollChangedListenerImp(scrollView1))


            //進行holder的初始化操作
            holder.scrollView = scrollView1
            holder.txt1 = convertView.findViewById(R.id.textView1)
            holder.txt2 = convertView.findViewById(R.id.textView2)
            holder.txt3 = convertView.findViewById(R.id.textView3)
            holder.txt4 = convertView.findViewById(R.id.textView4)
            holder.txt5 = convertView.findViewById(R.id.textView5)
            holder.txt6 = convertView.findViewById(R.id.textView6)
            holder.txt7 = convertView.findViewById(R.id.textView7)

            convertView.tag = holder
        } else {
            holder = convertView.tag as ViewHolder
        }
        holder.txt1!!.text = currentData[position].str1
        holder.txt2!!.text = currentData[position].str1!! + currentData[position].str2!!
        holder.txt3!!.text = currentData[position].str1!! + currentData[position].str3!!
        holder.txt4!!.text = currentData[position].str1!! + currentData[position].str4!!
        holder.txt5!!.text = currentData[position].str1!! + currentData[position].str5!!
        holder.txt6!!.text = currentData[position].str1!! + currentData[position].str6!!
        holder.txt7!!.text = currentData[position].str1!! + currentData[position].str7!!
        return convertView
    }

    internal inner class OnScrollChangedListenerImp(var mScrollViewArg: SyncHScrollView) :
            SyncHScrollView.OnScrollChangedListener {

        override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
            mScrollViewArg.smoothScrollTo(l, t)
        }
    }

    internal inner class ViewHolder {
        var txt1: TextView? = null
        var txt2: TextView? = null
        var txt3: TextView? = null
        var txt4: TextView? = null
        var txt5: TextView? = null
        var txt6: TextView? = null
        var txt7: TextView? = null
        var scrollView: HorizontalScrollView? = null
    }

    companion object {
        private val TAG = Type4Adapter::class.java.name
    }
}

/**
 * 最完美實現,使用 ListView + HorizontalScrollView 實現
 * Created by xiaoyulaoshi on 2018/1/31.
 */
class Type4Activity : Activity() {
    internal lateinit var mListView1: ListView
    internal lateinit var mHead: RelativeLayout
    internal lateinit var type4Adapter: Type4Adapter

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_type4)

        mHead = findViewById(R.id.head)
        mHead.isFocusable = true
        mHead.isClickable = true

        //TODO 劃重點:這里需要從傳入的列表頭拿到里面的右側滑動控件
        mHead.setOnTouchListener(ListViewAndHeadViewTouchListener())


        mListView1 = findViewById(R.id.lv_produce)
        mListView1.setOnTouchListener(ListViewAndHeadViewTouchListener())

        // 創建當前用于顯示視圖的數據
        val currentData = ArrayList<Data>()
        for (i in 0..49) {
            val data = Data()
            data.str1 = "股票>" + i
            data.str2 = "價格>1"
            data.str3 = "價格>2"
            data.str4 = "價格>3"
            data.str5 = "價格>4"
            data.str6 = "價格>5"
            data.str7 = "價格>6"
            data.str8 = "價格>7"
            currentData.add(data)
        }


        type4Adapter = Type4Adapter(this, R.layout.item_layout_type4, currentData, mHead)
        mListView1.adapter = type4Adapter
        // OnClick監聽
        mListView1.onItemClickListener = OnItemClickListener { arg0, arg1, arg2, arg3 ->
            Log.i("Type4Activity ListView", "onItemClick Event")
            Toast.makeText(this@Type4Activity, "點了第" + arg2 + "個",
                    Toast.LENGTH_SHORT).show()
        }

    }

    /**
     * TODO 劃重點:用來將頭部和列表上面的觸摸事件都分發給頭部的滑動控件
     */
    internal inner class ListViewAndHeadViewTouchListener : View.OnTouchListener {

        override fun onTouch(arg0: View, arg1: MotionEvent): Boolean {
            // 當在列頭 和 listView控件上touch時,將這個touch的事件分發給 ScrollView
            val headScrollView = mHead.findViewById<HorizontalScrollView>(R.id.horizontalScrollView1)
            headScrollView.onTouchEvent(arg1)
            return false
        }
    }

}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容