這個基本使用架構主要套用MVP Patterrn,架構中包含1個顯示在V(Activity)的RecyclerView,提供了以下幾個基本功能:
1.RecyclerView可以即時檢視,新增,修改,刪除項目(ItemView)
2.可設定每頁應該包含項目數,切換頁面。
3.透過FinalDB來儲存項目。
4.自定義RecyclerView的ItemView
首先從架構的PackageDiagram來介紹,該架構包含3個package。
主要的package為左上角的basic package,共有6個class,包含MVP組成元件以及RecyclerView的元件。
右上角的database package,共有2個class為提供儲存資料的功能。
下方的common package,只有1個class,提供取得Application context。
Package 相依性如下圖
接著從主要的basic package 的 class diagram開始介紹。
MVP構成的元件為RecyclerItemManager(M),RecyclerViewPresenter(P),
RecyclerViewActivity(V),可以看出V和M不直接溝通,而是透過P交互
(雖然在標準的MVP中,V和P是透過interface交互,為了方便介紹架構就不另外為了V和P建立interface)。
RecyclerViewActivity為介面層,只負責初始化UI以及接受使用者輸入並轉發給RecyclerViewPresenter,不包含任何業務邏輯。
RecyclerViewPresenter為邏輯層,主要接受介面層轉發的指令,並再轉發給Model,當Model處理完回傳資料給邏輯層,處理介面邏輯後再回傳給介面層。
RecyclerItemManager為Model,會持有RecyclerItem list,該list是資料的中心,主要處理業務邏輯,不含任何的介面邏輯。
而 RecyclerViewAdapter 和 RecyclerViewHolder 為構成RecyclerView的元件。
RecyclerViewHolder負責管理ItemView(顯示在RecyclerView的項目)。
RecyclerViewAdapter負責建立RecyclerViewHolder以及綁定數據到holder。
最後是RecyclerItem為資料類別,主要是提供數據並顯示在RecyclerView的ItemView。
接著來看看source code。
首先是RecyclerViewActivity,先來看看該Activity使用的介面檔案。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".basic.RecycleViewActivity"> <LinearLayout android:layout_width="match_parent" android:orientation="horizontal" android:layout_height="wrap_content"> <Button android:id="@+id/recycler_view_btn_add" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="add"/> </LinearLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
相當簡單,只包含1個Button,1個RecyclerView。
接著看看RecyclerViewActivity。
import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.codefoxx.recycleviewexample.R; import java.util.List; public class RecycleViewActivity extends AppCompatActivity implements OnClickListener { private static final String TAG = RecycleViewActivity.class.getSimpleName(); private RecyclerViewPresenter mRecyclerViewPresenter; private RecyclerView mRecyclerView; private RecyclerViewAdapter mRecyclerViewAdapter; private Button mAdd; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.recycler_view_main_activity); mRecyclerViewPresenter = new RecyclerViewPresenter(this); initData(); initUI(); } private void initData() { mRecyclerViewPresenter.loadData(); } private void initUI() { mAdd = (Button) findViewById(R.id.recycler_view_btn_add); mAdd.setOnClickListener(this); initRecyclerView(); } private void initRecyclerView() { mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(linearLayoutManager); setScrollListenerToRecyclerView(); mRecyclerViewAdapter = new RecyclerViewAdapter( mRecyclerViewPresenter.getItemsByPage(0)); mRecyclerView.setAdapter(mRecyclerViewAdapter); } public void recyclerViewClickEvent(View view) { int position = mRecyclerView.getChildAdapterPosition(view); mRecyclerViewPresenter.showActionDialog(position); } private void setScrollListenerToRecyclerView() { mRecyclerView.addOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); recyclerViewScrollToTopAndLoadPreviousPage(newState); recyclerViewScrollToBottomAndLoadNextPage(newState); } }); } private void recyclerViewScrollToBottomAndLoadNextPage(int newState) { boolean isScrollToBottomEdge = !mRecyclerView.canScrollVertically(1); if (newState == RecyclerView.SCROLL_STATE_IDLE && isScrollToBottomEdge) { mRecyclerViewPresenter.loadNextPage(); } } private void recyclerViewScrollToTopAndLoadPreviousPage(int newState) { boolean isScrollToTopEdge = !mRecyclerView.canScrollVertically(-1); if (newState == RecyclerView.SCROLL_STATE_IDLE && isScrollToTopEdge) { mRecyclerViewPresenter.loadPreviousPage(); } } public void reloadRecyclerView(int page) { List<RecyclerItem> itemsOnPage = mRecyclerViewPresenter.getItemsByPage(page); mRecyclerViewAdapter = new RecyclerViewAdapter(itemsOnPage); mRecyclerView.setAdapter(mRecyclerViewAdapter); } @Override public void onClick(View v) { int uiID = v.getId(); switch (uiID) { case R.id.recycler_view_btn_add: mRecyclerViewPresenter.addNewItem(); break; } } }
第16行為MVP的Presenter。
第18~19行為構成RecyclerView的基本元件,要使用RecyclerView必須一併使用這2個元件。
第21行為提供加入RecyclerView的項目用。
第27行為Presenter的初始化,把Activity傳入建構式中綁定View。
第28行初始化資料。
第29行初始化UI介面。
第33行為了保持View的簡單,委託Presenter讀取資料。
第37~38行初始化mAdd按鈕。
第39行初始化RecyclerView(重點)。
第43行初始化mRecyclerView變數。
第44~46行建立LinearLayoutManager為垂直並設定給mRecyclerView。
第47行為mRecyclerView設定滾動的監聽器,此監聽器中會偵測mRecyclerView是否已滾動到該頁最上方或最下方,再觸發讀取上一頁或下一頁的動作。
第48~49行初始化mRecyclerViewAdapter。
第50行設定Adapter到RecyclerView。
note:第42行的initRecyclerView為初始化RecyclerView的動作為固定用法。
第53~56行為設定點擊RecyclerView的事件,注意該方法名稱必須對應於RecyclerView的ItemView所使用的layout xml中的onclick名稱。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ... android:onClick="recyclerViewClickEvent" ...
第58~67行為設定監聽RecyclerView滾動的事件,在滾動中會判斷是否已到該頁最上方或最下方並觸發讀取上下頁的動作。
第69~74行即為當RecyclerView滾動到最下方並讀取下一頁的動作。
第76~81行即為當RecyclerView滾動到最上方並讀取上一頁的動作。
第83~87行為更新RecyclerView的動作,該動作會在model改變之後被觸發。
第90~97行為Activity的介面點擊事件。
接著是RecyclerViewAdapter
import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.codefoxx.recycleviewexample.R; import java.util.List; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecycleViewHolder> { private static final String TAG = RecyclerViewAdapter.class.getSimpleName(); private List<RecyclerItem> mRecycleItems; public RecyclerViewAdapter(List<RecyclerItem> source) { mRecycleItems = source; } @Override public RecycleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater .from(parent.getContext()).inflate(R.layout.recycle_item_view, parent, false); RecycleViewHolder viewHolder = new RecycleViewHolder(itemView); return viewHolder; } @Override public void onBindViewHolder(RecycleViewHolder holder, int position) { String listNumber = mRecycleItems.get(position).getListNumber(); holder.mListNumber.setText(mRecycleItems.get(position).getListNumber()); holder.mTime.setText(mRecycleItems.get(position).getTime()); holder.mDescription.setText(mRecycleItems.get(position).getDescription()); if (mRecycleItems.get(position).getIsLaunch()) { holder.mIsLaunch.setText("已啟用"); } else { holder.mIsLaunch.setText("未啟用"); } if (mRecycleItems.get(position).getIsVibrator()) { holder.mIsVibrator.setText("有震動"); } else { holder.mIsLaunch.setText("無震動"); } } @Override public int getItemCount() { return mRecycleItems.size(); } }
RecyclerViewAdapter為構成RecyclerView的元件,主要功能為建立ViewHolder以及綁定數據到ViewHolder。
基本的使用方式主要是覆寫3個方法(onCreateVieHolder, onBindViewHolder, getItemCount),並加入相關的實作內容。
第8行注意繼承RecyclerView.Adapter<RecycleViewHolder>
extends RecyclerView.Adapter為固定用法,<RecycleViewHolder>則是自定義類別,ViewHolder也是構成RecyclerView的基本元件。
第11行為adapter持有的數據,也是透過該數據綁定到ViewHolder上。
第18~23行為建立ViewHolder,在該方法中透過LayoutInflater產生需要的itemView並再將itemView傳入viewHolder中,最後回傳viewHolder。
注意第20行的R.layout.recycle_item_view為ItemView使用的介面檔。
note:該方法主要用意在建立viewHolder的介面,目前還未綁定數據到ViewHolder,綁定數據的動作是在onBindViewHolder方法。
第26~41行即為綁定數據到ViewHolder中。
第44~46行為回傳數據的大小。
接著來看看ItemView所使用的介面檔,該介面檔即是描述顯示在RecyclerView中的單個項目。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/basic_recycler_item_baisc" android:layout_width="match_parent" android:layout_height="110dp" android:descendantFocusability="blocksDescendants" android:orientation="vertical" android:onClick="recyclerViewClickEvent" android:weightSum="1"> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.6"> <ImageView android:id="@+id/basic_recycler_item_iv_uplayout_upSplitLine" android:layout_width="fill_parent" android:layout_height="1sp" android:layout_alignParentTop="true"> </ImageView> <TextView android:id="@+id/basic_recycler_item_tv_uplayout_list_number" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginLeft="10dp" android:layout_alignParentLeft="true" android:layout_gravity="center" android:text="1" android:textSize="20sp" android:visibility="invisible"/> <TextView android:id="@+id/basic_recycler_item_tv_uplayout_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_centerVertical="true" android:text="06:00" android:textSize="18sp"/> <TextView android:id="@+id/basic_recycler_item_et_uplayout_is_launch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="已啟動" android:textSize="18sp"/> <ImageView android:id="@+id/basic_recycler_item_et_uplayout_is_launch_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_gravity="center_horizontal" android:layout_toLeftOf="@+id/basic_recycler_item_et_uplayout_is_launch" android:adjustViewBounds="true" android:background="#00000000" android:maxHeight="50dp" android:maxWidth="50dp" android:scaleType="fitCenter"/> <TextView android:id="@+id/basic_recycler_item_et_uplayout_is_vibrator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dp" android:layout_alignParentRight="true" android:layout_centerInParent="true" android:text="有震動" android:textSize="18sp"/> <ImageView android:id="@+id/basic_recycler_item_et_uplayout_is_vibrator_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_gravity="center_horizontal" android:layout_toLeftOf="@+id/basic_recycler_item_et_uplayout_is_vibrator" android:adjustViewBounds="true" android:background="#00000000" android:maxHeight="50dp" android:maxWidth="50dp" android:scaleType="fitCenter" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.4"> <ImageView android:id="@+id/basic_recycler_item_iv_downlayout_upSplitLine" android:layout_width="fill_parent" android:layout_height="1sp" android:layout_alignParentTop="true"> </ImageView> <TextView android:id="@+id/basic_recycler_item_iv_downlayout_description_hint" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="描述:"/> <TextView android:id="@+id/basic_recycler_item_iv_downlayout_description" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/basic_recycler_item_iv_downlayout_description_hint" android:text="這是描述。"/> <ImageView android:id="@+id/basic_recycler_item_iv_downlayout_downSplitLine" android:layout_width="fill_parent" android:layout_height="1sp" android:layout_alignParentBottom="true"></ImageView> </RelativeLayout> </LinearLayout>
這裡唯一需要注意的是第8行
android:onClick="recyclerViewClickEvent"
這裡為了方便介紹直接使用onClick屬性指定回調方法名稱,該方法名稱即為在
RecyclerViewActivity第53行定義的方法,簡單的說就是當點擊R.id.basic_recycler_item_baisc UI元件時會觸發RecyclerViewActivity第53行定義的方法。
接著是RecyclerViewHolder
import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import com.codefoxx.recycleviewexample.R; public class RecycleViewHolder extends RecyclerView.ViewHolder { private static final String TAG = RecyclerViewAdapter.class.getSimpleName(); public LinearLayout mBasicLayout; public TextView mListNumber; public TextView mTime; public TextView mIsLaunch; public TextView mIsVibrator; public TextView mDescription; public RecycleViewHolder(View v) { super(v); mBasicLayout = (LinearLayout) v.findViewById(R.id.basic_recycler_item_baisc); mListNumber = (TextView) v.findViewById(R.id.basic_recycler_item_tv_uplayout_list_number); mTime = (TextView) v.findViewById(R.id.basic_recycler_item_tv_uplayout_time); mIsLaunch = (TextView) v.findViewById(R.id.basic_recycler_item_et_uplayout_is_launch); mIsVibrator = (TextView) v.findViewById(R.id.basic_recycler_item_et_uplayout_is_vibrator); mDescription = (TextView) v.findViewById(R.id.basic_recycler_item_iv_downlayout_description); } }
ViewHolder相當簡單,主要是管理ItemView,若想保持封裝性也可以將public 欄位改為private並提供相對應的public方法。
以上就是RecyclerView所使用到有關View的元件。
接著介紹MVP的Presenter
import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.widget.ArrayAdapter; import android.widget.Toast; import com.codefoxx.recycleviewexample.R; import com.codefoxx.recycleviewexample.database.RecyclerItemDBWrapper; import com.krtc.common.tool.DialogFactory; import java.util.List; import java.util.UUID; public class RecyclerViewPresenter { private static final String TAG = RecyclerViewPresenter.class.getSimpleName(); private RecycleViewActivity mRecycleViewActivity; public RecyclerViewPresenter( RecycleViewActivity recycleViewActivity) { mRecycleViewActivity = recycleViewActivity; } public void showActionDialog(final int position) { AlertDialog.Builder actionDialog = new Builder(mRecycleViewActivity); RecyclerItem item = RecyclerItemManager.getInstance() .getItem(position); actionDialog.setTitle("選擇動作" + "提醒為:" + item.getDescription()); ArrayAdapter<CharSequence> actionDialogAdapter = ArrayAdapter.createFromResource( mRecycleViewActivity, R.array.basic_recycler_view_item_action_select, android.R.layout.select_dialog_item); actionDialog.setAdapter(actionDialogAdapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final int REVIEW = 0; final int ADD = 1; final int DELETE = 2; final int MODIFY = 3; if (which == REVIEW) { reviewItem(position); } else if (which == ADD) { addNewItem(); } else if (which == DELETE) { deleteItem(position); } else if (which == MODIFY) { modifyItem(position); } dialog.dismiss(); } }); actionDialog.show(); } private void modifyItem(int position) { RecyclerItemManager.getInstance().modifyItem(position); mRecycleViewActivity.reloadRecyclerView(RecyclerItemManager.getInstance().getCurrentPageIndex()); } private void deleteItem(int position) { RecyclerItemManager.getInstance().deleteItem(position); mRecycleViewActivity.reloadRecyclerView(RecyclerItemManager.getInstance().getCurrentPageIndex()); } public void addNewItem() { RecyclerItemManager.getInstance().addItem(generateNewItem()); mRecycleViewActivity.reloadRecyclerView(RecyclerItemManager.getInstance().getCurrentPageIndex()); } private RecyclerItem generateNewItem() { RecyclerItem itemForAdd = new RecyclerItem(); itemForAdd.setId(UUID.randomUUID().toString()); itemForAdd.setTime("00:00"); itemForAdd.setDescription("new description"); itemForAdd.setIsLaunch(false); itemForAdd.setIsVibrator(false); itemForAdd.setListNumber(""); return itemForAdd; } private void reviewItem(int position) { RecyclerItem recyclerItem = RecyclerItemManager.getInstance() .getItem(position); Toast.makeText(mRecycleViewActivity, recyclerItem.toString(), Toast.LENGTH_LONG) .show(); } public void loadPreviousPage() { if (RecyclerItemManager.getInstance().isMorePreviousPage()) { showSwitchPreviousPageConfirmDialog(); } else { showNoMorePreviousPageToast(); } } private void showSwitchPreviousPageConfirmDialog() { DialogFactory .createTwoButtonAlertDialog(mRecycleViewActivity, "切換頁面", "是否切換到上一頁?", "否", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }, "是", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { int page = RecyclerItemManager.getInstance().decreaseCurrentPageIndex(); mRecycleViewActivity.reloadRecyclerView(page); } }).show(); } private void showNoMorePreviousPageToast() { Toast.makeText(mRecycleViewActivity, "已經沒有上一頁了", Toast.LENGTH_SHORT).show(); } public void loadNextPage() { if (RecyclerItemManager.getInstance().isMoreNextPage()) { showSwitchNextPageConfirmDialog(); } else { showNoMoreNextPageToast(); } } private void showSwitchNextPageConfirmDialog() { DialogFactory .createTwoButtonAlertDialog(mRecycleViewActivity, "切換頁面", "是否切換到下一頁?", "否", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }, "是", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { int page = RecyclerItemManager.getInstance().increaseCurrentPageIndex(); mRecycleViewActivity.reloadRecyclerView(page); } }).show(); } private void showNoMoreNextPageToast() { Toast.makeText(mRecycleViewActivity, "已經沒有下一頁了", Toast.LENGTH_SHORT).show(); } public List<RecyclerItem> getItemsByPage(int page) { return RecyclerItemManager.getInstance() .getItemsByPage(page); } public void loadData() { List<RecyclerItem> itemsForLoad = RecyclerItemDBWrapper.loadBasicRecyclerItem(); RecyclerItemManager.getInstance().cleanAndAddItems(itemsForLoad); } }
RecyclerViewPresenter主要接受RecyclerViewActivity轉發的指令,處理需要的邏輯之後再將該指令轉發給Model層,因此會持有對View和對Model的引用。
第15行持有對View的引用(mRecycleViewActivity)。
第17~20行藉由建構式傳入View,和mRecycleViewActivity連結。
第22~52行顯示動作對話框,當使用者點擊RecyclerView上的itemview時,該對話框顯示可操作動作。目前提供檢視,新增,刪除,修改,4個功能。
第54~57行為修改itemview動作。
第59~62行為刪除itemview動作。
第64~67行為新增itemview動作。
note:從以上動作內容可以看出一致性為首先委託RecyclerViewManager進行對model的修改,再通知View更新頁面。
第69~78行為新增itemview使用,這裡為了方便介紹所以新增類似的itemview,實際應用上可以提供使用者輸入新增的相關內容。
第80~85行為檢視itemview動作。
第87~93行為當RecyclerView滾動到該頁的最上方時讀取上一頁的內容。
第95~110行為顯示對話框提醒使用者是否切換到上一頁,使用者選擇否,不進行任何動作,使用者選擇是,就進行讀取上一頁的動作。
第112~114行為提醒使用者該頁已為第一頁。
第116~121行為當RecyclerView滾動到該頁的最下方時讀取下一頁的內容(相對於第87~93行的loadPreviousPage)。
第124~139行為顯示對話框提醒使用者是否切換到下一頁,使用者選擇否,不進行任何動作,使用者選擇是,就進行讀取下一頁的動作。
第141~143行為提醒使用者該頁已為最後頁。
第145~148行為委託RecyclerItemManager取得指定頁數的內容(ItemView)。
第150~153行為啟動RecyclerViewActivity時讀取儲存在資料庫的資料,注意這裡也是為了方便介紹所以直接在UI Thread操作資料庫,標準作法應該要在background thread進行。
接著介紹MVP的Model,RecyclerItemManager
RecyclerItemManager會持有一個RecyclerItem的List,該list的來源為資料庫的內容(RecyclerItemDBWrapper),並提供給RecyclerViewActivity來顯示。
import android.util.Log; import .database.RecyclerItemDBWrapper; import java.util.ArrayList; import java.util.List; public class RecyclerItemManager { private static final String TAG = RecyclerItemManager.class.getSimpleName(); //Must keep this value big than real item, 1000 is big enough private static final int ITEM_PER_PAGE = 20; private static final RecyclerItemManager sInstance = new RecyclerItemManager(); private List<RecyclerItem> mAllResults = new ArrayList<>(); private List<PageHolder> mAllPageHolders = new ArrayList<>(); private int mCurrentPageIndex; private RecyclerItemManager() { } public static RecyclerItemManager getInstance() { return sInstance; } public int getCurrentPageIndex() { return mCurrentPageIndex; } public void cleanAndAddItems(List<RecyclerItem> source) { mAllResults.clear(); for (RecyclerItem item : source) { mAllResults.add(item); } mAllPageHolders.clear(); computePages(); } private void computePages() { if (mAllResults.size() < ITEM_PER_PAGE) { int totalPage = 1; List<RecyclerItem> subList = mAllResults; mAllPageHolders.add(new PageHolder(totalPage, subList)); } else { int totalPage = computeTotalPage(); computeItemsOfPage(totalPage); } showAllPageHolders(); } private void showAllPageHolders() { for (PageHolder onePage : mAllPageHolders) { Log.d(TAG, "---"); Log.d(TAG, "PageHolder.getPage():" + onePage.getPage()); for (RecyclerItem one : onePage.getItems()) { Log.d(TAG, "item.getTime():" + one.getTime()); Log.d(TAG, "item.getDescription():" + one.getDescription()); } Log.d(TAG, "---"); } } private int computeTotalPage() { int pageCount = mAllResults.size() / ITEM_PER_PAGE; int itemsOfEndPage = mAllResults.size() % ITEM_PER_PAGE; int totalPage = 0; if (itemsOfEndPage == 0) { totalPage = pageCount; } else { totalPage = pageCount + 1; } return totalPage; } private void computeItemsOfPage(int totalPage) { for (int page = 0; page < totalPage; ++page) { int lastPage = totalPage - 1; if (page == lastPage) { int endElement = mAllResults.size(); List<RecyclerItem> subList = mAllResults.subList(page * ITEM_PER_PAGE, endElement); mAllPageHolders.add(new PageHolder(page, subList)); } else { List<RecyclerItem> subList = mAllResults .subList(page * ITEM_PER_PAGE, (page * ITEM_PER_PAGE) + (ITEM_PER_PAGE)); mAllPageHolders.add(new PageHolder(page, subList)); } } } public boolean isMorePreviousPage() { if (mCurrentPageIndex > 0) { return true; } return false; } public boolean isMoreNextPage() { if (mCurrentPageIndex < mAllPageHolders.size() - 1) { return true; } return false; } public int decreaseCurrentPageIndex() { mCurrentPageIndex--; return mCurrentPageIndex; } public int increaseCurrentPageIndex() { mCurrentPageIndex++; return mCurrentPageIndex; } public List<RecyclerItem> getItemsByPage(int page) { PageHolder currentPageHolder = mAllPageHolders.get(page); return currentPageHolder.getItems(); } public RecyclerItem getItem(int position) { PageHolder currentPageHolder = mAllPageHolders.get(mCurrentPageIndex); return currentPageHolder.getItems().get(position); } public void addItem(RecyclerItem item) { RecyclerItemDBWrapper.saveBasicRecyclerItem(item); cleanAndAddItems(RecyclerItemDBWrapper.loadBasicRecyclerItem()); } public void isCurrentPageIndexValid() { if (mCurrentPageIndex == mAllPageHolders.size()) { mCurrentPageIndex--; } } public void deleteItem(int position) { RecyclerItem itemForDelete = RecyclerItemManager.getInstance().getItem(position); RecyclerItemDBWrapper.deleteItem(itemForDelete); RecyclerItemManager.getInstance() .cleanAndAddItems(RecyclerItemDBWrapper.loadBasicRecyclerItem()); RecyclerItemManager.getInstance().isCurrentPageIndexValid(); } public void modifyItem(int position) { RecyclerItem itemForModify = RecyclerItemManager.getInstance().getItem(position); itemForModify.setDescription("alread fix"); itemForModify.setTime("alread fix"); RecyclerItemDBWrapper.update(itemForModify); RecyclerItemManager.getInstance() .cleanAndAddItems(RecyclerItemDBWrapper.loadBasicRecyclerItem()); } private static class PageHolder { private int mPage; private List<RecyclerItem> mItems; public PageHolder(int page, List<RecyclerItem> items) { mPage = page; mItems = items; } public int getPage() { return mPage; } public List<RecyclerItem> getItems() { return mItems; } } }
第11行為設定每頁有多少ItemView的數量。
第12行為Singleton Pattern的寫法(搭配第17~18行,第20~22行)
第13~14行為計算每頁有多少個ItemView用。
Note:第14行的變數較為重要,該List即為每頁的內容 。e.g., 若 list 有3個element,則代表RecyclerView共有3頁,而每個element可以再透過pageHolder.getItems()來取得該頁上的項目。
第15行為表示目前所在頁數。
第17~18行為建構式私有化,目的為不透過建構式讓外界實體化該類別(Singleton Pattern)。
第20~22行為提供外界取得該類別的實體(Singleton Pattern)。
第24~26行取得目前頁數。
第28~36行為重新計算有多少頁,以及每頁的項目。
第38~49行為計算頁數用。基本算法就是藉由ITEM_PER_PAGE來計算每頁的內容。
第51~61行為顯示頁數以及每頁的內容,只是單純用來顯示。
第63~74行計算總頁數。
第76~89行計算每頁的內容。
第91~96為回傳是否還有上一頁。
第98~103為回傳是否還有下一頁。
第105~108行遞減頁數。
第110~113行遞增頁數。
第115~118行取得指定頁數的全部內容。透過mAllPageHolders取得某個element的內容。
第120~123行取得指定頁數的指定內容。透過mAllPageHolders取得某個element的內容再指定某個位置。
第125~128行加入itemview用。首先加入項目到資料庫中再重新刷新頁數及每頁的內容。注意這裡最好也是透過background thread來進行。
第130~134行檢查並更新目前頁數。這個方法比較特別,主要是用於當刪除項目時,若被刪除項目為該頁的最後一項,則目前頁數必須往前移動一頁。
第136~142刪除項目動作。其實作內容為首先從mAllPageHolders取得要刪除項目(第137行),再從資料庫刪除項目(第138行),接著重新刷新list(第139~140行),最後檢查並設定目前頁數是否正確(第141行)。
第144~151行修改項目動作。其實作內容為首先從mAllPageHolders取得要修改的項目(第145行),設定修改內容(第146~147行),再從資料庫更新項目(第148行),接著重新刷新list(第149~150行)。
第153~170行PageHolder為表示某頁及其內容用。mPage為該頁的頁數,mItems為該頁的內容。
最後為RecyclerItem,該類別為基本的資料類別,無邏輯,用來代表RecyclerView上的ItemView。
import net.tsz.afinal.annotation.sqlite.Id; public class RecyclerItem { @Id private String id; private String listNumber; private String time; private boolean isLaunch; private boolean isVibrator; private String description; public String getListNumber() { return listNumber; } public void setListNumber(String listNumber) { this.listNumber = listNumber; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } public boolean getIsLaunch() { return isLaunch; } public void setIsLaunch(boolean isLaunch) { this.isLaunch = isLaunch; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean getIsVibrator() { return isVibrator; } public void setIsVibrator(boolean vibrator) { isVibrator = vibrator; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { return "BasicRecyclerItem{" + "listNumber='" + listNumber + '\'' + ", time='" + time + '\'' + ", isLaunch=" + isLaunch + ", isVibrator=" + isVibrator + ", description='" + description + '\'' + '}'; } }
比較需要注意的是第5行透過設定@Id為主鍵。
最重要的package介紹完之後,接著介紹其他協作package,這些package主要提供Application Context的取得以及資料庫的操作。
首先是database package,主要提供資料庫操作,使用的ORM為FinalDB,關於FinalDB參考這裡。
包含2個類別(FinalDBwrapper,RecyclerItemDBWrapper)。
FinalDBWrapper
import .common.RecyclerViewSingletonApplication; import net.tsz.afinal.FinalDb; public class FinalDBWrapper { private static final String TAG = FinalDBWrapper.class.getSimpleName(); public static final FinalDb DB_INSTANCE = FinalDb .create(RecyclerViewSingletonApplication.getInstance(), "test.db", true); }
相當簡單,主要就是第6~7行的DB_INSTANCE,初始化該變數並設定資料庫名稱,debug模式。後續操作資料庫的動作都是透過DB_INSTANCE來執行。
而RecyclerViewSingletonApplication就是繼承Application,用來取得context。
RecyclerItemDBWrapper
import .basic.RecyclerItem; import java.util.List; public class RecyclerItemDBWrapper { public static void saveBasicRecyclerItem(RecyclerItem item) { FinalDBWrapper.DB_INSTANCE.save(item); } public static List<RecyclerItem> loadBasicRecyclerItem() { return FinalDBWrapper.DB_INSTANCE.findAll(RecyclerItem.class); } public static void deleteItem(RecyclerItem itemForDelete) { FinalDBWrapper.DB_INSTANCE.delete(itemForDelete); } public static void update(RecyclerItem itemForModify) { FinalDBWrapper.DB_INSTANCE.update(itemForModify); } }
這個類別也相當簡單,主要提供資料庫的具體操作,具體操作都是透過FinalDBWrapper DB_INSTANCE 進行。
最後就是common package,只有一個類別RecyclerViewSingletonApplication,主要提供 Application context的取得
import android.app.Application; public class RecyclerViewSingletonApplication extends Application { private static final String TAG = RecyclerViewSingletonApplication.class.getSimpleName(); private static RecyclerViewSingletonApplication sUniqueInstance; public static RecyclerViewSingletonApplication getInstance() { return sUniqueInstance; } @Override public void onCreate() { super.onCreate(); sUniqueInstance = this; sUniqueInstance.initData(); } private void initData() { } }