RecyclerView 的基本使用
2017年1月21日
為了加強理解 RecyclerView 的使用方法,同時讓自己的寫作能力得到鍛煉,所以實踐一把,都是一些基本的使用,主要分析下拉刷新與上拉加載部分,細節很重要,以此來激勵自己寫出高質量的文章。
- 基本用法
- 基本介紹
- 代碼實現
-
SwipeRefreshLayout 配合實現下拉刷新
- 基本介紹
- 代碼實現
-
RecyclerView 實現上拉加載
- 滾動事件的分析
- 添加 FooterView 實現上拉加載
- 參考文章及總結
效果圖
基本使用
RecyclerView 的基本介紹
RecyclerView 是谷歌V7包下新增的控件,用來替代 ListView 的使用,在 RecyclerView 標準化了 ViewHolder 類似于 ListView中 convertView 用來做視圖緩存.
相信大家對于 ListView 都很熟悉,在我剛學 Android 開發的時候接觸就是這個強大的控件,也被稱為最難的控件之一。自從 RecyclerView 已經面市很久,也在很多應用中得到廣泛的使用,在整個開發者圈子里面也擁有很不錯的口碑,那說明 RecyclerView 擁有比 ListView,GridView 之類控件有很多的優點。
- 設置布局管理器以控制Item的布局方式,橫向、豎向以及瀑布流方式
- 可設置Item操作的動畫(刪除或者添加等)
- 可設置Item的間隔樣式(可繪制)
但是對于點擊事件需要自己寫回調借口實現,下面會詳細介紹,并且沒有了 ListView 強大的 addFootView() 的方法,需要自己去實現,不過還好拓展性很強。
代碼實現
1.添加庫依賴:首先要用這個控件,你需要在gradle文件中添加包的引用(配合官方CardView使用)
compile 'com.android.support:recyclerview-v7:25.0.0'
compile 'com.android.support:cardview-v7:25.0.0'
2.然后在 xml 文件里實現
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler_view"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"/>
3.接著就是在 Activity 里面設置
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private RecyclerAdapter mAdapter;
private LinearLayoutManager mLayoutManager;
private List<ShareBean> mData = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();//初始化數據
initView();//初始化布局
setListener();//設置監聽事件
}
private void initData() {
for (int i = 0; i < 4; i++) {
mData.add(new ShareBean(R.mipmap.ic_image, "Android vs IOS"));
}
}
private void initView() {
mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
//設置布局管理器
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
//確保尺寸是一個常數,避免計算每個item的size
mRecyclerView.setHasFixedSize(true);
//設置顯示動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new RecyclerAdapter(this, mData);
mRecyclerView.setAdapter(mAdapter);
}
private void setListener() {
//設置Items的點擊事件
mAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(final View view, int position) {
onClick(view, position);
}
});
}
}
4.適配器 Adapter 的代碼
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public OnItemClickListener itemClickListener;
private List<ShareBean> mData = null;
private LayoutInflater mInflater;
public RecyclerAdapter(Context context, List<ShareBean> mData) {
this.mData = mData;
this.mInflater = LayoutInflater.from(context);
}
//將布局轉化為 View 并傳遞給 RecyclerView 封裝好的 ViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
// 實例化展示的view
View view = mInflater.inflate(R.layout.item_recyclerview, parent, false);
// 實例化viewholder
return new ItemViewHolder(view);
}
//將數據與視圖進行綁定
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((ItemViewHolder) holder).mImageView.setImageResource(mData.get(position).getImage_id());
((ItemViewHolder) holder).mTextView.setText(mData.get(position).getName());
}
//返回 Item 的數量
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();;
}
public class ItemViewHolder extends RecyclerView.ViewHolder {
CardView mCardView;
CircleImageView mImageView;
TextView mTextView;
public ItemViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.textView);
mCardView = (CardView) itemView.findViewById(R.id.cardView);
mImageView = (CircleImageView) itemView.findViewById(R.id.image);
mCardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (itemClickListener != null) {
itemClickListener.onItemClick(view, getPosition());
}
}
});
}
}
//以下為點擊事件的接口回調部分
public void setOnItemClickListener(OnItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
}
以上是適配器基本的寫法,這個自定義 Adapter 和我們在使用 Listview 時候的 Adapter 相比還是有點不太一樣的,首先這邊我們需要繼承 RecyclerView.Adaper 類,然后實現兩個重要的方法 onBindViewHodler() 以及 onCreateViewHolder() ,這邊我們看出來區別,使用 RecyclerView 控件我們就可以把 ItemView 視圖創建和數據綁定這兩步進行分來進行管理,用法就更加方便而且靈活了,并且我們可以定制打造千變萬化的布局。同時這邊我們還需要創建一個 ViewHolder 類,該類必須繼承自 RecyclerView.ViewHolder 類,現在 Google 也要求我們必須要實現 ViewHolder 來承載 Item 的視圖。
特別注意一下最下面的點擊事件的回調函數,主要是對接口的熟悉程度,Java 是硬傷 55555.
同時 RecyclerView 有三種實現方式(上面介紹有),通過一下代碼設置
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//這里用線性顯示 類似于listview
mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//這里用線性宮格顯示 類似于grid view
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//這里用線性宮格顯示 類似于瀑布流
SwipeRefreshLayout 配合實現下拉刷新
基本介紹
SwipeRefrshLayout 是 Google 官方更新的一個 Widget ,可以實現下拉刷新的效果。該控件集成自 ViewGroup 在 support-v4 兼容包下,不過我們需要升級 supportlibrary 的版本到19.1以上。基本使用的方法如下:
- setOnRefreshListener(OnRefreshListener):添加下拉刷新監聽器
- setRefreshing(boolean):顯示或者隱藏刷新進度條
- isRefreshing():檢查是否處于刷新狀態
- setColorSchemeResources():設置進度條的顏色主題,最多設置四種,以前的setColorScheme()方法已經棄用了
代碼實現
1.首先先看一下 xml 布局,在 RecyclerView 布局外部嵌套一層 SwipeRefreshLayout 布局即可。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"/>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
2.在 Activity 中獲取 SwipeRefreshLayout 控件并且設置 OnRefreshListener 監聽器,同時實現里邊的 onRefresh() 方法,在該方法中進行網絡請求最新數據,然后刷新 RecyclerView 列表同時設置 SwipeRefreshLayout 的進度Bar的隱藏或者顯示效果。具體代碼如下:
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
addTopData();
}
});
private void addTopData() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
mData.add(i, new ShareBean(R.mipmap.ic_image_h, "下拉刷新數據" + i));
}
mAdapter.notifyDataSetChanged();
mSwipeRefreshLayout.setRefreshing(false);
}
}, 1000);
}
RecyclerView 實現上拉加載
滾動事件的分析
1.RecyclerView 本身已經提供了滑動的監聽接口,OnScrollListener,這個接口包含了以下的方法,代碼注釋介紹。
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
//當recycleView的滑動狀態改變時回調
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
//當RecycleView滑動之后被回調
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
2.RecyclerView 的滑動狀態
當前的recycleView不滑動(滑動已經停止時):
public static final int SCROLL_STATE_IDLE = 0;當前的recycleView被拖動滑動:
public static final int SCROLL_STATE_DRAGGING = 1;當前的recycleView在滾動到某個位置的動畫過程,但沒有被觸摸滾動.調用 scrollToPosition(int) 應該會觸發這個狀態:
public static final int SCROLL_STATE_SETTLING = 2;
3.滑動位置的監聽
3.1 以下是最簡單的頂部/底部的判斷方式:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
//當recycleView的滑動狀態改變時回調
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//獲取最后一個可見view的位置
int lastItemPosition = linearManager.findLastVisibleItemPosition();
//獲取第一個可見view的位置
int firstItemPosition =linearManager.findFirstVisibleItemPosition();
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastItemPosition + 1 == adapter.getItemCount()) {
//最后一個itemView的position為adapter中最后一個數據時,說明該itemView就是底部的view了
//需要注意position從0開始索引,adapter.getItemCount()是數據量總數
}
//同理檢測是否為頂部itemView時,只需要判斷其位置是否為0即可
if (newState == RecyclerView.SCROLL_STATE_IDLE && firstItemPosition == 0) {}
}
});
以上的實現方式存在諸多問題,比如:
- itemView會在滑動過程中只顯示一部分或者一半
- 檢測數據不夠時的RecycleView
3.2 對于顯示不全的問題
也就是說:當某一個itemView只顯示一部分的時候,此時已經算是一個position了,此時很可能去觸發判斷條件。所以官方 Api 里有一下兩個方法:
- findFirstCompletlyVisibleItemPosition()
- findLastCompletlyVisibleItemPosition()
就可以盡可能的避免這種問題。
3.3 檢測數據不夠時的狀態
如果為了避免這種情況,我們就需要對在檢測時多進行一步,若當前的RecycleView顯示的itemView不滿屏的情況下,其實并不存在滑動的說法(沒有加載更多,因為根本數據還沒有滿屏顯示),至于下拉刷新的,還是可以的,但一般都會使用SwipeRefreshLayout實現下拉刷新了.因此這里主要考慮的是關于滑動到底部加載更多的問題.
3.4 上拉加載更多代碼實現
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {//向下滾動
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (!loading && (visibleItemCount + pastVisiblesItems) == totalItemCount) {
loading = true;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
mData.add(new ShareBean(R.mipmap.ic_image_s, "上拉加載數據" + i));
}
mAdapter.notifyDataSetChanged();
loading = false;
}
}, 3000);
}
}
}
});
添加 FooterView 實現上拉加載
接下來完善UI,讓用戶知道確實在上拉加載的過程。用RecyclerView實現,使用 getItemType() 方法,就與 ListView 差不多了。不說多了,直接上完整代碼(注釋很清晰):
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public OnItemClickListener itemClickListener;
private List<ShareBean> mData = null;
private LayoutInflater mInflater;
//1.加入布局狀態標志-用來判斷此時加載是普通Item還是footView
//沒有更多了
public static final int NO_DATA_MORE = 0;
//上拉加載更多
public static final int PULLUP_LOAD_MORE = 1;
//正在加載中
public static final int LOADING_MORE = 2;
private int load_more_status = 0;
private static final int TYPE_ITEM = 0; //普通Item View
private static final int TYPE_FOOTER = 1; //頂部FootView
public RecyclerAdapter(Context context, List<ShareBean> mData) {
this.mData = mData;
this.mInflater = LayoutInflater.from(context);
}
//4.接著onCreateViewHolder(ViewGroup parent,int viewType)加載布局的時候根據viewType的類型來選擇指定的布局創建,返回即可:
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 將布局轉化為View并傳遞給Recycler封裝好的ViewHolder
if (viewType == TYPE_ITEM) {
View view = mInflater.inflate(R.layout.item_recyclerview, parent, false);
return new ItemViewHolder(view);
} else if (viewType == TYPE_FOOTER) {
View foot_view = mInflater.inflate(R.layout.part_footer, parent, false);
return new BottomViewHolder(foot_view);
}
return null;
}
//5.最后進行判斷數據的時候(onBindViewHolder),判斷holder的類型來進行判定數據即可.
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ItemViewHolder) {
((ItemViewHolder) holder).mImageView.setImageResource(mData.get(position).getImage_id());
((ItemViewHolder) holder).mTextView.setText(mData.get(position).getName());
} else if (holder instanceof BottomViewHolder) {
BottomViewHolder footViewHolder = (BottomViewHolder) holder;
switch (load_more_status) {
case PULLUP_LOAD_MORE:
footViewHolder.mFooterTv.setText("上拉加載更多...");
footViewHolder.mProgressBar.setVisibility(View.VISIBLE);
break;
case LOADING_MORE:
footViewHolder.mFooterTv.setText("正在加載中...");
footViewHolder.mProgressBar.setVisibility(View.VISIBLE);
break;
case NO_DATA_MORE:
footViewHolder.mFooterTv.setText("----我是有底線的----");
footViewHolder.mProgressBar.setVisibility(View.GONE);
break;
}
}
}
//3.重寫getItemViewType方法來判斷返回加載的布局的類型
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
//2.返回item數量
@Override
public int getItemCount() {
return mData.size() + 1;
}
/**
* //上拉加載更多
* PULLUP_LOAD_MORE=0;
* //正在加載中
* LOADING_MORE=1;
* //加載完成已經沒有更多數據了
* NO_MORE_DATA=2;
*
* @param status
*/
public void changeMoreStatus(int status) {
load_more_status = status;
notifyDataSetChanged();
}
public class ItemViewHolder extends RecyclerView.ViewHolder {
CardView mCardView;
CircleImageView mImageView;
TextView mTextView;
public ItemViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.textView);
mCardView = (CardView) itemView.findViewById(R.id.cardView);
mImageView = (CircleImageView) itemView.findViewById(R.id.image);
mCardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (itemClickListener != null) {
itemClickListener.onItemClick(view, getPosition());
}
}
});
}
}
/**
* 底部FootView布局
*/
public class BottomViewHolder extends RecyclerView.ViewHolder {
ProgressBar mProgressBar;
TextView mFooterTv;
public BottomViewHolder(View itemView) {
super(itemView);
mProgressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
mFooterTv = (TextView) itemView.findViewById(R.id.footer_tv);
}
}
public void setOnItemClickListener(OnItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
}
終于分析完了,感覺滑動需要繼續探究,,,嗯嗯,先就這樣吧。
參考文章及總結
以上說對基礎知識的一些梳理,存在諸多不足,請多指正。
主要參考文章: