2017.3.20更新
更新說明:之前看評論裏的童鞋們給我反映問題,很感謝大家指出,針對出現的問題我進行了一些改進。同時將加載更多的這個功能從主體的adapter中分離出來,使用了裝飾者模式,降低了代碼的耦合,這樣便於維護和修改。
裝飾者模式是常用的Java設計模式之一,不熟悉的童鞋可以自行查閱資料,先了解了裝飾者模式看以下內容會容易懂一點~這個設計模式在Android裏也是廣泛應用,最典型的就是Context的應用了。
……………………….我是分割線…………………………………………
Recyclerview是 Listview 的升級版本,在項目中使用較爲廣泛,官方也推薦我們使用 Recyclerview 來代替 Listview,在此就不多說 Recyclerview 的優勢特點 balala了。。。
在實際項目中,列表通常是分頁的,請求服務器也只會一次請求若干條,按需加載,這樣比較節省流量,這樣就有了我們很常見的上拉加載更多的功能,具體的實現效果如下圖:
該activity的佈局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="io.geek.myapplication.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
底部加載更多的view佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="wrap_content">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="正在加載..."
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
底部提示到底的view佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:padding="8dp"
android:gravity="center"
android:layout_height="wrap_content">
<TextView
android:text="已經到底了"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
數據item的view:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:gravity="center"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:background="@color/colorAccent"
android:layout_width="50dp"
android:layout_height="50dp"/>
<TextView
android:gravity="center"
android:id="@+id/tv_content"
tools:text="我是第一項"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
接下來就是代碼的編寫了,開頭就已經講到過我們在這要用到裝飾者模式,其實裝飾者模式並沒有多麼複雜,可以理解成給一個類裝飾點新功能,並且不會影響到這個類本來的功能。裝飾者模式寫法也很簡單,分爲以下幾步套路:
1. 裝飾者模式套路第一步:
創建基類(裝飾類和被裝飾類共同父類),首先寫一個抽象類BaseAdapter,繼承自RecyclerView.Adapter,這個Adapter中只封裝了與數據源相關的字段和方法:
/**
* 與數據源相關的字段和方法封裝在父類中
*/
public abstract class BaseAdapter<T> extends RecyclerView.Adapter {
protected List<T> dataSet = new ArrayList<>();
public void updateData(List dataSet) {
this.dataSet.clear();
appendData(dataSet);
}
public void appendData(List dataSet) {
if (dataSet != null && !dataSet.isEmpty()) {
this.dataSet.addAll(dataSet);
notifyDataSetChanged();
}
}
public List<T> getDataSet() {
return dataSet;
}
@Override
public int getItemCount() {
return dataSet.size();
}
}
2. 裝飾者模式套路第二步
創建裝飾類,然後開始編寫加載更多的adapter的包裝類,這個類繼承自BaseAdpater,這個裝飾類中只負責處理加載更多的邏輯,代碼如下:
/**
* 在這個裝飾器中,只做與加載更多相關操作。
public class LoadMoreAdapterWrapper extends BaseAdapter<String> {
private BaseAdapter mAdapter;
private static final int mPageSize = 10;
private int mPagePosition = 0;
private boolean hasMoreData = true;
private OnLoad mOnLoad;
public LoadMoreAdapterWrapper(BaseAdapter adapter, OnLoad onLoad) {
mAdapter = adapter;
mOnLoad = onLoad;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == R.layout.list_item_no_more) {
View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new NoMoreItemVH(view);
} else if (viewType == R.layout.list_item_loading) {
View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new LoadingItemVH(view);
} else {
return mAdapter.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof LoadingItemVH) {
requestData(mPagePosition, mPageSize);
} else if (holder instanceof NoMoreItemVH) {
} else {
mAdapter.onBindViewHolder(holder, position);
}
}
private void requestData(int pagePosition, int pageSize) {
//網絡請求,如果是異步請求,則在成功之後的回調中添加數據,並且調用notifyDataSetChanged方法,hasMoreData爲true
//如果沒有數據了,則hasMoreData爲false,然後通知變化,更新recylerview
if (mOnLoad != null) {
mOnLoad.load(pagePosition, pageSize, new ILoadCallback() {
@Override
public void onSuccess() {
notifyDataSetChanged();
mPagePosition = (mPagePosition + 1) * mPageSize;
hasMoreData = true;
}
@Override
public void onFailure() {
hasMoreData = false;
}
});
}
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
if (hasMoreData) {
return R.layout.list_item_loading;
} else {
return R.layout.list_item_no_more;
}
} else {
return mAdapter.getItemViewType(position);
}
}
@Override
public int getItemCount() {
return mAdapter.getItemCount() + 1;
}
static class LoadingItemVH extends RecyclerView.ViewHolder {
public LoadingItemVH(View itemView) {
super(itemView);
}
}
static class NoMoreItemVH extends RecyclerView.ViewHolder {
public NoMoreItemVH(View itemView) {
super(itemView);
}
}
}
public interface OnLoad {
void load(int pagePosition, int pageSize, ILoadCallback callback);
}
public interface ILoadCallback {
void onSuccess();
void onFailure();
}
解析如下:
1. 該類中的傳入的mAdapter就是需要被裝飾的adapter,mOnLoad是自定義的一個接口,傳入之後可以回調處理加載更多數據。
2. 首先重寫getItemCount方法,因爲在列表中最後始終會有一個加載更多或者是到底提示的view,所以item的數目始終是數據源的數目多一個。
3. 重寫getItemType方法:首先判斷position是否是最後一個,如果不是的話,則直接返回被裝飾的mAdapter的getItemType;如果是的話判斷hasMoreData變量,是true則返回加載更多的佈局文件ID,反之返回到底的佈局文件ID。這裏的hasMoreData是我們自己定義的一個boolean字段,通過改變這個值就可以最後一個view應該是加載更多還是到底。
4. 重寫onCreateViewHolder方法,在這個方法中通過判斷viewType的類型返回不同的ViewHolder。
5. 重寫onBindViewHolder方法,在這個方法中,判斷holder的類型的不同做出不同的操作,如果holder是加載更多的holder,那麼表示加載更多的view正在展示,這時候就應該做出加載更多的操作,這個操作放在了requestData方法中。
6. 在requestData方法中,觸發了OnLoad接口中的load回調,傳入的參數有當前的頁碼加載數據一頁的大小,加載完成之後的回調接口,可以看到加載完成之後會進行不同的作,如果成功,則設置hasMoreData爲true,並且通知數據發生改變,更新列表,改變前頁碼;如果失敗的話,則把hasMoreData設置爲false。
3. 裝飾者模式套路第三步
創建被裝飾類,同樣這個類也需要繼承自BaseAdapter:
/**
* 被裝飾類要和裝飾類繼承自同一父類
*/
public class MyAdapter extends BaseAdapter<String> {
private Context mContext;
public MyAdapter(Context context) {
mContext = context;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(mContext).inflate(R.layout.list_item_mine, parent, false);
return new MyViewHolder(v);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((MyViewHolder) holder).bind(getDataSet().get(position));
}
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView mTextView;
public MyViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.tv_content);
}
public void bind(CharSequence content) {
mTextView.setText(content);
}
}
}
4. 裝飾者模式套路第四步
最後一步就是如何使用,這個adapter就是一個普通的沒有上拉加載功能的adapter,如果要給它加上這個功能,只需要這麼使用:
public class MainActivity extends AppCompatActivity {
RecyclerView mRecyclerView;
BaseAdapter mAdapter;
int loadCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
//創建被裝飾者類實例
final MyAdapter adapter = new MyAdapter(this);
//創建裝飾者實例,並傳入被裝飾者和回調接口
mAdapter = new LoadMoreAdapterWrapper(adapter, new OnLoad() {
@Override
public void load(int pagePosition, int pageSize, final ILoadCallback callback) {
//此處模擬做網絡操作,2s延遲,將拉取的數據更新到adpter中
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List<String> dataSet = new ArrayList();
for (int i = 0; i < 10; i++) {
dataSet.add("我是一條數據");
}
//數據的處理最終還是交給被裝飾的adapter來處理
adapter.appendData(dataSet);
callback.onSuccess();
//模擬加載到沒有更多數據的情況,觸發onFailure
if (loadCount++ == 3) {
callback.onFailure();
}
}
}, 2000);
}
});
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
}
}
這樣就完整實現了 Recylerview 的加載更多功能,使用裝飾者模式使得整個代碼結構更加清晰,在被裝飾的adapter中就不用處理太多邏輯,專心處理數據展示即可,易於維護。
其中比較繞的地方可能就是數據加載的回調接口,不過仔細理一下還是沒有多大問題,總體來說還是很簡單的,如果還是有不明白的,可能你對 Recylerview 的使用方法還不夠了解,可以自行學習。
有不當之處請大家批評指正,謝謝:)