Android 自定义表格(第一行及第一列固定,其他列可滑动)

提示:结合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);
    }

}

总结

        该表格可以自适应第一列的宽度,但其他列宽度为公用一个宽度,原因是GridlayoutManager的项宽度无法设置为任意值,暂未解决。有大佬知道,欢迎交流。

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
要创建一个可以滑动表格固定第一列,您可以使用HTML、CSS和JavaScript结合实现。具体步骤如下: 1. 创建一个具有固定表头和固定第一列表格,并将其放置在一个可滚动的div容器内。 2. 使用CSS将表格第一列设置为固定定位,并将其左侧的边框设置为与表头对齐。 3. 使用JavaScript监听滚动事件,并将表头的位置设置为固定在容器的顶部,将第一列的位置设置为固定在容器的左侧。 下面是一个示例代码: HTML: ``` <div class="table-container"> <table> <thead> <tr> <th>Header 1</th> <th>Header 2</th> <th>Header 3</th> <th>Header 4</th> <th>Header 5</th> <th>Header 6</th> <th>Header 7</th> </tr> </thead> <tbody> <tr> <td>Row 1, Column 1</td> <td>Row 1, Column 2</td> <td>Row 1, Column 3</td> <td>Row 1, Column 4</td> <td>Row 1, Column 5</td> <td>Row 1, Column 6</td> <td>Row 1, Column 7</td> </tr> <tr> <td>Row 2, Column 1</td> <td>Row 2, Column 2</td> <td>Row 2, Column 3</td> <td>Row 2, Column 4</td> <td>Row 2, Column 5</td> <td>Row 2, Column 6</td> <td>Row 2, Column 7</td> </tr> <!-- more rows --> </tbody> </table> </div> ``` CSS: ``` .table-container { overflow-x: auto; max-width: 100%; } table { border-collapse: collapse; width: 100%; } th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; } th { background-color: #f2f2f2; } th:first-child, td:first-child { position: sticky; left: 0; z-index: 1; border-right: 1px solid #ddd; } ``` JavaScript: ``` $('.table-container').scroll(function() { var $this = $(this), $th = $this.find('thead th:first-child'), $td = $this.find('tbody td:first-child'); $th.css('left', $this.scrollLeft()); $td.css('top', $this.scrollTop()); }); ``` 请注意,这只是一个示例代码,具体实现方式可能因表格结构和样式而异。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值