Android开发之 SwipeRefreshLayout

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/AnalyzeSystem/article/details/51301848

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+所以不存在任何问题)、以及下拉出现的位置和最大的下拉位置。

扫描二维码关注公众号,回复: 3785436 查看本文章
 /**
  * @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实现,并且同样支持下拉刷新上啦加载更多,下面是相关链接

    @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);

   }

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

猜你喜欢

转载自blog.csdn.net/AnalyzeSystem/article/details/51301848
今日推荐