带阻尼回弹效果的RecyclerView
16lz
2021-01-24
一、前提
接到新需求,要求列表滑动过程增加阻尼回弹效果,且即使列表不能填充一整个屏幕的情况下也支持滑动。
有人说,给RecyclerView加上 android:overScrollMode="always" 就行了,事实证明,NO!这个东西只是在滑动到边缘是多了个水波阴影而已,没有阻尼回弹。
又有人说,给ListView加上 android:overScrollMode="always" 就行了,经过尝试,貌似可以,但是有bug,还相当严重。况且我还要把RecyclerView改成ListView,太麻烦了。
本着不重复造轮子的前提,搜索了一大波。github上面也有很多现成的框架,不过由于项目要求,不能随便引入框架,所以不敢直接depend,本来想copy源码改吧改吧,后来发现这些框架做的都很“大”,冗余功能代码量多,索性放弃。后来终于找到一个简单的实现方式。借鉴作者思路,继续修改使之符合我的功能要求。站在巨人的肩膀上会让成功来的更快!
二、直接上代码:
package com.zzz.test.view.widget;import android.content.Context;import android.graphics.Rect;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.TranslateAnimation;import android.widget.LinearLayout;/** * @author xxx * @date 20-4-29 */public class OverScrollLayout extends LinearLayout { private static final int ANIM_TIME = 400; private RecyclerView childView; private Rect original = new Rect(); private boolean isMoved = false; private float startYpos; /** * 阻尼系数 */ private static final float DAMPING_COEFFICIENT = 0.3f; private boolean isSuccess = false; private ScrollListener mScrollListener; public OverScrollLayout(Context context) { this(context, null); } public OverScrollLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public OverScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onFinishInflate() { super.onFinishInflate(); childView = (RecyclerView) getChildAt(0); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); original.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom()); } public void setScrollListener(ScrollListener listener) { mScrollListener = listener; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { float touchYpos = ev.getY(); if (touchYpos >= original.bottom || touchYpos <= original.top) { if (isMoved) { recoverLayout(); } return true; } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: startYpos = ev.getY(); case MotionEvent.ACTION_MOVE: int scrollYpos = (int) (ev.getY() - startYpos); boolean pullDown = scrollYpos > 0 && canPullDown(); boolean pullUp = scrollYpos < 0 && canPullUp(); if (pullDown || pullUp) { cancelChild(ev); int offset = (int) (scrollYpos * DAMPING_COEFFICIENT); childView.layout(original.left, original.top + offset, original.right, original.bottom + offset); if (mScrollListener != null) { mScrollListener.onScroll(); } isMoved = true; isSuccess = false; return true; } else { startYpos = ev.getY(); isMoved = false; isSuccess = true; return super.dispatchTouchEvent(ev); } case MotionEvent.ACTION_UP: if (isMoved) { recoverLayout(); } return !isSuccess || super.dispatchTouchEvent(ev); default: return true; } } /** * 取消子view已经处理的事件 * * @param ev event */ private void cancelChild(MotionEvent ev) { ev.setAction(MotionEvent.ACTION_CANCEL); super.dispatchTouchEvent(ev); } /** * 位置还原 */ private void recoverLayout() { TranslateAnimation anim = new TranslateAnimation(0, 0, childView.getTop() - original.top, 0); anim.setDuration(ANIM_TIME); childView.startAnimation(anim); childView.layout(original.left, original.top, original.right, original.bottom); isMoved = false; } /** * 判断是否可以下拉 * * @return true:可以,false:不可以 */ private boolean canPullDown() { final int firstVisiblePosition = ((LinearLayoutManager) childView.getLayoutManager()).findFirstVisibleItemPosition(); if (firstVisiblePosition != 0 && childView.getAdapter().getItemCount() != 0) { return false; } int mostTop = (childView.getChildCount() > 0) ? childView.getChildAt(0).getTop() : 0; return mostTop >= 0; } /** * 判断是否可以上拉 * * @return true:可以,false:不可以 */ private boolean canPullUp() { final int lastItemPosition = childView.getAdapter().getItemCount() - 1; final int lastVisiblePosition = ((LinearLayoutManager) childView.getLayoutManager()).findLastVisibleItemPosition(); if (lastVisiblePosition >= lastItemPosition) { final int childIndex = lastVisiblePosition - ((LinearLayoutManager) childView.getLayoutManager()).findFirstVisibleItemPosition(); final int childCount = childView.getChildCount(); final int index = Math.min(childIndex, childCount - 1); final View lastVisibleChild = childView.getChildAt(index); if (lastVisibleChild != null) { return lastVisibleChild.getBottom() <= childView.getBottom() - childView.getTop(); } } return false; } public interface ScrollListener { /** * 滚动事件回调 */ void onScroll(); }}
使RecyclerView支持阻尼回弹的话,把RecyclerView放到OverScrollView中,其他照旧。要想监听阻尼滑动,直接设置scrollListener。
更多相关文章
- Android(安卓)ORM 数据库的使用
- afinal logo Android的快速开发框架 afinal
- Android(安卓)开源照相和图片选择框架PictureSelector
- android真实项目教程(一)——App应用框架搭建_by_CJJ
- Android热补丁动态修复技术(四):完善框架①
- Android(安卓)ORM框架GreenDao入门学习
- (4.2.9)【android开源工具】Android(安卓)ORMLite 框架的入门用法
- Android中网络框架简单封装的实例方法
- Android端腾讯性能监控框架Matrix源码分析之第一篇