下拉刷新,上拉加载更多的SwipeRefreshLayout(可自定义动画)

参考地址:https://blog.csdn.net/ozackyg/article/details/40509213

为啥重复搞,搞得还没人家好,因为除了需求,还有理解。我是这么认为的。

因为懒所以写出来留作自用,以后就是修修改改了。

打造自己的“下拉刷新,上拉加载更多,自定义动画及布局”控件

(拷贝SwipeRefreshLayout源码进行修改)

不斗图的码农你见过吗?

 

丑丑爆了,原谅我的审美和能力。。。

新写了一个自定义加载动画

设计思路:

也就是一个大的布局套着一个可滚动的子viewGroup

内外两个都可以滑动的View,那么主要问题就是解决滑动冲突了。

当然要重现父类View的拦截器部分:

 

onInterceptTouchEvent事件:

如果(子view可以向上滚动&&也可以向下滚动){
    //TODO直接放行,触摸事件交给子view处理
}否则{
    //TODO调用父类触摸事件OntouchEvent(由onTouchEvnent决定是否拦截)
}

TouchEvent事件:

能触发onTouchenvet事件有四种情况:
1.已经滑到顶部了,还要继续下滑动(触发顶部刷新)
2.已经滑到顶部了,这时候上划则滚动子view
3.已经滑到底部了,还要继续滑动(触发底部刷新)
4.已经滑到底部了,这时候下滑则滚动子view

由上四种情况分别处理:
1,3情况 触发父view的刷新加载动画
2,4情况 则不消费onTouch事件,并把结果返还给拦截器onInterceptTouchEvent处理把事件交给子view

 

上代码:

package com.huan.squirrel.pushcardlayout.pushcardlayout;


import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ListViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;


/**
 * @author:Squirrel桓
 * @time:2018/8/28
 */
public class PushCardLayout extends ViewGroup implements NestedScrollingParent,
        NestedScrollingChild {

    private int mTouchSlop;
    private final NestedScrollingParentHelper mNestedScrollingParentHelper;
    private final NestedScrollingChildHelper mNestedScrollingChildHelper;

    //滚动内容区
    private View contentLayout;
    //底部布局,顶部布局
    private LinearLayout bottomLayout, topLayout;
    private View topLayoutView;
    private View bottomLayoutView;

    //数据加载监听
    private PushCardDatalistener dataListener;
    //动画加载监听
    private PushCardAnimationListener animationListener;

    public void setDataListener(PushCardDatalistener dataListener) {
        this.dataListener = dataListener;
    }

    public void setAnimationListener(PushCardAnimationListener animationListener) {
        this.animationListener = animationListener;
    }

    public View getTopLayoutView() {
        return topLayoutView;
    }

    /**
     * 设置顶部view
     *
     * @param topLayoutView
     */
    public void setTopLayoutView(View topLayoutView) {
        topLayout.removeAllViews();
        topLayout.addView(topLayoutView);
        this.topLayoutView = topLayoutView;
    }

    public View getBottomLayoutView() {
        return bottomLayoutView;
    }

    /**
     * 设置底部view
     *
     * @param bottomLayoutView
     */
    public void setBottomLayoutView(View bottomLayoutView) {
        bottomLayout.removeAllViews();
        bottomLayout.addView(bottomLayoutView);
        this.bottomLayoutView = bottomLayoutView;
    }

    public PushCardLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
        mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
        mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
        setNestedScrollingEnabled(true);
        initView(context, attrs);
    }


    private int mMediumAnimationDuration;//动画时长
    private final DecelerateInterpolator mDecelerateInterpolator;
    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
    private static final int[] LAYOUT_ATTRS = new int[]{
            android.R.attr.enabled
    };

    private void initView(Context context, AttributeSet attrs) {
        final DisplayMetrics metrics = getResources().getDisplayMetrics();//分辨率
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();//距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。

        mMediumAnimationDuration = getResources().getInteger(
                android.R.integer.config_mediumAnimTime);//动画时长

        setWillNotDraw(false);//ViewGroup默认情况下,出于性能考虑,会被设置成WILL_NOT_DROW,这样,ondraw就不会被执行了。
        //调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。就可以重写ondraw()


        creatTopLayout(context);
        creatBottomLayout(context);
        /****** (START)  测试添加 可删除   ***********/
        /*ImageView imageView = new ImageView(getContext());
        imageView.setImageResource(R.mipmap.ic_launcher_round);
        setTopLayoutView(imageView);
        ImageView imageView2 = new ImageView(getContext());
        imageView2.setImageResource(R.mipmap.ic_launcher_round);
        setBottomLayoutView(imageView2);*/
        /******  (END) 测试添加 可删除   ***********/

        setChildrenDrawingOrderEnabled(true);//设置子view按照顺序绘制
        mSpinnerOffsetEnd = bottomLayoutHeight / 2;
        mTotalDragDistance = mSpinnerOffsetEnd;

        final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
        setEnabled(a.getBoolean(0, true));
        a.recycle();
    }

    /**
     * 初始化顶部布局
     * TODO 暂时用底部布局高度作为默认高度,动态顶部,底部高度 ,用到时候在处理吧
     *
     * @param context
     */
    private int bottomLayoutHeight = (int)(100*getResources().getDisplayMetrics().density);

    public int getBottomLayoutHeight() {
        return bottomLayoutHeight;
    }

    private void creatBottomLayout(Context context) {
        bottomLayout = new LinearLayout(context);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, bottomLayoutHeight);
        layoutParams.gravity = Gravity.CENTER;
        bottomLayout.setLayoutParams(layoutParams);
        bottomLayout.setGravity(Gravity.CENTER);
        //bottomLayout.setGravity(Gravity.BOTTOM);
        addView(bottomLayout);
    }

    /**
     * 初始化底部布局
     *
     * @param context
     */
    private int topLayoutHeight = (int)(100*getResources().getDisplayMetrics().density);

    private void creatTopLayout(Context context) {
        topLayout = new LinearLayout(context);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, topLayoutHeight);
        layoutParams.gravity = Gravity.CENTER;
        topLayout.setLayoutParams(layoutParams);
        topLayout.setGravity(Gravity.CENTER);
        addView(topLayout);
    }

    private int topLayoutOffsetTop = 0;//header距离顶部的距离
    private int bottomLayoutOffsetTop = 0;//footer距离顶部的距离

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        if (getChildCount() == 0) {
            return;
        }
        if (contentLayout == null) {
            ensureTarget();
        }
        if (contentLayout == null) {
            return;
        }
        final View child = contentLayout;
        final int childLeft = getPaddingLeft();
        final int childTop = getPaddingTop();
        final int childWidth = width - getPaddingLeft() - getPaddingRight();
        final int childHeight = height - getPaddingTop() - getPaddingBottom();
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);

        final DisplayMetrics metrics = getResources().getDisplayMetrics();//分辨率
        int topLayoutWidth = topLayout.getMeasuredWidth();
        topLayoutHeight = topLayout.getMeasuredHeight();
        topLayoutOffsetTop = -topLayoutHeight;
        topLayout.layout((width / 2 - topLayoutWidth / 2), -topLayoutHeight,
                (width / 2 + topLayoutWidth / 2), 0);

        int bottomLayoutWidth = bottomLayout.getMeasuredWidth();
        bottomLayoutHeight = bottomLayout.getMeasuredHeight();
        bottomLayoutOffsetTop = height;
        bottomLayout.layout((width / 2 - bottomLayoutWidth / 2), height,
                (width / 2 + bottomLayoutWidth / 2), height + bottomLayoutHeight);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (contentLayout == null) {
            ensureTarget();
        }
        if (contentLayout == null) {
            return;
        }
        contentLayout.measure(MeasureSpec.makeMeasureSpec(
                getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
        topLayout.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec((int) topLayoutHeight, MeasureSpec.EXACTLY));

        bottomLayout.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec((int) bottomLayoutHeight, MeasureSpec.EXACTLY));
    }


    private void ensureTarget() {
        if (contentLayout == null) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!child.equals(topLayout) && !child.equals(bottomLayout)) {
                    contentLayout = child;
                    break;
                }
            }
        }
    }


    @Override
    public void requestDisallowInterceptTouchEvent(boolean b) {
        // if this is a List < L or another view that doesn't support nested
        // scrolling, ignore this request so that the vertical scroll event
        // isn't stolen
        if ((android.os.Build.VERSION.SDK_INT < 21 && contentLayout instanceof AbsListView)
                || (contentLayout != null && !ViewCompat.isNestedScrollingEnabled(contentLayout))) {
            // Nope.
        } else {
            super.requestDisallowInterceptTouchEvent(b);
        }
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        ensureTarget();
        boolean handled = false;
        if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) {
            Log.i("CGQ", "不拦截子view滚动");
            mReturningToStart = false;
            if (isUped || isDowned) {
                finishSpinner(0);
            }
        }
        if (isEnabled() && !mReturningToStart && (!canChildScrollUp() || !canChildScrollDown())
                && !mRefreshing && !mNestedScrollInProgress) {
            handled = onTouchEvent(ev);
            Log.i("CGQ", "事件分发");
        }

        //Log.i("CGQ", "handled=" + handled);
        //父控件消费,子空间就不执行了。父控件不消费,再交给子空间处理
        return !handled ? super.onInterceptTouchEvent(ev) : handled;
    }


    private float mDownY;
    private boolean mIsBeingDraggedTop;//顶部拖拽状态

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (!canRefresh) {
            return false;
        }

        int action = event.getAction();
        boolean handled = false;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 移动的起点
                mDownY = event.getY();
                mInitialDownY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(event.getY() - mDownY) < mTouchSlop) {
                    Log.i("CGQ", "未达到滚动最小值");
                    return false;
                }
                float offset_c = (event.getY() - mDownY);//当前滑动间距
                if (offset_c > 0) {//下滑手势
                    if (isDowned) {//已经是下滑到底状态
                        finishSpinner(0);
                    } else {//不是下滑到底状态
                        if (canChildScrollUp()) {
                            Log.i("CGQ", "下滑");
                        } else {
                            Log.i("CGQ", "下滑并触发");
                            handled = true;
                            float y = event.getY();
                            startDragging(y);
                            final float overscrollTop = offset_c * DRAG_RATE;
                            //下滑并触发
                            moveSpinner(overscrollTop);
                        }
                    }
                } else {//上拉手势
                    if (isUped) {//已经是上拉到顶状态
                        finishSpinner(0);
                    } else {
                        if (canChildScrollDown()) {
                            Log.i("CGQ", "上滑");
                        } else {
                            Log.i("CGQ", "上滑并触发");
                            handled = true;
                            float y = event.getY();
                            startDragging(y);
                            final float overscrollTop = offset_c * DRAG_RATE;
                            //下拉显示头部
                            moveSpinner(overscrollTop);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                finishSpinner(0);
                break;
            case MotionEvent.ACTION_CANCEL:
                finishSpinner(0);
                break;
        }
        return handled;
    }

    /**
     * @param offset 偏移量
     */
    private void scrollTopLayout(int offset) {
        if (contentLayout != null) {
            ViewCompat.offsetTopAndBottom(contentLayout, offset);//正数向下移动,负数向上移动
            //topLayout.bringToFront();
            if (isUping) {
                ViewCompat.offsetTopAndBottom(topLayout, offset);
            } else {
                ViewCompat.offsetTopAndBottom(bottomLayout, offset);
            }
            mContenViewOffsetTop = contentLayout.getTop();
        }

    }

    private float mInitialMotionY;
    private static final float DRAG_RATE = .5f;
    private float mInitialDownY;

    private void startDragging(float y) {
        final float yDiff = y - mInitialDownY;
        if (yDiff > mTouchSlop && !mIsBeingDraggedTop) {
            mInitialMotionY = mInitialDownY + mTouchSlop;
            mIsBeingDraggedTop = true;
        }
    }

    private float mTotalDragDistance = -1;
    int mSpinnerOffsetEnd;
    protected int mOriginalOffsetTop;
    int mContenViewOffsetTop;

    /**
     * @param overscrollTop
     * @param
     */
    private void moveSpinner(float overscrollTop) {
        boolean isTop = false;
        int targetY2 = 0;
        if (overscrollTop < 0) {
            isTop = true;
            overscrollTop = -overscrollTop;
            //更改状态
            isDowning = true;
        } else {
            //更改状态
            isUping = true;
        }
        Log.i("CGQ", "moveSpinner = " + overscrollTop);
        float originalDragPercent = overscrollTop / mTotalDragDistance;

        float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
        float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
        float slingshotDist = mUsingCustomStart ? mSpinnerOffsetEnd - mOriginalOffsetTop
                : mSpinnerOffsetEnd;
        float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)
                / slingshotDist);
        float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
                (tensionSlingshotPercent / 4), 2)) * 2f;
        float extraMove = (slingshotDist) * tensionPercent * 2;
        targetY2 = (int) ((slingshotDist * dragPercent) + extraMove);

        int h = !isTop ? (targetY2 - mContenViewOffsetTop) : (-targetY2 - mContenViewOffsetTop);

        if (animationListener != null) {
            animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, (float) Math.abs(contentLayout.getTop()) / bottomLayoutHeight);
        }
        Log.i("CGQ", "slingshotDist=" + slingshotDist + ",h=" + h);
        scrollTopLayout(h);
    }

    private boolean isUping = false;//正在上拉刷新
    private boolean isDowning = false;//正在下拉
    private boolean isUped = false;//正在上拉到指定位置(默认拉到三分之二就触发加载)
    private boolean isDowned = false;//正在下拉到指定位置(默认拉到三分之二就触发加载)

    /**
     * 恢复
     */
    public void setCancel() {
        finishSpinner(0);
    }

    //是否可以上拉下拉滑动
    private boolean canRefresh = true;

    /**
     * 是否可以上拉下拉滑动
     *
     * @param canRefresh
     */
    public void setCanRefresh(boolean canRefresh) {
        this.canRefresh = canRefresh;
    }

    /**
     * 恢复动画
     */
    private void finishSpinner(float overscrollTop) {
        if (contentLayout == null) {
            return;
        }
        //处理是否触发刷新或者加载更多
        if (Math.abs(contentLayout.getTop()) > bottomLayoutHeight / 3 * 2 && (!isUped && !isDowned)) {//拉到2/3以上则触发
            //填充动画(自动下拉到最大高度)
            float startValue = Math.abs(contentLayout.getTop())/(float)bottomLayoutHeight;
            ValueAnimator animator = ValueAnimator.ofFloat(startValue, 1);

            animator.setDuration((int)(200*(1-startValue)));
            animator.setInterpolator(mDecelerateInterpolator);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float scale = (float) animation.getAnimatedValue();
                    if(contentLayout.getTop()>0&& (topLayout.getTop() != topLayoutOffsetTop )){
                        ViewCompat.offsetTopAndBottom(contentLayout, (int) (( bottomLayoutHeight - contentLayout.getTop()) * scale));//正数向下移动,负数向上移动
                        ViewCompat.offsetTopAndBottom(topLayout, (int) ( (bottomLayoutHeight + (topLayoutOffsetTop - topLayout.getTop())) * scale));
                    }else if( (topLayout.getTop() != topLayoutOffsetTop )){
                        ViewCompat.offsetTopAndBottom(contentLayout, (int) ((- bottomLayoutHeight - contentLayout.getTop()) * scale));//正数向下移动,负数向上移动
                        ViewCompat.offsetTopAndBottom(bottomLayout, (int) ((-bottomLayoutHeight + (bottomLayoutOffsetTop - bottomLayout.getTop())) * scale));
                    }
                    mContenViewOffsetTop = contentLayout.getTop();
                    if (scale == 0) {//动画开始
                        if (animationListener != null) {
                            animationListener.onStart(isUping ? topLayoutView : bottomLayoutView);
                        }
                    } else if (scale == 1) {//动画结束
                        if (animationListener != null) {
                            animationListener.onEnd(isUping ? topLayoutView : bottomLayoutView);
                        }
                        if (isUping) {//上拉处理
                            isUping = false;
                            isUped = true;
                            if (dataListener != null) {
                                dataListener.onRefreshData();
                            }
                        }
                        if (isDowning) {//下拉处理
                            isDowning = false;
                            isDowned = true;
                            if (dataListener != null) {
                                dataListener.onLoadMoreData();
                            }
                        }
                    } else {//动画进行中
                        if (animationListener != null) {
                            animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, scale);
                        }
                    }
                }
            });
            animator.start();
        } else {
            //回滚动画
            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
            animator.setDuration(200);
            animator.setInterpolator(mDecelerateInterpolator);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float scale = (float) animation.getAnimatedValue();
                    ViewCompat.offsetTopAndBottom(contentLayout, (int) (-contentLayout.getTop() * scale));//正数向下移动,负数向上移动
                    if (topLayout.getTop() != topLayoutOffsetTop) {
                        ViewCompat.offsetTopAndBottom(topLayout, (int) ((topLayoutOffsetTop - topLayout.getTop()) * scale));
                    }
                    if (bottomLayout.getTop() != bottomLayoutOffsetTop) {
                        ViewCompat.offsetTopAndBottom(bottomLayout, (int) ((bottomLayoutOffsetTop - bottomLayout.getTop()) * scale));
                    }
                    mContenViewOffsetTop = contentLayout.getTop();
                    if (scale == 0) {//动画开始
                        if (animationListener != null) {
                            animationListener.onStart(isUping ? topLayoutView : bottomLayoutView);
                        }
                    } else if (scale == 1) {//动画结束
                        isUped = false;
                        isDowned = false;
                        isUping = false;
                        isDowning = false;
                        if (animationListener != null) {
                            animationListener.onEnd(isUping ? topLayoutView : bottomLayoutView);
                        }
                    } else {//动画进行中
                        if (animationListener != null) {
                            animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, scale);
                        }
                    }
                }
            });
            animator.start();
        }
    }

    // NestedScrollingParent
    private float mTotalUnconsumed;
    private boolean mNestedScrollInProgress;
    private final int[] mParentScrollConsumed = new int[2];
    private final int[] mParentOffsetInWindow = new int[2];
    // Whether the client has set a custom starting position;
    boolean mUsingCustomStart;
    private boolean mReturningToStart;
    boolean mRefreshing = false;
    private OnChildScrollUpCallback mChildScrollUpCallback;

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return isEnabled() && !mReturningToStart && !mRefreshing
                && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        // Reset the counter of how much leftover scroll needs to be consumed.
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
        // Dispatch up to the nested parent
        startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
        mTotalUnconsumed = 0;
        mNestedScrollInProgress = true;
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        // If we are in the middle of consuming, a scroll, then we want to move the spinner back up
        // before allowing the list to scroll
        if (dy > 0 && mTotalUnconsumed > 0) {
            if (dy > mTotalUnconsumed) {
                consumed[1] = dy - (int) mTotalUnconsumed;
                mTotalUnconsumed = 0;
            } else {
                mTotalUnconsumed -= dy;
                consumed[1] = dy;
            }
            moveSpinner(mTotalUnconsumed);
        }

        // If a client layout is using a custom start position for the circle
        // view, they mean to hide it again before scrolling the child view
        // If we get back to mTotalUnconsumed == 0 and there is more to go, hide
        // the circle so it isn't exposed if its blocking content is moved
        if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0
                && Math.abs(dy - consumed[1]) > 0) {
            //mCircleView.setVisibility(View.GONE);
        }

        // Now let our nested parent consume the leftovers
        final int[] parentConsumed = mParentScrollConsumed;
        if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
            consumed[0] += parentConsumed[0];
            consumed[1] += parentConsumed[1];
        }
    }


    @Override
    public int getNestedScrollAxes() {
        return mNestedScrollingParentHelper.getNestedScrollAxes();
    }

    @Override
    public void onStopNestedScroll(View target) {
        mNestedScrollingParentHelper.onStopNestedScroll(target);
        mNestedScrollInProgress = false;
        // Finish the spinner for nested scrolling if we ever consumed any
        // unconsumed nested scroll
        if (mTotalUnconsumed > 0) {
            finishSpinner(mTotalUnconsumed);
            mTotalUnconsumed = 0;
        }
        // Dispatch up our nested parent
        stopNestedScroll();
    }


    @Override
    public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
                               final int dxUnconsumed, final int dyUnconsumed) {
        // Dispatch up to the nested parent first
        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                mParentOffsetInWindow);

        // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are
        // sometimes between two nested scrolling views, we need a way to be able to know when any
        // nested scrolling parent has stopped handling events. We do that by using the
        // 'offset in window 'functionality to see if we have been moved from the event.
        // This is a decent indication of whether we should take over the event stream or not.
        final int dy = dyUnconsumed + mParentOffsetInWindow[1];
        if (dy < 0 && !canChildScrollUp()) {
            mTotalUnconsumed += Math.abs(dy);
            moveSpinner(mTotalUnconsumed);
        }
    }

    // NestedScrollingChild

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return mNestedScrollingChildHelper.isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {
        return mNestedScrollingChildHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        mNestedScrollingChildHelper.stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return mNestedScrollingChildHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                        int dyUnconsumed, int[] offsetInWindow) {
        return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed,
                                           int[] offsetInWindow) {
        return mNestedScrollingChildHelper.dispatchNestedPreScroll(
                dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX,
                                    float velocityY) {
        return dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY,
                                 boolean consumed) {
        return dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

    //判断是否可以下拉
    public boolean canChildScrollUp() {
        if (mChildScrollUpCallback != null) {
            return mChildScrollUpCallback.canChildScrollUp(this, contentLayout);
        }
        if (contentLayout instanceof ListView) {
            return ListViewCompat.canScrollList((ListView) contentLayout, -1);
        }
        return contentLayout.canScrollVertically(-1);
    }

    //判断是否可以上拉
    public boolean canChildScrollDown() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (contentLayout instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) contentLayout;
                // AppLog.e(absListView.getFirstVisiblePosition()+"  :   "+absListView.getChildAt(absListView.getChildCount()-1).getBottom()+"  :   "+absListView.getPaddingBottom());
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return contentLayout.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(contentLayout, 1);
        }
    }

    public void setOnChildScrollUpCallback(@Nullable OnChildScrollUpCallback callback) {
        mChildScrollUpCallback = callback;
    }

    /**
     * Classes that wish to override {@link PushCardLayout#canChildScrollUp()} method
     * behavior should implement this interface.
     */
    public interface OnChildScrollUpCallback {
        /**
         * Callback that will be called when {@link PushCardLayout#canChildScrollUp()} method
         * is called to allow the implementer to override its behavior.
         *
         * @param parent SwipeRefreshLayout that this callback is overriding.
         * @param child  The child view of SwipeRefreshLayout.
         * @return Whether it is possible for the child view of parent layout to scroll up.
         */
        boolean canChildScrollUp(@NonNull PushCardLayout parent, @Nullable View child);

        /**
         * Callback that will be called when {@link PushCardLayout#canChildScrollDown()} method
         * is called to allow the implementer to override its behavior.
         *
         * @param parent SwipeRefreshLayout that this callback is overriding.
         * @param child  The child view of SwipeRefreshLayout.
         * @return Whether it is possible for the child view of parent layout to scroll down.
         */
        boolean canChildScrollDown(@NonNull PushCardLayout parent, @Nullable View child);
    }

    /**
     * 数据监听器
     */
    public interface PushCardDatalistener {
        /**
         * 上拉加载更多
         */
        void onLoadMoreData();

        /**
         * 下拉刷新
         */
        void onRefreshData();
    }

    /**
     * 动画监听器
     */
    public interface PushCardAnimationListener {
        /**
         * 开始初始化操作
         */
        void onStart(View targetView);

        /**
         * 0-1属性动画,下拉百分比动画
         *
         * @param targetView 目标view(头部或者底部)
         * @param isUpper    //判断头部动画还是底部动画
         * @param value      动画百分比
         */
        void onRuning(View targetView, boolean isUpper, float value);

        /**
         * 动画结束
         */
        void onEnd(View targetView);
    }

}

