美文网首页高级UI
侧滑效果[第十六篇]:侧滑框架SmartSwipe之下拉刷新

侧滑效果[第十六篇]:侧滑框架SmartSwipe之下拉刷新

作者: NoBugException | 来源:发表于2019-11-22 16:42 被阅读0次

    在我们做项目的时候,一些场景需要用到下拉刷新数据以及上拉拉取更多数据,SmartSwipe框架中的SmartSwipeRefresh可以满足绝大部分需求。

    SmartSwipeRefresh的源码如下:

    /**
     * A wrapper of DrawerConsumer(or SlidingConsumer) to build Refresh util for View(s)
     *
     * Supports for: View/ViewGroup/LinearLayout/RelativeLayout/ListView/RecyclerView/WebView/etc...
     * <pre>
     *     Usage:
     *      1. with global default header and footer via SmartSwipeRefresh.setDefaultRefreshViewCreator(creator)
     *          1). SmartSwipeRefresh.drawerMode(view, false).setDataLoader(loader);
     *          2). SmartSwipeRefresh.behindMode(view, false).setDataLoader(loader);
     *          3). SmartSwipeRefresh.translateMode(view, false).setDataLoader(loader);
     *          4). SmartSwipeRefresh.scaleMode(view, false).setDataLoader(loader);
     *      2. with specified header and footer
     *          1). SmartSwipeRefresh.drawerMode(view, false, false).setDataLoader(loader).setHeader(header).setFooter(footer);
     *          2). SmartSwipeRefresh.behindMode(view, false, false).setDataLoader(loader).setHeader(header).setFooter(footer);
     *          3). SmartSwipeRefresh.translateMode(view, false, false).setDataLoader(loader).setHeader(header).setFooter(footer);
     *          4). SmartSwipeRefresh.scaleMode(view, false, false).setDataLoader(loader).setHeader(header).setFooter(footer);
     *
     *      3. more DrawerConsumer(SlidingConsumer extends DrawerConsumer) features
     *          DrawerConsumer consumer = smartSwipeRefresh.getSwipeConsumer();
     *          //behindMode or translateMode or scaleMode
     *          SlidingConsumer consumer = smartSwipeRefresh.getSwipeConsumer().as(SlidingConsumer.class);
     * </pre>
     * if
     * @see SmartSwipeRefreshViewCreator
     * @see SmartSwipeRefreshDataLoader
     * @see SmartSwipeRefreshHeader
     * @see SmartSwipeRefreshFooter
     * @see ClassicHeader
     * @see ClassicFooter
     * @author billy.qi
     */
    public class SmartSwipeRefresh {
    
        private static SmartSwipeRefreshViewCreator mCreator;
    
        private DrawerConsumer mConsumer;
        private SmartSwipeRefreshHeader mHeader;
        private SmartSwipeRefreshFooter mFooter;
        private RefreshView mActiveRefreshView;
        private boolean mHorizontal;
        private SmartSwipeRefreshDataLoader mDataLoader;
        private boolean mNoMoreData;
    
        public static void setDefaultRefreshViewCreator(SmartSwipeRefreshViewCreator creator) {
            mCreator = creator;
        }
    
    
        /**
         * looks like drawer: header and footer show above the contentView
         * @param contentView any view which needs to refresh and load more
         * @param horizontal works horizontally or not
         * @return this
         */
        public static SmartSwipeRefresh drawerMode(View contentView, boolean horizontal) {
            return drawerMode(contentView, horizontal, true);
        }
    
        /**
         * looks like drawer: header and footer show above the contentView
         * @param contentView any view which needs to refresh and load more
         * @param horizontal works horizontally or not
         * @param withDefaultHeaderAndFooter use default header and footer via {@link #setDefaultRefreshViewCreator(SmartSwipeRefreshViewCreator)}
         * @return this
         * @see #drawerMode(View, boolean)
         */
        public static SmartSwipeRefresh drawerMode(View contentView, boolean horizontal, boolean withDefaultHeaderAndFooter) {
            return create(contentView, new DrawerConsumer(), horizontal, withDefaultHeaderAndFooter);
        }
    
        /**
         * header and footer show behind the contentView, and stay at its position while contentView is moving
         * @param contentView any view which needs to refresh and load more
         * @param horizontal works horizontally or not
         * @return this
         */
        public static SmartSwipeRefresh behindMode(View contentView, boolean horizontal) {
            return behindMode(contentView, horizontal, true);
        }
    
        /**
         * header and footer show behind the contentView, and stay at its position while contentView is moving
         * @param contentView any view which needs to refresh and load more
         * @param horizontal works horizontally or not
         * @param withDefaultHeaderAndFooter use default header and footer via {@link #setDefaultRefreshViewCreator(SmartSwipeRefreshViewCreator)}
         * @return this
         * @see #behindMode(View, boolean)
         */
        public static SmartSwipeRefresh behindMode(View contentView, boolean horizontal, boolean withDefaultHeaderAndFooter) {
            return slideMode(contentView, SlidingConsumer.FACTOR_COVER, horizontal, withDefaultHeaderAndFooter);
        }
    
        /**
         * header and footer show followed the contentView (moves pixel by pixel with contentView)
         * @param contentView any view which needs to refresh and load more
         * @param horizontal works horizontally or not
         * @return this
         */
        public static SmartSwipeRefresh translateMode(View contentView, boolean horizontal) {
            return translateMode(contentView, horizontal, true);
        }
    
        /**
         * header and footer show followed the contentView (moves pixel by pixel with contentView)
         * @param contentView any view which needs to refresh and load more
         * @param horizontal works horizontally or not
         * @param withDefaultHeaderAndFooter use default header and footer via {@link #setDefaultRefreshViewCreator(SmartSwipeRefreshViewCreator)}
         * @return this
         * @see #translateMode(View, boolean)
         */
        public static SmartSwipeRefresh translateMode(View contentView, boolean horizontal, boolean withDefaultHeaderAndFooter) {
            return slideMode(contentView, SlidingConsumer.FACTOR_FOLLOW, horizontal, withDefaultHeaderAndFooter);
        }
    
        /**
         * header and footer show behind the contentView
         * Always in the middle of the space left after the main view has moved
         * @param contentView any view which needs to refresh and load more
         * @param horizontal works horizontally or not
         * @return this
         */
        public static SmartSwipeRefresh scaleMode(View contentView, boolean horizontal) {
            return scaleMode(contentView, horizontal, true);
        }
    
        /**
         * header and footer show behind the contentView
         * Always in the middle of the space left after the main view has moved
         * @param contentView any view which needs to refresh and load more
         * @param horizontal works horizontally or not
         * @param withDefaultHeaderAndFooter use default header and footer via {@link #setDefaultRefreshViewCreator(SmartSwipeRefreshViewCreator)}
         * @return this
         * @see #scaleMode(View, boolean)
         */
        public static SmartSwipeRefresh scaleMode(View contentView, boolean horizontal, boolean withDefaultHeaderAndFooter) {
            return slideMode(contentView, 0.5F, horizontal, withDefaultHeaderAndFooter);
        }
    
        /**
         * header and footer show behind the contentView, and there relative movement specified by relativeMoveFactor
         * @param contentView contentView to refresh
         * @param relativeMoveFactor the factor header and footer moves relative to contentView
         * @param horizontal is refresh horizontally(true) or vertically(false)
         * @param withDefaultHeaderAndFooter use default header and footer by {@link SmartSwipeRefreshViewCreator} (if set)
         *                                   or {@link ClassicHeader} / {@link ClassicFooter} (if creator not set)
         * @return this
         * @see SmartSwipeRefreshViewCreator
         * @see ClassicHeader
         * @see ClassicFooter
         * @see SlidingConsumer#setRelativeMoveFactor(float)
         */
        public static SmartSwipeRefresh slideMode(View contentView, float relativeMoveFactor, boolean horizontal, boolean withDefaultHeaderAndFooter) {
            return create(contentView, new SlidingConsumer().setRelativeMoveFactor(relativeMoveFactor), horizontal, withDefaultHeaderAndFooter);
        }
    
        public static SmartSwipeRefresh create(View contentView, DrawerConsumer consumer, boolean horizontal, boolean withDefaultHeaderAndFooter) {
            SmartSwipeRefresh ssr = new SmartSwipeRefresh();
            ssr.mConsumer = SmartSwipe.wrap(contentView)
                    .addConsumer(consumer)
                    //disable motion event to handle refresh view after drag released
                    .setDisableSwipeOnSettling(true)
                    //add listener to support refresh and load more events
                    .addListener(ssr.swipeListener)
                    //set distance calculator for current DrawerConsumer instance
                    .setSwipeDistanceCalculator(new ScaledCalculator(0.4F))
                    //hold on if swipe opened when release, otherwise, auto close it
                    .setReleaseMode(SwipeConsumer.RELEASE_MODE_AUTO_CLOSE | SwipeConsumer.RELEASE_MODE_HOLE_OPEN)
                    //set default rebound factor
                    .setOverSwipeFactor(0.5F)
                    //enable nested scroll fling to auto refresh or load more by default
                    // If need to disable, like this: smartSwipeRefresh.getSwipeConsumer().setDisableNestedFly(true)
                    .setDisableNestedFly(false)
                    .as(DrawerConsumer.class);
            ssr.mHorizontal = horizontal;
            if (withDefaultHeaderAndFooter) {
                if (mCreator != null) {
                    ssr.setHeader(mCreator.createRefreshHeader(contentView.getContext()));
                    ssr.setFooter(mCreator.createRefreshFooter(contentView.getContext()));
                } else {
                    ssr.setHeader(new ClassicHeader(contentView.getContext()));
                    ssr.setFooter(new ClassicFooter(contentView.getContext()));
                }
            }
            return ssr;
        }
    
        private SimpleSwipeListener swipeListener = new SimpleSwipeListener() {
            @Override
            public void onSwipeStart(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
                mActiveRefreshView = null;
                switch (direction) {
                    case SwipeConsumer.DIRECTION_LEFT:
                    case SwipeConsumer.DIRECTION_TOP:
                        mActiveRefreshView = mHeader;
                        break;
                    case SwipeConsumer.DIRECTION_RIGHT:
                    case SwipeConsumer.DIRECTION_BOTTOM:
                        mActiveRefreshView = mFooter;
                        break;
                    default:
                }
                if (mActiveRefreshView != null) {
                    mActiveRefreshView.onStartDragging();
                }
            }
    
            @Override
            public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
                if (mDataLoader == null) {
                    //no data loader, close it
                    finished(false);
                    return;
                }
                if (mActiveRefreshView == mHeader) {
                    consumer.lockAllDirections();
                    mActiveRefreshView.onDataLoading();
                    mDataLoader.onRefresh(SmartSwipeRefresh.this);
                } else if (mActiveRefreshView == mFooter) {
                    consumer.lockAllDirections();
                    mActiveRefreshView.onDataLoading();
                    if (mNoMoreData) {
                        finished(true);
                    } else {
                        mDataLoader.onLoadMore(SmartSwipeRefresh.this);
                    }
                }
            }
    
            @Override
            public void onSwipeClosed(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
                consumer.unlockAllDirections();
                if (mActiveRefreshView != null) {
                    mActiveRefreshView.onReset();
                    mActiveRefreshView = null;
                }
            }
    
            @Override
            public void onSwipeProcess(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction, boolean settling, float progress) {
                if (mActiveRefreshView != null) {
                    mActiveRefreshView.onProgress(!settling, progress);
                }
            }
        };
    
        public SmartSwipeRefresh disableRefresh() {
            int direction = mHorizontal ? SwipeConsumer.DIRECTION_LEFT : SwipeConsumer.DIRECTION_TOP;
            mConsumer.disableDirection(direction);
            return this;
        }
    
        public SmartSwipeRefresh disableLoadMore() {
            int direction = mHorizontal ? SwipeConsumer.DIRECTION_RIGHT : SwipeConsumer.DIRECTION_BOTTOM;
            mConsumer.disableDirection(direction);
            return this;
        }
    
        /**
         * fake to refresh without swipe motion event
         * @return this
         */
        public SmartSwipeRefresh startRefresh() {
            int direction = mHorizontal ? SwipeConsumer.DIRECTION_LEFT : SwipeConsumer.DIRECTION_TOP;
            openDirection(direction);
            return this;
        }
    
        /**
         * fake to load more without swipe motion event
         * @return this
         */
        public SmartSwipeRefresh startLoadMore() {
            int direction = mHorizontal ? SwipeConsumer.DIRECTION_RIGHT : SwipeConsumer.DIRECTION_BOTTOM;
            openDirection(direction);
            return this;
        }
    
        private void openDirection(final int direction) {
            mConsumer.lockAllDirections();
            mConsumer.getWrapper().post(new Runnable() {
                @Override
                public void run() {
                    mConsumer.open(true, direction);
                }
            });
        }
    
        /**
         * finish current drawer( header or footer)
         * @param success data load success or not
         * @return this
         */
        public SmartSwipeRefresh finished(boolean success) {
            if (mActiveRefreshView != null) {
                if (success && mActiveRefreshView == mHeader) {
                    //auto set mNoMoreData as false when refresh success
                    setNoMoreData(false);
                }
                long animationDuration = mActiveRefreshView.onFinish(success);
                if (animationDuration > 0) {
                    mConsumer.getWrapper().postDelayed(mResetRunnable, animationDuration);
                    return null;
                }
            }
            mConsumer.smoothClose();
            return this;
        }
    
        public SmartSwipeRefreshDataLoader getDataLoader() {
            return mDataLoader;
        }
    
        /**
         * set the data loader to do refresh and load more business
         * @param dataLoader data loader
         * @return this
         * @see SmartSwipeRefreshDataLoader
         */
        public SmartSwipeRefresh setDataLoader(SmartSwipeRefreshDataLoader dataLoader) {
            this.mDataLoader = dataLoader;
            return this;
        }
    
        public boolean isNoMoreData() {
            return mNoMoreData;
        }
    
        /**
         * mark footer there is no more data to load
         * @param noMoreData no more data or not
         * @return this
         */
        public SmartSwipeRefresh setNoMoreData(boolean noMoreData) {
            this.mNoMoreData = noMoreData;
            if (mFooter != null) {
                mFooter.setNoMoreData(noMoreData);
            }
            return this;
        }
    
        public SmartSwipeRefreshHeader getHeader() {
            return mHeader;
        }
    
        /**
         * set the refresh header
         * @param header refresh header
         * @return this
         */
        public SmartSwipeRefresh setHeader(SmartSwipeRefreshHeader header) {
            this.mHeader = header;
            if (header != null) {
                header.onInit(mHorizontal);
            }
            mConsumer.setDrawerView(mHorizontal ? SwipeConsumer.DIRECTION_LEFT :SwipeConsumer.DIRECTION_TOP, header == null ? null : header.getView());
            return this;
        }
    
        public SmartSwipeRefreshFooter getFooter() {
            return mFooter;
        }
    
        /**
         * set the refresh footer
         * @param footer refresh footer
         * @return this
         */
        public SmartSwipeRefresh setFooter(SmartSwipeRefreshFooter footer) {
            this.mFooter = footer;
            if (footer != null) {
                footer.onInit(mHorizontal);
            }
            mConsumer.setDrawerView(mHorizontal ? SwipeConsumer.DIRECTION_RIGHT : SwipeConsumer.DIRECTION_BOTTOM, footer == null ? null : footer.getView());
            return this;
        }
    
        private Runnable mResetRunnable = new Runnable() {
            @Override
            public void run() {
                mConsumer.smoothClose();
                mConsumer.unlockAllDirections();
            }
        };
    
        public boolean isHorizontal() {
            return mHorizontal;
        }
    
        public DrawerConsumer getSwipeConsumer() {
            return mConsumer;
        }
    
        private interface RefreshView {
            /**
             * get view to display
             * @return View
             */
            View getView();
    
            /**
             * Called before RefreshView add to {@link DrawerConsumer} or {@link SlidingConsumer}
             * @param horizontal true: will be layout at left(/right) if this is a SmartSwipeRefreshHeader(/SmartSwipeRefreshFooter) instance.
             *                   false: will be layout at top(/bottom) if this is a SmartSwipeRefreshHeader(/SmartSwipeRefreshFooter) instance
             */
            void onInit(boolean horizontal);
    
            /**
             * Called when dragging state determined
             */
            void onStartDragging();
    
            /**
             * Call while swipe distance changes
             * @param dragging user dragging event or not
             * @param progress [0F, 1F + overSwipeFactor]
             */
            void onProgress(boolean dragging, float progress);
    
            /**
             * Call when {@link SmartSwipeRefresh#finished(boolean)} called
             * @param success is data load success or not
             * @return time delay for finish animation plays before header or footer close
             */
            long onFinish(boolean success);
    
            /**
             * Called when SwipeConsumer closed
             * @since v1.0.3
             */
            void onReset();
    
            /**
             * Called when header or footer fully swiped and animate rebound to the fully distance
             */
            void onDataLoading();
        }
    
        public interface SmartSwipeRefreshHeader extends RefreshView {
    
        }
    
        public interface SmartSwipeRefreshFooter extends RefreshView {
            /**
             * mark footer that there is no more data to load
             * @param noMoreData true: no more data, false: maybe has more data to load
             */
            void setNoMoreData(boolean noMoreData);
        }
    
        /**
         * The refresh data loader
         * When refresh or load more event emits, its methods will be called
         */
        public interface SmartSwipeRefreshDataLoader {
            /**
             * Called when {@link SmartSwipeRefreshHeader} swipe released and it has been fully swiped
             * @param ssr {@link SmartSwipeRefresh}
             */
            void onRefresh(SmartSwipeRefresh ssr);
            /**
             * Called when {@link SmartSwipeRefreshFooter} swipe released and it has been fully swiped
             * @param ssr {@link SmartSwipeRefresh}
             */
            void onLoadMore(SmartSwipeRefresh ssr);
        }
    
        /**
         * creator of {@link SmartSwipeRefreshHeader} and {@link SmartSwipeRefreshFooter}
         * @see #setDefaultRefreshViewCreator(SmartSwipeRefreshViewCreator)
         */
        public interface SmartSwipeRefreshViewCreator {
    
            /**
             * create the refresh header view
             * @param context context
             * @return header
             */
            SmartSwipeRefreshHeader createRefreshHeader(Context context);
    
            /**
             * create the refresh footer view
             * @param context context
             * @return footer
             */
            SmartSwipeRefreshFooter createRefreshFooter(Context context);
        }
    }
    

    以上工具类可以直接拿来使用即可,其中比较重要的知识点有以下几个:自带的头尾布局SmartSwipeRefreshDataLoader抽屉模式(DrawerConsumer)抽屉模式(SlidingConsumer)缩放模式平移模式自定义的Header和Footer构造器以及其它小功能

    下面来一一介绍下这几种重要的元素。

    (1) 自带的头尾布局

    纵所周知,无论是下拉还是上拉,都会有头尾布局的展示,SmartSwipeRefresh当然也有自带的头和尾,创建头尾布局的代码如下:

    ssr.setHeader(new ClassicHeader(contentView.getContext()));
    ssr.setFooter(new ClassicFooter(contentView.getContext()));
    

    所以,ClassicHeaderClassicFooter就是contentView的头和尾,代码如下:

    /**
     * classic header for {@link SmartSwipeRefresh}
     * @author billy.qi
     */
    public class ClassicHeader extends RelativeLayout implements SmartSwipeRefresh.SmartSwipeRefreshHeader {
        public TextView mTitleTextView;
        public ImageView mProgressImageView;
        public int mStrResId;
        public ObjectAnimator animator;
    
        public ClassicHeader(Context context) {
            super(context);
            if (isInEditMode()) {
                onInit(false);
            }
        }
    
        public ClassicHeader(Context context, AttributeSet attrs) {
            super(context, attrs);
            if (isInEditMode()) {
                onInit(false);
            }
        }
    
        public ClassicHeader(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            if (isInEditMode()) {
                onInit(false);
            }
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public ClassicHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            if (isInEditMode()) {
                onInit(false);
            }
        }
    
        @Override
        public View getView() {
            return this;
        }
    
        @Override
        public void onInit(boolean horizontal) {
    
            ViewGroup.LayoutParams layoutParams = getLayoutParams();
            if (horizontal) {
                LayoutInflater.from(getContext()).inflate(R.layout.ssr_classic_header_footer_horizontal, this);
                if (layoutParams == null) {
                    int width = SmartSwipe.dp2px(60, getContext());
                    layoutParams = new ViewGroup.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT);
                }
            } else {
                LayoutInflater.from(getContext()).inflate(R.layout.ssr_classic_header_footer, this);
                if (layoutParams == null) {
                    int height = SmartSwipe.dp2px(60, getContext());
                    layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);
                }
            }
            setLayoutParams(layoutParams);
            Drawable background = getBackground();
            if (background == null) {
                setBackgroundColor(0xFF7FC9EB);
            }
            mProgressImageView = findViewById(R.id.ssr_classics_progress);
            mProgressImageView.setVisibility(GONE);
            mTitleTextView = findViewById(R.id.ssr_classics_title);
            mTitleTextView.setText(R.string.ssr_header_pulling);
            animator = ObjectAnimator.ofFloat(mProgressImageView, "rotation", 0, 3600);
            animator.setDuration(5000);
            animator.setInterpolator(null);
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setRepeatMode(ValueAnimator.RESTART);
        }
    
        public void cancelAnimation() {
            animator.cancel();
            mProgressImageView.setVisibility(GONE);
        }
    
        public void showAnimation() {
            animator.start();
            mProgressImageView.setVisibility(VISIBLE);
        }
    
        @Override
        public void onStartDragging() {
    
        }
    
        @Override
        public void onProgress(boolean dragging, float progress) {
            if (dragging) {
                setText(progress >= 1 ? R.string.ssr_header_release : R.string.ssr_header_pulling);
            } else if (progress <= 0) {
                cancelAnimation();
            }
        }
    
        @Override
        public long onFinish(boolean success) {
            cancelAnimation();
            setText(success ? R.string.ssr_header_finish : R.string.ssr_header_failed);
            return 500;
        }
    
        @Override
        public void onReset() {
    
        }
    
        @Override
        public void onDataLoading() {
            showAnimation();
            setText(R.string.ssr_footer_refreshing);
        }
    
    
        public void setText(int strResId) {
            if (mStrResId != strResId && mTitleTextView != null) {
                mStrResId = strResId;
                mTitleTextView.setText(strResId);
            }
        }
    }
    
    /**
     * classic footer for {@link SmartSwipeRefresh}
     * @author billy.qi
     */
    public class ClassicFooter extends ClassicHeader implements SmartSwipeRefresh.SmartSwipeRefreshFooter {
    
        public boolean mNoMoreData;
    
        public ClassicFooter(Context context) {
            super(context);
        }
    
        public ClassicFooter(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public ClassicFooter(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public ClassicFooter(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        @Override
        public void onProgress(boolean dragging, float progress) {
            if (mNoMoreData) {
                cancelAnimation();
                return;
            }
            if (dragging) {
                setText(progress >= 1 ? R.string.ssr_footer_release : R.string.ssr_footer_pulling);
            } else if (progress <= 0) {
                cancelAnimation();
            }
        }
    
        @Override
        public long onFinish(boolean success) {
            cancelAnimation();
            if (!mNoMoreData) {
                setText(success ? R.string.ssr_footer_finish : R.string.ssr_footer_failed);
            }
            return 500;
        }
    
        @Override
        public void onDataLoading() {
            if (!mNoMoreData) {
                showAnimation();
                setText(R.string.ssr_footer_refreshing);
            }
        }
    
    
        @Override
        public void setNoMoreData(boolean noMoreData) {
            this.mNoMoreData = noMoreData;
            setText(R.string.ssr_footer_no_more_data);
        }
    }
    

    其中,用到的布局如下:

    ssr_classic_header_footer.xml

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:paddingTop="20dp"
           tools:paddingBottom="20dp"
           tools:background="#eee"
           tools:parentTag="android.widget.RelativeLayout">
    
        <ImageView
                android:id="@+id/ssr_classics_progress"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginEnd="20dp"
                android:layout_marginRight="20dp"
                android:layout_centerVertical="true"
                android:layout_toLeftOf="@+id/ssr_classics_title"
                android:layout_toStartOf="@+id/ssr_classics_title"
                android:contentDescription="@android:string/untitled"
                android:tint="#666"
                android:src="@android:drawable/stat_notify_sync"/>
    
        <TextView
                android:id="@+id/ssr_classics_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:maxLines="1"
                android:textColor="#666666"
                android:textSize="15sp"
                android:text="@string/ssr_header_pulling"/>
    
    </merge>
    

    ssr_classic_header_footer_horizontal.xml

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           tools:paddingLeft="20dp"
           tools:paddingRight="20dp"
           tools:background="#584646"
           tools:parentTag="android.widget.RelativeLayout">
    
        <ImageView
                android:id="@+id/ssr_classics_progress"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginBottom="20dp"
                android:layout_centerHorizontal="true"
                android:layout_above="@+id/ssr_classics_title"
                android:contentDescription="@android:string/untitled"
                android:tint="#666"
                android:src="@android:drawable/stat_notify_sync"/>
    
        <TextView
                android:id="@+id/ssr_classics_title"
                android:layout_width="16dp"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:textColor="#666666"
                android:textSize="15sp"
                android:text="@string/ssr_header_pulling"/>
    
    </merge>
    

    用到的string如下:

    <string name="ssr_header_pulling">Pull To Refresh</string>
    <string name="ssr_header_release">Release To Refresh</string>
    <string name="ssr_header_refreshing">Refreshing…</string>
    <string name="ssr_header_finish">Refresh Success</string>
    <string name="ssr_header_failed">Refresh Failed</string>
    
    <string name="ssr_footer_pulling">Pull To Load More</string>
    <string name="ssr_footer_release">Release To Load More</string>
    <string name="ssr_footer_refreshing">Wait for Loading…</string>
    <string name="ssr_footer_finish">Load Success</string>
    <string name="ssr_footer_failed">Load Failed</string>
    <string name="ssr_footer_no_more_data">All Data Loaded</string>
    

    头和尾的代码已经贴出,大家也可以根据自己的意愿修改这个默认头尾布局。

    (2)SmartSwipeRefreshDataLoader

    代码示例如下:

        SmartSwipeRefresh.SmartSwipeRefreshDataLoader loader = new SmartSwipeRefresh.SmartSwipeRefreshDataLoader() {
            @Override
            public void onRefresh(final SmartSwipeRefresh ssr) {
                //加载刷新数据
                loadRefreshData(new Callback() {
                    void success() {
                        ssr.finished(true);
                        //刷新完成后,如果数据不足一页,可以提前设置已加载完成的状态
                        boolean loadCompleted = false;
                        ssr.setNoMoreData(loadCompleted);
                    }
                    void failed() {
                        ssr.finished(false);
                    }
                })
            }
    
            @Override
            public void onLoadMore(final SmartSwipeRefresh ssr) {
                //加载下一页数据
                loadMoreData(new Callback() {
                    void success() {
                        ssr.finished(true);
                        // 是否已全部加载完成
                        boolean loadCompleted = true;
                        ssr.setNoMoreData(loadCompleted);
                    }
                    void failed() {
                        ssr.finished(false);
                    }
                })
            }
        };
    
        SmartSwipeRefresh.behindMode(findViewById(R.id.container), false).setDataLoader(loader);
    

    看到以上代码,SmartSwipeRefreshDataLoader作用一目了然,比如:下拉刷新(或上拉更多)请求网络某接口,请求成功则将成功的状态告诉SmartSwipeRefresh,请求失败则将失败的状态告诉SmartSwipeRefresh,最终根据状态显示刷新界面的不同状态。

    (3)抽屉模式(DrawerConsumer)

    代码实现如下:

        //抽屉模式,封装使用的是DrawerConsumer,下拉时,被刷新的主体不动,刷新控件显示在主体上方拖动显示(factor如果为0,则不可越界)
        SmartSwipeRefresh.drawerMode(mRecyclerView, false)
                .setDataLoader(loader);
    

    第一个参数是绑定的view;第二个参数是下拉和上拉的方向,false为纵向,true为横向。

    当然,也可以是三个参数:

        SmartSwipeRefresh.drawerMode(mRecyclerView, false, true)
                .setDataLoader(loader);
    

    第三个参数的意思是,是否设置头和尾,true为设置,false为不设置。

    drawerMode的默认效果是:

    297.gif

    drawerMode依赖于DrawerConsumerDrawerConsumer有一个mOverSwipeFactor属性,这个属性的取值范围是[0,1],当前默认值为0.5,当取值为1时的效果如下:

    298.gif

    我们发现,它的越界现象越来越严重了,当然,将mOverSwipeFactor属性设置为0时,它不可越界,效果如下:

    299.gif

    最终实现代码如下:

        //抽屉模式,封装使用的是DrawerConsumer,下拉时,被刷新的主体不动,刷新控件显示在主体上方拖动显示(factor如果为0,则不可越界)
        SmartSwipeRefresh.drawerMode(mRecyclerView, false)
                .setDataLoader(loader)
                .getSwipeConsumer()
                .setOverSwipeFactor(0);
    
    (4)抽屉模式(SlidingConsumer)

    代码实现如下:

        //刷新控件被固定在后面,封装使用的是SlidingConsumer,下拉时,被刷新的主体被拖动,从而显示在后面的刷新控件
        SmartSwipeRefresh.behindMode(mRecyclerView, false)
                .setDataLoader(loader);
    

    behindMode使用SlidingConsumer封装,默认效果如下:

    300.gif

    behindMode间接依赖于DrawerConsumerDrawerConsumer有一个mOverSwipeFactor属性,这个属性的默认值是0.5,也就是说,刷新布局上方留了刷新布局一半高度的空白,如果将值改为1,那么刷新布局上方留了刷新布局同样高度的空白,如图:

    301.gif

    如果不想要这个空白,则将mOverSwipeFactor的值设置为0即可,如图:

    302.gif

    代码实现如下:

        //刷新控件被固定在后面,封装使用的是SlidingConsumer,下拉时,被刷新的主体被拖动,从而显示在后面的刷新控件
        SmartSwipeRefresh.behindMode(mRecyclerView, false)
                .setDataLoader(loader)
                .getSwipeConsumer()
                .setOverSwipeFactor(0);
    

    behindMode的直接依赖于SlidingConsumer,而SlidingConsumer有三个属性需要了解一下,分别是mRelativeMoveFactormDrawerExpandablemEdgeAffinity

    • mRelativeMoveFactor:联动系数,取值范围是[0,1],默认为0.5,当mRelativeMoveFactor为0.5时,抽屉布局(刷新布局)只有一半被包裹布局盖住,当mRelativeMoveFactor为0时,抽屉布局(刷新布局)完全被包裹布局盖住,效果如下:
    303.gif

    当mRelativeMoveFactor为1时,抽屉布局(刷新布局)不被包裹布局盖住,效果如下:

    304.gif

    代码实现如下:

        //刷新控件被固定在后面,封装使用的是SlidingConsumer,下拉时,被刷新的主体被拖动,从而显示在后面的刷新控件
        SmartSwipeRefresh.behindMode(mRecyclerView, false)
                .setDataLoader(loader)
                .getSwipeConsumer()
                .setOverSwipeFactor(0)
                .as(SlidingConsumer.class)
                .setRelativeMoveFactor(1f);
    
    • mDrawerExpandable:抽屉view的尺寸是否可扩展

    如果mOverSwipeFactor的值为0时,mDrawerExpandable属性无效,当mOverSwipeFactor不为0时,mDrawerExpandable才会生效。mDrawerExpandable的默认值为false,表示抽屉布局(刷新布局)不具备扩展性,如果为true,则抽屉布局(刷新布局)具备扩展性。

    不具备扩展性的图片效果如下:

    305.gif

    具备扩展性的图片效果如下:

    306.gif

    代码实现如下:

        //刷新控件被固定在后面,封装使用的是SlidingConsumer,下拉时,被刷新的主体被拖动,从而显示在后面的刷新控件
        SmartSwipeRefresh.behindMode(mRecyclerView, false)
                .setDataLoader(loader)
                .getSwipeConsumer()
                .setOverSwipeFactor(0.5f)
                .as(SlidingConsumer.class)
                .setRelativeMoveFactor(0.5f)
                .setDrawerExpandable(true);
    
    • mEdgeAffinity:是否边缘亲和

    当mDrawerExpandable为true时,mEdgeAffinity无效,所以先将mDrawerExpandable属性设置为false。

    mEdgeAffinity的默认值是false,当mEdgeAffinity为false时,留白在抽屉布局(刷新布局)的上方,图片效果如下:

    图片.png

    当mEdgeAffinity为true时,留白在抽屉布局(刷新布局)和绑定布局之间,图片效果如下:

    图片.png

    代码实现如下:

        //刷新控件被固定在后面,封装使用的是SlidingConsumer,下拉时,被刷新的主体被拖动,从而显示在后面的刷新控件
        SmartSwipeRefresh.behindMode(mRecyclerView, false)
                .setDataLoader(loader)
                .getSwipeConsumer()
                .setOverSwipeFactor(0.5f)
                .as(SlidingConsumer.class)
                .setRelativeMoveFactor(0.5f)
                .setDrawerExpandable(false)
                .setEdgeAffinity(true);
    
    (5)缩放模式

    基本使用如下:

        //缩放模式(伪),下拉时,被刷新的主体向下移动,刷新控件的中间位置始终在主体下拉后的空白区域的正中间
        SmartSwipeRefresh.scaleMode(mRecyclerView, false).setDataLoader(loader);
    

    scaleMode虽然被称之为缩放模式,但是其实并不是缩放,所以称之为伪缩放模式,它和behindMode基本差不多,唯一的区别就是:

    behindMode:mRelativeMoveFactor(联动系数)的默认值为0;
    scaleMode:mRelativeMoveFactor(联动系数)的默认值为0.5;

    不管mRelativeMoveFactor的默认值是什么,它可以动态设置,不是吗?

    所以,这里不再赘述了。

    (6)平移模式

    平移模式的代码实现如下:

        //平移模式,下拉时,被刷新的主体与刷新控件一起移动
        SmartSwipeRefresh.translateMode(mRecyclerView, false).setDataLoader(loader);
    

    效果如下:

    308.gif

    translateMode和behindMode基本差不多,唯一的区别就是:

    behindMode:mRelativeMoveFactor(联动系数)的默认值为0;
    translateMode:mRelativeMoveFactor(联动系数)的默认值为1;

    不管mRelativeMoveFactor的默认值是什么,它可以动态设置,不是吗?

    所以,这里不再赘述了。

    (7)自定义的Header和Footer构造器

    SmartSwipeRefresh有默认的Header和Footer,但是这个默认的Header和Footer布局并不能满足大部分的需求,那么有没有办法可以自定义Header和Footer布局?

    答案是有的。

    SmartSwipeRefresh有一个setDefaultRefreshViewCreator方法可以创建Header和Footer布局,代码实现如下:

        SmartSwipeRefresh.setDefaultRefreshViewCreator(new SmartSwipeRefresh.SmartSwipeRefreshViewCreator() {
            @Override
            public SmartSwipeRefresh.SmartSwipeRefreshHeader createRefreshHeader(Context context) {
                return new ClassicHeader(MainActivity.this);
            }
    
            @Override
            public SmartSwipeRefresh.SmartSwipeRefreshFooter createRefreshFooter(Context context) {
                return new ClassicFooter(MainActivity.this);
            }
        });
    

    ClassicHeaderClassicFooter这两个类的代码已经在上文贴出,如果想要自定义布局的话,只需要重命名两个文件名以及修改这两个类中的布局即可。

    (8)其它小功能

    【自动刷新】

    smartSwipeRefresh.startRefresh(); //立即开启自动刷新
    

    【自动加载更多】

    smartSwipeRefresh.startLoadMore();
    

    【禁用刷新功能】

    smartSwipeRefresh.disableRefresh();
    

    【禁用加载更多】

    smartSwipeRefresh.disableLoadMore();
    

    [本章完...]

    相关文章

      网友评论

        本文标题:侧滑效果[第十六篇]:侧滑框架SmartSwipe之下拉刷新

        本文链接:https://www.haomeiwen.com/subject/tstqwctx.html