您的位置:首页 > 移动开发 > Android开发

[置顶] Android开发之 SwipeRefreshLayout

2016-05-03 15:50 597 查看

SwipeRefreshLayout概述

用户通过手势或者点击某个按钮实现内容视图的刷新,布局里加入SwipeRefreshLayout嵌套一个子视图如ListView、RecyclerView等,触发刷新会通过OnRefreshListener的onRefresh方法回调,我们在这里执行页面数据的刷新,每次手势的完成都会执行一次通知,根据滑动距离判断是否需要回调。setRefreshing(false)通过代码直接取消刷新,true则手动设置刷新调出刷新视图。setEnabled(false)通过boolean控制是否禁用手势刷新

SwipeRefreshLayout用法

[b]xml布局[/b]

<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>


[b]代码调用[/b]

public class SwipeRefreshLayoutBasicFragment extends Fragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//...........=略................
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swiperefresh);

// 设置下拉刷新的圆的颜色
mSwipeRefreshLayout.setColorScheme(
R.color.swipe_color_1, R.color.swipe_color_2,
R.color.swipe_color_3, R.color.swipe_color_4);

return view;
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//初始化ListView布局
mListView.setAdapter(adapter);
//绑定视图刷新的监听
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//TODO
//重新获取完网络数据刷新Adapter,完成后需要调用onRefreshComplete方法取消滑出来的圆形进度
}
});
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
//手动点击按钮刷新视图如果当前视图状态没有刷新需要调用setRefreshing(true)
if (!mSwipeRefreshLayout.isRefreshing()) {
mSwipeRefreshLayout.setRefreshing(true);
}
initiateRefresh();
return true;
}
return super.onOptionsItemSelected(item);
}

/**
* 刷新Adapter并且取消刷新滑出来的进度视图
**/
private void onRefreshComplete(List<String> result) {
mSwipeRefreshLayout.setRefreshing(false);
adapter.onRefresh(result)
}
}


SwipeRefreshLayout官方实践Demo

在官网找到三个simple,前面两个simple主要是上面提到的基本用法,对我们来说用处不大

SwipeRefreshLayoutBasic

SwipeRefreshListFragment

SwipeRefreshMultipleViews

[b]MultiSwipeRefreshLayout源码[/b]

在SwipeRefreshMultipleViews Simple里面提供了一个SwipeRefreshLayout的继承类MultiSwipeRefreshLayout,该类的作用用于子视图列表添加EmptyView,v4包里面没有这里贴上源码:

public class MultiSwipeRefreshLayout extends SwipeRefreshLayout {

private View[] mSwipeableChildren;

public MultiSwipeRefreshLayout(Context context) {
super(context);
}

public MultiSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

/**
* Set the children which can trigger a refresh by swiping down when they are visible. These
* views need to be a descendant of this view.
*/
public void setSwipeableChildren(final int... ids) {
assert ids != null;

mSwipeableChildren = new View[ids.length];
for (int i = 0; i < ids.length; i++) {
mSwipeableChildren[i] = findViewById(ids[i]);
}
}

/**
* This method controls when the swipe-to-refresh gesture is triggered. By returning false here
* we are signifying that the view is in a state where a refresh gesture can start.
*
* <p>As {@link android.support.v4.widget.SwipeRefreshLayout} only supports one direct child by
* default, we need to manually iterate through our swipeable children to see if any are in a
* state to trigger the gesture. If so we return false to start the gesture.
*/
@Override
public boolean canChildScrollUp() {
if (mSwipeableChildren != null && mSwipeableChildren.length > 0) {
for (View view : mSwipeableChildren) {
if (view != null && view.isShown() && !canViewScrollUp(view)) {
return false;
}
}
}
return true;
}
/**
* Utility method to check whether a {@link View} can scroll up from it's current position.
* Handles platform version differences, providing backwards compatible functionality where
* needed.
*/
private static boolean canViewScrollUp(View view) {
if (android.os.Build.VERSION.SDK_INT >= 14) {
return ViewCompat.canScrollVertically(view, -1);
} else {
if (view instanceof AbsListView) {
final AbsListView listView = (AbsListView) view;
return listView.getChildCount() > 0 &&
(listView.getFirstVisiblePosition() > 0
|| listView.getChildAt(0).getTop() < listView.getPaddingTop());
} else {
return view.getScrollY() > 0;
}
}
}
}


[b]MultiSwipeRefreshLayout xml布局[/b]

<com.example.android.swiperefreshmultipleviews.MultiSwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<GridView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2" />

<TextView
android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/empty_text"
android:layout_gravity="center"/>

</FrameLayout>

</com.example.android.swiperefreshmultipleviews.MultiSwipeRefreshLayout>


[b]MultiSwipeRefreshLayout代码调用[/b]

public class SwipeRefreshMultipleViewsFragment extends Fragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//.............略...........
mSwipeRefreshLayout = (MultiSwipeRefreshLayout) view.findViewById(R.id.swiperefresh);
//设置进度圆形的颜色
mSwipeRefreshLayout.setColorScheme(
R.color.swipe_color_1, R.color.swipe_color_2,
R.color.swipe_color_3, R.color.swipe_color_4);

mGridView = (GridView) view.findViewById(android.R.id.list);
mEmptyView = view.findViewById(android.R.id.empty);

return view;
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);

mGridView.setAdapter(adapter);

// 当GridView没有数据时显示EmptyView
mGridView.setEmptyView(mEmptyView);

//设置需要Refresh视图
mSwipeRefreshLayout.setSwipeableChildren(android.R.id.list, android.R.id.empty);

mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//TODO
}
});
}

}


[b]MultiSwipeRefreshLayout实践[/b]

在官方提供的simple里面发现,EmptyView显示时,进度视图可能无法显示处理,但是OnRefreshListener的回调还是执行了,根据我的怀疑做了一个小小的实验,取消了setEmpty方法,发现就没问题,不过叠加显示会有问题了,需要手动控制Visibility,根据以上知识做了一个简单的demo实践(鸡汤:主题报错A TaskDescription’s primary color should be opaque,原因是颜色值缺损,需要补齐00-FF),效果图如下,奉上源码:http://download.csdn.net/detail/analyzesystem/9508674



SwipeRefreshLayout源码剖析



自定义控件SwipeRefreshLayout是一个自定义ViewGroup,这里面用到的NestedScrolling系列之前BottomBar篇提到过这里就不再累赘叙述,自定义的ViewGroup内部涉及到MaterialProgressDrawable进度图片、CircleImageView(v4包里面的不是开源库那个),圆形进度图片的一些方法在SwipeRefreshLayout里面间接调用,下面从SwipeRefreshLayout的相关方法简单理解。

reset方法就是调用子view的方法取消相应的动画,并且隐藏view,setProgressViewOffset方法对我们来说还是非常有用的,用于设置CircleView的进出动画是否执行缩放(API 11有兼容,向下无法执行缩放透明,开发兼容个人4.0+所以不存在任何问题)、以及下拉出现的位置和最大的下拉位置。

/**
* @param 设置下拉出现小圆圈是否是缩放出现
* @param 出现的位置
* @param 最大的下拉位置
**/
public void setProgressViewOffset(boolean scale, int start, int end) {
mScale = scale;
mCircleView.setVisibility(View.GONE);
mOriginalOffsetTop = mCurrentTargetOffsetTop = start;
mSpinnerFinalOffset = end;
mUsingCustomStart = true;
mCircleView.invalidate();
}


设置下拉圆圈的大小,通过注解ProgressDrawableSize两个类型: LARGE, DEFAULT,在SwipeRefreshLayout里面根据这两种类型选择不同的大小:CIRCLE_DIAMETER 、CIRCLE_DIAMETER_LARGE

private static final int CIRCLE_DIAMETER = 40;
private static final int CIRCLE_DIAMETER_LARGE = 56;

public void setSize(int size) {
if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) {
return;
}
final DisplayMetrics metrics = getResources().getDisplayMetrics();
if (size == MaterialProgressDrawable.LARGE) {
mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
} else {
mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
}
// force the bounds of the progress circle inside the circle view to
// update by setting it to null before updating its size and then
// re-setting it
mCircleView.setImageDrawable(null);
mProgress.updateSizes(size);
mCircleView.setImageDrawable(mProgress);
}


在构造函数内部初始化必须变量,并为ViewGroup添加一个CircleView

/**
* Constructor that is called when inflating SwipeRefreshLayout from XML.
*
* @param context
* @param attrs
*/
public SwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//系统默认的最小滑动系数值
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
//默认动画时常400
mMediumAnimationDuration = getResources().getInteger(
android.R.integer.config_mediumAnimTime);

setWillNotDraw(false);
mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);

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

final DisplayMetrics metrics = getResources().getDisplayMetrics();
mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);
//创建CircleView并添加到ViewGroup
createProgressView();
ViewCompat.setChildrenDrawingOrderEnabled(this, true);
// the absolute offset has to take into account that the circle starts at an offset
mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
mTotalDragDistance = mSpinnerFinalOffset;
//通过 NestedScrolling 处理嵌套滑动
mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}


判断字视图是否支持滑动刷新,根据View类型和ViewCompat调用底层方法判断,如果你要自定义SwipeRefreshLayout需要重写方法,参考示例如MultiSwipeRefreshLayout内部具体实现

/**
* @return Whether it is possible for the child view of this layout to
*         scroll up. Override this if the child view is a custom view.
*/
public boolean canChildScrollUp() {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}


内部提供了几个setColor系列的方法,其实本质在调用子View进行赋值,这里不累赘叙述以mProgress为例

/**
* Set the colors used in the progress animation. The first
* color will also be the color of the bar that grows in response to a user
* swipe gesture.
*
* @param colors
*/
@ColorInt
public void setColorSchemeColors(int... colors) {
ensureTarget();
mProgress.setColorSchemeColors(colors);
}


再过完一遍代码后发现,很多发放与我们使用都无关,就不细解了,如果你还想了解更多,在这里奉上我在github搜到的关于SwipeRefreshLayout源码分析一篇:https://github.com/hanks-zyh/SwipeRefreshLayout

SwipeRefreshLayout开源项目推荐

SwipeRefresh开源库推荐两个,一个是基于ListView支持下拉刷新上啦加载更多的,一个是使用Builder构建,基于RecyclerView实现,并且同样支持下拉刷新上啦加载更多,下面是相关链接

SwipeRefreshLayout



代码调用风格:

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

mRefreshLayout = (RefreshLayout) findViewById(R.id.swipe_container);
mListView = (ListView) findViewById(R.id.list);
footerLayout = getLayoutInflater().inflate(R.layout.listview_footer, null);

mListView.addFooterView(footerLayout);
mRefreshLayout.setChildView(mListView);

mListView.setAdapter(mAdapter);

mRefreshLayout.setColorSchemeResources(R.color.google_blue,
R.color.google_green,
R.color.google_red,
R.color.google_yellow);

mRefreshLayout.setOnRefreshListener(new RefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// start to refresh
}
});
mRefreshLayout.setOnLoadListener(new RefreshLayout.OnLoadListener() {
@Override
public void onLoad() {
// start to load
}
});
}


在本篇博客发表一段时间后,在此实战了该库,发现每次都要调用一些基本配置,个人感觉很麻烦,于是乎RefreshLayoutHelper类诞生

/**
* Created by idea on 2016/5/20.
*/
public class RefreshLayoutHelper {

private View footerLayout;
private TextView footerLable;
private ProgressBar progressBar;

public static RefreshLayoutHelper instance;
private RefreshLayout refreshLayout;
private ListView mListView;
private RefreshLayout.OnRefreshListener onRefreshListener;
private RefreshLayout.OnLoadListener onLoadMoreListener;
private BaseActivity mActivity;

private RefreshLayoutHelper() {

}

public static RefreshLayoutHelper getInstance() {

if (instance == null) {
synchronized (RefreshLayoutHelper.class) {
if (instance == null) {
instance = new RefreshLayoutHelper();
}
}
}
return instance;
}
/***
* Activity初始化调用
**/
public RefreshLayoutHelper init(BaseActivity mActivity,RefreshLayout refreshLayout,ListView mListView,RefreshLayout.OnRefreshListener onRefreshListener,RefreshLayout.OnLoadListener onLoadListener){
this.mActivity = mActivity;
this.refreshLayout = refreshLayout;
this.mListView = mListView;
this.onRefreshListener = onRefreshListener;
this.onLoadMoreListener = onLoadListener;
return instance;
}

/***
* 添加加载更多footer
**/
public RefreshLayoutHelper configFooterView(){
footerLayout = mActivity.getLayoutInflater().inflate(R.layout.footer, null);
footerLayout.setVisibility(View.GONE);
footerLable = (TextView) footerLayout.findViewById(R.id.text_more);
progressBar = (ProgressBar) footerLayout.findViewById(R.id.load_progress_bar);
footerLable.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showLoadMoreView();
onLoadMoreListener.onLoad();
}
});

//这里可以替换为自定义的footer布局
//you can custom FooterView
mListView.addFooterView(footerLayout);
refreshLayout.setChildView(mListView);
return instance;
}

/**
* 初始化配置直接弹出刷新动画转圈
* @param colorResIds
*/
public void initSwipeRefreshLayout(int... colorResIds){
refreshLayout.setColorSchemeResources(colorResIds);
refreshLayout.setProgressViewOffset(false, 0, 24);

refreshLayout.setOnRefreshListener(onRefreshListener);
refreshLayout.setOnLoadListener(onLoadMoreListener);

refreshLayout.setRefreshing(true);
onRefreshListener.onRefresh();
}

/**
* footer显示加载进度
**/
public void showLoadMoreView() {
footerLable.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}

/**
* 显示加载更多
**/
public void cancelLoadMoreView() {
footerLable.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
}

/**
* 判断是否还有下一页,选择是否隐藏加载更多
*/
public void completeRefreshView(final BaseAdapter adapter,final int pageSize){

refreshLayout.postDelayed(new Runnable() {
@Override
public void run() {
refreshLayout.setRefreshing(false);
refreshLayout.setLoading(false);
cancelLoadMoreView();
if (adapter.getCount() != 0 && adapter.getCount() % pageSize == 0) {
footerLayout.setVisibility(View.VISIBLE);
} else {
footerLayout.setVisibility(View.INVISIBLE);
}
}
}, 1000);

}
}


调用流程如下:

public class XXXActivity extends BaseActivity implements RefreshLayout.OnRefreshListener, RefreshLayout.OnLoadListener {

protect void onCreat(..){
setContentView..
refreshLayoutHelper = RefreshLayoutHelper.getInstance()
.init(this,refreshLayout,mListView,this,this)
.configFooterView();
//.................略...................
mListView.setAdapter(adapter)
refreshLayoutHelper.initSwipeRefreshLayout(R.color.swipe_color_1, R.color.swipe_color_2,
R.color.swipe_color_3, R.color.swipe_color_4);

}

}


SwipePresenter

代码调用风格:

presenter = new SwipePresenter.Builder()
.onCreated(new Runnable() {
@Override
public void run() {
recyclerview.setLayoutManager(new StaggeredGridLayoutManager(
2, StaggeredGridLayoutManager.VERTICAL)
);
recyclerview.setAdapter(adapter);
}
})
.swipeRefreshLayout(swipeRefreshLayout)
.recyclerView(recyclerview)
.emptyView(emptyView)
.onRefresh(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
presenter.stopRefresh();
}
})
.onLoadMore(new SwipePresenter.AutoLoadMoreListener(4) {
@Override
public void onLoadMore(RecyclerView recyclerView) {
finishLoadingMore(); // or you can replace this with presenter.finishLoadingMore();
}
})
.build();


结语

花了一点时间整理SwipeRefreshLayout这块的知识还是值得的,收获也有不少,以前没用过的方法比如setProgressViewOffset以前就没用过,再比如MultiSwipeRefreshLayout这个意外收获,此刻的心情是开森的,在此,借古人一句话送给大家:时而学习之不亦说乎!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: