Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 用RecyclerView實現新聞列表頁,包括頭部的圖片輪播,兩種Item顯示方式,下拉刷新和上拉加載以及限制列表的加載條目數

用RecyclerView實現新聞列表頁,包括頭部的圖片輪播,兩種Item顯示方式,下拉刷新和上拉加載以及限制列表的加載條目數

編輯:關於Android編程

效果圖: \

1、實現列表頭部的圖片輪播,方式:給RecyclerView添加HeaderView。

RecyclerView默認是沒有添加HeaderView方法的,所以我從網上找到了一種從外部添加View的方式加以實現。 要給RecyclerView添加HeaderView,首先要准備一個View:
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rpv_news_detail"
android:layout_width="match_parent"
android:layout_height="180dp"
app:rollviewpager_play_delay="3000" />

 

這是一個開源的圖片輪播控件,可以實現圖片的循環輪播。在github上可以找到。用法可以到這裡看說明。本節只是說明怎樣給RecyclerView添加HeaderView。 HeaderView已經有了,接著要在RecyclerView的Adapter定義一個方法:
// headerView的setter
public void setHeaderView(View headerView) {
this.headerView = headerView;
notifyItemInserted(0);
}

 

上面的方法接收一個View參數,賦值給Adapter的成員變量headerView,然後調用notify方法通知RecyclerView在0號位置添加了一個Item。這樣就可以達到添加HeaderView的效果。 此方法的調用時機實在給RecyclerView設置Adapter之前,也就是在調用setAdapter方法之前。 到這裡並沒有結束,我們還要繼續在Adapter類裡操作。首先,現在在RecyclerView裡已經不止一種類型的View,所以我們要復寫getItemViewType(int position)方法,在裡面進行View的區分。


@Override
public int getItemViewType(int position) {
if (headerView == null) return NORMAL;
if (position == 0) return HEADER;
return NORMAL;
}
上面的方法很好理解,如果headerView為空則返回普通View,如果在位置0處,則返回Header,其他情況都返回普通View,其中的NORMAL和HEADER是兩個自定義的整型常量,用於區分View,可以是任意不同的整型值。 接著,在onCreateViewHolder方法中通過viewTyper的判斷,做不同的操作。
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (headerView != null && viewType == HEADER)
return new ViewHolder(headerView);

View view = LayoutInflater.from(mContext).inflate(R.layout.item_news_detail_rv, parent, false);
return new ViewHolder(view);
}
}

 

如果是HEADER,則將headerView傳進去,否則,正常的View傳進去。ViewHolder:
public class ViewHolder extends RecyclerView.ViewHolder {
private ImageView iv_item_news_detail_rv;
private TextView tv_title;
private TextView tv_author;
private TextView tv_zan;

public ViewHolder(View itemView) {
super(itemView);
if (itemView == headerView) return;
iv_item_news_detail_rv = (ImageView) itemView.findViewById(R.id.iv_item_news_detail_rv);
tv_title = (TextView) itemView.findViewById(R.id.tv_title_news_detail);
tv_author = (TextView) itemView.findViewById(R.id.tv_author_news_detail);
tv_zan = (TextView) itemView.findViewById(R.id.tv_zan_news_detail);
}
}

 

可以看到在ViewHolder的構造函數裡,判斷itemView的種類,如果是headerView,則直接返回,如果不是,則獲取普通布局裡控件的引用。 最後,在onBindViewHolder中:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

if (getItemViewType(position) == HEADER)
return;
final int pos = getRealPosition(holder);
NewsModel currNM = mList.get(pos);
ViewHolderNormal normalHolder = (ViewHolderNormal) holder;
normalHolder.tv_title.setText(currNM.title);
normalHolder.tv_author.setText(currNM.source);
normalHolder.tv_zan.setText(currNM.replyCount + "跟帖");
Glide.with(mContext).load(currNM.imgsrc ).into(normalHolder.iv_item_news_detail_rv);
}

 

如果是HEADER,則直接返回,如果不是,則填充數據。其中有一句


final int pos = getRealPosition(holder);
因為添加了一個headerView,其他的Item的position都往後移了以為,所以要進行動態計算:

private int getRealPosition(RecyclerView.ViewHolder holder) {
int position = holder.getLayoutPosition();
return headerView == null ? position : position - 1;
}

 

至此,就完成了對RecyclerView的HeaderView的添加。

2、在添加HeaderView的基礎上,實現RecyclerView加載兩種不同布局的Item。

