博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
重拾安卓:自定义View之表格封装实现
阅读量:4122 次
发布时间:2019-05-25

本文共 10131 字,大约阅读时间需要 33 分钟。

今天开始更新【重拾安卓】系列文章。

因业务需要又要做一个 Android 原生的项目,记录下时隔几年之后再开发安卓的那些事。讲的不会太基础,基本上是自定义View封装,复杂功能的实现等等,有需要的小伙伴可以关注~


安卓对表格的支持不是太友好,前端很快能实现的简单表格,安卓写的话要费很大精力。

拿到需求之后,稍微复杂点的功能在 github 上搜一下有没有好用的第三方框架,无疑是最节省时间的。表格还真有几个不错的框架,star 最多的是 ,的确很强大,只需设置数据就能自动生成表格。

但考虑各种因素还是决定自己撸一个表格,一是后端返回的数据结构还没定,二是需求并不是太复杂,只是个简单表格,三是找找手感~

一、需求分析及实现原理

最终效果:

实现目标:

  1. 行数不固定,超出父容器可以上下滚动
  2. 列数不固定,不管有多少列,都平分父容器宽度,每列的宽度一致
  3. 表头设置灰色背景,单元格是白色背景

实现原理:

两层 RecyclerView 嵌套,最外层是垂直方向的 RecyclerView,每一行是一个 item。每行又包含一个内层 RecyclerView,每行的每个单元格是内层 RecyclerViewitem

二、代码实现

为了方便重用,我们把这个课表封装成自定义 View,并对外暴露一个方法设置数据。

Android 自定义 View 有三种方式:组合、扩展、重写。我们这里用的是组合的方式,即把已有的控件组合起来形成符合需求的自定义控件。

2.1 自定义View 主文件 StudentWorkTableView

新建一个 Java 类 StudentWorkTableView 并继承 LinearLayout ,实现它的构造方法,就创建了一个自定义 View。

为什么继承 LinearLayout ?其实继承其他的 RelativeLayoutConstraintLayout 都可以,一般是你的 xml 最外层用的是什么布局,就继承什么。

构造方法要实现三个,因为不同的创建方式走的构造方法不一样,所以都要求实现。

构造方法小技巧:把前两个参数少的构造方法里的 super 改成 this,并填充默认值变成三个参数,就会都调用三个参数的构造方法了,业务逻辑只需写在最后一个构造方法里即可。

这个 View 很简单,先在构造方法里绑定 xml 布局,再执行初始化方法初始数据,然后在 onLayout 中计算每个单元格的宽度,最后对外暴露一个方法设置数据。自定义 View 基本都是这个套路。

注意这里用到了第三方框架 ButterKnife ,简化了 findViewById ,不熟悉的同学可以查查相关资料。

代码注释写的比较详细,就不多说了直接看代码。

public class StudentWorkTableView extends LinearLayout {
@BindView(R.id.recycler_view_week_table) RecyclerView recyclerView; private Context mContext; private List
mList; private int mCellWidth; private StudentWorkTableAdapter mTableAdapter; public StudentWorkTableView(Context context) {
this(context, null, 0); } public StudentWorkTableView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0); } public StudentWorkTableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); View view = View.inflate(context, R.layout.view_student_work_table, this); ButterKnife.bind(view, this); mContext = context; } /** * 对外暴露的方法,设置表格的数据 * * @param list */ public void setData(List
list) {
mList = list; init(); } /** * 初始化方法 */ private void init() {
LinearLayoutManager lm = new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false); recyclerView.setLayoutManager(lm); recyclerView.setItemAnimator(new DefaultItemAnimator()); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b); // onLayout 时 View 的宽高已经确定了,可以拿到比较准确的值 int width = getWidth(); // 计算每列即每个单元格的宽度。用 View 总宽度除以列数就得到了每个单元格的宽度 mCellWidth = width / mList.get(0).getTableList().size(); if (mTableAdapter == null) {
//把单元格宽度传给 Adapter,在 Adapter 中对单元格重设宽度 mTableAdapter = new StudentWorkTableAdapter(mContext, mCellWidth, R.layout.item_student_work_table_view, mList); recyclerView.setAdapter(mTableAdapter); } }}