一个类完事了。要啥动画自己写去吧!

使用部分:

1.layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.CustomerActivity">

    <com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout
        android:id="@+id/pcl_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never">
        <include layout="@layout/layout_main"/>
    </com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout>
</android.support.constraint.ConstraintLayout>

2.activity

package com.huan.squirrel.pushcardlayout.activity;

import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.huan.squirrel.pushcardlayout.R;
import com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout;

public class CustomerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_customer);
        initView();
    }

    private void initView() {
        //初始化
        PushCardLayout pcl_layout = findViewById(R.id.pcl_layout);

        /*************************   自定义头部和底部布局   ************************************************/
        TextView textView = new TextView(this);
        textView.setText("下拉刷新数据");
        textView.setTextSize(20);
        textView.setBackgroundColor(Color.RED);

        TextView textView2 = new TextView(this);
        textView2.setText("上拉加载更多数据");
        textView2.setTextSize(20);
        textView2.setBackgroundColor(Color.YELLOW);

        //设置顶部布局view
        pcl_layout.setTopLayoutView(textView);
        //设置底部布局view
        pcl_layout.setBottomLayoutView(textView2);

        //禁用滑动 pcl_layout.setCanRefresh(false);

        /*************************   设置数据监听器,可触发网络请求  数据加载完成请手动恢复 pcl_layout.setCancel();  ********************************/
        pcl_layout.setDataListener(new PushCardLayout.PushCardDatalistener() {
            @Override
            public void onLoadMoreData() {
                Toast.makeText(CustomerActivity.this, "加载更多。。。", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onRefreshData() {
                Toast.makeText(CustomerActivity.this, "刷新数据。。。", Toast.LENGTH_SHORT).show();
            }
        });

        /*************************   设置动画监听器,可自定义动画   ************************************************/
        pcl_layout.setAnimationListener(new PushCardLayout.PushCardAnimationListener() {
            @Override
            public void onStart(View targetView) {
                Log.i("A", "Animation Start ...");

            }

            @Override
            public void onRuning(View targetView,boolean isUpper, final float value) {
                Log.i("A", "Animation onRuning:" + value);
                ((TextView) targetView).setRotation(value * 360);
                //isUpper 可判断是头部动画还是底部动画

            }

            @Override
            public void onEnd(View targetView) {
                Log.i("A", "Animation End ...");

            }
        });

    }
}

写给自己用的,写的不好还请大家指出。(许久不写安卓了,要好好学习,多看看源码)

github地址:https://github.com/squirrelhuan/PushCardLayout

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 实现下拉刷新上拉加载多的话,你可以使用Compose中的`SwipeRefreshLayout`和`LazyColumnFor`。 `SwipeRefreshLayout`可以用来实现下拉刷新,它可以包含其他的Compose控件,并且在用户下拉时会触发刷新事件。 而`LazyColumnFor`是一个可懒加载的Column,它会根据屏幕的空间加载多的item,这样可以实现上拉加载多的效果。 以下是一个简单的示例代码: ``` @Composable fun RefreshableList(data: List<String>) { SwipeRefreshLayout(refreshing = state.refreshing) { LazyColumnFor(data) { item -> Text(item) } } } ``` 在这段代码中,`SwipeRefreshLayout`包含了一个`LazyColumnFor`,而`LazyColumnFor`则根据传入的`data`生成了若干个`Text`控件,当用户下拉时会触发刷新事件,并且根据屏幕的空间自动加载多。 ### 回答2: compose 是一种用于构建用户界面的声明式工具集,可以通过函数式编程方式来创建可组合的UI组件。要实现下拉刷新上拉加载多功能,可以借助 compose 提供的布局组件和手势监听等功能。 首先,可以使用 compose 的 LazyColumn 组件来展示列表数据,并且添加一个监听手势的方法,以实现下拉刷新上拉加载多的功能。 下拉刷新的实现可以通过监听手势的位置变化,当手指位置向下滑动一定距离之后,可以通过修改列表数据源来触发刷新操作。 上拉加载多的实现可以通过监听手势的位置变化,当手指位置向上滑动一定距离之后,可以通过加载多数据并添加到列表数据源的方式来触发加载多操作。 在代码中,可以使用 onGragGesture 方法监听手势的位置变化,通过判断手势状态来执行相应的刷新和加载逻辑。 具体的实现流程如下: 1. 创建一个列表数据源列表,用于展示列表数据。 2. 使用 LazyColumn 组件来展示列表数据,并添加 onGragGesture 方法监听手势。 3. 在 onGragGesture 方法中,根据手指位置的变化和手势状态的变化来执行对应的刷新和加载逻辑。 4. 当手势状态变为 Dragging,并且手指位置向下滑动一定距离时,触发下拉刷新操作。可以通过修改列表数据源并新列表进行刷新操作。 5. 当手势状态变为 Dragging,并且手指位置向上滑动一定距离时,触发上拉加载多操作。可以通过加载多数据并添加到列表数据源的方式来实现加载多功能。 通过上述步骤,可以使用 compose 来实现下拉刷新上拉加载多功能。使用 compose 提供的布局组件和手势监听等功能,可以轻松实现用户界面的交互。 ### 回答3: 使用compose实现下拉刷新上拉加载多的过程如下: 1. 首先,在页面组件中定义一个状态变量,用于表示当前的刷新和加载多的状态。 2. 使用useCallback或者useState来定义一个函数,用于处理下拉刷新的逻辑。 3. 在页面组件中,使用useEffect来监听下拉刷新的事件,一旦触发下拉刷新事件,就调用上一步定义的处理函数。 4. 在页面组件中,使用useEffect监听滚动事件。一旦滚动到页面底部,就触发加载多的逻辑。 5. 使用条件渲染来根据不同的状态显示不同的内容,例如加载中、加载失败等。 6. 使用ScrollView组件或者FlatList组件来渲染长列表,并包裹上拉加载多的内容。 7. 在加载多的逻辑处理函数中,新列表数据,将新的数据追加到原有的数据列表中,以实现加载多的效果。 8. 在下拉刷新的逻辑处理函数中,重置列表数据,重新加载最新的数据,以实现下拉刷新的效果。 以上就是使用compose实现下拉刷新上拉加载多的基本步骤。具体的实现代码可以根据具体的场景和需求来灵活调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值