寫在前面:
最近發現一個很酷的開源項目,正好自己也打算寫一個TableView,但是。。。寫代碼哪有抄代碼爽!所以我決定來學習一下大佬的代碼(先貼出來Github地址---->TableView)
學習第一步,先看基本架構圖:
按照圖上的解釋,TableView的實現方式是多個RecyclerView組合的結果,多個RecyclerView在TableView的統一架構下實現數據聯動、滑動聯動。至於爲什麼選擇使用多個RecyclerView來實現的,原作者給出的理由是RecyclerView自帶資源回收機制,並且非常高效,直接使用可以節省非常大的開發成本。
第二步:clone項目到本地
我將代碼從github上克隆到本地,開始運行並分析源碼。
這是運行效果:
可以看出來它的功能是非常強大的吧,整行、整列、單個選擇都OJBK,而且全部支持自定義樣式。。。
首先是從MainActivity開始看吧:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.add(
R.id.activity_container
, new MainFragment()
, MainFragment.class.getSimpleName()
).commit();
}
}
}
從代碼上看就是這樣的非常簡單,在activity中使用了一個Fragment,接下來看MainFragment中幹了什麼:
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_main, container, false);
//。。。省略了部分代碼。。。
//獲取到TableView
mTableView = layout.findViewById(R.id.tableview);
initializeTableView();//這裏是配置TableView主要的地方
return layout;
}
//這裏是配置TableView主要的地方
private void initializeTableView() {
// 這是一個數據中配置該表格的主要參數,比如總行數,總列數等
// Create TableView View model class to group view models of TableView
TableViewModel tableViewModel = new TableViewModel();
// TableViewAdapter繼承自AbstractTableAdapter
//原作者在註釋表示“TableViewAdapter”只是一個簡單的使用例子,也就是說
//如果我們要自己使用“TableView”,那麼也是要自己寫一個新的Adapter,就像使用RecyclerView一樣
TableViewAdapter tableViewAdapter = new TableViewAdapter(tableViewModel);
mTableView.setAdapter(tableViewAdapter);
mTableView.setTableViewListener(new TableViewListener(mTableView));
// Create an instance of a Filter and pass the TableView.
//mTableFilter = new Filter(mTableView);
// Load the dummy data to the TableView
tableViewAdapter.setAllItems(tableViewModel.getColumnHeaderList(), tableViewModel
.getRowHeaderList(), tableViewModel.getCellList());
}
這是MainFragment中的部分代碼,也是主要用來配置TableView的代碼。
先從 TableViewModel tableViewModel = new TableViewModel(); 開始,也就是TableViewModel類,這個類中定義了很多參數,這些參數決定了表格中該顯示什麼東西。
其中比較重要的三個方法是:
/**
* 循環創建"行"的頭文件,
* <code>ROW_SIZE</code> 表示最大行數,如果這個值爲500,那麼生成的表格就有500行
* <code>RowHeader</code> 表示"行"的第一列的數據,它繼承自Cell
*/
@NonNull
private List<RowHeader> getSimpleRowHeaderList() {
List<RowHeader> list = new ArrayList<>();
for (int i = 0; i < ROW_SIZE; i++) {
RowHeader header = new RowHeader(String.valueOf(i), "row " + i);
list.add(header);
}
return list;
}
/**
* 循環創建列
* 這裏它使用了隨機數來使某些列的標題不一樣(同時列的寬度的發生了變化)
* <code>ColumnHeader</code> 表示"列"的第一行的數據,它繼承自Cell
*/
@NonNull
private List<ColumnHeader> getRandomColumnHeaderList() {
List<ColumnHeader> list = new ArrayList<>();
for (int i = 0; i < COLUMN_SIZE; i++) {
String title = "column " + i;
int nRandom = new Random().nextInt();
if (nRandom % 4 == 0 || nRandom % 3 == 0 || nRandom == i) {
title = "large column " + i;
}
ColumnHeader header = new ColumnHeader(String.valueOf(i), title);
list.add(header);
}
return list;
}
/**
* 雙層循環創建數據域的每一個元素(也就是表格中的每一個單元格)
* <code>Cell</code> 也就是每個單元格對象
*/
@NonNull
private List<List<Cell>> getCellListForSortingTest() {
List<List<Cell>> list = new ArrayList<>();
for (int i = 0; i < ROW_SIZE; i++) {
List<Cell> cellList = new ArrayList<>();
for (int j = 0; j < COLUMN_SIZE; j++) {
Object text = "cell " + j + " " + i;
final int random = new Random().nextInt();
if (j == 0) {
text = i;//第一列的所有數據爲"行"數
} else if (j == 1) {
text = random;//第二列的所有數據都是隨機數
} else if (j == MOOD_COLUMN_INDEX) {
text = random % 2 == 0 ? HAPPY : SAD;//該列表示情緒
} else if (j == GENDER_COLUMN_INDEX) {
text = random % 2 == 0 ? BOY : GIRL;//該類表示性別
}
// Create dummy id.
String id = j + "-" + i;
Cell cell = new Cell(id, text);
cellList.add(cell);
}
list.add(cellList);
}
return list;
}
TableViewModel這類通過這三個方法描述了一個表格繪製所需要的數據。多少行、多少列、每一列(行)對應的數據都在這個類中生成好了;可以這樣理解:TableViewModel就是表格數據的抽象,它決定(創建)了一個表格應該有些什麼東西,但具體如何繪製和它沒有關係。
接下來就是將這個數據具體的繪製出來了,請大家回到這一行
TableViewAdapter tableViewAdapter = new TableViewAdapter(tableViewModel);
TableViewAdapter是一個將抽象數據(TableViewModel)進行具體繪製的類,它與我們常用的RecyclerView.Adapter非常相似。
首先TableViewAdapter繼承自AbstractTableAdapter;AbstractTableAdapter實現了ITableAdapter接口。所以爲了更好的理解源碼,我們先從ITableAdapter接口開始看:
public interface ITableAdapter<CH, RH, C> {
//獲取“列”的頭標籤的類型(在本項目的例子中,該方法只是全部返回0
int getColumnHeaderItemViewType(int position);
//獲取“行”的頭標籤的類型(在本項目的例子中,該方法只是全部返回0
int getRowHeaderItemViewType(int position);
//獲取“單元格”的類型(在本項目的例子中,該方法只是全部返回0
int getCellItemViewType(int position);
//獲取左上角的Corner
View getCornerView();
//這個方法用於創建具體的單元格View
@NonNull
AbstractViewHolder onCreateCellViewHolder(@NonNull ViewGroup parent, int viewType);
//這個方法用於對創建的單元格View綁定數據
void onBindCellViewHolder(@NonNull AbstractViewHolder holder, @Nullable C cellItemModel, int columnPosition, int rowPosition);
//這個方法用於創建具體“列”的頭標籤View
@NonNull
AbstractViewHolder onCreateColumnHeaderViewHolder(@NonNull ViewGroup parent, int viewType);
//這個方法用於對創建的“列”的頭標籤View綁定數據
void onBindColumnHeaderViewHolder(@NonNull AbstractViewHolder holder, @Nullable CH columnHeaderItemModel, int columnPosition);
//這個方法用於創建具體“行”的頭標籤View
@NonNull
AbstractViewHolder onCreateRowHeaderViewHolder(@NonNull ViewGroup parent, int viewType);
//這個方法用於對創建的“行”的頭標籤View綁定數據
void onBindRowHeaderViewHolder(@NonNull AbstractViewHolder holder, @Nullable RH rowHeaderItemModel, int rowPosition);
//這個方法用於創建左上角的CornerView
@NonNull
View onCreateCornerView(@NonNull ViewGroup parent);
//這個方法返回一個TableView的抽象接口,放到後面再講
ITableView getTableView();
/**
* Sets the listener for changes of data set on the TableView.
* 設置數據變動監聽
* @param listener The AdapterDataSetChangedListener listener.
*/
void addAdapterDataSetChangedListener(@NonNull AdapterDataSetChangedListener<CH, RH, C> listener);
}
該接口的方法還挺通俗易懂的吧,然後我們繼續看實現了這個接口的抽象類AbstractTableAdapter:
public abstract class AbstractTableAdapter<CH, RH, C> implements ITableAdapter<CH, RH, C> {
public void setTableView(@NonNull ITableView tableView) {
mTableView = tableView;
initialize();
}
private void initialize() {
Context context = mTableView.getContext();
// Create Column header RecyclerView Adapter
mColumnHeaderRecyclerViewAdapter = new ColumnHeaderRecyclerViewAdapter<>(context,
mColumnHeaderItems, this);
// Create Row Header RecyclerView Adapter
mRowHeaderRecyclerViewAdapter = new RowHeaderRecyclerViewAdapter<>(context,
mRowHeaderItems, this);
// Create Cell RecyclerView Adapter
mCellRecyclerViewAdapter = new CellRecyclerViewAdapter<>(context, mCellItems, mTableView);
}
}
在這個抽象類中。。。它又創建了三個Adapter。。。分別用於ColumnHeader、RowHeader、Cell,所以還得繼續一步步分析。。。(另外該類的泛型CH、RH、C,分別表示ColumnHeader、RowHeader、Cell)。這裏創建的三個Adapter比較簡單,它們都是集成自抽象類(AbstractRecyclerViewAdapter<T>),從名字就看看出來它集成自RecyclerViewAdapter<AbstractViewHolder>。
在AbstractRecyclerViewAdapter<T>中重寫了兩個方法:
public abstract class AbstractRecyclerViewAdapter<T> extends RecyclerView
.Adapter<AbstractViewHolder> {
protected List<T> mItemList;
@Override
public int getItemCount() {
return mItemList.size();
}
@Override
public int getItemViewType(int position) {
return 1;
}
}
其它方法都是針對mItemList的增刪改查。邏輯都比較簡單,就不一一貼出來了。
讓我們回到AbstractTableAdapter,在它創建完三個分別用於ColumnHeader、RowHeader、Cell的Adapter之後,它還提供了針對這三個Adapter的增刪改查的方法,以及對數據修改的監聽方法
addAdapterDataSetChangedListener(@NonNull AdapterDataSetChangedListener<CH, RH, C> listener)
(。。。怎麼感覺一腳踩坑裏了。。。這代碼怎麼這麼多多多多多多多。。。)
接下來繼續回到TableViewAdapter,(終於看完了。。。心好累)
現在我們知道TableViewAdapter是怎麼繪製各個“單元格”了吧,由於TableView所涉及的子View比較多,所它的Adapter寫的也比較複雜。原作者能把各個子View之間的邏輯整理的如此清楚,其Java段位可見一斑,只能說大佬不愧是大佬。
(一篇博客不好寫的太長。。。所以我打算將剩下的關於點擊事件、刷動事件等內容寫在另一篇,未完待續)
各位看官如果覺得喜歡,或者有任何意見和建議都可以直接給我留言。
最後再貼一下大佬的Github項目地址---->TableView