滑动列表仿ios尼阻回弹效果实现

滑动列表实现仿ios的尼阻回弹,可作用于 listView , gridView ,RecyclerView等列表组件,别的组件没试过。有兴趣的可以试一试。

实现原理:定义一个外层容器,内层可以是recyclerview,Listview,ScrollView等可以滑动的组件,主要是在事件分发上动手脚,在事件分发dispatchTouchEvent中根据内层recyclerview中可见的item是否处于顶部或者底部来处理事件分发(自己消费掉或者将事件交由当前view的onlnterceptTouchEvent进行事件拦截,而此处事件不拦截,交给recycelrview进行消费)

下面的自定义控件是一个viewGroup,列表控件嵌套在其中,可实现尼阻回弹效果,

其中ANIM_TIME这个常量代表的是回弹的时间,值越大,回弹速度越快,这个根据自己设置就好。

不多说。直接复制代码干了。。。


import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.TranslateAnimation;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;

import java.util.ArrayList;
import java.util.List;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 阻尼回弹效果
 *
 * @author lixinxiao
 */
public class PullRecyclerViewGroup extends LinearLayout implements ViewTreeObserver.OnGlobalLayoutListener {

    /**
     * 滚动时间
     */
    private static final long ANIM_TIME = 200;

    /**
     * 如果按下时不能上拉和下拉,会在手指移动时更新为当前手指的Y值
     */
    private View childView;

    /**
     * 如果按下时不能上拉和下拉,会在手指移动时更新为当前手指的Y值
     */
    private Rect originalRect = new Rect();

    //滚动时,移动的view和位置
    private List<View> mMoveViews = new ArrayList<>();
    private List<Rect> mMoveRects = new ArrayList<>();

    /**
     * 如果按下时不能上拉和下拉,会在手指移动时更新为当前手指的Y值
     */
    private boolean isMoved = false;

    /**
     * 如果按下时不能上拉和下拉,会在手指移动时更新为当前手指的Y值
     */
    private float startY;

    /**
     * 阻尼
     */
    private static final float OFFSET_RADIO = 0.5f;

    private boolean isRecyclerReuslt = false;


    public PullRecyclerViewGroup(Context context) {
        this(context, null);
    }

