Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發之 SwipeRefreshLayout

Android開發之 SwipeRefreshLayout

編輯:關於Android編程

SwipeRefreshLayout概述

用戶通過手勢或者點擊某個按鈕實現內容視圖的刷新,布局裡加入SwipeRefreshLayout嵌套一個子視圖如ListView、RecyclerView等,觸發刷新會通過OnRefreshListener的onRefresh方法回調,我們在這裡執行頁面數據的刷新,每次手勢的完成都會執行一次通知,根據滑動距離判斷是否需要回調。setRefreshing(false)通過代碼直接取消刷新,true則手動設置刷新調出刷新視圖。setEnabled(false)通過boolean控制是否禁用手勢刷新

SwipeRefreshLayout用法

xml布局


    

代碼調用

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 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.
     *
     * 

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布局



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

        

        

    </framelayout>

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

\

代碼調用風格:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;"> @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 } }); }

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這個意外收獲,此刻的心情是開森的,在此,借古人一句話送給大家:時而學習之不亦說乎!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved