实现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