先准備好兩種布局:
android:layout_width="match_parent"
android:layout_height="110dp"
android:orientation="horizontal">

android:id="@+id/iv_item_news_detail_rv"
android:layout_width="120dp"
android:layout_height="110dp"
android:layout_marginLeft="10dp"
android:src="@mipmap/i1" />

android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">


android:id="@+id/tv_title_news_detail"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginLeft="3dp"
android:lineSpacingExtra="10dp"
android:lines="2"
android:paddingLeft="5dp"
android:paddingTop="10dp"
android:text="Snapshots of the development version are available in Sonatype's snapshots repository."
android:textColor="@android:color/black"
android:textSize="18sp" />

android:id="@+id/tv_author_news_detail"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_below="@+id/tv_title_news_detail"
android:layout_marginLeft="3dp"
android:paddingLeft="5dp"
android:text="New Text"
android:textColor="@android:color/darker_gray"
android:textSize="15sp" />

android:id="@+id/tv_zan_news_detail"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_below="@+id/tv_title_news_detail"
android:layout_marginLeft="3dp"
android:layout_toRightOf="@+id/tv_author_news_detail"
android:gravity="right"
android:paddingLeft="5dp"
android:text="New Text"
android:textColor="@android:color/darker_gray"
android:textSize="15sp" />
效果圖: \


android:layout_width="match_parent"
android:layout_height="180dp"
android:orientation="vertical">

android:id="@+id/tv_title_item_image"
android:layout_width="match_parent"
android:layout_height="40dp"
android:padding="10dp"
android:text="標題"
android:textColor="@android:color/black"
android:textSize="17sp" />

android:layout_width="match_parent"
android:layout_height="110dp">

android:id="@+id/iv_left_item_image"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:src="@mipmap/i1" />

android:id="@+id/iv_center_item_image"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:src="@mipmap/i1" />

android:id="@+id/iv_right_item_image"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:src="@mipmap/i1" />


android:layout_width="match_parent"
android:layout_height="30dp">

android:id="@+id/tv_source_item_image"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:paddingLeft="10dp"
android:text="要聞"
android:textColor="@android:color/darker_gray"
android:textSize="16sp" />

android:id="@+id/tv_reply_item_image"
android:layout_width="match_parent"
android:layout_height="30dp"
android:gravity="right"
android:paddingRight="10dp"
android:text="跟帖"
android:textColor="@android:color/darker_gray"
android:textSize="16sp" />

 

效果圖: \ 在Adapter創建兩個對應的ViewHolder類:


public class ViewHolderNormal extends RecyclerView.ViewHolder {
private ImageView iv_item_news_detail_rv;
private TextView tv_title;
private TextView tv_author;
private TextView tv_zan;

public ViewHolderNormal(View itemView) {
super(itemView);
if (itemView == headerView) return;
iv_item_news_detail_rv = (ImageView) itemView.findViewById(R.id.iv_item_news_detail_rv);
tv_title = (TextView) itemView.findViewById(R.id.tv_title_news_detail);
tv_author = (TextView) itemView.findViewById(R.id.tv_author_news_detail);
tv_zan = (TextView) itemView.findViewById(R.id.tv_zan_news_detail);
}
}

public class ViewHolderMultiImage extends RecyclerView.ViewHolder {

private ImageView iv_center;
private ImageView iv_right;
private ImageView iv_item_news_detail_rv;
private TextView tv_title;
private TextView tv_author;
private TextView tv_zan;

public ViewHolderMultiImage(View itemView) {
super(itemView);

iv_item_news_detail_rv = (ImageView) itemView.findViewById(R.id.iv_left_item_image);
iv_center = (ImageView) itemView.findViewById(R.id.iv_center_item_image);
iv_right = (ImageView) itemView.findViewById(R.id.iv_right_item_image);
tv_title = (TextView) itemView.findViewById(R.id.tv_title_item_image);
tv_author = (TextView) itemView.findViewById(R.id.tv_source_item_image);
tv_zan = (TextView) itemView.findViewById(R.id.tv_reply_item_image);
}
}

 

現在由於是三種ViewType,所以修改getItemViewType:


@Override
public int getItemViewType(int position) {
if (headerView == null) {
NewsModel currNM = mList.get(position - 1);
if (currNM.imgextra == null || currNM.imgextra.size() == 0)
return NORMAL;
return MULTIIMAGE;
}
if (position == 0) return HEADER;
NewsModel currNM = mList.get(position - 1);
if (currNM.imgextra == null || currNM.imgextra.size() == 0)
return NORMAL;
return MULTIIMAGE;
}

 

