用事件分发的原理结合SwipeRefreshLayout写一个RecyclerView的上下拉
vm5363
8年前
<p>很久之前我看到网上写了很多关于ListView、GridView上下拉的资源,所以就萌生了自己是否能写一个上下拉。最近我看了很多关于RecyclerView的上下拉的做法,基本上都是在RecyclerView的OnScrollListener里面判断是否在头部底部,然后再给RecyclerView添加头尾部,这样的方法既简便又容易让人理解。但我今天恰恰不用这种方法去添加上下拉,我用的方法其实跟最早时候的ListView、GridView添加上下拉是一样的原理-------事件分发。</p> <p>其实这种方法也比较简单,只是需要有一定事件分发的知识。那我们先讲讲原理:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/aaf3f3aea5a886412c97e15d0fe507d6.png"></p> <p style="text-align:center">原理图.png</p> <p>首先我们创建两个布局文件,contentView和footerView,我们只是把contentView和footerView都放在了一个垂直分布的LinearLayout里面,然后将contentView设置为match_parent,很自然地,footerView就被挤到屏幕外面去了。然后我们就通过事件分发的机制,不断滑动的同时判断</p> <pre> <code class="language-java">1,滑动的状态是否是处于上拉状态; 2.RecyclerView是否已经滑到底部。</code></pre> <p>如果你理解了这样的两个简单条件,恭喜你,那你就基本搞懂了整个上下拉的原理了。</p> <p>原理讲解开始前先理解几个用到的变量:</p> <pre> <code class="language-java">private SwipeRefreshLayout swipeRefreshLayout; private RecyclerView recyclerView; private TextView tvLoadingText; //x上次保存的 private int mLastMotionX; //y上次保存的 private int mLastMotionY; //滑动状态 private int mPullState; //上滑 private int PULL_UP_STATE = 2; private int PULL_FINISH_STATE = 0; //当前滑动的距离,偏移量 private int curTransY; //尾部的高度 private int footerHeight; //内容布局 private View contentView; //尾布局 private View footerView; private LinearLayout linearView; //是否上拉加载更多 private boolean isLoadNext = false; //是否在加载中 private boolean isLoading = false; private OnSwipeRecyclerViewListener onSwipeRecyclerViewListener; private boolean isCancelLoadNext = false;</code></pre> <p>让我们看看具体的实现代码:</p> <pre> <code class="language-java">@Override public boolean onInterceptTouchEvent(MotionEvent ev) { int x = (int)ev.getX(); int y = (int)ev.getY(); switch (ev.getAction()){ //手指触摸屏幕 case MotionEvent.ACTION_DOWN: mLastMotionX = x; mLastMotionY = y; break; //手指移动 case MotionEvent.ACTION_MOVE: int deltaX = x - mLastMotionX; int deltaY = y - mLastMotionY; //这里是判断左右滑动和上下滑动,如果是上下滑动和滑动了一定的距离,被认为是上下滑动 if(Math.abs(deltaX) < Math.abs(deltaY) && Math.abs(deltaY) > 10){ //进入条件判断,如果isRreshViewScroll返回true,则事件被拦截 if(isRefreshViewScroll(deltaY)){ return true; } } break; } return super.onInterceptTouchEvent(ev); } private boolean isRefreshViewScroll(int deltaY) { //条件1:deltaY<0,现在处于上下拉的状态;条件2:RecyclerView是否到达底部; //条件3:curTransY<=footerHeight是下拉的偏移量;条件4:是否处于上拉或者下拉状态 if(deltaY < 0 && RecyclerViewUtil.isBottom(recyclerView) && curTransY <= footerHeight && !isLoading && !isCancelLoadNext){ //处于下拉状态 mPullState = PULL_UP_STATE; isLoading = true; return true; } return false; }</code></pre> <p>基本上,理解了以上部分,这个上下拉的原理你算是可以毕业了,是不是很开心?让我们再往下看另外一半。</p> <pre> <code class="language-java">@Override public boolean onTouchEvent(MotionEvent event) { int y = (int)event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_MOVE: float deltaY = y - mLastMotionY; if(mPullState == PULL_UP_STATE){ //算出偏移量 curTransY += deltaY; //如果偏移量大于footerView的高度,把下拉的高度赋给偏移量 if (Math.abs(curTransY) > Math.abs(footerHeight)) { curTransY = - footerHeight; } //把View整体向上滑动 linearView.setTranslationY(curTransY); if(Math.abs(curTransY) == Math.abs(footerHeight)){ isLoadNext = true; } } mLastMotionY = y; return true; //在这里UP与CANCEL是一样的 case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if(isLoadNext){ //设置footerView的变化 changeFooterState(true); mPullState = PULL_FINISH_STATE; //设置上拉监听 if(onSwipeRecyclerViewListener != null){ onSwipeRecyclerViewListener.onLoadNext(); }else { //如果没有拉到底,则再次把尾部隐藏 hideTranslationY(); isLoading = false; } } return true; } return super.onTouchEvent(event); }</code></pre> <p>好了,基本上已经完成所有的原理扫盲了,接下来就是围绕着这个原理把简单的代码补上。</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swiperefreshlayout" 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.v7.widget.RecyclerView> </android.support.v4.widget.SwipeRefreshLayout> </RelativeLayout></code></pre> <p>contentView</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/loading_text" android:layout_width="match_parent" android:layout_height="55dp" android:gravity="center" android:text="加载更多" android:textColor="#000000" android:background="#EEEEE0"/> </LinearLayout></code></pre> <p>footerView</p> <pre> <code class="language-java">public SwipeRecyclerView(Context context) { this(context, null); } public SwipeRecyclerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwipeRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } public RecyclerView getRecyclerView(){ return recyclerView; } public SwipeRefreshLayout getSwipeRefreshLayout(){ return swipeRefreshLayout; } private void initView(Context context){ linearView = new LinearLayout(context); linearView.setOrientation(LinearLayout.VERTICAL); final LinearLayout.LayoutParams linearParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT); addView(linearView, linearParams); contentView = LayoutInflater.from(context).inflate(R.layout.swiperecyclerview,null); swipeRefreshLayout = (SwipeRefreshLayout)contentView.findViewById(R.id.swiperefreshlayout); recyclerView = (RecyclerView)contentView.findViewById(R.id.recyclerview); footerView = LayoutInflater.from(context).inflate(R.layout.swiperecyclerview_footerview,null); tvLoadingText = (TextView)footerView.findViewById(R.id.loading_text); //设置SwipeRefreshLayout的监听 swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { if (!isLoading) { isLoading = true; swipeRefreshLayout.setRefreshing(true); (new Handler()).postDelayed(new Runnable() { @Override public void run() { swipeRefreshLayout.setRefreshing(false); if (onSwipeRecyclerViewListener != null) { onSwipeRecyclerViewListener.onRefresh(); } isLoading = false; } }, 2000); } } }); linearView.addView(contentView); linearView.addView(footerView); //测量并设置各个布局的高度 getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { int height = getHeight(); if (height != 0) { getViewTreeObserver().removeOnGlobalLayoutListener(this); ViewGroup.LayoutParams recycleParams = contentView.getLayoutParams(); recycleParams.height = height; contentView.setLayoutParams(recycleParams); ViewGroup.LayoutParams footerParams =tvLoadingText.getLayoutParams(); footerHeight = footerParams.height; ViewGroup.LayoutParams contentParams = linearView.getLayoutParams(); contentParams.height = height + footerHeight; linearView.setLayoutParams(contentParams); // 设置偏移量为0 curTransY = 0; } } }); } public void setSwipeRefreshColor(int color){ swipeRefreshLayout.setColorSchemeColors(getResources().getColor(color)); }</code></pre> <p><em>以上是初始化的相关代码</em></p> <p>以下是一些工具方法:</p> <pre> <code class="language-java">//数据改变完之后,调用这方法,重置各种数值 public void onLoadFinish(){ if(curTransY == 0){ return; } isLoading = false; hideTranslationY(); } //用动画的方式把footerView隐藏 private void hideTranslationY() { ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(linearView, "translationY",curTransY, 0).setDuration(1000); objectAnimator.setInterpolator(new DecelerateInterpolator()); objectAnimator.start(); objectAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { curTransY = 0; changeFooterState(false); } }); } private void changeFooterState(boolean loading){ if(loading){ tvLoadingText.setText("正在努力的加载中..."); } else{ tvLoadingText.setText("加载更多"); } } public void setOnSwipeRecyclerViewListener(OnSwipeRecyclerViewListener onSwipeRecyclerViewListener){ this.onSwipeRecyclerViewListener = onSwipeRecyclerViewListener; } public interface OnSwipeRecyclerViewListener{ public void onRefresh(); public void onLoadNext(); }</code></pre> <p>接下来看看使用:</p> <p><img src="https://simg.open-open.com/show/9d8eb3f1a057b3e23b3d6beb5dc26c41.png"></p> <p style="text-align:center">简单的使用.png</p> <p>有兴趣的同学可以去下载源码去研究一下,以上我还有一个判断RecyclerView各种状态类似于判断是否到顶部或者底部的工具类,是由我有个很好人的师兄提供给我的,他也在我写代码的时候给我提供很多很好的建议。慢慢地,我觉得一些自定义ViewHolder的原理并没有想象中的难,只有你是否肯花时间去研究。如果对这个上下拉有什么意见可以跟我讨论,一起提高!</p> <p> </p> <p> </p> <p>来自:http://www.jianshu.com/p/4ef91430009c</p> <p> </p>