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主要是上面提到的基本用法,对我们来说用处不大

  1. SwipeRefreshLayoutBasic

  2. SwipeRefreshListFragment

  3. 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这个意外收获,此刻的心情是开森的,在此,借古人一句话送给大家:时而学习之不亦说乎!

5 0
原创粉丝点击