在不是headerView的情況下,根據所獲取的新聞是圖片新聞還是普通新聞,返回對應的ViewType。 在onCreateViewHolder也要增加一些判斷:


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (headerView != null && viewType == HEADER)
return new ViewHolderNormal(headerView);
if (viewType == NORMAL) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_news_detail_rv, parent, false);
return new ViewHolderNormal(view);
}
View view = LayoutInflater.from(mContext).inflate(R.layout.item_image_news_detail_rv, parent, false);
return new ViewHolderMultiImage(view);
}

 

最後在onBindViewHolder中:


@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

if (getItemViewType(position) == HEADER)
return;
final int pos = getRealPosition(holder);
NewsModel currNM = mList.get(pos);
switch (getItemViewType(position)) {
case NORMAL:
ViewHolderNormal normalHolder = (ViewHolderNormal) holder;
normalHolder.tv_title.setText(currNM.title);
normalHolder.tv_author.setText(currNM.source);
normalHolder.tv_zan.setText(currNM.replyCount + "跟帖");
Glide.with(mContext).load(currNM.imgsrc).into(normalHolder.iv_item_news_detail_rv);

break;
case MULTIIMAGE:
ViewHolderMultiImage imageHolder = (ViewHolderMultiImage) holder;
imageHolder.tv_title.setText(currNM.title);
imageHolder.tv_author.setText(currNM.source);
imageHolder.tv_zan.setText(currNM.replyCount + "跟帖");
Glide.with(mContext).load(currNM.imgsrc).into(imageHolder.iv_item_news_detail_rv);

if (currNM.imgextra != null && currNM.imgextra.size() > 1) {
Glide.with(mContext).load(currNM.imgextra.get(0).imgsrc).into(imageHolder.iv_center);
Glide.with(mContext).load(currNM.imgextra.get(1).imgsrc).into(imageHolder.iv_right);
}
break;
}
}

 

代碼都很簡單。這樣就實現了RecyclerView加載兩種不同ViewType的Item。

3、實現下拉刷新。

這裡我用的是android的v4包裡的SwipeRefreshLayout。首先布局文件中,讓SwipeRefreshLayout包裹RecyclerView:


android:id="@+id/srl_news_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:visibility="gone">

android:id="@+id/rv_news_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />

 

接著在頁面中獲取SwipeRefreshLayout的引用,並給其設置一些參數,如刷新進度條的顏色、背景顏色、位置:


// 背景顏色
srl_news_detail.setProgressBackgroundColorSchemeResource(android.R.color.white);
// 進度條變換顏色
srl_news_detail.setColorSchemeResources(android.R.color.holo_blue_light,
android.R.color.holo_red_light, android.R.color.holo_orange_light,
android.R.color.holo_green_light);
// 進度條顯示位置
srl_news_detail.setProgressViewOffset(false, 0, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
.getDisplayMetrics()));

 

最後給SwipeRefreshLayout設置onRefreshListener:


srl_news_detail.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
page = 0;
mPresenter.loadRefreshData(getUrl());
}
});

 

其中的
mPresenter.loadRefreshData(getUrl());

 


就是做的加載數據並更新頁面的操作。


4、實現上拉加載數據以及限制列表的加載條目數。

實現上拉加載是通過監聽RecyclerView的滑動狀態實現的。首先,我們可以通過LayoutManager的findLastVisibleItemPosition獲取到RecyclerView的最後一個可見Item的Position,而通過Adapter的getItemCount方法又可以獲取RecyclerView的總條目數,通過Position + 1 == Count的結果,即可判斷RecyclerView是否已經滑動到了最底部,是的話,就可以做加載更多的操作了。


// 監聽RecyclerView滑動狀態,如果滑動到最後,實現上拉刷新
rv_news_detail.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == adapter.getItemCount()) {
if (adapter.getItemCount() < 360) {
adapter.changeLoadStatus(NewsDetailAdapter.LOADING);
page += 10;
LogUtils.e("NewsDetailPresenter", getUrl());
mPresenter.loadMoreData(getUrl());
} else {
adapter.changeLoadStatus(NewsDetailAdapter.NO_MORE_DATA);
}
}
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
}
});

 

