赞
踩
作为在测绘地理信息行业中穿梭的打工人,遇到各种数据采集需求,既然有数据采集需求,那当然少不了数据展示功能,最常见的如表格方式展示。
当然,类似表格这些控件网上也有挺多开源的,但是经过我一番思考,决定自己动手撸一个,还能了解下原理。
如下图所示,我们把表格拆分成三部分,表头、固定列、表格内容,其中固定列顾名思义,位置固定,内容部分,当宽度超过可视范围时,可左右滚动
对于表格垂直方向的滚动,我们可以用Rrecyclerview
来实现,那么水平方向的滚动,我们可以使用HorizontalScrollerView
,
这样我们就可以得到一个初步的表格雏形,对应类暂且叫RPWDataGridView
关键属性、接口代码:
class RPWDataGridView<T> @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : LinearLayout(context, attrs) { private val headerView: RPWDataGridIRowItemView //表头 private val recyclerView: RecyclerView//表格内容 private val columns = mutableListOf<RPWDataGridColumn>() //列参数,每一行共用同一列参数,保证每个单元格的宽度一致 private var horScrollOffset = 0 //当前水平滚动偏移量,保证每一行滚动量一致 private val dataSource = mutableListOf<T>() //数据源 private var dataGridAdapter = DataGridAdapter() //数据适配器 fun build(vararg columns: RPWDataGridColumn) {//构建表格结构 //... } /** * 设置表格数据源 */ fun setDataSource(data: List<T>) { //... } }
众所周知,每一行里面又会按列分成狠多单元格,所以我们还得再把HorizontalScrollerView
按列细分,里面单元格通过动态添加TextView
来实现,由于需要固定列,所以为了方便实现固定的逻辑,我们做如下设计:
然后封装一个表格的行控件,暂且命名为RPWDataGridIRowItemView
,该控件View的结构如上图所示,固定列使用一个LinearLayout
,滚动列使用HorizontalScrollerView
, 代码层面,伪代码:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/llRoot" android:layout_width="match_parent" android:layout_height="@dimen/ui_data_grid_row_min_height" android:background="@drawable/data_grid_view_row_item_background" android:clickable="true" android:focusable="true" android:focusableInTouchMode="true" android:orientation="horizontal"> <LinearLayout android:id="@+id/llFreezeColumn" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" android:showDividers="middle" /> <View android:id="@+id/viewVerHeaderDivider" android:background="@color/ui_data_grid_header_divider_color" android:layout_width="@dimen/ui_data_grid_header_divider_size" android:layout_height="match_parent"/> <com.rpw.view.RPWHorizontalScrollView android:id="@+id/horScrollView" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:scrollbars="none"> <LinearLayout android:id="@+id/llScrollColumn" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:showDividers="middle" /> </com.rpw.view.RPWHorizontalScrollView> </LinearLayout>
class RPWDataGridIRowItemView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : LinearLayout(context, attrs) { //冻结列父布局 private val llFreezeColumn: LinearLayout //滚动列父布局 private val llScrollColumn: LinearLayout //RPWDataGridColumn为列参数 fun addColumn(column: RPWDataGridView.RPWDataGridColumn) { if (column.freeze) { llFreezeColumn.addView(TextView()) }else{ llScrollColumn.addView(TextView()) } } }
然后把他作为RecyclerView
的 ItemView
加载到每一行中。
那么问题来了,每一行都有自己的滚动View,各滚各的,这跟表格也不一样。
所以,为了解决这个问题,我们需要给每个HorizontalScrollerView
注册滚动监听,当某个HorizontalScrollerView
发生滚动,我们把其他的HorizontalScrollerView
也设置同样的滚动量不就可以对齐了吗。
是的,但是在实现这个逻辑前,由于他不对外暴露滚动状态,我们还得继承HorizontalScrollerView
重写 onScrollChanged
函数,暂且命名为RPWHorizontalScrollView
,我们专属的水平滚动View。
国际惯例,上关键代码:
public class RPWHorizontalScrollView extends HorizontalScrollView {
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (null != listener)
listener.onCustomScrollChange(RPWHorizontalScrollView.this, l, t, oldl, oldt);
//通知滚动变化
}
}
接下来,我们还需要补充一下对齐RecyclerView
中所有已加载的ItemView
,这个代码需要写到表格控件RPWDataGridView
中,与其他行共享同一偏移量,对齐关键代码如下:
/** * 对齐当前视图下每一行的滚动偏移 */ private fun alignItems(scrollX: Int) { val layoutManager = recyclerView.layoutManager as LinearLayoutManager for (i in 0..layoutManager.childCount) { val v = layoutManager.getChildAt(i) if (v != null) { val vh = recyclerView.getChildViewHolder(v) as RPWDataGridView<*>.DataGridViewHolder vh.rowView.scrollTo(scrollX, 0) Log.i(TAG, "alignItems: $horScrollOffset") } } horScrollOffset = scrollX headerView.setHorOffset(horScrollOffset) tHorOffset(horScrollOffset) //给表头也设置相同的滚动量 }
然后在适配器中监听和绑定每一行的滚动量,给他设置到全局horScrollOffset
中,在适配器onBindViewHolde
r 的时候,给他设置这个偏移量,实现新的行也对齐。
完整封装的代码就不在这里详细展示了,有兴趣可以到Gitee上查看
with(rpwDataGridView) { verDividerParams.show = false verDividerParams.showHeaderDivider = true horDividerParams.show = true horDividerParams.showHeaderDivider = true //region build column //构建表格结构 build( RPWDataGridView.RPWDataGridColumn( DensityUtil.dpToPx(this@MainActivity, 100), "姓名", true ), RPWDataGridView.RPWDataGridColumn( DensityUtil.dpToPx(this@MainActivity, 100), "密码", false ), RPWDataGridView.RPWDataGridColumn( DensityUtil.dpToPx(this@MainActivity, 200), "身份证号码", false ), RPWDataGridView.RPWDataGridColumn( DensityUtil.dpToPx(this@MainActivity, 200), "出生年月", false ), RPWDataGridView.RPWDataGridColumn( DensityUtil.dpToPx(this@MainActivity, 60), "性别", false ), RPWDataGridView.RPWDataGridColumn( DensityUtil.dpToPx(this@MainActivity, 150), "手机号码", false ), RPWDataGridView.RPWDataGridColumn( DensityUtil.dpToPx(this@MainActivity, 150), "邮箱", false ), RPWDataGridView.RPWDataGridColumn( DensityUtil.dpToPx(this@MainActivity, 300), "地址", false ), ) //endregion //绑定每行显示的数据 setRowBuildListener(object : RPWDataGridView.RowBuildListener<ItemData> { override fun onBuildRow(rowItemView: RPWDataGridIRowItemView, data: ItemData) { rowItemView.cells[0].text = data.name rowItemView.cells[1].text = data.password rowItemView.cells[2].text = "11235842364564582" rowItemView.cells[3].text = "2024-04-28" rowItemView.cells[4].text = data.sex rowItemView.cells[5].text = data.phone rowItemView.cells[6].text = data.email rowItemView.cells[7].text = data.address } }) //监听单元格点击 setRowClickListener(object : RPWDataGridView.RowClickListener<ItemData> { override fun onRowClick(data: ItemData, rowIndex: Int, columnIndex: Int) { Toast.makeText( this@MainActivity, "点击坐标[$rowIndex:$columnIndex]", Toast.LENGTH_SHORT ).show() } override fun onRowLongClick( t: ItemData, rowIndex: Int, columnIndex: Any? ): Boolean { rpwDataGridView.startSelect(true) return true } }) //监听页面状态变化 setStatusListener(object : RPWDataGridView.DataGridViewStatusListener { override fun onStatusChange(statusEnum: RPWDataGridViewStatusEnum) { Toast.makeText( this@MainActivity, "状态改变:$statusEnum", Toast.LENGTH_SHORT ).show() } }) val ds = mutableListOf<ItemData>() repeat(1000) {//添加1000条测试数据 ds.add( ItemData( "WPR$it", it.toString(), "$it", "广东省广州市番禺区xxxxxx$it 号", "123456789" ) ) } setDataSource(ds) }
嗯嗯嗯~~按照这个思路,实现如下:
至此,简单的表格效果已有,目前发现有一些UI体验层面的bug,后面我会看情况在Gitee中完善,因为是想写一个简单易用的表格控件,所以对每个单元格里面的View都写死成TextView
了,另一方面是我需求没那么复杂。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。