Android開源表格的實現分析

Android開源表格的實現分析,有的時候我們會遇到著這種需求:表格最上方的表頭和最右方的表頭以及中間的表體區域是不一樣的;表格最右邊的表頭和中間的區域可以一起上下滑動,表格最上邊的表頭可以和中間的區域一起左右滑動;

用圖來說明最為直觀:

這裡寫圖片描述

這是github上一個比較多人下載的一個開源控件。如果是你,你會怎麼做?作者用瞭十分聰明的佈局,很簡單明瞭。它的地址在這裡


    
        <framelayout android:id="@+id/first_frame_item" android:layout_height="wrap_content" android:layout_width="wrap_content">
        
    </framelayout>

這裡寫圖片描述

如上圖所示,垂直方向上是一個RecyclerView,這樣就實現瞭上下的聯動。這個RecyclerView的每個子佈局水平排列,又都由一個FrameLayout和一個RecyclerView組成;最上方的表頭由一個recyclerView來實現,而第一個格子是特殊的,用Framelayout來實現;
每一個水平方向的recycleView都與頂部的recycleView互相設置滾動監聽,由此實現左右方向的聯動。

接下來,隻要對這個recycylerView做相應的操作即可。主要代碼如下:

public class PanelLineAdapter extends RecyclerView.Adapter{
...
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。

剩下的就是實現這個抽象類,按照不同的類型實現其中的方法;具體的在代碼中去看吧!

You May Also Like