在RecyclerView滑動的時候獲取lastVisibleItemPosition,在RecyclerView的滑動狀態處於空閒且已經滑動到最底部的時候,做加載數據的操作,同時在此時可以限制RecyclerView的加載條目數,在上面的onScrollStateChanged方法中,我調用Adapte的getItemCount方法獲取現在RecyclerView裡的條目數,如果其大於等於360條的話,則不做加載更多數據,只提示沒有更多數據了。 上面已經實現了上拉加載功能,為了讓界面更友好一點,我們可以給RecyclerView添加一個FooterView,用於提示用戶做一些操作。 首先准備一個FootView布局:


android:id="@+id/tv_footer"
android:layout_width="match_parent"
android:layout_height="90dp"
android:gravity="center"
android:padding="10dp"
android:text="上拉刷新..."
android:textColor="@android:color/darker_gray"
android:textSize="18sp" />
一個TextView,用於顯示一些文字,提示用戶可以做的操作或應用正在做的操作。 修改Adapter的getItemViewType方法:

@Override
public int getItemViewType(int position) {
if (headerView == null) {
NewsModel currNM = mList.get(position - 1);
if (currNM.imgextra == null || currNM.imgextra.size() == 0)
return NORMAL;
return MULTIIMAGE;
}
if (position == 0) return HEADER;
if (position + 1 == getItemCount()) return FOOTER;
NewsModel currNM = mList.get(position - 1);
if (currNM.imgextra == null || currNM.imgextra.size() == 0)
return NORMAL;
return MULTIIMAGE;
}

 

在Adapter中增加一個ViewHolder:


public class ViewHolderFooter extends RecyclerView.ViewHolder {
private TextView text;

public ViewHolderFooter(View itemView) {
super(itemView);
text = (TextView) itemView;
}
}

 

修改Adapter的onCreateViewHolder方法:


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (headerView != null && viewType == HEADER)
return new ViewHolderNormal(headerView);
if (viewType == NORMAL) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_news_detail_rv, parent, false);
return new ViewHolderNormal(view);
}

if (viewType == FOOTER) {
TextView view = (TextView) LayoutInflater.from(mContext).inflate(R.layout.layout_footer, parent, false);
return new ViewHolderFooter(view);
}

View view = LayoutInflater.from(mContext).inflate(R.layout.item_image_news_detail_rv, parent, false);
return new ViewHolderMultiImage(view);
}

 

修改Adapter的onBindViewHolder方法:


@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

if (getItemViewType(position) == HEADER)
return;

if (getItemViewType(position) == FOOTER) {
ViewHolderFooter footerHolder = (ViewHolderFooter) holder;
switch (load_status) {
case LOAD_MORE:
footerHolder.text.setText("上拉加載更多...");
break;
case LOADING:
footerHolder.text.setText("正在加載更多數據...");
break;
case NO_MORE_DATA:
footerHolder.text.setText("沒有更多數據");
break;
}
return;
}


final int pos = getRealPosition(holder);
NewsModel currNM = mList.get(pos);
switch (getItemViewType(position)) {
case NORMAL:
ViewHolderNormal normalHolder = (ViewHolderNormal) holder;
normalHolder.tv_title.setText(currNM.title);
normalHolder.tv_author.setText(currNM.source);
normalHolder.tv_zan.setText(currNM.replyCount + "跟帖");
Glide.with(mContext).load(currNM.imgsrc).into(normalHolder.iv_item_news_detail_rv);

break;
case MULTIIMAGE:
ViewHolderMultiImage imageHolder = (ViewHolderMultiImage) holder;
imageHolder.tv_title.setText(currNM.title);
imageHolder.tv_author.setText(currNM.source);
imageHolder.tv_zan.setText(currNM.replyCount + "跟帖");
Glide.with(mContext).load(currNM.imgsrc).into(imageHolder.iv_item_news_detail_rv);

if (currNM.imgextra != null && currNM.imgextra.size() > 1) {
Glide.with(mContext).load(currNM.imgextra.get(0).imgsrc).into(imageHolder.iv_center);
Glide.with(mContext).load(currNM.imgextra.get(1).imgsrc).into(imageHolder.iv_right);
}
break;
}
}

 

看到上面的方法中可以根據不同的狀態更改FooterView的文字內容,可以在Adapter中定義一個public方法,用於改變status:


// 改變Footer布局的文字
public void changeLoadStatus(int status) {
load_status = status;
notifyDataSetChanged();
}

 

這樣,在上拉加載的時候,用戶就可以看到對應的提示。正如開頭的圖片中展示的那樣。   完整源碼參看:https://github.com/miyuexingchen/Swen/blob/master/app/src/main/java/com/wcc/swen/adapter/NewsDetailAdapter.java
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved