一. 簡介
RecyclerView 是android5.0提出的代替ListView的新控件,還可以實現GridView的效果,自帶分割線,也可以自定義分割線,增加List顯示的美觀性,而新增的LayoutManager可用來確定item的排列方式,可以通過LayoutManager來設置list要展示的是垂直還是水平,還添加了默認的增加和刪除item動畫。
二. 配置
在model下添加:
compile 'com.android.support:recyclerview-v7:21.0.0'
三. RecyclerView的使用和下拉刷新,上拉加載更多
(1)簡單佈局
activity_layout.xml佈局:
<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" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout>
注:在佈局中SwipeRefreshLayout是下拉加載的控件,需要包裹RecyclerView.
recycler_layout.xml list的佈局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="45dp" android:orientation="vertical"> <TextView android:id="@+id/tv_recycler" android:gravity="center" android:background="#0f0" android:textColor="#f00" android:textSize="20dp" android:layout_margin="3dp" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
footview_layout.xml上拉加載更多的foot佈局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/foot" android:layout_width="match_parent" android:layout_height="45dp" android:gravity="center" android:textSize="15sp" android:background="#fff" android:layout_gravity="center" android:layout_marginBottom="1dp"/> </LinearLayout>
注:列表佈局我只顯示了一個TextView,簡單明瞭。需要注意的是父控件的高度是wrap_content,這樣當TextView隱藏的時候,LinnearLayout也就跟着隱藏了,從而達到foot的整體隱藏。
(2)RecyclerView適配器
都知道寫ListView展示數據都需要適配器,RecyclerView也不例外
RecyclerViewAdapter.java :
import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.List; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyHolder> { List<String> list; //數據集合 Context context; public RecyclerViewAdapter(List<String> list, Context context){ this.list = list; this.context = context; } //初始化item佈局 @Override public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.recycler_layout, parent, false); MyHolder holder = new MyHolder(view); return holder; } //想要怎麼處理item,等同於BaseAdapter的getView @Override public void onBindViewHolder(MyHolder holder, int position) { holder.textView.setText(list.get(position)); } //展示的數量 @Override public int getItemCount() { return list.size(); } //自動的HolderView優化,初始化item控件 public class MyHolder extends RecyclerView.ViewHolder { TextView textView; public MyHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.tv_recycler); } } }
注:RecyclerView的適配器跟ListView略不同,但可以看出解耦性很高。
(3)MainActivity.java 下拉刷新,上拉加載更多整體實現代碼
import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private SwipeRefreshLayout swipe; private List<String> list; private int lastVisibleItem = 0; private final int PAGE_COUNT = 20; private GridLayoutManager mLayoutManager; private ItemAdapter adapter; private Handler mHandler = new Handler(Looper.getMainLooper()); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); } //添加演示數據 private void initData() { list = new ArrayList<>(); for (int i = 1; i <= 50; i++) list.add("item " + i); //下拉刷新 swipe = (SwipeRefreshLayout) findViewById(R.id.swipe); swipe.setColorSchemeColors(Color.RED); //設置下拉刷新的動畫顏色 swipe.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { //下拉刷新的處理事件 @Override public void onRefresh() { swipe.setRefreshing(true); Log.i("textShow", "now : " + adapter.getRealLastPosition()); updateRecyclerView2(0, adapter.getRealLastPosition());//刷新加載50條數據 mHandler.postDelayed(new Runnable() { @Override public void run() { swipe.setRefreshing(false); //關閉下拉刷新,此處做下拉刷新的結果處理 } }, 1000); } }); } private void initView() { recyclerView = (RecyclerView) findViewById(R.id.recyclerView); adapter = new ItemAdapter(getDatas(0, PAGE_COUNT), this, getDatas(0, PAGE_COUNT).size() > 0 ? true : false); mLayoutManager = new GridLayoutManager(this, 1); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setAdapter(adapter); // recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); // 實現上拉加載重要步驟,設置滑動監聽器,RecyclerView自帶的ScrollListener recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); // 在newState爲滑到底部時 if (newState == RecyclerView.SCROLL_STATE_IDLE) { // 如果沒有隱藏footView,那麼最後一個條目的位置就比我們的getItemCount少1,自己可以算一下 if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Override public void run() { // 然後調用updateRecyclerview方法更新RecyclerView if (adapter.getRealLastPosition() == list.size()) updateRecyclerView(0, 0); else updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); Log.i("textShow", "sum1 = " + adapter.getRealLastPosition()); } }, 500); } // 如果隱藏了提示條,我們又上拉加載時,那麼最後一個條目就要比getItemCount要少2 if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Override public void run() { Log.i("textShow", "sum2 = " + adapter.getRealLastPosition()); updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); } }, 500); } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // 在滑動完成後,拿到最後一個可見的item的位置 lastVisibleItem = mLayoutManager.findLastVisibleItemPosition(); // recyclerView.scrollToPosition(49); } }); } //還有數據需要加載 private List<String> getDatas(final int firstIndex, final int lastIndex) { List<String> resList = new ArrayList<>(); for (int i = firstIndex; i < lastIndex; i++) { if (i < list.size()) { resList.add(list.get(i)); } } return resList; } // 上拉加載時調用的更新RecyclerView的方法 private void updateRecyclerView(int fromIndex, int toIndex) { // 獲取從fromIndex到toIndex的數據 List<String> newDatas = getDatas(fromIndex, toIndex); if (newDatas.size() > 0) { // 然後傳給Adapter,並設置hasMore爲true adapter.updateList(newDatas, true); } else { adapter.updateList(null, false); } } // 下拉刷新時調用的更新RecyclerView的方法 private void updateRecyclerView2(int fromIndex, int toIndex) { // 獲取從fromIndex到toIndex的數據 List<String> newDatas = getDatas(fromIndex, toIndex); if (newDatas.size() > 0) { // 然後傳給Adapter,並設置hasMore爲true adapter.updateList2(newDatas, false); } else { adapter.updateList2(null, false); } } }
四. 添加分割線
(1)新建MyItemDecoration.java類,用於分割線的處理
package com.jmg.recyclerviewtext; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; public class MyItemDecoration extends RecyclerView.ItemDecoration { /* * 定義4個常量值,代表佈局方向,分別是豎向線性佈局、橫向線性佈局、豎向網格佈局、橫向網格佈局 */ public static final int LINEAR_LAYOUT_ORIENTATION_VERTICAL = 0; public static final int LINEAR_LAYOUT_ORIENTATION_HORIZONTAL = 1; public static final int GRID_LAYOUT_ORIENTATION_VERTICAL = 2; public static final int GRID_LAYOUT_ORIENTATION_HORIZONTAL = 3; private int orientation = -1; // 當前的佈局方向 // 如果是網格佈局我們要計算出每一行或者每一列(取決於佈局方向)中的子項數目 private int rawOrColumnSum = 0; // Drawable 對象用於繪製分隔線 private Drawable myDivider = null; public MyItemDecoration(Context context, int orientation) { /* 這個構造方法用於處理線性佈局傳入的情況,我們要對myDivider對象進行初始化 * (繪製的顏色和寬度等等) * R.drawable.my_list_divider 是我們自定義的一個drawable資源文件,我們通過 * myContext來獲取它 */ myDivider = context.getResources().getDrawable(R.drawable.my_list_divider); if(orientation == LinearLayoutManager.HORIZONTAL) { this.orientation = LINEAR_LAYOUT_ORIENTATION_HORIZONTAL; }else if(orientation == LinearLayoutManager.VERTICAL) { this.orientation = LINEAR_LAYOUT_ORIENTATION_VERTICAL; } } public MyItemDecoration(Context context, int orientation, int rawOrColumnSum) { // 這個構造方法用於處理網格佈局傳入的情況,原理同上 myDivider = context.getResources().getDrawable(R.drawable.my_list_divider); if(orientation == GridLayoutManager.HORIZONTAL) { this.orientation = GRID_LAYOUT_ORIENTATION_HORIZONTAL; } else if(orientation == GridLayoutManager.VERTICAL) { this.orientation = GRID_LAYOUT_ORIENTATION_VERTICAL; } this.rawOrColumnSum = rawOrColumnSum; } // 在這個方法中。我們對佈局方向進行判斷,由此來調用正確的分隔線繪製方法 @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); if(orientation == LINEAR_LAYOUT_ORIENTATION_HORIZONTAL || orientation == LINEAR_LAYOUT_ORIENTATION_VERTICAL) { linearLayoutDrawItemDecoration(c, parent); } else if(orientation == GRID_LAYOUT_ORIENTATION_HORIZONTAL || orientation == GRID_LAYOUT_ORIENTATION_VERTICAL) { gridLayoutItemDecoration(c, parent); } } /* * 當排布方式爲線性佈局的時候,繪製分割線的方法: */ private void linearLayoutDrawItemDecoration(Canvas canvas, RecyclerView parent) { int childCount = parent.getChildCount(); // 獲取RecyclerView控件中的子控件總數 int left, top, right, bottom; View child = parent.getChildAt(0); // 獲取分割線的高度(把分割線看成一個小矩形) int drawableHeight = myDivider.getIntrinsicHeight(); // 如果是豎直排布,那麼分割線爲橫線 if(orientation == LINEAR_LAYOUT_ORIENTATION_VERTICAL) { left = parent.getLeft(); right = parent.getRight(); // 獲取子控件開始 x 座標和結束 x 座標 for (int i = 1; i < childCount; i++) { top = child.getBottom() - drawableHeight/2; // 獲取開始點y座標 bottom = child.getBottom() + drawableHeight/2; // 獲取結束點y座標 myDivider.setBounds(left, top, right, bottom); // 設置繪製區域,下同 myDivider.draw(canvas); // 在Canvas對象上繪製區域 child = parent.getChildAt(i); } // 如果是水平排布,那麼分割線爲豎線 } else if(orientation == LINEAR_LAYOUT_ORIENTATION_HORIZONTAL) { top = child.getTop(); bottom = child.getBottom(); // 獲取子控件的開始 y 座標和結束 y 座標 for(int i = 1; i < childCount; i++) { left = child.getRight() - drawableHeight/2; // 獲取開始點 x 座標 right = child.getRight() + drawableHeight/2; // 獲取結束點 x 座標 myDivider.setBounds(left, top, right, bottom); // 設置繪製區域 myDivider.draw(canvas); child = parent.getChildAt(i); } } } /* * 當排布方式爲網格佈局的時候,分割線的繪製方法: */ private void gridLayoutItemDecoration(Canvas canvas, RecyclerView parent) { // 順着佈局方向上的要繪製的分割線條數 int childCount = parent.getChildCount(); int lineSum = childCount / rawOrColumnSum - 1; lineSum += childCount % rawOrColumnSum == 0 ? 0 : 1; // 獲取分割線的高度(把分割線看成一個小矩形) int drawableHeight = myDivider.getIntrinsicHeight(); int left, right, top, bottom; View child = parent.getChildAt(0); // 佈局方向爲豎直排布方式 if(orientation == GRID_LAYOUT_ORIENTATION_VERTICAL) { left = parent.getLeft(); right = parent.getRight(); for(int i = 0; i < lineSum; i++) { // 循環用於繪製橫向分割線 child = parent.getChildAt(i*rawOrColumnSum); top = child.getBottom() - drawableHeight/2; bottom = child.getBottom() + drawableHeight/2; myDivider.setBounds(left, top, right, bottom); myDivider.draw(canvas); } top = parent.getTop(); bottom = parent.getBottom(); for(int i = 0; i < rawOrColumnSum-1; i++) { // 循環用於繪製豎向分割線 child = parent.getChildAt(i); left = child.getRight() - drawableHeight/2; right = child.getRight() + drawableHeight/2; myDivider.setBounds(left, top, right, bottom); myDivider.draw(canvas); } // 佈局方向爲橫向排布方式 } else if(orientation == GRID_LAYOUT_ORIENTATION_HORIZONTAL) { top = parent.getTop(); bottom = parent.getBottom(); for(int i = 0; i <= lineSum; i++) { // 循環繪製豎向分割線 child = parent.getChildAt(i*rawOrColumnSum); left = child.getRight() - drawableHeight/2; right = child.getRight() + drawableHeight/2; myDivider.setBounds(left, top, right, bottom); myDivider.draw(canvas); } left = parent.getLeft(); right = parent.getRight(); for(int i = 0; i < rawOrColumnSum; i++) { // 循環繪製橫向分割線 child = parent.getChildAt(i); top = child.getBottom() - drawableHeight/2; bottom = child.getBottom() + drawableHeight/2; myDivider.setBounds(left, top, right, bottom); myDivider.draw(canvas); } } } }(2)添加分割線顏色,在drawable下新建my_list_divider.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <size android:height="4dp"></size> <gradient android:startColor="#ff0000" android:centerColor="#00ff00" android:endColor="#0000ff"> </gradient> </shape>
(3)在MainActivity.java中引用
我這裏引用的是兩個參數的構造方法,第一個參數:context,第二個參數:分割線顯示的方向
五. 效果圖
下拉刷新及默認分割線:
上拉加載更多及自定義分割線:
六. 拓展
在MyItemDecoration的構造方法中,獲取圖片資源的getDrawable()方法已經過時,這樣雖然不會報錯,但是並不好
myDivider = context.getResources().getDrawable(R.drawable.my_list_divider);
谷歌給了新的方法來替換,過時方法被替換肯定是有原因的,我們就是要與時俱進
myDivider = ContextCompat.getDrawable(context,R.drawable.my_list_divider);
每天進步一點點!