2.2 布局文件 view_student_work_table.xml

对应的布局文件 view_student_work_table.xml

布局很简单,只有一个 RecyclerView

2.3 外层 RecyclerView 的适配器 StudentWorkTableAdapter

这个适配器是控制每行的显示。

Adapter 用到了吊炸天的 BaseRecyclerViewAdapterHelper ,节省了很多代码。只需在 convert() 方法里找到view 并设置数据 即可。

public class StudentWorkTableAdapter extends BaseQuickAdapter
{
private Context mContext; private int mCellWidth; private StudentWorkTableCellAdapter mCellAdapter; public StudentWorkTableAdapter(Context context, int cellWidth, int layoutResId, @Nullable List
data) {
super(layoutResId, data); mContext = context; mCellWidth = cellWidth; } @Override protected void convert(BaseViewHolder helper, TableListModel item) {
RecyclerView recyclerView = helper.getView(R.id.content_recycler_view); //注意这个RecyclerView要用横向的布局,以展示每一列 LinearLayoutManager lm = new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false); recyclerView.setLayoutManager(lm); //设置adapter mCellAdapter = new StudentWorkTableCellAdapter(mContext, mCellWidth, R.layout.item_student_work_cell, item.getTableList()); recyclerView.setAdapter(mCellAdapter); }}

2.4 外层 RecyclerView 的 item 布局文件 item_student_work_table_view.xml

外层的 item 布局文件里也只有一个 RecyclerView,外层 RecyclerView 用来展示行,内层 RecyclerView 用来展示列。

2.5 内层 RecyclerView 的适配器 StudentWorkTableCellAdapter

这个适配器是控制每个单元格。表头跟其他行的样式不一样,所以需要在数据上做个区分,这里简单的把表头的数据 id 都设为 111 了。判断如果是表头则改变背景样式。

public class StudentWorkTableCellAdapter extends BaseQuickAdapter
{
private float mCellWidth; private TextView tvTitle; private Context mContext; public StudentWorkTableCellAdapter(Context context, float cellWidth, int layoutResId, @Nullable List
data) {
super(layoutResId, data); mCellWidth = cellWidth; mContext = context; } @Override protected void convert(BaseViewHolder helper, TableTitleModel item) {
tvTitle = helper.getView(R.id.tv_item_cell_table); tvTitle.setText(item.getName()); ViewGroup.LayoutParams layoutParams = tvTitle.getLayoutParams(); layoutParams.width = (int)mCellWidth; if (item.getId().equals("111")){
//根据标记判断是表头还是普通单元格,如果是表头就改变背景色 tvTitle.setBackground(mContext.getResources().getDrawable(R.drawable.rect_table_title)); } }}

2.6 内层 RecyclerView 的 item 布局文件 item_student_work_cell.xml

这是每个单元格的布局文件,无论多复杂的布局都可以做,这里只放一个 TextView 演示。

2.7 其他

普通单元格的背景样式

表头的背景样式

样式文件放在 src/main/res/drawable 目录下。

以上就是表格自定义 View 的实现和封装。

三、使用

封装完之后就是使用啦,在需要使用的页面的 xml 布局文件中引入封装好的自定义 View 即可

在代码中通过 id 找到 StudentWorkTableView,然后设置数据

@BindView(R.id.work_table_view)StudentWorkTableView workTableView;private List
tableListModels;private void initWorkTableView() {
//从 assets 的 json 文件中读取数据 String json = AssetsUtils.getJson("work_table_data.json", getActivity()); Gson gson = new Gson(); tableListModels = gson.fromJson(json, new TypeToken
>(){
}.getType()); //设置数据给 TableView workTableView.setData(tableListModels);}

数据是通过读取本地的 json 文件模拟的假数据,正常情况下应该请求接口获取数据的。获取到数据之后调用 workTableView.setData(tableListModels); 把数据设置进自定义 View 就可以啦。

附上 TableListModel 对象,get()、set() 方法省略

public class TableListModel {
private List
tableList;}

TableTitleModel 对象,get()、set() 方法省略

public class TableTitleModel {
private String id; private String name;}

四、延伸

如何获取本地 json 文件的数据呢?

  1. 先建一个 assets 目录,位置是 src/main/assets,跟 javares 平级。
  2. 在 assets 目录下新建并编写 json 文件
  3. 在 java 代码中读取 json

读取 json 封装成了个工具类 AssetsUtils

/** * 读取 assets 文件夹中的文件工具类 */public class AssetsUtils {
/** * 获取assets中的json * @param fileName * @param context * @return */ public static String getJson(String fileName, Context context){
StringBuilder stringBuilder = new StringBuilder(); try {
InputStream is = context.getAssets().open(fileName); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); String line; while ((line=bufferedReader.readLine()) != null){
stringBuilder.append(line); } } catch (IOException e) {
e.printStackTrace(); } return stringBuilder.toString(); }}

附上 json 文件

[  {
"tableList": [ {
"id": "111", "name": "星期一" }, {
"id": "111", "name": "星期二" }, {
"id": "111", "name": "星期三" }, {
"id": "111", "name": "星期四" }, {
"id": "111", "name": "星期五" }, {
"id": "111", "name": "星期六" }, {
"id": "111", "name": "星期日" } ] }, {
"tableList": [ {
"id": "11", "name": "小紫" }, {
"id": "12", "name": "小明" }, {
"id": "13", "name": "小红" }, {
"id": "14", "name": "小绿" }, {
"id": "15", "name": "小黄" }, {
"id": "14", "name": "张三" }, {
"id": "15", "name": "李四" } ] }, {
"tableList": [ {
"id": "11", "name": "小紫" }, {
"id": "12", "name": "小明" }, {
"id": "13", "name": "小红" }, {
"id": "14", "name": "小绿" }, {
"id": "15", "name": "小黄" }, {
"id": "14", "name": "张三" }, {
"id": "15", "name": "李四" } ] }, {
"tableList": [ {
"id": "11", "name": "小紫" }, {
"id": "12", "name": "小明" }, {
"id": "13", "name": "小红" }, {
"id": "14", "name": "小绿" }, {
"id": "15", "name": "小黄" }, {
"id": "14", "name": "张三" }, {
"id": "15", "name": "李四" } ] }, {
"tableList": [ {
"id": "11", "name": "小紫" }, {
"id": "12", "name": "小明" }, {
"id": "13", "name": "小红" }, {
"id": "14", "name": "小绿" }, {
"id": "15", "name": "小黄" }, {
"id": "14", "name": "张三" }, {
"id": "15", "name": "李四" } ] }, {
"tableList": [ {
"id": "11", "name": "小紫" }, {
"id": "12", "name": "小明" }, {
"id": "13", "name": "小红" }, {
"id": "14", "name": "小绿" }, {
"id": "15", "name": "小黄" }, {
"id": "14", "name": "张三" }, {
"id": "15", "name": "李四" } ] }]

五、下集预告

简单的表格不过瘾?再撸一个有合并单元格的复杂表头表格吧,效果图如下:

这基本能覆盖大部分场景了,依然是纯手撸,不用其他框架,敬请期待~

转载地址:http://otvpi.baihongyu.com/

你可能感兴趣的文章
【Python】学习笔记——-6.2、使用第三方模块
查看>>
【Python】学习笔记——-7.0、面向对象编程
查看>>
【Python】学习笔记——-7.2、访问限制
查看>>
【Python】学习笔记——-7.3、继承和多态
查看>>
【Python】学习笔记——-7.5、实例属性和类属性
查看>>
Linux设备模型(总线、设备、驱动程序和类)之四:class_register
查看>>
git中文安装教程
查看>>
虚拟机 CentOS7/RedHat7/OracleLinux7 配置静态IP地址 Ping 物理机和互联网
查看>>
弱类型、强类型、动态类型、静态类型语言的区别是什么?
查看>>
Struts2技术内幕图书 转载
查看>>
Java异常分类
查看>>
项目中的jackson与json-lib使用比较
查看>>
Jackson Tree Model Example
查看>>
j2ee-验证码
查看>>
日志框架logj的使用
查看>>
js-高德地图规划路线
查看>>
常用js收集
查看>>
mydata97的日期控件
查看>>
如何防止sql注入
查看>>
maven多工程构建与打包
查看>>