按照國際慣例,先上效果圖。
下拉重新整理:

上拉加載:
說一下第一張圖,頂端的黑邊是做gif圖的時候出現的,不明其意,尴尬咯!下面進入正題。
下拉重新整理
思路:一切複雜的事情都是先從簡單入手,分為以下步驟:
1. 我們可以把頭部的下拉重新整理布局當做清單的一個item,就是頭部item,它的布局肯定是跟我們内容不一樣的,這個好辦,RecyclerView.Adapter類提供了一個getItemViewType(int position)方法,這個方法可以讓我們輕松實作一個清單可以有不同布局。
2. 我們發現當我們下拉的時候,該布局的内容是需要改變的,這個也容易,隻要在适配類添加方法就可以辦到,比如我這個例子隻是簡單的改變文字描述
private TextView tvContentHead;
class HeadViewHolder extends RecyclerView.ViewHolder{
public HeadViewHolder(View itemView) {
super(itemView);
tvContentHead = (TextView) itemView.findViewById(R.id.tvContentHead);
}
}
public void setText(String text){
tvContentHead.setText(text);
}
setText() 就是我給外部提供的方法,HeadViewHoler類就是我們的頭部Item
3. 我們的清單是到達頂部時,才執行下拉重新整理的,是以我們應當要判斷清單是否到達頂部,RecyclerView可以監聽OnScrollListener類,并且實作onScrolled(RecyclerView recyclerView, int dx, int dy)方法,這個方法是監聽清單的滑動,那怎麼樣才知道清單已經滑動到頂部了呢?網上有很多資料,列舉了幾個方法,但是我覺得最有信服力是canScrollVertically方法,下面看代碼
this.addOnScrollListener(new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (recyclerView.canScrollVertically(-1)){
isTop = false;
}else {
isTop = true;
}
if (!recyclerView.canScrollVertically(1)){
pullRefreshListener.isLoading();
}
}
});
recyclerView.canScrollVertically(-1) 這個方法傳回false時,說明清單已經滑動到頂部了,這時我們就可以操作下拉重新整理了
4. 下面是整個下拉重新整理的核心内容 已經到達頂部了,我們怎麼樣才可以讓他有下拉的感覺呢? 我這裡使用一個技巧,說到這個技巧我都想笑,那就是外邊距,對,沒錯,我就是通過控制清單的上邊外邊距實作的下拉重新整理,開始時,我們的頭部本來就是存在的,是以我們把marginTop設定為負頭部的高度值,這樣給人的感覺就是沒有頭部内容了,是以當我們到底頂部的時候,就可以通過控制上邊外邊距來控制下來效果,當然,還有解決滑動沖突,下面看代碼:
this.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
y = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (!isTop) {
y = event.getRawY();
} else {
float mY = event.getRawY();
float dY = mY - y;
if (dY >= 0 && isOpenRefresh) {
int top = (int) (dY + topMargin) >= 0 ? 0 : (int) (dY + topMargin);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.setMargins(0, top, 0, 0);
setLayoutParams(layoutParams);
if (dY >= headHeight) {
isRefresh = true;
dropDownRefreshListener.isRefreshTip();
} else {
isRefresh = false;
dropDownRefreshListener.noRefreshTip();
}
return true;
}
}
break;
case MotionEvent.ACTION_UP:
if (isTop) {
if (isRefresh) {
dropDownRefreshListener.isRefreshing();
}else {
dropDownRefreshListener.noRefresh();
}
}
break;
}
return false;
}
});
}
實作onTouch,當到達頂部時,就計算下來距離,然後改變上邊外邊距,當下拉到一定數值時,提示使用者可以可以重新整理了。我這裡是使用了回調dropDownRefreshListener.isRefreshTip();友善使用者自定義資訊。當我們正在執行下拉操作時,onTouch傳回true,直接消費,其他操作直接傳回false,這樣就解決了滑動沖突。當手指離開螢幕時,如果isRefesh為true,說明可以執行重新整理操作,否則反之。
5. 我們的接口都提供出來了,那就看看我們的實作吧,其實很簡單,上代碼吧
myAadapter = new MyTestAadapter(this,list);
recyclerView.setAdapter(myAadapter);
recyclerView.setRefresh(true);
recyclerView.onDropDownResfreshListener(new MyRecyclerView.DropDownRefreshListener() {
@Override
public void isRefreshing() {
myAadapter.setText("正在重新整理");
//模拟網絡加載資料,這裡使用延遲政策
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//加載完成
recyclerView.hideRefeshLayout();
myAadapter.setText("下拉重新整理");
}
}, 4000);
}
@Override
public void noRefresh() {
recyclerView.hideRefeshLayout();
myAadapter.setText("下拉重新整理");
}
@Override
public void isRefreshTip() {
myAadapter.setText("松開重新整理");
}
@Override
public void noRefreshTip() {
myAadapter.setText("下拉重新整理");
}
});
recyclerView.setRefresh(true)表示開啟下拉重新整理,四個回調方法一看就明白了。
上拉加載
上拉加載就比較簡單了,
添加一個底部item,跟頭部同理,判斷是否到達底部,
if (!recyclerView.canScrollVertically(1)){
pullRefreshListener.isLoading();
}
到達底部則實作回調,下面看看接口的實作:
recyclerView.onPullResfreshListener(new MyRecyclerView.PullRefreshListener() {
@Override
public void isLoading() {
if (!islaod){
islaod = true;
myAadapter.setFooterText("正在加載");
//模拟網絡加載資料,這裡使用延遲政策
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List<String> list = new ArrayList<>();
for (int i=0;i<10;i++){
list.add("加載内容"+i);
}
myAadapter.addData(list);
islaod = false;
//加載完成
myAadapter.setFooterText("上拉加載");
}
}, 4000);
}
}
});
當正在加載時,如果使用者繼續上拉,就會再次回調isLoading,isLaod是為了防止這個情況的發生。延遲政策是模拟一下資料加載。
下面發一下完整代碼:
MyRecyclerView類:
public class MyRecyclerView extends RecyclerView {
private float y;
private boolean isTop = false;
private boolean isRefresh = false;
private boolean isOpenRefresh = false;
private int topMargin;
private int headHeight = 200;
public MyRecyclerView(Context context) {
this(context,null);
}
public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.addOnScrollListener(new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (recyclerView.canScrollVertically(-1)){
isTop = false;
}else {
isTop = true;
}
if (!recyclerView.canScrollVertically(1)){
pullRefreshListener.isLoading();
}
}
});
this.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
y = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (!isTop) {
y = event.getRawY();
} else {
float mY = event.getRawY();
float dY = mY - y;
if (dY >= 0 && isOpenRefresh) {
int top = (int) (dY + topMargin) >= 0 ? 0 : (int) (dY + topMargin);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.setMargins(0, top, 0, 0);
setLayoutParams(layoutParams);
if (dY >= headHeight) {
isRefresh = true;
dropDownRefreshListener.isRefreshTip();
} else {
isRefresh = false;
dropDownRefreshListener.noRefreshTip();
}
return true;
}
}
break;
case MotionEvent.ACTION_UP:
if (isTop) {
if (isRefresh) {
dropDownRefreshListener.isRefreshing();
}else {
dropDownRefreshListener.noRefresh();
}
}
break;
}
return false;
}
});
}
/**
* 是否開啟下拉重新整理
* @param isOpenRefresh
*/
public void setRefresh(boolean isOpenRefresh){
this.isOpenRefresh = isOpenRefresh;
if (isOpenRefresh){
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)getLayoutParams();
topMargin = layoutParams.topMargin;
// Log.e("setRefresh","topMargin="+topMargin);
}
}
/**
* 隐藏下拉重新整理布局
*/
public void hideRefeshLayout(){
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.setMargins(0, topMargin, 0, 0);
setLayoutParams(layoutParams);
isRefresh = false;
}
public void onDropDownResfreshListener(DropDownRefreshListener dropDownRefreshListener){
this.dropDownRefreshListener = dropDownRefreshListener;
}
public interface DropDownRefreshListener{
/**
* 當手指離開螢幕時,下拉距離符合加載要求,執行這個方法
*/
void isRefreshing();
/**
* 當手指離開螢幕時,下拉距離不符合加載要求,執行這個方法
*/
void noRefresh();
/**
* 當手指還在螢幕上時,下拉距離符合加載要求,執行這個方法
*/
void isRefreshTip();
/**
* 當手指還在螢幕上時,下拉距離不符合加載要求,執行這個方法
*/
void noRefreshTip();
}
private DropDownRefreshListener dropDownRefreshListener;
public void onPullResfreshListener(PullRefreshListener pullRefreshListener){
this.pullRefreshListener = pullRefreshListener;
}
public interface PullRefreshListener{
/**
* 當上拉距離符合加載要求,執行這個方法
*/
void isLoading();
}
private PullRefreshListener pullRefreshListener;
}
MyTestAadapter類:
public class MyTestAadapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private LayoutInflater inflater;
private List<String> list;
public static final int HEAD = 1;
public static final int DEF_VIEW = 2;
public static final int FOOTER = 3;
public MyTestAadapter(Context context, List<String> list){
this.context = context;
this.inflater = LayoutInflater.from(context);
this.list = list;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder = null;
switch (viewType){
case DEF_VIEW:
viewHolder = new DefViewHolder(inflater.inflate(R.layout.recycler_item_def,parent,false));
break;
case HEAD:
viewHolder = new HeadViewHolder(inflater.inflate(R.layout.recycler_item_head,parent,false));
break;
case FOOTER:
viewHolder = new FooterViewHolder(inflater.inflate(R.layout.recycler_item_footer,parent,false));
break;
}
return viewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)){
case DEF_VIEW:
DefViewHolder defViewHolder = (DefViewHolder) holder;
defViewHolder.tvContent.setText(list.get(position-1));
break;
case HEAD:
break;
case FOOTER:
break;
}
}
@Override
public int getItemCount() {
return list.size()==0?0:list.size()+2;
}
@Override
public int getItemViewType(int position) {
if (position==0){
return HEAD;
} if (list.size()>0 && list.size()+1==position){
return FOOTER;
}else {
return DEF_VIEW;
}
}
public void addData(List<String> stringList){
for (String s : stringList){
list.add(s);
}
notifyItemRangeInserted(getItemCount()-2,stringList.size());
}
class DefViewHolder extends RecyclerView.ViewHolder{
private TextView tvContent;
public DefViewHolder(View itemView) {
super(itemView);
tvContent = (TextView) itemView.findViewById(R.id.tvContent);
}
}
private TextView tvContentHead;
class HeadViewHolder extends RecyclerView.ViewHolder{
public HeadViewHolder(View itemView) {
super(itemView);
tvContentHead = (TextView) itemView.findViewById(R.id.tvContentHead);
}
}
public void setText(String text){
tvContentHead.setText(text);
}
private TextView tvContentFooter;
class FooterViewHolder extends RecyclerView.ViewHolder{
public FooterViewHolder(View itemView) {
super(itemView);
tvContentFooter = (TextView) itemView.findViewById(R.id.tvContentFooter);
}
}
public void setFooterText(String text){
tvContentFooter.setText(text);
}
}
demo位址: http://download.csdn.net/download/u012992345/10015294