侧滑效果[第十六篇]:侧滑框架SmartSwipe之下拉刷新
在我们做项目的时候,一些场景需要用到
下拉刷新数据
以及上拉拉取更多数据
,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()));
所以,ClassicHeader
和ClassicFooter
就是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.gifdrawerMode依赖于DrawerConsumer
,DrawerConsumer
有一个mOverSwipeFactor属性,这个属性的取值范围是[0,1],当前默认值为0.5,当取值为1时的效果如下:
我们发现,它的越界现象越来越严重了,当然,将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
封装,默认效果如下:
behindMode间接依赖于DrawerConsumer
,DrawerConsumer
有一个mOverSwipeFactor属性,这个属性的默认值是0.5,也就是说,刷新布局上方留了刷新布局一半高度的空白,如果将值改为1,那么刷新布局上方留了刷新布局同样高度的空白,如图:
如果不想要这个空白,则将mOverSwipeFactor的值设置为0即可,如图:
302.gif代码实现如下:
//刷新控件被固定在后面,封装使用的是SlidingConsumer,下拉时,被刷新的主体被拖动,从而显示在后面的刷新控件
SmartSwipeRefresh.behindMode(mRecyclerView, false)
.setDataLoader(loader)
.getSwipeConsumer()
.setOverSwipeFactor(0);
behindMode的直接依赖于SlidingConsumer
,而SlidingConsumer
有三个属性需要了解一下,分别是mRelativeMoveFactor
、mDrawerExpandable
、mEdgeAffinity
。
- mRelativeMoveFactor:联动系数,取值范围是[0,1],默认为0.5,当mRelativeMoveFactor为0.5时,抽屉布局(刷新布局)只有一半被包裹布局盖住,当mRelativeMoveFactor为0时,抽屉布局(刷新布局)完全被包裹布局盖住,效果如下:
当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.giftranslateMode和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);
}
});
ClassicHeader
和ClassicFooter
这两个类的代码已经在上文贴出,如果想要自定义布局的话,只需要重命名两个文件名以及修改这两个类中的布局即可。
(8)其它小功能
【自动刷新】
smartSwipeRefresh.startRefresh(); //立即开启自动刷新
【自动加载更多】
smartSwipeRefresh.startLoadMore();
【禁用刷新功能】
smartSwipeRefresh.disableRefresh();
【禁用加载更多】
smartSwipeRefresh.disableLoadMore();
[本章完...]