Android自定义控件实例(2)——AndroidTableView,支持行列合并
2017-04-05 16:47
387 查看
一、前言
由于项目中许多数据涉及到表格展示,而且表格控件最好能够支持跨行、跨列合并。鉴于能不重复造轮子就不造的思想,去github上搜索了一番,SortableTableView和AsymmetricGridView都还算是点击量比较高的两个开源项目。但前者不支持跨行列合并,后者只支持2行2列的合并(Currently only has good support for
items with rowSpan = 2 and columnSpan = 2.)。如果觉着跨行改成单行、并用重复值填充能够接受的话,SortableTableView还是挺不错的。
由于没有找到支持表格的跨行列合并的开源项目,只能自己动手自定义一个支持跨行列合并的表格控件。
二、思路
一开始想基于GridView或者TableLayout实现跨行列合并,通过实践后发现,GridView本身并不支持跨行合并,而TableLayout是通过添加TableRow来控制表格的行,虽然可通过嵌套TableLayout的方式实现跨行合并,但针对跨行单元格的行有交叉的情况,无法支持。如下图所示:
最后想到,GridLayout(网格布局)本身可以将整个容器划分成n行*m列个网格,每个网格可放置一个组件,并且支持组件跨行、跨列。因此完全可以借助于GridLayout实现作者的需求。
三、基于GridLayout实现自定义表格控件
既然GridLayout支持跨行、跨列合并,那就简单了。直接通过代码往GridLayout里添加TextView或其他组件,并设置其行列号和跨行列个数不就OK了?实践证明我想的太简单了,还是对GridLayout布局使用不熟悉导致的。GridLayout本身不支持滚动条,超出其显示范围的组件会被隐藏掉。如下图所示:
因此,为了使表格支持滚动条,需要将GridLayout布局放到ScrollView中,并将GridLayout的高度设置为wrap_content。此外在ScrollView的上部加一个LinearLayout,用以展示表格的表头。本文自定义表格控件布局设计示意图如下:
四、编码过程中遇到的难点
1、表头与表体数据对齐:
为了使得表头和表数据各列分割线对齐,需要借助于LinearLayout的weight属性。然后用表头的宽度乘以表格数据中各列的跨度再除以总列数即为每个数据格的宽度。即:TableViewWidth*ColumnSpan/ColumnCount。数据格的高度可以用WRAP_CONTENT。
注:一开始试了GridLayout.Sepc的weight参数,但展示有问题,表头的列和数据的列不对齐,具体原因没查出来。截图如下:
2、如何得到表格主体的宽度
为了绘制表格主体(GridLayout),需要先得到表格的宽度,得到这个宽度后,才能设置每个数据格的宽度。表格的宽度即表头(LinearLayout)的宽度,在调用OnMeasure之前,无法知道表头的宽度。因此,本文将表格主体的绘制放到表头的ViewTreeObserver.addOnGlobalLayoutListener()回调方法中。示意代码如下:
五、代码结构图如下
六、自定义合并单元格示例
MainActivity布局、代码文件和截图如下所示:
七、自定义适配器示例
自定义适配器时,只需要继承TableHeaderAdapter和TableDataAdapter,并重写TableHeaderAdapter的getHeaderView()和TableDataAdapter的addGridLayoutView()方法即可。例如,本文给出的SimpleTableHeaderAdapter和SimpleTableDataAdapter。
总结
程序的编写借鉴了SortableTableView的编码思路,通过GridLayout基本实现了跨行、列合并单元格。由于对GridLayout使用不熟悉,导致绕了很多弯路。程序代码本身比较简单,读者也可以根据自己的需求自定义自己的适配器。
程序本身在显示方面仍有如下小问题,如果某一个单元格很高且跨多行,则相同行的其他数据格最靠下的那个会拉伸(如下图)。
此时,只能通过动态计算数据格的高度得到GridLayout的最终高度,然后设置每个数据格的权重来解决(下一步优化方向)。如果不追求美观的话,则本程序完全可以使用。
源码下载
https://github.com/WJKCharlie/AndroidTableView
参考资料
AsymmetricGridView:https://github.com/felipecsl/AsymmetricGridView
SortableTableView: https://github.com/ISchwarz23/SortableTableView
由于项目中许多数据涉及到表格展示,而且表格控件最好能够支持跨行、跨列合并。鉴于能不重复造轮子就不造的思想,去github上搜索了一番,SortableTableView和AsymmetricGridView都还算是点击量比较高的两个开源项目。但前者不支持跨行列合并,后者只支持2行2列的合并(Currently only has good support for
items with rowSpan = 2 and columnSpan = 2.)。如果觉着跨行改成单行、并用重复值填充能够接受的话,SortableTableView还是挺不错的。
由于没有找到支持表格的跨行列合并的开源项目,只能自己动手自定义一个支持跨行列合并的表格控件。
二、思路
一开始想基于GridView或者TableLayout实现跨行列合并,通过实践后发现,GridView本身并不支持跨行合并,而TableLayout是通过添加TableRow来控制表格的行,虽然可通过嵌套TableLayout的方式实现跨行合并,但针对跨行单元格的行有交叉的情况,无法支持。如下图所示:
最后想到,GridLayout(网格布局)本身可以将整个容器划分成n行*m列个网格,每个网格可放置一个组件,并且支持组件跨行、跨列。因此完全可以借助于GridLayout实现作者的需求。
三、基于GridLayout实现自定义表格控件
既然GridLayout支持跨行、跨列合并,那就简单了。直接通过代码往GridLayout里添加TextView或其他组件,并设置其行列号和跨行列个数不就OK了?实践证明我想的太简单了,还是对GridLayout布局使用不熟悉导致的。GridLayout本身不支持滚动条,超出其显示范围的组件会被隐藏掉。如下图所示:
因此,为了使表格支持滚动条,需要将GridLayout布局放到ScrollView中,并将GridLayout的高度设置为wrap_content。此外在ScrollView的上部加一个LinearLayout,用以展示表格的表头。本文自定义表格控件布局设计示意图如下:
四、编码过程中遇到的难点
1、表头与表体数据对齐:
为了使得表头和表数据各列分割线对齐,需要借助于LinearLayout的weight属性。然后用表头的宽度乘以表格数据中各列的跨度再除以总列数即为每个数据格的宽度。即:TableViewWidth*ColumnSpan/ColumnCount。数据格的高度可以用WRAP_CONTENT。
注:一开始试了GridLayout.Sepc的weight参数,但展示有问题,表头的列和数据的列不对齐,具体原因没查出来。截图如下:
2、如何得到表格主体的宽度
为了绘制表格主体(GridLayout),需要先得到表格的宽度,得到这个宽度后,才能设置每个数据格的宽度。表格的宽度即表头(LinearLayout)的宽度,在调用OnMeasure之前,无法知道表头的宽度。因此,本文将表格主体的绘制放到表头的ViewTreeObserver.addOnGlobalLayoutListener()回调方法中。示意代码如下:
ViewTreeObserver viewTreeObserver = tableHeaderView.getViewTreeObserver(); viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { tableHeaderView.getViewTreeObserver().removeOnGlobalLayoutListener(this); tableDataAdapter.setTableDataViewWidth(tableHeaderView.getWidth()); setupTableDataView(); forceRefresh(); } });
五、代码结构图如下
六、自定义合并单元格示例
MainActivity布局、代码文件和截图如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.wjk.tableview.TableView android:id="@+id/tableview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="0dp"></com.wjk.tableview.TableView> </LinearLayout>
package com.wjk.androidtableview; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Pair; import com.wjk.tableview.TableView; import com.wjk.tableview.common.TableCellData; import com.wjk.tableview.common.TableHeaderColumnModel; import com.wjk.tableview.toolkits.SimpleTableDataAdapter; import com.wjk.tableview.toolkits.SimpleTableHeaderAdapter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class MainActivity extends AppCompatActivity { private TableView tableView; private Map<Integer, Pair<String,Integer>> columns; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tableView = (TableView)findViewById(R.id.tableview); initData(); SimpleTableDataAdapter dataAdapter = new SimpleTableDataAdapter(this,getTableData(), 6); dataAdapter.setTextSize(18); TableHeaderColumnModel columnModel = new TableHeaderColumnModel(columns); SimpleTableHeaderAdapter headerAdapter = new SimpleTableHeaderAdapter(this,columnModel); headerAdapter.setTextSize(20); tableView.setTableAdapter(headerAdapter,dataAdapter); tableView.setHeaderElevation(20); } private void initData(){ columns = new LinkedHashMap<>(); columns.put(0,new Pair<>("今年的收成不错",2)); columns.put(1,new Pair<>("明年的收成肯定会更好",2)); columns.put(2,new Pair<>("为人民服务",2)); } private List<TableCellData> getTableData() { List<TableCellData> cellDatas = new ArrayList<>(); cellDatas.add(new TableCellData("1", 0, 0, 2, 2)); cellDatas.add(new TableCellData("2", 0, 2, 1, 2)); cellDatas.add(new TableCellData("21", 0, 4, 1, 2)); cellDatas.add(new TableCellData("33", 1, 2)); cellDatas.add(new TableCellData("4", 1, 3)); /*cellDatas.add(new TableCellData("5", 1, 4)); cellDatas.add(new TableCellData("6", 1, 5));*/ cellDatas.add(new TableCellData("7", 2, 0)); cellDatas.add(new TableCellData("8", 2, 1)); cellDatas.add(new TableCellData("9", 2, 2)); cellDatas.add(new TableCellData("10", 2, 3)); /*cellDatas.add(new TableCellData("11", 2, 4, 1, 2));*/ cellDatas.add(new TableCellData("11", 1, 4, 2, 2)); cellDatas.add(new TableCellData("12", 3, 0)); cellDatas.add(new TableCellData("13", 3, 1)); cellDatas.add(new TableCellData("14", 3, 2, 1, 2)); cellDatas.add(new TableCellData("15", 3, 4)); cellDatas.add(new TableCellData("16", 3, 5)); for (int i = 4; i < 20; ++i) { cellDatas.add(new TableCellData(String.valueOf(i * 5 - 3), i, 0)); cellDatas.add(new TableCellData(String.valueOf(i * 5 - 2), i, 1)); cellDatas.add(new TableCellData(String.valueOf(i * 5 - 1), i, 2, 1, 2)); cellDatas.add(new TableCellData(String.valueOf(i * 5 + 0), i, 4)); cellDatas.add(new TableCellData(String.valueOf(i * 5 + 1), i, 5)); } return cellDatas; } }
七、自定义适配器示例
自定义适配器时,只需要继承TableHeaderAdapter和TableDataAdapter,并重写TableHeaderAdapter的getHeaderView()和TableDataAdapter的addGridLayoutView()方法即可。例如,本文给出的SimpleTableHeaderAdapter和SimpleTableDataAdapter。
总结
程序的编写借鉴了SortableTableView的编码思路,通过GridLayout基本实现了跨行、列合并单元格。由于对GridLayout使用不熟悉,导致绕了很多弯路。程序代码本身比较简单,读者也可以根据自己的需求自定义自己的适配器。
程序本身在显示方面仍有如下小问题,如果某一个单元格很高且跨多行,则相同行的其他数据格最靠下的那个会拉伸(如下图)。
此时,只能通过动态计算数据格的高度得到GridLayout的最终高度,然后设置每个数据格的权重来解决(下一步优化方向)。如果不追求美观的话,则本程序完全可以使用。
源码下载
https://github.com/WJKCharlie/AndroidTableView
参考资料
AsymmetricGridView:https://github.com/felipecsl/AsymmetricGridView
SortableTableView: https://github.com/ISchwarz23/SortableTableView
相关文章推荐
- android自定义控件实例(Linearlayout组合TextView和ImageView)
- Android中的webview支持页面中的文件上传实例代码
- Android自定义View实例AnalogClock源码
- android自定义控件:可旋转View:可作为ImageView、ImageButton
- android中使用自定义控件是报android.view.InflateException: Binary XML 异常
- 【Android应用实例之三】跟随手指的小球——自定义SurfaceView应用
- 【Android应用实例之二】跟随手指的小球——自定义View应用
- UITableView实例教程:创建Table View的detail view
- android应用开发之ImageView,SeekBar,TableHost,ProgressBar的使用
- Android Google Map实例 - 不同的图标标注在同一图层(Android mapview)
- Android自定义View之一:初探实例
- Android深入浅出系列之实例应用—简单的手指拖动图片,图片滑来滑去显示应用Gallery和BaseAdapter以及ImageView的使用
- Android TextView 支持的HTML标签
- Android Google Map实例 - 发布Android Google Map 程序(Android mapview)
- Android Google Map实例 - 安装到手机后的效果(Android mapview)
- android AutoCompleteTextView 实现输入提示,类似百度支持输入拼音提示中文(gray)
- [原创] asp.net生成HTML的合并table行列rowspan的新方法
- Android Google Map实例 - 添加Google Map自定义图层(Android mapview)
- Android Google Map实例 - 添加GPS位置标注(Android mapview)
- 先祝大家中秋节快乐,为大家介绍下中秋篇Android文件下载与存储实例,感谢大家的支持哈!