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 可以重複使用
寫程式的效率也自然跟著變高了
以上就是這次落落長的教學文...
Android RecyclerView
No comments:
Post a Comment