提示:结合HorizontalScrollView和RecyclerView,自定义可滑动表格
先看效果
一、创建表格基础构成
1.第一列
表头由一个TableCell(自定义的TextView,后面会提到)和一个竖向的RecyclerView构成。创建代码:
LinearLayout lyHeader = new LinearLayout(mContext);
lyHeader.setOrientation(VERTICAL);
lyHeader.setLayoutParams(new LinearLayout.LayoutParams(-2, -2));
//第一行第一列,因为左右上下滑动时,这个都不动,因此固定
tvFirstHeader = new TableCell(mContext,200);
tvFirstHeader.setTextColor(headerColor);
//第一列,竖向的RecyclerView
lyHeader.addView(tvFirstHeader);
rvFirstColumn = new RecyclerView(mContext);
rvFirstColumn.setLayoutManager(new LinearLayoutManager(mContext));
lyHeader.addView(rvFirstColumn);
2.第一行及内容表
这部分由一个横向的RecyclerView及一个HorizonScrollView包含一个RecyclerView构成,代码如下:
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(VERTICAL);
layout.setLayoutParams(new LayoutParams(-2, -1));
rvHeader = new RecyclerView(mContext);
rvHeader.setLayoutParams(new LayoutParams(-1, -2));
LinearLayoutManager manager = new LinearLayoutManager(mContext);
rvHeader.setLayoutManager(manager);
layout.addView(rvHeader);
rvItems = new RecyclerView(mContext);
layout.addView(rvItems);
HorizontalScrollView scrollView = new HorizontalScrollView(mContext);
scrollView.setLayoutParams(new LayoutParams(-1, -1));
scrollView.addView(layout);
scrollView.setFillViewport(true);
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);//取消滑到顶端的阴影
scrollView.setHorizontalScrollBarEnabled(false);
2.滑动处理
横向滑动处理:第一行(除第一个单元格)和表格内容,由一个横向的HorizonScrollView包含,因此横向滑动由HorizonScrollView控制。
竖向滑动处理:第一列除第一个单元格外,是一个RecyclerView,表格内容为一个GridlayoutManager的RecyclerView。下滑时,将这两个RecyclerView的位置统一即可。
rvFirstColumn.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mdx = dx;
mdy = dy;
if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
rvItems.scrollBy(dx, dy);
}
}
});
rvItems.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mdx = dx;
mdy = dy;
if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
rvFirstColumn.scrollBy(dx, dy);
}
}
});
二、添加数据
1.添加表头(第一行)
public void setHeaderData(List<String> headerData){
if(headerData.isEmpty())return;
this.headerList = new ArrayList<>(headerData);
List<String> headers = new ArrayList<>(headerData);
tvFirstHeader.setText(headers.get(0));
headers.remove(0);
headerAdapter.setItemList(headers);
rvItems.setLayoutManager(new GridLayoutManager(mContext,headerList.size()-1));
}
2.添加数据表
public void setRowData(List<List<String>> rowDataList){
if(headerList.isEmpty() || rowDataList.isEmpty()) return;
firstColumnList.clear();
itemList.clear();
addRowData(rowDataList);
}
public void addRowData(List<List<String>> rowDataList){
List<List<String>> list = new ArrayList<>(rowDataList);
for (List<String> rowData : list) {
List<String> row = new ArrayList<>(rowData);
if(row.size()>0){
firstColumnList.add(row.get(0));
row.remove(0);
itemList.addAll(row);
}
}
fistColumnAdapter.setItemList(firstColumnList);
itemAdapter.setItemList(itemList);
rvFirstColumn.scrollBy(mdx,mdy);
rvItems.scrollBy(mdx,mdy);
List<String> fist = new ArrayList<>(firstColumnList);
fist.add(tvFirstHeader.getText().toString().trim());
int firstW = getMaxWidth(fist);
int otherW = Math.max(getMaxWidth(headerList),getMaxWidth(itemList));
int sum = otherW * headerAdapter.getItemCount()+firstW;
if(sum < ScreenSizeUtils.getInstance(mContext).getScreenWidth()){
//如果ScrollTableView小于屏幕宽度,则按比例设置宽度,宽度占满屏幕
int sw = DensityUtil.dip2px(mContext,ScreenSizeUtils.getInstance(mContext).getScreenWidth());
firstW = (int) (firstW*1.0f/sum * sw);
otherW = (sw - firstW)/headerAdapter.getItemCount();
}
tvFirstHeader.setWidth(firstW);
fistColumnAdapter.setItemWidth(firstW);
headerAdapter.setItemWidth(otherW);
itemAdapter.setItemWidth(otherW);
}
private int getMaxWidth(List<String> list){
int width = 0;
Paint paint = new Paint();
paint.setTextSize(14);
for (String s : list) {
width = (int) Math.max(width,paint.measureText(s));
}
return DensityUtil.dip2px(mContext,width)+60;
}
public int getItemCount(){
return firstColumnList.size()*headerList.size();
}
其中setRowData为初始化时调用,addRowData用于向表尾添加数据。
三、所有代码
1.ScrollTableView
public class ScrollTableView extends LinearLayout {
private final Context mContext;
private RecyclerView rvHeader,rvFirstColumn,rvItems;
private TableCell tvFirstHeader;
private TableAdapter headerAdapter,fistColumnAdapter,itemAdapter;
private List<String> headerList = new ArrayList<>();
private final List<String> firstColumnList = new ArrayList<>();
private final List<String> itemList = new ArrayList<>();
private final int headerColor = 0x0ff262a2d;
private int mdx = 0,mdy = 0;
public ScrollTableView(Context context) {
this(context, null, 0);
}
public ScrollTableView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrollTableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init();
}
private void init() {
this.setOrientation(HORIZONTAL);
this.addView(getRowHeader());
this.addView(getFirstColumnAndItemLayout());
headerAdapter = new TableAdapter(mContext);
headerAdapter.setTextColor(headerColor);
rvHeader.setAdapter(headerAdapter);
tvFirstHeader.setHeader(true);
headerAdapter.isHeader(true);
fistColumnAdapter = new TableAdapter(mContext);
fistColumnAdapter.setItemWidth(200);
fistColumnAdapter.setTextColor(headerColor);
rvFirstColumn.setAdapter(fistColumnAdapter);
itemAdapter = new TableAdapter(mContext);
rvItems.setAdapter(itemAdapter);
rvFirstColumn.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mdx = dx;
mdy = dy;
if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
rvItems.scrollBy(dx, dy);
}
}
});
rvItems.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mdx = dx;
mdy = dy;
if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
rvFirstColumn.scrollBy(dx, dy);
}
}
});
}
private LinearLayout getRowHeader(){
tvFirstHeader = new TableCell(mContext,200);
tvFirstHeader.setTextColor(headerColor);
LinearLayout lyHeader = new LinearLayout(mContext);
lyHeader.setOrientation(VERTICAL);
lyHeader.setLayoutParams(new LinearLayout.LayoutParams(-2, -2));
lyHeader.addView(tvFirstHeader);
rvFirstColumn = new RecyclerView(mContext);
rvFirstColumn.setLayoutManager(new LinearLayoutManager(mContext));
lyHeader.addView(rvFirstColumn);
return lyHeader;
}
private HorizontalScrollView getFirstColumnAndItemLayout(){
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(VERTICAL);
layout.setLayoutParams(new LayoutParams(-2, -1));
rvHeader = new RecyclerView(mContext);
rvHeader.setLayoutParams(new LayoutParams(-1, -2));
LinearLayoutManager manager = new LinearLayoutManager(mContext);
manager.setOrientation(RecyclerView.HORIZONTAL);
rvHeader.setLayoutManager(manager);
layout.addView(rvHeader);
rvItems = new RecyclerView(mContext);
layout.addView(rvItems);
HorizontalScrollView scrollView = new HorizontalScrollView(mContext);
scrollView.setLayoutParams(new LayoutParams(-1, -1));
scrollView.addView(layout);
scrollView.setFillViewport(true);
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);//取消滑到顶端的阴影
scrollView.setHorizontalScrollBarEnabled(false);
return scrollView;
}
public void setHeaderData(List<String> headerData){
if(headerData.isEmpty())return;
this.headerList = new ArrayList<>(headerData);
List<String> headers = new ArrayList<>(headerData);
tvFirstHeader.setText(headers.get(0));
headers.remove(0);
headerAdapter.setItemList(headers);
rvItems.setLayoutManager(new GridLayoutManager(mContext,headerList.size()-1));
}
public void setRowData(List<List<String>> rowDataList){
if(headerList.isEmpty() || rowDataList.isEmpty()) return;
firstColumnList.clear();
itemList.clear();
addRowData(rowDataList);
}
public void addRowData(List<List<String>> rowDataList){
List<List<String>> list = new ArrayList<>(rowDataList);
for (List<String> rowData : list) {
List<String> row = new ArrayList<>(rowData);
if(row.size()>0){
firstColumnList.add(row.get(0));
row.remove(0);
itemList.addAll(row);
}
}
fistColumnAdapter.setItemList(firstColumnList);
itemAdapter.setItemList(itemList);
rvFirstColumn.scrollBy(mdx,mdy);
rvItems.scrollBy(mdx,mdy);
List<String> fist = new ArrayList<>(firstColumnList);
fist.add(tvFirstHeader.getText().toString().trim());
int firstW = getMaxWidth(fist);
int otherW = Math.max(getMaxWidth(headerList),getMaxWidth(itemList));
int sum = otherW * headerAdapter.getItemCount()+firstW;
if(sum < ScreenSizeUtils.getInstance(mContext).getScreenWidth()){
//如果ScrollTableView小于屏幕宽度,则按比例设置宽度,宽度占满屏幕
int sw = DensityUtil.dip2px(mContext,ScreenSizeUtils.getInstance(mContext).getScreenWidth());
firstW = (int) (firstW*1.0f/sum * sw);
otherW = (sw - firstW)/headerAdapter.getItemCount();
}
tvFirstHeader.setWidth(firstW);
fistColumnAdapter.setItemWidth(firstW);
headerAdapter.setItemWidth(otherW);
itemAdapter.setItemWidth(otherW);
}
private int getMaxWidth(List<String> list){
int width = 0;
Paint paint = new Paint();
paint.setTextSize(14);
for (String s : list) {
width = (int) Math.max(width,paint.measureText(s));
}
return DensityUtil.dip2px(mContext,width)+60;
}
public int getItemCount(){
return firstColumnList.size()*headerList.size();
}
}
TableAdapter
@SuppressLint("NotifyDataSetChanged")
public class TableAdapter extends RecyclerView.Adapter<TableAdapter.MyViewHolder> {
private final Context mContext;
private List<String> mItemList;
private int itemWidth = 360;
private boolean isHeader = false;
private int textColor = 0x0ff61686E;
public TableAdapter(Context context) {
this.mContext = context;
this.mItemList = new ArrayList<>();
}
public void setItemWidth(int width) {
this.itemWidth = width;
this.notifyDataSetChanged();
}
public void setTextColor(int headerColor) {
this.textColor = headerColor;
this.notifyDataSetChanged();
}
public void setItemList(List<String> itemList) {
this.mItemList = itemList;
this.notifyDataSetChanged();
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
TableCell view = new TableCell(mContext,itemWidth);
view.setHeader(isHeader);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
String item = mItemList.get(position);
TableCell tv = (TableCell)holder.itemView;
tv.setText(item);
tv.setTextColor(textColor);
}
@Override
public int getItemCount() {
return mItemList.size();
}
public void isHeader(boolean isHeader) {
this.isHeader = isHeader;
}
static class MyViewHolder extends RecyclerView.ViewHolder {
MyViewHolder(View itemView) {
super(itemView);
}
}
}
TableCell
public class TableCell extends androidx.appcompat.widget.AppCompatTextView {
private int width = -2;
private boolean isHeader = false;
public TableCell(@NonNull Context context,int width) {
super(context);
this.width = width;
initView();
}
public TableCell(@NonNull Context context) {
super(context);
initView();
}
private void initView() {
this.setBackgroundColor(Color.WHITE);
ViewGroup.MarginLayoutParams params = new LinearLayout.LayoutParams(width-2, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0,0,2,2);
this.setLayoutParams(params);
this.setTextSize(14);
this.setGravity(Gravity.CENTER);
this.setPadding(10,10,10,10);
}
public void setHeader(boolean isHeader) {
this.isHeader = isHeader;
this.setBackgroundColor(isHeader? 0x0ffF5FFFA:Color.WHITE);
this.setPadding(10,isHeader? 15:10,10,isHeader? 15:10);
}
@Override
public void setWidth(int pixels) {
this.width = pixels;
refreshWidth();
}
private void refreshWidth(){
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) getLayoutParams();
params.width = width-2;
params.setMargins(0,0,2,2);
this.setLayoutParams(params);
}
@Override
protected void onDraw(Canvas canvas) {
if(isHeader){
//获取当前控件的画笔
TextPaint paint = getPaint();
//设置画笔的描边宽度值
paint.setStrokeWidth(0.5f);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
}
super.onDraw(canvas);
}
}