2015-10-17
11:10 AM
RecyclerView 是 Android 釋出用以取代 ListView 的元件
他比 ListView 效能更好 資源運用更靈活
不過在初始化方面是稍微複雜了點
現在我們就來一步步介紹該怎麼加入 RecyclerView 並使用 SwipeRefreshLayout
實作向下滑動更新的功能
首先我們先來看看 xml layout 的部分
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/favItemHolder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/pullToRefreshCateRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<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/cateRecyclerView"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"/>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
</LinearLayout>
這時你會看到右方預覽介面是空白的
這是正常的結果所以不用擔心
接下來我們要新增 RecyclerView 裡面的 Item Layout
並將內容置中
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/fav_item_background_shape"
android:layout_marginBottom="5px"
android:layout_marginTop="10px"
android:layout_marginLeft="5px"
android:layout_marginRight="5px">
<LinearLayout
android:paddingLeft="5dp"
android:paddingTop="3dp"
android:paddingRight="5dp"
android:paddingBottom="5dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/cateItemTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="#E7E7E7"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:text="Title"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10px">
<TextView
android:id="@+id/cateItemDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Description of this item."
android:textColor="#E1E1E1"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
</LinearLayout>
</FrameLayout>
看起來就會像這樣
那麼介面完成了 現在就要開始程式碼的部分
RecyclerView 需要一個 Adapter 配合
主要用途是 Item 的操作, 以及 layout 的載入
我們先來看 Adapter 該如何建立
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.ihad.ptt.model.bean.CategoryBean;
import java.util.Map;
import java.util.Set;
public class CateRecyclerAdapter extends RecyclerView.Adapter<CateRecyclerAdapter.ItemHolder> {
// 物件儲存 Map
private Map<Integer, CategoryBean> categoryBeans;
// 點擊處理
private ItemHolder.IClickHandler clickHandler;
public CateRecyclerAdapter(Map<Integer, CategoryBean> categoryBeans, ItemHolder.IClickHandler handler) {
this.categoryBeans = categoryBeans;
this.clickHandler = handler;
}
// 載入 Layout
@Override
public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cate_item, parent, false);
// create ViewHolder
ItemHolder viewHolder = new ItemHolder(itemLayoutView, clickHandler);
return viewHolder;
}
// 更新 View
@Override
public void onBindViewHolder(ItemHolder holder, int position) {
CategoryBean categoryBean = categoryBeans.get( position + 1 );
if( categoryBean == null ) return;
holder.cateItemTitle.setText( categoryBean.getName() );
holder.cateItemDesc.setText( categoryBean.getDesc() );
}
// 取得已加入 Item 數量
@Override
public int getItemCount() {
return categoryBeans.size();
}
// 取得 Item
public CategoryBean getItem(int key){
return categoryBeans.get( key );
}
// 已加入 Item 是否重複判斷
public boolean isDuplicate( Map<Integer, CategoryBean> categoryBeanMap ){
if( !categoryBeans.isEmpty() ){
Set<Integer> keySet = categoryBeanMap.keySet();
int foundCount = 0;
for (Integer key : keySet) {
if ( categoryBeans.containsKey(key) ) foundCount++;
}
if ( foundCount == categoryBeanMap.size() ) return true;
}
return false;
}
// 加入 Item
public void addItem(CategoryBean categoryBean){
categoryBeans.put( categoryBean.getSerialNum(), categoryBean );
}
// 清除所有 Item
public void clear(){
this.notifyItemRangeRemoved( 0, categoryBeans.size() );
categoryBeans.clear();
}
// Item
public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView cateItemTitle;
public TextView cateItemDesc;
public IClickHandler clickHandler;
public ItemHolder(View view, IClickHandler handler) {
super(view);
view.setOnClickListener(this);
cateItemTitle = (TextView) view.findViewById(R.id.cateItemTitle);
cateItemDesc = (TextView) view.findViewById(R.id.cateItemDesc);
clickHandler = handler;
}
@Override
public void onClick(View view) {
int position = getLayoutPosition();
clickHandler.onClick(view, position);
}
public interface IClickHandler {
void onClick(View caller, int position);
}
}
}
這個部分應該沒什麼問題
接下來我們來看該如何連結所有的 Layout 及 Adapter 並將 Item 讀入 RecyclerView
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.Toast;
import cmc.toolkit.CTrace;
import com.ihad.ptt.model.bean.CategoryBean;
import com.ihad.ptt.model.bean.FavoriteBoardBean;
import roboguice.fragment.RoboFragment;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class CategoryFragment extends RoboFragment {
public static final String CATE_PAGE = "CATE_PAGE";
private int mPage;
// 滑動更新用
private SwipeRefreshLayout cateSwipeRefreshLayout;
private SwipeRefreshLayout cateSubSwipeRefreshLayout;
// RecyclerView 的呈現方式
// 有線性(LinearLayoutManager), 塊狀(GridLayoutManager) 以及不規則塊狀(StaggeredGridLayoutManager) 三種
private LinearLayoutManager mCateLinearLayoutManager;
// Adapter
private CateRecyclerAdapter mCateAdapter;
// 是否正在讀取
private boolean isCateLoading = false;
// 是否已讀取完整資料
private boolean noCateMoreData = false;
// 若是在 Activity 中建立的不需要管這個 本例為使用 Fragment 加載
public static CategoryFragment newInstance(int page) {
Bundle args = new Bundle();
args.putInt(CATE_PAGE, page);
CategoryFragment fragment = new CategoryFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPage = getArguments().getInt(CATE_PAGE);
// 分類目錄
mCateAdapter = new CateRecyclerAdapter( new LinkedHashMap<Integer, CategoryBean>(), new CateRecyclerAdapter.ItemHolder.IClickHandler() {
@Override
public void onClick(View view, int position) {
// 使用 Position 取得物件 這裡 +1 是因為我使用 1 based 方式建立的序號
CategoryBean categoryBean = mCateAdapter.getItem( position + 1 );
if( categoryBean == null ) {
Toast.makeText(view.getContext(), "Find nothing", Toast.LENGTH_SHORT).show();
}
else{
// 點擊後的處理 可自行設定 若是在 Activity 新增則不須再取得 Activity
MainActivity mainActivity = (MainActivity) getActivity();
mainActivity.goCateSubList(categoryBean);
}
}
});
}
// 取得 Layout 並初始化各項元件
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// 這個是放 RecyclerView 的 Layout
View view = inflater.inflate(R.layout.cate_frag_page, container, false);
cateSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.pullToRefreshCateRecycler);
// 設定滑動更新 loader 的顏色 預設為黑色
cateSwipeRefreshLayout.setColorSchemeColors(0xFF4FC3F7, 0xFF8558E0, 0xFFFF326F, 0xFFF9F765);
// 滑動更新監聽設定
cateSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// 清除所有 Item
mCateAdapter.clear();
// 呼叫重新整理 function
MainActivity mainActivity = (MainActivity) getActivity();
mainActivity.reloadCatePage();
// 設定為尚有資料
noCateMoreData = false;
}
});
// new 一個 LinearLayoutManager 結果將呈現為縱向捲動清單
mCateLinearLayoutManager = new LinearLayoutManager(getActivity());
mCateLinearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
// 取得 RecyclerView
RecyclerView cateRecyclerView = (RecyclerView) view.findViewById(R.id.cateRecyclerView);
// 底下一併介紹其他呈現方式的設定方法
// List layout
cateRecyclerView.setLayoutManager(mCateLinearLayoutManager);
// Grid layout
//cateRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));
// StaggeredGrid layout
//cateRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));
// 將 Adapter 指定給 RecyclerView
cateRecyclerView.setAdapter(mCateAdapter);
// 加入動畫 若指定 null 就不會有動畫
cateRecyclerView.setItemAnimator(new DefaultItemAnimator());
// 向下捲動自動加載新資料
cateRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int visibleItemCount = mCateLinearLayoutManager.getChildCount();
int totalItemCount = mCateLinearLayoutManager.getItemCount();
int pastVisiblesItems = mCateLinearLayoutManager.findFirstVisibleItemPosition();
// 視情況修改在第幾項 Item 顯示時自動讀取下一頁
// 本範例為單次讀取 20 筆資料, 捲動至第 10 筆時自動讀取下一頁
if( totalItemCount > 10 ){
totalItemCount = totalItemCount - 10;
}
// 確保為向下捲動
if( dy < 0 ) return;
// 用來控制重複讀取以及已無新資料的狀況
if (!isCateLoading && !noCateMoreData) {
// 顯示項目已超過十項 自動讀取資料
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
isCateLoading = true;
// 呼叫讀取下一頁資料的 function
MainActivity mainActivity = (MainActivity) getActivity();
mainActivity.nextCatePage();
}
}
}
});
return view;
}
// 設定為讀取中 顯示 loader
public void cateRefreshing(boolean refreshing){
if( cateSwipeRefreshLayout == null ) return;
cateSwipeRefreshLayout.setRefreshing(refreshing);
}
// 加入讀取完成的資料
public boolean addCateItems(Map<Integer, CategoryBean> categoryBeanMap){
Set<Integer> keySet = categoryBeanMap.keySet();
for( Integer key : keySet ){
CategoryBean categoryBean = categoryBeanMap.get(key);
mCateAdapter.addItem(categoryBean);
// 因資料建立時是使用 1 based 方式所以需要 - 1
mCateAdapter.notifyItemChanged(categoryBean.getSerialNum() - 1);
}
return false;
}
// 資料已加入 設定為可讀取下一頁資料
public void addCateFinished(){
isCateLoading = false;
}
}
到這裡就完成了
本範例使用的雖然是 Fragment 但大致上使用方式使相同的
若要在直接在 Activity 中加入 RecyclerView 方法是一樣的
但 Adapter 的建立就不需要分開在不同的地方
Fragment 是因為若不先在 OnCreate 時建立 Adapter
而在 OnCreateView 內建立的話會有問題
Log 會顯示 RecyclerView 沒有配對的 Adapter 將忽略載入
這種情況即使你有將物件加入 Adapter 也會呈現空白的資料
原因目前還不清楚 但若在 Activity 內就不會有這個問題
以下是在 Activity 內建立的範例
@InjectView(R.id.pullToRefreshCateRecycler)
private SwipeRefreshLayout cateSwipeRefreshLayout;
@InjectView(R.id.cateRecyclerView)
private RecyclerView cateRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCateAdapter = new CateRecyclerAdapter( new LinkedHashMap<Integer, CategoryBean>(), new CateRecyclerAdapter.ItemHolder.IClickHandler() {
@Override
public void onClick(View view, int position) {
CategoryBean categoryBean = mCateAdapter.getItem( position + 1 );
if( categoryBean == null ) {
Toast.makeText(view.getContext(), "Find nothing", Toast.LENGTH_SHORT).show();
}
else{
goCateSubList(categoryBean);
}
}
});
cateSwipeRefreshLayout.setColorSchemeColors(0xFF4FC3F7, 0xFF8558E0, 0xFFFF326F, 0xFFF9F765);
cateSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mCateAdapter.clear();
reloadCatePage();
noCateMoreData = false;
}
});
mCateLinearLayoutManager = new LinearLayoutManager(getActivity());
mCateLinearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
RecyclerView cateRecyclerView = (RecyclerView) view.findViewById(R.id.cateRecyclerView);
cateRecyclerView.setAdapter(mCateAdapter);
cateRecyclerView.setItemAnimator(new DefaultItemAnimator());
cateRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int visibleItemCount = mCateLinearLayoutManager.getChildCount();
int totalItemCount = mCateLinearLayoutManager.getItemCount();
int pastVisiblesItems = mCateLinearLayoutManager.findFirstVisibleItemPosition();
if( totalItemCount > 10 ){
totalItemCount = totalItemCount - 10;
}
if( dy < 0 ) return;
if (!isCateLoading && !noCateMoreData) {
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
isCateLoading = true;
nextCatePage();
}
}
}
});
}
就這樣囉
一開始可能稍嫌複雜, 但習慣後其實滿方便的
同一個 Adapter 可以重複使用
寫程式的效率也自然跟著變高了
以上就是這次落落長的教學文...
各項資料連結
Creating Lists and Cards
Android RecyclerView