高级UI

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

2019-11-22  本文已影响0人  NoBugException

在我们做项目的时候,一些场景需要用到下拉刷新数据以及上拉拉取更多数据,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

303.gif

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

304.gif

代码实现如下:

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

如果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);

当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();

[本章完...]

上一篇下一篇

猜你喜欢

热点阅读