實作RecyclerView下拉重新整理的功能,網上有很多文章,但大多文章都是将RecyclerView外面套了一層SwipRefreshLayout以此來達到下拉重新整理的目的!個人覺得這種方式不太優雅,于是通過查找資料及閱讀源碼,找到了一個比較優雅的方式實作RecyclerView的下拉重新整理,本文将帶你以一種優雅的方式實作RecyclerView的下拉重新整理。
從基礎入手
萬丈高樓平地起,做什麼事都不是一蹴而就的,再偉大的工程也是一點點完成的。這裡就先從簡單的入手,先為RecyclerView添加頭部。
為RecyclerView添加頭部View
我們都知道實作RecyclerView的資料和視圖的綁定是通過繼承RecyclerView.Adapter類,同樣,為RecyclerView添加頭部也是需要繼承RecyclerView.Adapter類,然後,在子類裡面做文章。在添加頭部之前有必要先了解一下RecyclerView.Adapter的幾個常用的方法,分别為以下幾個方法
onCreateViewHolder(ViewGroup parent, int viewType)
getItemViewType(int position)
onBindViewHolder(MyViewHolder holder, int position)
getItemCount()
使用過ListView的都知道,為了優化ListView的性能,我們在寫Adapter時需要自己寫一個ViewHolder類來重用ListView中的item視圖,而在為RecyclerView實作Adapter時,強制我們要有一個ViewHolder。
onCreateViewHolder方法就是用來建立新View;
getItemViewType方法是可以根據不同的position可以傳回不同的類型;
onBindViewHolder是将資料與視圖綁定;
getItemCount方法就是獲得需要顯示資料的總數。
了解了Adapter中的幾個方法,下面就利用這幾個方法為RecyclerView添加頭部View,下面上代碼
@Override
public RefreshViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//這裡的viewType即為getItemViewType傳回的type
if (viewType == TYPE_REFRESH_HEADER) {
return new RefreshViewHolder(mInflater.inflate(R.layout.refresh_header_item, parent, false));
}
return new RefreshViewHolder(mInflater.inflate(R.layout.normal_item, parent, false));
}
@Override
public void onBindViewHolder(RefreshViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return mLists.size();
}
@Override
public int getItemViewType(int position) {
//當position位置為0時,傳回為頭部的類型
if (position == ) {
return TYPE_REFRESH_HEADER;
}
return TYPE_NORMAL;
}
可以看出,在onCreateViewHolder方法中,為不同的viewType設定了不同的類型,即為RecyclerView添加了頭部,看下效果圖
好了,現在已經為RecyclerView添加了頭部,下一步就是将頭部變成重新整理的View。
添加下拉重新整理的View
現在将頭部View修改一下,直接上代碼
<?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="bottom">
<RelativeLayout
android:id="@+id/header_content"
android:layout_width="match_parent"
android:layout_height="80dp"
android:paddingTop="10dip">
<TextView
android:id="@+id/tvRefreshStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/pullRefresh"
android:textColor="#B5B5B5" />
<ImageView
android:id="@+id/ivHeaderArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="35dp"
android:layout_marginRight="10dp"
android:layout_toLeftOf="@id/tvRefreshStatus"
android:src="@drawable/ic_pulltorefresh_arrow" />
</RelativeLayout>
</LinearLayout>
好了,看下效果圖
現在這個效果顯然不是我們想要的,我們想要的效果是在正常狀态下頭部的下拉重新整理不可見,當下拉到一定程度再顯示。
這裡有兩點需要注意:
- 在正常的狀态下,下拉重新整理的頭部是不可見的;
- 當下拉到一定程度再将頭部重新整理顯示出來。
現在,來實作正常狀态下的效果,正常狀态下不可見,這時可以将頭部重新整理的View高度設定為0,下面看下代碼
public ArrowRefreshHeader(Context context) {
this(context,null);
}
public ArrowRefreshHeader(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,);
}
public ArrowRefreshHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();//初始化視圖
}
private void init() {
LinearLayout.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(, , , );
this.setLayoutParams(layoutParams);
this.setPadding(, , , );
//将refreshHeader高度設定為0
mContentLayout = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.refresh_header_item, null);
addView(mContentLayout, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ));
//初始化控件
mArrowImageView = findViewById(R.id.ivHeaderArrow);
mStatusTextView = findViewById(R.id.tvRefreshStatus);
mProgressBar = findViewById(R.id.refreshProgress);
//初始化動畫
mRotateUpAnim = new RotateAnimation(f, -f,
Animation.RELATIVE_TO_SELF, f, Animation.RELATIVE_TO_SELF, f);
mRotateUpAnim.setDuration();
mRotateUpAnim.setFillAfter(true);
mRotateDownAnim = new RotateAnimation(-f, f,
Animation.RELATIVE_TO_SELF, f, Animation.RELATIVE_TO_SELF, f);
mRotateDownAnim.setDuration();
mRotateDownAnim.setFillAfter(true);
//将mContentLayout的LayoutParams高度和寬度設為自動包裹并重新測量
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mMeasuredHeight = getMeasuredHeight();//獲得測量後的高度
}
這裡使用了自定義View,寫了一個ArrowRefreshHeader繼承至LinearLayout,在構造方法中将頭部重新整理的View進行了初始化,這裡這句代碼是關鍵
mContentLayout = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.refresh_header_item, null);
addView(mContentLayout, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ));
可以看到,這裡将頭部重新整理的View的高度設定成了0,這樣,就實作了在正常狀态下,頭部重新整理不顯示的效果。将RefreshHeaderAdapter的onCreateViewHolder方法修改一下,如下
@Override
public RefreshViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_REFRESH_HEADER) {
// return new RefreshViewHolder(mInflater.inflate(R.layout.refresh_header_item, parent, false));
return new RefreshViewHolder(new ArrowRefreshHeader(mContext));
}
return new RefreshViewHolder(mInflater.inflate(R.layout.normal_item, parent, false));
}
再看下效果
成功實作在正常狀态下不顯示頭部重新整理View的效果,下面繼續實作第2步,當下拉到一定程度進行顯示。既然這裡有下拉,肯定涉及到了手勢監聽,自然是需要一個類繼承自RecyclerView,然後重寫onTouchEvent方法,下面看代碼
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mLastY == -) {
mLastY = e.getRawY();
}
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = e.getRawY();
sumOffSet = ;
break;
case MotionEvent.ACTION_MOVE:
float deltaY = (e.getRawY() - mLastY) / ;//為了防止滑動幅度過大,将實際手指滑動的距離除以2
mLastY = e.getRawY();
sumOffSet += deltaY;//計算總的滑動的距離
if (isOnTop() && !mRefreshing) {
mRefreshHeader.onMove(deltaY, sumOffSet);//接口回調,移動重新整理的頭部View
if (mRefreshHeader.getVisibleHeight() > ) {
return false;
}
}
break;
default:
mLastY = -; // reset
if (isOnTop()&& !mRefreshing) {
if (mRefreshHeader.onRelease()) {
//手指松開
}
}
break;
}
return super.onTouchEvent(e);
}
這部分代碼就是擷取手指滑動的距離,然後利用接口回調,将下拉的距離傳遞到自定義的View中,讓頭部重新整理的View改變距離及改變狀态。先看下定義的接口,接口中主要就是集中重新整理的狀态以及代表各個狀态的變量,代碼如下
public interface IRefreshHeader {
int STATE_NORMAL = ;//正常狀态
int STATE_RELEASE_TO_REFRESH = ;//下拉的狀态
int STATE_REFRESHING = ;//正在重新整理的狀态
int STATE_DONE = ;//重新整理完成的狀态
void onReset();
/**
* 處于可以重新整理的狀态,已經過了指定距離
*/
void onPrepare();
/**
* 正在重新整理
*/
void onRefreshing();
/**
* 下拉移動
*/
void onMove(float offSet, float sumOffSet);
/**
* 下拉松開
*/
boolean onRelease();
/**
* 下拉重新整理完成
*/
void refreshComplete();
/**
* 擷取HeaderView
*/
View getHeaderView();
/**
* 擷取Header的顯示高度
*/
int getVisibleHeight();
}
我們的自定義View即ArrowRefreshHeader這個類實作了上面的接口,上面說道,通過接口回調的形式将移動的距離通過onMove方法回傳給了ArrowRefreshHeader,現在看下ArrowRefreshHeader中onMove方法的具體實作,如下
if (getVisibleHeight() > || offSet > ) {
setVisibleHeight((int) offSet + getVisibleHeight());
if (mState <= STATE_RELEASE_TO_REFRESH) { // 未處于重新整理狀态,更新箭頭
if (getVisibleHeight() > mMeasuredHeight) {
onPrepare();
} else {
onReset();
}
}
}
這裡将移動的距離通過setVisibleHeight方法進行顯示,再看下這個方法的實作
public void setVisibleHeight(int height) {
if (height < ) height = ;
LayoutParams lp = (LayoutParams) mContentLayout.getLayoutParams();
lp.height = height;
mContentLayout.setLayoutParams(lp);
}
這個方法的主要功能就是将距離設定給了LayoutParams中的height字段,然後再重新設定mContentLayout的布局屬性。
通過以上的方法,便可以實作當下拉到一定距離時顯示頭部重新整理View的功能了,下面看下實作的效果
到了這一步已經可以實作下拉以及重新整理的功能了,但是隻會一直重新整理,根本停不下來!這裡是因為我們沒有調用重新整理完成的回調方法,下面開始實作重新整理完成是的功能。
重新整理完成的實作及設定重新整理時的監聽
實作完成重新整理的功能
現在看下當重新整理的狀态變為重新整理完成,做了什麼
@Override
public void refreshComplete() {
setState(STATE_DONE);//設定重新整理的狀态為已完成
//延遲200ms後複位,主要是為了顯示“重新整理完成”的字樣,不延遲的話由于時間太短就看不見“重新整理完成”的字樣
new Handler().postDelayed(new Runnable() {
public void run() {
reset();
}
}, );
}
可以看到refreshComplete方法主要是将狀态标志位設定為已完成,同時延遲200ms将下來重新整理的狀态複位,下面分别看下setState方法和reset方法都做了什麼
public void setState(int state) {
//狀态沒有改變時什麼也不做
if (state == mState) return;
switch (state) {
case STATE_NORMAL:
if (mState == STATE_RELEASE_TO_REFRESH) {
mArrowImageView.startAnimation(mRotateDownAnim);
}
if (mState == STATE_REFRESHING) {
mArrowImageView.clearAnimation();
}
mStatusTextView.setText("下拉重新整理");
break;
case STATE_RELEASE_TO_REFRESH:
mArrowImageView.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.INVISIBLE);
if (mState != STATE_RELEASE_TO_REFRESH) {
mArrowImageView.clearAnimation();
mArrowImageView.startAnimation(mRotateUpAnim);
mStatusTextView.setText("釋放立即重新整理");
}
break;
case STATE_REFRESHING:
mArrowImageView.clearAnimation();
mArrowImageView.setVisibility(View.INVISIBLE);
mProgressBar.setVisibility(View.VISIBLE);
smoothScrollTo(mMeasuredHeight);
mStatusTextView.setText("正在重新整理...");
break;
case STATE_DONE:
mArrowImageView.setVisibility(View.INVISIBLE);
mProgressBar.setVisibility(View.INVISIBLE);
mStatusTextView.setText("重新整理完成");
break;
default:
}
mState = state;//儲存目前重新整理的狀态
}
在setState方法裡,主要及時根據不同的重新整理狀态的标志,設定視圖的顯示隐藏以及文字的改變。
public void reset() {
smoothScrollTo();
setState(STATE_NORMAL);
}
reset方法就是将頭部重新整理View的高度還設定為0,就是将頭部重新整理View隐藏通知将重新整理的狀态設定為STATE_NORMAL。
看完了方法,下面就在自己實作的RecyclerView中調用重新整理完成的方法,代碼如下
public void refreshComplete() {
if (mRefreshing) {
mRefreshing = false;
mRefreshHeader.refreshComplete();
}
}
設定重新整理時的監聽
這裡,将重新整理時的監聽放在當重新整理視圖顯示正在重新整理時,即當觸發了重新整理并且手指擡起時,可能說的難懂,相信看下代碼就明白了
mLastY = -; // reset
if (isOnTop()&& !mRefreshing) {
if (mRefreshHeader.onRelease()) {
//手指松開
if (mRefreshListener != null) {
mRefreshing = true;
mRefreshListener.onRefresh();//調用正在重新整理的監聽,在此方法中實作網絡的請求操作。
}
}
}
下拉重新整理的使用
下拉重新整理的功能已經全部完成了,下面看下怎麼使用
public class MainActivity extends AppCompatActivity {
private List<String> mStringList = new ArrayList<>();
private RefreshHeaderRecyclerView mRecyclerView;
private RefreshHeaderAdapter mAdapter;
private static final int FINISH = ;
@SuppressLint("HandlerLeak")
private Handler sHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == FINISH) {
Toast.makeText(MainActivity.this,"重新整理完成!",Toast.LENGTH_SHORT).show();
mRecyclerView.refreshComplete();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
setupRecyclerView();
}
private void setupRecyclerView() {
mRecyclerView = findViewById(R.id.recyclerView);
mAdapter = new RefreshHeaderAdapter(mStringList, this);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
requestData();//模拟資料的請求
}
});
}
private void requestData() {
new Thread(new Runnable() {
@Override
public void run() {
MainActivity.this.toString();
try {
Thread.sleep();
Message message = Message.obtain();
message.what = FINISH;
sHandler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
private void initData() {
for (int i = ; i < ; i++) {
mStringList.add("");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
sHandler.removeCallbacks(null);
}
}
上面的代碼不難,相信你一看就懂,這裡就不講解了,看下最終的效果,如圖
實作下拉重新整理的步驟總結
- 自定義Adapter繼承至RecyclerView的Adapter
- 重寫getItemType()方法
- 在onCreateViewHolder()方法中執行個體化RefreshHeader對象。
- 建立一個類繼承至LinearLayout并實作IRehreshView接口
- 在初始化時将布局屬性設定為0,既隐藏頭部重新整理。
- 重寫RecyclerView主要是重寫RecyclerView的onTouchEvent方法,根據滑動的距離來顯示頭部重新整理的View
- 設定監聽
結束語
相信按照上面的步驟,你一定可以自己動手實作RecyclerView的下拉重新整理功能。源碼點選這裡擷取
轉載請注明出處:www.wizardev.com