前言
大家好呀,最近烦事特别多,现在好不容易静下心写第二篇Table系列文章,第一篇文章:Android 左右竖直滚动的Table(一)主要是用HorizontalScrollView+NestScrollView+RecyclerView实现Table复杂的滚动效果(效果动图可看上面第一篇文章),但是这个会带来一个致命的问题就是Item的不能够复用,因为ScrollView的特性,它会一次性渲染全部的item,所以数据量到几百的时候,要很久才能进来这个页面,这个东西给产品经理看到还不diss你。。。嗯,今天主要介绍单纯地用RecyclerView来实现这个Table复杂的滑动效果吧。
分析
同样的,我们上次系列一文章已经分析过了滑动的设计,并且给出了大概的布局,今天我们就把ScrollView的控件全部给剔除掉,只剩下RecyclerView会是怎样的?下面是我们画出大概的布局:
现在我们很容易看出要想实现效果,必须RecyclerView3和RecyclerView2保持竖直滚动,RecyclerView2和RecyclerView1保持水平滚动,而RecyclerView2采用嵌套RecyclerView来展示table数据,所以与RecyclerView1保持水平滚动的对象是RecyclerView2中嵌套的水平RecyclerView,这样就实现了水平竖直滚动的Table。
代码设计
1.布局文件
主布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_content_table"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/corner_view_height"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_table_title"
android:layout_width="@dimen/corner_view_width"
android:layout_height="match_parent"
android:gravity="center"
android:text="表格"
android:textColor="#000000"
android:textSize="14sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_column_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#EAEAEA"
android:overScrollMode="never" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- 左侧header的父容器 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_row_header"
android:layout_width="@dimen/corner_view_width"
android:layout_height="wrap_content"
android:background="#EAEAEA" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_cell_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:overScrollMode="never" />
</LinearLayout>
</LinearLayout>
其它item的布局文件和Android 左右竖直滚动的Table(一)是一样的:
- 列标题布局文件item_table_reuse_column:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/column_width"
android:layout_height="@dimen/corner_view_height"
android:layout_gravity="center">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="测试"
android:padding="3dp"
android:textSize="14sp" />
</LinearLayout>
- 行标题布局文件item_table_reuse_row:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/corner_view_width"
android:layout_height="@dimen/row_height"
android:layout_gravity="center">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="3dp"
android:text="测试"
android:textSize="14sp" />
</LinearLayout>
- cell的父布局文件item_table_reuse_cell_parent:
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_cell_child"
android:layout_width="match_parent"
android:layout_height="@dimen/row_height"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_table_blog_cell_child" />
- cell的父布局RecyclerView的item布局文件item_table_reuse_cell_child:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/column_width"
android:layout_height="@dimen/row_height"
android:layout_gravity="center">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="测试"
android:padding="3dp"
android:textSize="14sp" />
</LinearLayout>
dimens文件:
<dimen name="corner_view_width">50dp</dimen>
<dimen name="corner_view_height">50dp</dimen>
<dimen name="column_width">100dp</dimen>
<dimen name="row_height">40dp</dimen>
2.Activity页面
public class VHSlideTableActivity extends AppCompatActivity {
private RecyclerView mRvColumnHeader;
private RecyclerView mRvRowHeader;
private RecyclerView mRvCellParent;
private CommonAdapter mColumnAdapter, mRowAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vhslide_table_reuse);
mRvColumnHeader = findViewById(R.id.rv_column_header);
mRvRowHeader = findViewById(R.id.rv_row_header);
mRvCellParent = findViewById(R.id.rv_cell_parent);
List<String> columnList = getColumnHeaderList();
List<String> rowList = getRowHeaderList();
List<List<String>> cellList = getCellList();
mRvColumnHeader.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false));
mColumnAdapter = new CommonAdapter(R.layout.item_table_reuse_column, columnList);
mColumnAdapter.setOnItemChildClickListener((adapter, view, position) -> {
String name = mColumnAdapter.getItem(position);
Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
});
mRvColumnHeader.setAdapter(mColumnAdapter);
mRvColumnHeader.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL));
CellParentAdapter cellParentAdapter = new CellParentAdapter(R.layout.item_table_reuse_cell_parent, cellList);
mRvCellParent.setLayoutManager(new LinearLayoutManager(this));
mRvCellParent.setAdapter(cellParentAdapter);
mRvCellParent.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
mRvRowHeader.setLayoutManager(new LinearLayoutManager(this));
mRowAdapter = new CommonAdapter(R.layout.item_table_reuse_row, rowList);
mRowAdapter.setOnItemChildClickListener((adapter, view, position) -> {
String name = mRowAdapter.getItem(position);
Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
});
mRvRowHeader.setAdapter(mRowAdapter);
mRvRowHeader.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
final List<RecyclerView> syncRecyclerViews = new ArrayList<>();
syncRecyclerViews.add(mRvCellParent);
syncRecyclerViews.add(mRvRowHeader);
//添加监听,使所有的RecyclerView保持同步
RecyclerView.OnScrollListener listener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
for (RecyclerView item : syncRecyclerViews) {
if (recyclerView == item) {
continue;
}
item.clearOnScrollListeners();
item.scrollBy(dx, dy);
item.addOnScrollListener(this);
}
}
};
mRvRowHeader.addOnScrollListener(listener);
mRvCellParent.addOnScrollListener(listener);
cellParentAdapter.addRecyclerView(mRvColumnHeader);
}
public class CellParentAdapter extends BaseQuickAdapter<List<String>, BaseViewHolder> {
private HashSet<RecyclerView> observerList = new HashSet<>();
private int firstPos = -1;
private int firstOffset = -1;
public CellParentAdapter(int layoutResId, @Nullable List<List<String>> data) {
super(layoutResId, data);
}
@Override
protected void convert(@NotNull BaseViewHolder holder, List<String> data) {
RecyclerView recyclerView = holder.getView(R.id.rv_cell_child);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), RecyclerView.HORIZONTAL, false));
CommonAdapter commonAdapter = new CommonAdapter(R.layout.item_table_reuse_cell_child, data);
if (recyclerView.getItemDecorationCount() == 0) {
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.HORIZONTAL));
}
commonAdapter.setOnItemChildClickListener((adapter, view, position) -> {
String name = commonAdapter.getItem(position);
Toast.makeText(getContext(), name, Toast.LENGTH_SHORT).show();
});
addRecyclerView(recyclerView);
recyclerView.setAdapter(commonAdapter);
}
/**
* 维护RecyclerView集合,控制联动滑动
*
* @param recyclerView
*/
public void addRecyclerView(RecyclerView recyclerView) {
recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager != null && firstPos > 0 && firstOffset > 0) {
layoutManager.scrollToPositionWithOffset(CellParentAdapter.this.firstPos + 1, CellParentAdapter.this.firstOffset);
}
observerList.add(recyclerView);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstPos = linearLayoutManager.findFirstVisibleItemPosition();
View firstVisibleItem = linearLayoutManager.findViewByPosition(firstPos);
if (firstVisibleItem != null) {
int firstRight = linearLayoutManager.getDecoratedRight(firstVisibleItem);
for (RecyclerView rv : observerList) {
if (recyclerView != rv) {
LinearLayoutManager layoutManager = (LinearLayoutManager) rv.getLayoutManager();
if (layoutManager != null) {
CellParentAdapter.this.firstPos = firstPos;
CellParentAdapter.this.firstOffset = firstRight;
layoutManager.scrollToPositionWithOffset(firstPos + 1, firstRight);
}
}
}
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
}
/**
* 控制集合RecyclerView联动滑动
*
* @param recyclerView
* @param position
*/
public void scrollRecyclerView(RecyclerView recyclerView, int position) {
for (RecyclerView rv : observerList) {
if (recyclerView != rv) {
LinearLayoutManager layoutManager = (LinearLayoutManager) rv.getLayoutManager();
if (layoutManager != null) {
layoutManager.scrollToPositionWithOffset(position, 0);
}
}
}
}
}
public class CommonAdapter extends BaseQuickAdapter<String, BaseViewHolder> {
public CommonAdapter(int layoutResId, @Nullable List<String> data) {
super(layoutResId, data);
addChildClickViewIds(R.id.tv_name);
}
@Override
protected void convert(@NotNull BaseViewHolder holder, String s) {
holder.setText(R.id.tv_name, s);
}
}
public List<String> getColumnHeaderList() {
List<String> list = new ArrayList<>();
for (int i = 0; i < 15; i++) {
list.add("column " + i);
}
return list;
}
public List<String> getRowHeaderList() {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add("row " + i);
}
return list;
}
public List<List<String>> getCellList() {
List<List<String>> cellList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
List<String> list = new ArrayList<>();
for (int j = 0; j < 15; j++) {
list.add("row" + i + " column" + j);
}
cellList.add(list);
}
return cellList;
}
}
这里主要是数据和列表的展示,还有就是用了一个集合去存储需要滑动的列表RecyclerView,这样相互监听就可以达到一起滚动的效果,很容易理解,相信大家的能力,下面是BaseRecyclerViewAdapterHelper的依赖:
//强大的RecyclerView适配器
implementation ‘com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4’
总结
其实这样操作也没有什么难度,重要的是集合存储需要滑动的RecyclerView,然后利用其LayoutManage去控制它们的滑动。这里很好地处理了复用问题,不然item项会有部分滑动,而其它还是停在原地。最后,可能还会有系列三的出现,主要想把一些想法写出来,又到夜深了,睡觉。。。。
最后附上下载地址。