“哎呀,最近新接手一個項目里要用 RecyclerView 的地方好多着裹,而且基本上都需要上拉刷新和下拉加載领猾,我才寫了兩個頁面,手都快斷了骇扇,有沒有什么比較好的方法摔竿?”
“什么!你還在自己一點點地寫少孝?那你得寫到啥時候去继低?”
“那怎么辦?”
“你是不是傻稍走,Github 上那么多優(yōu)秀的開源庫袁翁,用一個啊⌒隽常或者是自己寫一個簡單的粱胜,別寫那么復雜的,能滿足自己需求的就行狐树”貉梗”
“有道理,我去看看”
......
“Github 上的優(yōu)秀的好多啊褪迟,我都看花了眼冗恨,不知道該選哪一個?”
“實在不行味赃,你就自己寫一個唄掀抹,如果只是實現(xiàn)你的需求應該不難,來說說看心俗,你的需求是什么傲武?”
“也不是很復雜,就簡單的一個城榛,能上拉加載更多揪利,下拉刷新的就行,簡單不狠持?”
“你出去別說你是程序員疟位,會被懟死的……”
“咋了,這不行嗎喘垂?”
“不是不行甜刻,你這需求提了跟沒提一個樣绍撞,有沒有點實質(zhì)性的東西?”
“嗯……得院,那我重說
- 在布局文件中只使用一個控件傻铣,不做任何的嵌套;
- 在 RecyclerView 沒有數(shù)據(jù)的時候祥绞,頁面上顯示提示信息非洲,‘暫無數(shù)據(jù)’ 就行;
- 支持下拉刷新蜕径,刷新顯示的 Header 就用官方的那個小圓圈就行了两踏;
- 支持上拉加載,如果有下一頁數(shù)據(jù)丧荐, Footer 顯示一個 Loading 配上‘加載中……’缆瓣,如果沒有數(shù)據(jù),就顯示‘你已經(jīng)扯到底了’虹统。
嗯弓坞,就這么多〕道螅”
“還行渡冻,也不算難,有什么想法嗎忧便?”
“暫時沒了族吻,后面想到再加吧≈樵觯”
“…………”
“那行超歌,那我們先來看看具體怎么實現(xiàn)吧〉俳蹋”
“1. 同一個控件內(nèi)既能刷新又要可以上拉加載巍举,官方的控件應該是沒有的,那就自定義一個吧凝垛,自定義一個 ViweGroup 懊悯,然后把 SwipeRefreshLayout 和 RecyclerView 包裹起來,然后再給 RecyclerView 添加一個滑動監(jiān)聽事件梦皮;
- 在沒有數(shù)據(jù)的情況下顯示提示信息炭分,這個可以放一個 TextView 或者是 ImageView ,然后在獲取數(shù)據(jù)之后剑肯,判斷是否為空捧毛,如果數(shù)據(jù)不為空就顯示 RecyclerView,如果數(shù)據(jù)為空,就顯示 TextView岖妄;
- 需要動態(tài)的修改 Footer 顯示的內(nèi)容型将,那就在 RecyclerView 的 Adapter 中動態(tài)更改唄。
”
“等等荐虐,那個 Footer 的動態(tài)更改能不能不讓我做啊,每次都要寫動態(tài)改變代碼丸凭,實在不想寫福扬。”
“沒讓你寫啊惜犀,我是說放在我們內(nèi)部的 Adapter 里面铛碑。是這樣的,我打算自己實現(xiàn)一個 Adapter 虽界,在這個 Adapter 里面去動態(tài)的更新 Footer汽烦。”
“你實現(xiàn)了 Adapter莉御,那我外面還能用嗎撇吞?
“可以啊,咋不行礁叔,我自定義的 Adapter 把你外層需要用到的 Adapter 包裹起來牍颈,你還是你,不過琅关,你外面還有一層煮岁。玩過俄羅斯套娃嗎?”
“沒有……”
“想想手機和手機殼的關(guān)系涣易,這個跟那個類似画机。”
“好像懂了……”
“那我們來實現(xiàn)吧新症〔绞希”
“從哪下手啊账劲?我怎么感覺一臉懵逼戳护。”
“先從布局文件開始瀑焦,先來寫顯示出來的列表布局腌且,在里面寫上你要的那幾個控件。然后再給 Footer 來一個布局榛瓮∑潭”
me_recyclerview.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.AppCompatTextView
android:id="@+id/list_tip_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="沒有數(shù)據(jù)"
android:textSize="16sp" />
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/list_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/list_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
“嗯,這就是列表布局了,內(nèi)容很簡單就是你現(xiàn)在寫的樣子……精续,好了坝锰,再來寫個 Footer 布局≈馗叮”
“等等顷级,這個簡單,我來确垫」保”
“行行行,你來删掀∠杓剑”
me_foot.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="48dp">
<ProgressBar
android:id="@+id/item_footer_progress"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.4"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/item_footer_message"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:gravity="center"
android:text="加載中"
app:layout_constraintLeft_toRightOf="@+id/item_footer_progress" />
</android.support.constraint.ConstraintLayout>
“還行,那我們繼續(xù)披泪?”
“好嘞纤子。”
“剛才說了款票,需要使用一個 Adapter 來包裹外層調(diào)用方的 Adapter 控硼,那么還需要自定義一個 Adapter ,就叫 MeRefreshListAdapter 吧徽职。來繼續(xù)”
MeRefreshListAdapter.java
private static final int TYPE_FOOTER = -1;
private RecyclerView.Adapter adapter;
private LayoutInflater inflater;
private boolean isShowFooter;
private boolean isNoData;
public MeRefreshListAdapter(RecyclerView.Adapter adapter, Context context, boolean isShowFooter, boolean isNoData) {
this.adapter = adapter;
this.isShowFooter = isShowFooter;
this.isNoData = isNoData;
inflater = LayoutInflater.from(context);
}
“你這里那個 RecyclerView.Adapter 干啥使得象颖?”
“那個就是外面你傳進來的 Adapter 啊,你外面的 Adapter 肯定得繼承自 RecyclerView.Adapter 吧姆钉,那我得保證说订,你加的什么泛型我這邊都能用啊。比如你有一個是繼承自 RecyclerView.Adapter<Person.ViewHolder>,還有一個是繼承自 RecyclerView.Adapter<City.ViewHolder>”。
“哦~”
“還有裸准,這個 MeRefreshListAdapter 需要控制 Footer 和外層 Adapter 的 Item 的創(chuàng)建與綁定塔插,總的來說屋摇,他們是兩類,我不管你外層的 Item 的類型有幾個,我都要加上 1 ,這個 1 就是 Footer沾谜。并且,我內(nèi)部只對 Footer 這一個類型的 Item 進行控制胀莹,其余的還是交給你外層去做基跑。不過這時候就需要進行判斷了,到底哪一個是 Footer 類型的描焰,哪一個是普通類型(外層的 Item 類型)媳否。”
“這個我知道,簡單篱竭,F(xiàn)ooter 肯定是在最后一個的力图,那就直接把最后一個歸為 Footer 就行了,不對掺逼,當不顯示 Footer 的時候吃媒,最后一個 Item 也是普通類型,那就是在顯示 Footer 的情況下的最后一個 Item 是 Footer吕喘∠郏”
“嗯,是的兽泄,這樣一來,我們自定義的 Adapter 也就出來了漾月〔∩遥”
MeRefreshListAdapter.java
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
switch (viewType) {
case TYPE_FOOTER:
View footer = inflater.inflate(R.layout.me_list_footer, parent, false);
viewHolder = new FooterHolder(footer);
break;
default:
viewHolder = adapter.onCreateViewHolder(parent, viewType);
break;
}
return viewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof FooterHolder) {
FooterHolder footerHolder = (FooterHolder) holder;
footerHolder.progressBar.setVisibility(isNoData ? View.GONE : View.VISIBLE);
footerHolder.message.setText(isNoData ? "--- 扯到底了 ---" : "加載中……");
}else{
adapter.onBindViewHolder(holder,position);
}
}
@Override
public int getItemCount() {
return isShowFooter ? adapter.getItemCount() + 1 : adapter.getItemCount();
}
@Override
public int getItemViewType(int position) {
if (isShowFooter && position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return adapter.getItemViewType(position);
}
}
static class FooterHolder extends RecyclerView.ViewHolder {
ProgressBar progressBar;
TextView message;
FooterHolder(View itemView) {
super(itemView);
progressBar = itemView.findViewById(R.id.item_footer_progress);
message = itemView.findViewById(R.id.item_footer_message);
}
}
public void setShowFooter(boolean flag) {
this.isShowFooter = flag;
this.notifyDataSetChanged();
}
public void setNoData(boolean flag) {
this.isNoData = flag;
this.notifyDataSetChanged();
}
“當然了,還需要對外開放更新 Adapter 的方法梁肿,這樣才能實時控制嘛蜓陌。”
“我們接下來干啥吩蔑?”
“寫接口钮热,我們需要自定義兩個接口方法,用來在外層調(diào)用烛芬。很簡單隧期,就下面這樣的∽嘎Γ”
RefreshLoadListener.java
public interface RefreshLoadListener {
/**
* 上拉加載更多
*/
void upLoad();
/**
* 下拉刷新
*/
void downRefresh();
}
“接口也寫完了仆潮,現(xiàn)在開始進入正題了。自定義 ViewGroup 繼承自 LinearLayout遣臼,然后重寫其中的構(gòu)造方法性置。四個構(gòu)造方法都要重寫啊,不寫的話可能會運行報錯揍堰∨羟常”
MeRecyclerView.java
public class MeRecyclerView extends LinearLayout implements SwipeRefreshLayout.OnRefreshListener {
private SwipeRefreshLayout refreshLayout;
private RecyclerView refreshList;
private AppCompatTextView listTip;
private RefreshLoadListener loadListener;
private MeRefreshListAdapter meRefreshListAdapter;
private Context mContext;
private RecyclerView.LayoutManager layoutManager;
private boolean isShowFooter;
private boolean isNoData;
private int lastVisibleItem;
public MeRecyclerView(Context context) {
super(context);
init(context);
}
public MeRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MeRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public MeRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
this.mContext = context;
LayoutInflater.from(context).inflate(R.layout.me_recyclerview, this, true);
refreshLayout = findViewById(R.id.list_swipe_refresh);
listTip = findViewById(R.id.list_tip_message);
refreshList = findViewById(R.id.list_list);
refreshLayout.setOnRefreshListener(this);
}
}
“你看,剛才在初始化操作的時候屏歹,我們就設置了 SwipeRefreshLayout 的刷新接口隐砸,現(xiàn)在就到了我們需要實現(xiàn)他的時候了,可能跟你平時寫的不太一樣西采,因為雖然我們實現(xiàn)了這個接口凰萨,但我們還是得把這個具體實現(xiàn)的機會交給外層,讓外層進行實際的數(shù)據(jù)獲取。當然了胖眷,我們?nèi)绻斍绊撁嫣幱谒⑿聽顟B(tài)武通,那 Footer 肯定是不能顯示出來的,這個時候就需要操作一下剛才我們寫的 MeRefreshListAdapter ”
MeRecyclerView.java
@Override
public void onRefresh() {
if (null != loadListener) {
isNoData = false;
isShowFooter = false;
meRefreshListAdapter.setNoData(isNoData);
meRefreshListAdapter.setShowFooter(isShowFooter);
loadListener.downRefresh();
}
}
“好了珊搀,下拉刷新結(jié)束了冶忱,是不是很簡單?”
“這就結(jié)束了境析,這也太快了囚枪。”
“下面來看上拉加載更多劳淆×凑樱”
“刷新的容易,加載更多的沛鸵,要控制下面 Footer 顯示還要改變顯示的文字括勺,想想頭就大∏”
“你想啥呢疾捍,那些都做完了啊,剛在在 MeRefreshListAdapter 不是就做過了……”
“完全沒意識到……”
“上拉加載其實跟刷新一樣的栏妖,都挺好實現(xiàn)的乱豆,無非就是給 RecyclerView 添加一個滑動監(jiān)聽,然后再根據(jù)當前位置去判斷是否加載新數(shù)據(jù)吊趾。你看”
MeRecyclerView.java
refreshList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
int totalCount = layoutManager.getItemCount() - 1;
if (totalCount > 18 && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem == totalCount && !isNoData && !isShowFooter) {
if (null != loadListener) {
setFooter();
loadListener.upLoad();
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (layoutManager instanceof LinearLayoutManager) {
lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
}
}
});
private void setFooter() {
isShowFooter = true;
meRefreshListAdapter.setShowFooter(true);
}
"好了宛裕,上拉加載更多也解決了,不過這里我偷了個懶趾徽。在獲取當前列表的最后一項的 position 時续滋,我判斷了 LinearLayoutManager ,其他的沒進行判斷孵奶,不過也簡單疲酌,GridLayoutManager 是繼承自 LinearLayoutManager ,而 StaggeredGridLayoutManager 獲取最后一個 item 的 position 返回的是一個數(shù)組了袁,取出其中最大的即可朗恳。"
“好吧,那這樣是不是就算完成了爸嘟搿?”
“想多了崭庸,看起來怀浆,我們是做完這么多的谊囚,但是,并沒有做完执赡,比如镰踏,我們還需要進行停止刷新,要不然那個無論是上拉加載還是刷新都會有那個 loading 在那不停地動沙合〉煳保”
MeRecyclerView.java
public void stopRefresh(int pageCount, boolean isNoData) {
this.isNoData = isNoData;
meRefreshListAdapter.setNoData(isNoData);
showData(meRefreshListAdapter.getItemCount() > 0);
if (pageCount == 1) {
refreshLayout.setRefreshing(false);
} else {
if (!isNoData) {
isShowFooter = false;
meRefreshListAdapter.setShowFooter(isShowFooter);
}
}
}
private void showData(boolean b) {
refreshList.setVisibility(b ? VISIBLE : VISIBLE);
listTip.setVisibility(b ? GONE : VISIBLE);
}
"再比如,我們還需要與外部的 LayoutManager 以及 Adapter 建立聯(lián)系以及在外部數(shù)據(jù)變動的時候首懈,通知我們進行刷新"
MeRecyclerView.java
public void setLayoutManager(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
refreshList.setLayoutManager(layoutManager);
}
public void setAdapter(RecyclerView.Adapter adapter) {
meRefreshListAdapter = new MeRefreshListAdapter(adapter, mContext, isShowFooter, isNoData);
refreshList.setAdapter(meRefreshListAdapter);
}
public void notifyDataSetChanged() {
meRefreshListAdapter.notifyDataSetChanged();
}
"這還不算完……"
“還沒完绊率?”
“肯定的啊,你想啊究履,頁面剛打開的時候滤否,你是不是得立即去刷新一下,做事總得主動點嘛最仑,所以我們還需要一個可以開始刷新的顽聂。”
“我知道盯仪,是讓頁面進來的時候去刷新一下,開始獲取數(shù)據(jù)對吧蜜葱,我們可以直接調(diào)用下拉刷新的方法啊全景,那樣最省事。就像這樣牵囤“只疲”
public void startRefresh() {
refreshLayout.setRefreshing(true);
onRefresh();
}
“是的,這么樣就行了揭鳞。不過還有一個最最最重要的就是得讓外部把接口實現(xiàn)了炕贵,所以還要這么一個方法∫俺纾”
public void setLoadListener(RefreshLoadListener loadListener) {
this.loadListener = loadListener;
}
"這個称开,我肯定不會晚啊,不過如果忘記了乓梨,肯定都不能用啊鳖轰。我先拿去試試看好不好用啊。"
“……”
“真爽扶镀,用起來特簡單蕴侣。在布局文件里面只用一個控件,就是剛才我們自定義的那個臭觉,然后昆雀,像往常一樣設置 LayoutManager 和 Adapter 不過需要在服務器返回數(shù)據(jù)之后自己手動停止刷新辱志。還是挺簡單的。哦狞膘,對揩懒,還有一個要自己啟動刷新】透裕”
RefreshActivity.java
@BindView(R.id.refresh_demo_list)
MeRecyclerView refreshDemoList;
int page = 1;
int size = 20;
List<Map<String, Object>> dataList;
ListAdapter listAdapter;
LinearLayoutManager layoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh);
ButterKnife.bind(this);
dataList = new ArrayList<>();
listAdapter = new ListAdapter(this, R.layout.item_simgle_check, dataList);
layoutManager = new LinearLayoutManager(this);
refreshDemoList.setLoadListener(this);
refreshDemoList.setLayoutManager(layoutManager);
refreshDemoList.setAdapter(listAdapter);
}
@Override
protected void onResume() {
super.onResume();
refreshDemoList.startRefresh();
}
@Override
public void downRefresh() {
page = 1;
getData();
}
@Override
public void upLoad() {
page += 1;
getData();
}
private void update(List<Map<String, Object>> maps) {
if (page > 5) {
maps.clear();
}
if (maps.size() > 0) {
if (page == 1) {
dataList.clear();
dataList.addAll(maps);
} else {
dataList.addAll(maps);
}
refreshDemoList.notifyDataSetChanged();
refreshDemoList.stopRefresh(page, false);
} else {
refreshDemoList.stopRefresh(page, true);
}
}
"好了旭从,跟你學完了,我還去繼續(xù)寫代碼了场仲,還有什么問題和悦,下次再來問你。希望有一天渠缕,我能不問你也能把問題解決了鸽素。??"