實現RecyclerView下拉刷新的功能,網上有不少文章,但大多文章都是將RecyclerView外面套了一層SwipRefreshLayout以此來達到下拉刷新的目的!我的以爲這種方式不太優雅,因而經過查找資料及閱讀源碼,找到了一個比較優雅的方式實現RecyclerView的下拉刷新,本文將帶你以不同的方式實現RecyclerView的下拉刷新。java
萬丈高樓平地起,作什麼事都不是一蹴而就的,再偉大的工程也是一點點完成的。這裏就先從簡單的入手,先爲RecyclerView添加頭部。android
咱們都知道實現RecyclerView的數據和視圖的綁定是經過繼承RecyclerView.Adapter類,一樣,爲RecyclerView添加頭部也是須要繼承RecyclerView.Adapter類,而後,在子類裏面作文章。在添加頭部以前有必要先了解一下RecyclerView.Adapter的幾個經常使用的方法,分別爲如下幾個方法git
onCreateViewHolder(ViewGroup parent, int viewType) 複製代碼
getItemViewType(int position) 複製代碼
onBindViewHolder(MyViewHolder holder, int position) 複製代碼
getItemCount()
複製代碼
使用過ListView的都知道,爲了優化ListView的性能,咱們在寫Adapter時須要本身寫一個ViewHolder類來重用ListView中的item視圖,而在爲RecyclerView實現Adapter時,強制咱們要有一個ViewHolder。github
onCreateViewHolder方法就是用來建立新View;markdown
getItemViewType方法是能夠根據不一樣的position能夠返回不一樣的類型;網絡
onBindViewHolder是將數據與視圖綁定;ide
getItemCount方法就是得到須要顯示數據的總數。oop
瞭解了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 == 0) { return TYPE_REFRESH_HEADER; } return TYPE_NORMAL; } 複製代碼
能夠看出,在onCreateViewHolder方法中,爲不一樣的viewType設置了不一樣的類型,即爲RecyclerView添加了頭部,看下效果圖 post
如今將頭部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,0); } 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(0, 0, 0, 0); this.setLayoutParams(layoutParams); this.setPadding(0, 0, 0, 0); //將refreshHeader高度設置爲0 mContentLayout = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.refresh_header_item, null); addView(mContentLayout, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0)); //初始化控件 mArrowImageView = findViewById(R.id.ivHeaderArrow); mStatusTextView = findViewById(R.id.tvRefreshStatus); mProgressBar = findViewById(R.id.refreshProgress); //初始化動畫 mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateUpAnim.setDuration(200); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateDownAnim.setDuration(200); 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, 0)); 複製代碼
能夠看到,這裏將頭部刷新的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)); } 複製代碼
再看下效果
@Override public boolean onTouchEvent(MotionEvent e) { if (mLastY == -1) { mLastY = e.getRawY(); } switch (e.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = e.getRawY(); sumOffSet = 0; break; case MotionEvent.ACTION_MOVE: float deltaY = (e.getRawY() - mLastY) / 2;//爲了防止滑動幅度過大,將實際手指滑動的距離除以2 mLastY = e.getRawY(); sumOffSet += deltaY;//計算總的滑動的距離 if (isOnTop() && !mRefreshing) { mRefreshHeader.onMove(deltaY, sumOffSet);//接口回調,移動刷新的頭部View if (mRefreshHeader.getVisibleHeight() > 0) { return false; } } break; default: mLastY = -1; // reset if (isOnTop()&& !mRefreshing) { if (mRefreshHeader.onRelease()) { //手指鬆開 } } break; } return super.onTouchEvent(e); } 複製代碼
這部分代碼就是獲取手指滑動的距離,而後利用接口回調,將下拉的距離傳遞到自定義的View中,讓頭部刷新的View改變距離及改變狀態。先看下定義的接口,接口中主要就是集中刷新的狀態以及表明各個狀態的變量,代碼以下
public interface IRefreshHeader { int STATE_NORMAL = 0;//正常狀態 int STATE_RELEASE_TO_REFRESH = 1;//下拉的狀態 int STATE_REFRESHING = 2;//正在刷新的狀態 int STATE_DONE = 3;//刷新完成的狀態 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() > 0 || offSet > 0) { setVisibleHeight((int) offSet + getVisibleHeight()); if (mState <= STATE_RELEASE_TO_REFRESH) { // 未處於刷新狀態,更新箭頭 if (getVisibleHeight() > mMeasuredHeight) { onPrepare(); } else { onReset(); } } } 複製代碼
這裏將移動的距離經過setVisibleHeight方法進行顯示,再看下這個方法的實現
public void setVisibleHeight(int height) { if (height < 0) height = 0; 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(); } }, 200); } 複製代碼
能夠看到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(0); setState(STATE_NORMAL); } 複製代碼
reset方法就是將頭部刷新View的高度還設置爲0,就是將頭部刷新View隱藏通知將刷新的狀態設置爲STATE_NORMAL。
看完了方法,下面就在本身實現的RecyclerView中調用刷新完成的方法,代碼以下
public void refreshComplete() { if (mRefreshing) { mRefreshing = false; mRefreshHeader.refreshComplete(); } } 複製代碼
這裏,將刷新時的監聽放在當刷新視圖顯示正在刷新時,即當觸發了刷新而且手指擡起時,可能說的難懂,相信看下代碼就明白了
mLastY = -1; // 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 = 1; @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(1500); Message message = Message.obtain(); message.what = FINISH; sHandler.sendMessage(message); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } private void initData() { for (int i = 0; i < 15; i++) { mStringList.add(""); } } @Override protected void onDestroy() { super.onDestroy(); sHandler.removeCallbacks(null); } } 複製代碼
上面的代碼不難,相信你一看就懂,這裏就不講解了,看下最終的效果,如圖
相信按照上面的步驟,你必定能夠本身動手實現RecyclerView的下拉刷新功能。源碼點擊這裏獲取
轉載請註明出處:www.wizardev.com