Android开发之 SwipeRefreshLayout
来源:互联网 发布:dnf装备数据查询 编辑:程序博客网 时间:2024/05/15 12:32
SwipeRefreshLayout概述
用户通过手势或者点击某个按钮实现内容视图的刷新,布局里加入SwipeRefreshLayout嵌套一个子视图如ListView、RecyclerView等,触发刷新会通过OnRefreshListener的onRefresh方法回调,我们在这里执行页面数据的刷新,每次手势的完成都会执行一次通知,根据滑动距离判断是否需要回调。setRefreshing(false)通过代码直接取消刷新,true则手动设置刷新调出刷新视图。setEnabled(false)通过boolean控制是否禁用手势刷新
SwipeRefreshLayout用法
xml布局
<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>
代码调用
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
MultiSwipeRefreshLayout源码
在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; } } }}
MultiSwipeRefreshLayout xml布局
<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>
MultiSwipeRefreshLayout代码调用
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 } }); }}
MultiSwipeRefreshLayout实践
在官方提供的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这个意外收获,此刻的心情是开森的,在此,借古人一句话送给大家:时而学习之不亦说乎!
- Android开发之 SwipeRefreshLayout
- (4.1.44) Android开发之 SwipeRefreshLayout
- Android 5.0 之SwipeRefreshLayout
- Android之SwipeRefreshLayout
- Android 5.0 之SwipeRefreshLayout
- Android开发之SwipeRefreshLayout详解和自定义加载更多
- android下拉刷新之SwipeRefreshLayout
- 自学android笔记之SwipeRefreshLayout
- Android开发之Android自带的下拉刷新控件SwipeRefreshLayout
- Android开发:SwipeRefreshLayout无法显示emptyView
- Android开发笔记 下拉刷新布局SwipeRefreshLayout
- Android控件ListView下拉刷新之SwipeRefreshLayout
- android之官方下拉刷新组件SwipeRefreshLayout
- android之官方下拉刷新组件SwipeRefreshLayout
- android之官方下拉刷新组件SwipeRefreshLayout
- Android之SwipeRefreshLayout下拉刷新控件
- android之swiperefreshlayout与scrollview冲突解决
- android之官方下拉刷新组件SwipeRefreshLayout
- 流程再造、流程优化为供给侧改革提供信息化支撑服务的思路
- iOS开发多线程篇—GCD的基本使用
- redis分布式锁和调度锁
- bzoj 1657: [Usaco2006 Mar]Mooo 奶牛的歌声(单调栈)
- 23转化为二进制
- Android开发之 SwipeRefreshLayout
- SICP ex1-20 ex1-22
- 230转化为二进制
- 北京的春天下过雨
- 图片分组加载
- 【Arduino官方教程第一辑】示例程序 2-8 曲调2
- Java实战RDD与DataFrame动态转换操作
- 数论一·Miller-Rabin质数测试
- 前端页面完成后如何放在IDEA项目工程的目录下能正确加载css样式