    public PullRecyclerViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullRecyclerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //关闭右侧滚动条
        this.setVerticalScrollBarEnabled(false);
    }

    /**
     * 加载布局后初始化,这个方法会在加载完布局后调用
     */
    @Override
    protected void onFinishInflate() {
        //此处为容器中的子view   必须有RecyclerView、ListView、ScrollView,当然这里忽略ListView和ScrollView
        if (getChildCount() > 0) {
            for (int i = 0; i < getChildCount(); i++) {
                if (getChildAt(i) instanceof RecyclerView || getChildAt(i) instanceof ListView || getChildAt(i) instanceof ScrollView) {
                    if (childView == null) {
                        childView = getChildAt(i);
                    } else {
                        throw new RuntimeException("PullRecyclerViewGroup 中只能存在一个RecyclerView、ListView或者ScrollView");
                    }
                }
            }
        }

        if (childView == null) {
            throw new RuntimeException("PullRecyclerViewGroup 子容器中必须有一个RecyclerView、ListView或者ScrollView");
        }
        //布局重绘监听,比如华为屏幕键盘可以弹出和隐藏,改变布局,加监听就可以虽键盘弹出关闭的变化而变化
        getViewTreeObserver().addOnGlobalLayoutListener(this);

        super.onFinishInflate();
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //ScrollView中唯一的子控件的位置信息,这个位置在整个控件的生命周期中保持不变
        originalRect.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
        for (int i = 0; i < mMoveViews.size(); i++) {
            final View v = mMoveViews.get(i);
            v.addOnLayoutChangeListener(new OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    Rect rect = new Rect();
                    rect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
                    mMoveRects.add(rect);
                    v.removeOnLayoutChangeListener(this);
                }
            });
        }
    }

    /**
     * 事件分发
     */

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (childView == null) {
            return super.dispatchTouchEvent(ev);
        }

        //如果当前view的Y上的位置
        boolean isTouchOutOfScrollView = ev.getY() >= originalRect.bottom || ev.getY() <= originalRect.top;
        //如果不在view的范围内
        if (isTouchOutOfScrollView) {
            //如果不在view的范围内
            if (isMoved) {
                recoverLayout();
            }
            return true;
        }

        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //记录按下时的Y
                startY = ev.getY();
            case MotionEvent.ACTION_MOVE:
                float nowY = ev.getY();
                int scrollY = (int) (nowY - startY);
                if ((isCanPullDown() && scrollY > 0) || (isCanPullUp() && scrollY < 0)
                        || (isCanPullDown() && isCanPullUp())) {
                    int offset = (int) (scrollY * OFFSET_RADIO);
                    childView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset);
                    for (int i = 0; i < mMoveViews.size(); i++) {
                        if (mMoveViews.get(i) != null) {
                            mMoveViews.get(i).layout(mMoveRects.get(i).left, mMoveRects.get(i).top + offset, mMoveRects.get(i).right, mMoveRects.get(i).bottom + offset);
                        }
                    }
                    isMoved = true;
                    isRecyclerReuslt = false;
                    return true;
                } else {
                    startY = ev.getY();
                    isMoved = false;
                    isRecyclerReuslt = true;
                    recoverLayout();
                    return super.dispatchTouchEvent(ev);
                }
            case MotionEvent.ACTION_UP:

                if (isMoved) {
                    recoverLayout();
                }

                if (isRecyclerReuslt) {
                    return super.dispatchTouchEvent(ev);
                } else {
                    return true;
                }
            default:
                return true;
        }
    }

    /**
     * 位置还原
     */
    private void recoverLayout() {

        if (!isMoved) {
            //如果不在view的范围内
            return;
        }

        for (int i = 0; i < mMoveViews.size(); i++) {
            if (mMoveRects.get(i) != null) {
                TranslateAnimation anims = new TranslateAnimation(0, 0, mMoveViews.get(i).getTop(), mMoveRects.get(i).top);
                anims.setDuration(ANIM_TIME);
                mMoveViews.get(i).startAnimation(anims);
                mMoveViews.get(i).layout(mMoveRects.get(i).left, mMoveRects.get(i).top, mMoveRects.get(i).right, mMoveRects.get(i).bottom);
            }
        }

        TranslateAnimation anim = new TranslateAnimation(0, 0, childView.getTop() - originalRect.top, 0);
        anim.setDuration(ANIM_TIME);
        childView.startAnimation(anim);

        childView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);

        isMoved = false;
    }

    /**
     * 容器的的事件都在事件分发中处理,这里处理的是事件分发传递过来的事件,
     * 

* 传递过来的为RecyclerVIew的事件 不拦截,直接交给reyclerview处理 * * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return false; } /** * 判断是否可以下拉 * * @return */ private boolean isCanPullDown() { final RecyclerView.Adapter adapter = ((RecyclerView) childView).getAdapter(); if (null == adapter) { return true; } final int firstVisiblePosition = ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findFirstVisibleItemPosition(); if (firstVisiblePosition != 0 && adapter.getItemCount() != 0) { return false; } int mostTop = (((RecyclerView) childView).getChildCount() > 0) ? ((RecyclerView) childView).getChildAt(0).getTop() : 0; return mostTop >= 0; } /** * 判断是否可以上拉 * * @return */ private boolean isCanPullUp() { final RecyclerView.Adapter adapter = ((RecyclerView) childView).getAdapter(); if (null == adapter) { return true; } final int lastItemPosition = adapter.getItemCount() - 1; final int lastVisiblePosition = ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findLastVisibleItemPosition(); if (lastVisiblePosition >= lastItemPosition) { final int childIndex = lastVisiblePosition - ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findFirstVisibleItemPosition(); final int childCount = ((RecyclerView) childView).getChildCount(); final int index = Math.min(childIndex, childCount - 1); final View lastVisibleChild = ((RecyclerView) childView).getChildAt(index); if (lastVisibleChild != null) { return lastVisibleChild.getBottom() <= childView.getBottom() - childView.getTop(); } } return false; } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onGlobalLayout() { //华为手机屏幕下方的返回、home键显示隐藏改变布局 requestLayout(); getViewTreeObserver().removeOnGlobalLayoutListener(this); } /** * 跟随弹性移动的view * * @param view */ public void setMoveViews(View view) { this.mMoveViews.add(view); requestLayout(); } }

上面是java代码的实现。下面再贴上xml代码

<com.yikelive.view.PullRecyclerViewGroup
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_siteTitle">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="16dp"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            tools:listitem="@layout/item_live_detail_ticket"/>
com.yikelive.view.PullRecyclerViewGroup>

然后就没有了。。。

你可能感兴趣的:(Android)