【干货】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
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容