有的时候我们会遇到着这种需求:表格最上方的表头和最右方的表头以及中间的表体区域是不一样的;表格最右边的表头和中间的区域可以一起上下滑动,表格最上边的表头可以和中间的区域一起左右滑动;
用图来说明最为直观:
这是github上一个比较多人下载的一个开源控件。如果是你,你会怎么做?作者用了十分聪明的布局,很简单明了。它的地址在这里
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/first_frame_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/first_row_line_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_content_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
如上图所示,垂直方向上是一个RecyclerView,这样就实现了上下的联动。这个RecyclerView的每个子布局水平排列,又都由一个FrameLayout和一个RecyclerView组成;最上方的表头由一个recyclerView来实现,而第一个格子是特殊的,用Framelayout来实现;
每一个水平方向的recycleView都与顶部的recycleView互相设置滚动监听,由此实现左右方向的联动。
接下来,只要对这个recycylerView做相应的操作即可。主要代码如下:
public class PanelLineAdapter extends RecyclerView.Adapter<PanelLineAdapter.ViewHolder>{
...
public PanelLineAdapter(PanelAdapter panelAdapter, RecyclerView contentRV, RecyclerView headerRecyclerView) {//@4726
this.panelAdapter = panelAdapter;//@4726
this.headerRecyclerView = headerRecyclerView;
initRecyclerView(headerRecyclerView);
setUpHeaderRecycler();
}
...
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder viewHolder = new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.listitem_content_row, parent, false));
initRecyclerView(viewHolder.columnRecyclerView);
return viewHolder;
}
...
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
//为每一行的recyclerView绑定数据
PanelLineItemAdapter panelLineItemAdapter = (PanelLineItemAdapter) holder.columnRecyclerView.getAdapter();
if(panelLineItemAdapter==null){
panelLineItemAdapter = new PanelLineItemAdapter(position+1,panelAdapter);//@5223
holder.columnRecyclerView.setAdapter(panelLineItemAdapter);
}else{
panelLineItemAdapter.setRow(position+1);
panelLineItemAdapter.notifyDataSetChanged();
}
//为每一行前面的framelayout绑定数据
if (holder.firstColumnItemVH == null) {
RecyclerView.ViewHolder viewHolder = panelAdapter.onCreateViewHolder(holder.firstColumnItemView, panelAdapter.getItemViewType(position+1, 0));
holder.firstColumnItemVH = viewHolder;
panelAdapter.onBindViewHolder(holder.firstColumnItemVH, position+1, 0);
holder.firstColumnItemView.addView(viewHolder.itemView);
}else {
panelAdapter.onBindViewHolder(holder.firstColumnItemVH, position+1, 0);
}
}
public void notifyDataChanged() {
setUpHeaderRecycler();
notifyDataSetChanged();
}
...
@Override
public int getItemCount() {
return panelAdapter.getRowCount()-1;
}
}
PanelLineAdapter 是最外面这个RecyclerView的适配器,我们知道适配器里最主要的就是这三个方法,RecyclerView设置数据的流程大致是这样的:
因此getItemCount
返回的是行数,onCreateViewHolder
负责创建每一项的视图,这里面有一个initRecyclerView
的方法:
//最短的滑动距离
shortestDistance = columnRecyclerView.getWidth()/2;
//总的页数
totalPage = ((int) Math.ceil((panelAdapter.getColumnCount()-1)/7));
columnRecyclerView.setHasFixedSize(true);
final LinearLayoutManager recyclerLinear = (LinearLayoutManager) columnRecyclerView.getLayoutManager();
if (recyclerLinear != null && firstPos > 0 && firstOffset > 0) {
recyclerLinear.scrollToPositionWithOffset(PanelLineAdapter.this.firstPos+1, PanelLineAdapter.this.firstOffset);
}
observerList.add(columnRecyclerView);
/**
* 为每一个水平recyclerView设置滑动事件
**/
columnRecyclerView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = motionEvent.getX();
break;
case MotionEvent.ACTION_MOVE:
if(currentPage==totalPage&&downX-motionEvent.getX()>0){
return true;
}
break;
case MotionEvent.ACTION_UP:
slideDistance = motionEvent.getX()-downX;
if(Math.abs(slideDistance)>shortestDistance){
//滑动距离足够,执行翻页
if(slideDistance>0){
//上一页
currentPage = currentPage==1?1:currentPage-1;
}else {
//下一页
currentPage = currentPage==totalPage?totalPage:currentPage+1;
}
}
columnRecyclerView.smoothScrollBy((int)((currentPage - 1) * getWidth() - scrollX),0);
if(onPageListener!=null){
onPageListener.getCurrentPager(currentPage);
}
return true;
case MotionEvent.ACTION_POINTER_DOWN:
for (RecyclerView rv : observerList) {
rv.stopScroll();
}
default:
break;
}
return false;
}
});
/**
* 水平方向的联动
*/
columnRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstPos = linearLayoutManager.findFirstVisibleItemPosition();//获得屏幕内第一个可见的Item的位置
View firstVisibleItem = linearLayoutManager.getChildAt(0);
if(firstVisibleItem!=null){
int firstRight = linearLayoutManager.getDecoratedRight(firstVisibleItem);//获得第一个view的getRight();距右边屏幕的距离
for (RecyclerView rv : observerList) {
if (recyclerView != rv) {
LinearLayoutManager layoutManager = (LinearLayoutManager) rv.getLayoutManager();
if (layoutManager != null) {
PanelLineAdapter.this.firstPos = firstPos;
PanelLineAdapter.this.firstOffset = firstRight;
layoutManager.scrollToPositionWithOffset(firstPos+1, firstRight);
}
}
}
}
scrollX += dx;
}
});
主要是为每个水平recyclerView做初始的工作,水平方向联动的实现原理就是:
当其中任一条recyclerView滑动一段距离时,其他所有的recyclerview滑动相应距离即可;
onBindViewHolder
负责为每一项试图绑定数据,为每一条水平recyclerView设置适配器Adapter,为每一条的FrameLayout添加视图;
这里比较厉害的是,为FrameLayout设置数据时,就是把一个RecyclerView的每一项的布局设置进去;
接下来是每一项水平recyclerView的适配器:
public static class PanelLineItemAdapter extends RecyclerView.Adapter{
private PanelAdapter panelAdapter;
private int row;
public PanelLineItemAdapter(int row, PanelAdapter panelAdapter) {//@4726
this.row = row;
this.panelAdapter = panelAdapter;//@4726
}
@Override
public int getItemViewType(int position) {
return panelAdapter.getItemViewType(row,position+1);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return this.panelAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
this.panelAdapter.onBindViewHolder(holder,row, position + 1);
}
@Override
public int getItemCount() {
return panelAdapter.getColumnCount()-1;
}
...
}
不得不说,作者的封装用的很好,它只向开发者暴露了一个抽象的PanelAdapter 类。用户只要实现这个类确定每个View的布局和数据便可实现想要的功能。
public abstract class PanelAdapter {
//获得table的行数
public abstract int getRowCount();
//获得table的列数
public abstract int getColumnCount();
//获得view类型
public int getItemViewType(int row, int column) {
return 0;
}
//绑定数据
public abstract void onBindViewHolder(RecyclerView.ViewHolder holder, int row, int column);
//创建view
public abstract RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
}
这个类里面包含了用户最关心的问题,这个表格有多少行多少列,每一个view长什么样,需要填充什么数据,而把一些用户不关心的底层实现封装在内部。内部实现时会把PanelAdapter里面的view和数据传递给具体的adapter。
剩下的就是实现这个抽象类,按照不同的类型实现其中的方法;具体的在代码中去看吧!