今天开始更新【重拾安卓】系列文章。
因业务需要又要做一个 Android 原生的项目,记录下时隔几年之后再开发安卓的那些事。讲的不会太基础,基本上是自定义View封装,复杂功能的实现等等,有需要的小伙伴可以关注~
安卓对表格的支持不是太友好,前端很快能实现的简单表格,安卓写的话要费很大精力。
拿到需求之后,稍微复杂点的功能在 github 上搜一下有没有好用的第三方框架,无疑是最节省时间的。表格还真有几个不错的框架,star 最多的是 smartTable ,的确很强大,只需设置数据就能自动生成表格。
但考虑各种因素还是决定自己撸一个表格,一是后端返回的数据结构还没定,二是需求并不是太复杂,只是个简单表格,三是找找手感~
一、需求分析及实现原理
最终效果:
实现目标:
- 行数不固定,超出父容器可以上下滚动
- 列数不固定,不管有多少列,都平分父容器宽度,每列的宽度一致
- 表头设置灰色背景,单元格是白色背景
实现原理:
两层 RecyclerView
嵌套,最外层是垂直方向的 RecyclerView
,每一行是一个 item
。每行又包含一个内层 RecyclerView
,每行的每个单元格是内层 RecyclerView
的 item
。
二、代码实现
为了方便重用,我们把这个课表封装成自定义 View,并对外暴露一个方法设置数据。
Android 自定义 View 有三种方式:组合、扩展、重写。我们这里用的是组合的方式,即把已有的控件组合起来形成符合需求的自定义控件。
2.1 自定义View 主文件 StudentWorkTableView
新建一个 Java 类 StudentWorkTableView
并继承 LinearLayout
,实现它的构造方法,就创建了一个自定义 View。
为什么继承 LinearLayout
?其实继承其他的 RelativeLayout
、ConstraintLayout
都可以,一般是你的 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<TableListModel> 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<TableListModel> 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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view_week_table"