下拉刷新框架TwinklingRefreshLayout的使用

TwinklingRefreshLayout

TwinklingRefreshLayout介紹

TwinklingRefreshLayout延伸了Google的SwipeRefreshLayout的思想,不在列表控件上動刀,而是使用一個ViewGroup來包含列表控件,以保持其較低的耦合性和較高的通用性。其主要特性有:

  1. 支持RecyclerView、ScrollView、AbsListView系列(ListView、GridView)、WebView以及其它可以獲取到scrollY的控件
  2. 支持加載更多
  3. 默認(rèn)支持 越界回彈,隨手勢速度有不同的效果
  4. 可開啟沒有刷新控件的純凈越界回彈模式
  5. setOnRefreshListener中擁有大量可以回調(diào)的方法
  6. 將Header和Footer抽象成了接口,并回調(diào)了滑動過程中的系數(shù),方便實(shí)現(xiàn)個性化的Header和Footer
  7. 支持NestedScroll,嵌套CoordinatorLayout

目前已經(jīng)支持了所有的View,比如是一個FrameLayout,LinearLayout,AnyView。

image.png

依賴

  implementation 'com.lcodecorex:tkrefreshlayout:1.0.7'

基本使用

在xml中添加TwinklingRefreshLayout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout
            android:id="@+id/refreshLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:overScrollMode="never"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                tools:listitem="@layout/item_rv" />

        </com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout>


    </LinearLayout>
</layout>

Android系統(tǒng)為了跟iOS不一樣,當(dāng)界面OverScroll的時候會顯示一個陰影。為了達(dá)到更好的顯示效果,最好禁用系統(tǒng)的overScroll,如上給RecyclerView添加android:overScrollMode="never"。

在Activity或者Fragment中配置

TwinklingRefreshLayout不會自動結(jié)束刷新或者加載更多,需要手動控制

public class MainActivity2 extends AppCompatActivity {

    private ActivityMain2Binding mBinding;

    private List<String> mDatas = new ArrayList<>();

    private RvAdapter mRvAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main2);


        //是否需要下拉刷新,默認(rèn)需要true
        mBinding.refreshLayout.setEnableRefresh(true);

        //是否需要加載更多,默認(rèn)需要true
        mBinding.refreshLayout.setEnableLoadmore(true);

        //是否自動加載更多, 滑到底部默認(rèn)加載更多,默認(rèn)false
        mBinding.refreshLayout.setAutoLoadMore(true);

        //如果你想進(jìn)入到界面的時候主動調(diào)用下刷新,可以調(diào)用startRefresh()/startLoadmore()方法。
        mBinding.refreshLayout.startRefresh();
        // mBinding.refreshLayout.startLoadMore();

        //是否允許進(jìn)入越界回彈模式,默認(rèn)true
        mBinding.refreshLayout.setEnableOverScroll(true);

        //是否開啟懸浮刷新模式(支持切換到像SwipeRefreshLayout一樣的懸浮刷新模式),默認(rèn)false
        mBinding.refreshLayout.setFloatRefresh(false);


        mRvAdapter = new RvAdapter(this, mDatas);
        mBinding.recyclerView.setAdapter(mRvAdapter);

        loadData();


        //TwinklingRefreshLayout不會自動結(jié)束刷新或者加載更多,需要手動控制
        mBinding.refreshLayout.setOnRefreshListener(new RefreshListenerAdapter() {
            @Override
            public void onRefresh(TwinklingRefreshLayout refreshLayout) {
                super.onRefresh(refreshLayout);

                new Handler().postDelayed(() -> {
                    loadData();
                    Toast.makeText(MainActivity2.this, "下拉刷新", Toast.LENGTH_SHORT).show();
                    refreshLayout.finishRefreshing();
                }, 1000);
            }

            @Override
            public void onLoadMore(TwinklingRefreshLayout refreshLayout) {
                super.onLoadMore(refreshLayout);

                new Handler().postDelayed(() -> {
                    loadMore();
                    Toast.makeText(MainActivity2.this, "上拉加載", Toast.LENGTH_SHORT).show();
                    refreshLayout.finishLoadmore();
                }, 1000);
            }
        });
    }

    private void loadData() {
        mDatas.clear();
        for (int i = 1; i <= 30; i++) {
            mDatas.add("趙麗穎" + i);
        }
        mRvAdapter.notifyDataSetChanged();
    }

    private void loadMore() {
        for (int i = 1; i <= 10; i++) {
            mDatas.add("趙麗穎更多" + i);
        }
        mRvAdapter.notifyDataSetChanged();
    }
}

使用finishRefreshing()方法結(jié)束刷新,finishLoadmore()方法結(jié)束加載更多。此處OnRefreshListener還有其它方法,可以選擇需要的來重寫。

擴(kuò)展屬性

image.png

動態(tài)設(shè)置相關(guān)屬性

如果你想進(jìn)入到界面的時候主動調(diào)用下刷新

如果你想進(jìn)入到界面的時候主動調(diào)用下刷新,可以調(diào)用startRefresh()/startLoadmore()方法。

        mBinding.refreshLayout.startRefresh();
        //mBinding.refreshLayout.startLoadMore();

開啟純凈的越界回彈模式,也就是所有刷新相關(guān)的View都不顯示,只顯示越界回彈效果

setPureScrollModeOn()

靈活的設(shè)置是否禁用上下拉

setEnableRefresh、setEnableLoadmore

是否在底部越界的時候自動切換到加載更多模式

setAutoLoadMore

是否允許越界回彈,默認(rèn)支持越界回彈

這一點(diǎn)很多類似SwipeRefreshLayout的刷新控件都沒有做到(包括SwipeRefreshLayout),因?yàn)闆]有攔截下來的時間會傳遞給列表控件,而列表控件的滾動狀態(tài)很難獲取。解決方案就是給列表控件設(shè)置了OnTouchListener并把事件交給GestureDetector處理,然后在列表控件的OnScrollListener中監(jiān)聽View是否滾動到了頂部(沒有OnScrollListener的則采用延時監(jiān)聽策略)。

setEnableOverScroll

支持切換到像SwipeRefreshLayout一樣的懸浮刷新模式了

setFloatRefresh(boolean)

設(shè)置頭部/底部個性化刷新效果,頭部需要實(shí)現(xiàn)IHeaderView,底部需要實(shí)現(xiàn)IBottomView

setHeaderView(IHeaderView headerView)、setBottomView(IBottomView bottomView)

setDefaultHeader、setDefaultFooter
現(xiàn)在已經(jīng)提供了設(shè)置默認(rèn)的Header、Footer的static方法,可在Application或者一個Activity中這樣設(shè)置:

TwinklingRefreshLayout.setDefaultHeader(SinaRefreshView.class.getName());
TwinklingRefreshLayout.setDefaultFooter(BallPulseView.class.getName());

添加一個固定在頂部的Header(效果還需要優(yōu)化)

addFixedExHeader

設(shè)置滾動事件的作用對象。

setTargetView(View view)

是否允許在越界的時候顯示刷新控件,默認(rèn)是允許的,也就是Fling越界的時候Header或Footer照常顯示,反之就是不顯示;可能有特殊的情況,刷新控件會影響顯示體驗(yàn)才設(shè)立了這個狀態(tài)。

setOverScrollTopShow、setOverScrollBottomShow、setOverScrollRefreshShow

setWaveHeight、setHeaderHeight、setBottomHeight、setOverScrollHeight

setMaxHeadHeight 設(shè)置頭部可拉伸的最大高度。
setHeaderHeight 頭部固定高度(在此高度上顯示刷新狀態(tài))
setMaxBottomHeight
setBottomHeight 底部高度
setOverScrollHeight 設(shè)置最大的越界高度

setOnRefreshListener大量可以回調(diào)的方法

onPullingDown(TwinklingRefreshLayout refreshLayout, float fraction) 正在下拉的過程
onPullingUp(TwinklingRefreshLayout refreshLayout, float fraction) 正在上拉的過程
onPullDownReleasing(TwinklingRefreshLayout refreshLayout, float fraction) 下拉釋放過程
onPullUpReleasing(TwinklingRefreshLayout refreshLayout, float fraction) 上拉釋放過程
onRefresh(TwinklingRefreshLayout refreshLayout) 正在刷新
onLoadMore(TwinklingRefreshLayout refreshLayout) 正在加載更多
其中fraction表示當(dāng)前下拉的距離與Header高度的比值(或者當(dāng)前上拉距離與Footer高度的比值)。

純凈的越界回彈模式

開啟純凈的越界回彈模式,也就是所有刷新相關(guān)的View都不顯示,只顯示越界回彈效果。

mBinding.refreshLayout.setPureScrollModeOn();
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>


    <com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout
        android:id="@+id/refreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="300dp"
                    android:background="#f00" />


                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="300dp"
                    android:background="#ff0" />


                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="300dp"
                    android:background="#0f0" />


                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="300dp"
                    android:background="#00f" />


            </LinearLayout>
        </ScrollView>

    </com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout>


</layout>
public class MainActivity6 extends AppCompatActivity {
    private ActivityMain6Binding mBinding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main6);

        mBinding.refreshLayout.setPureScrollModeOn();
    }
}

使用庫中已有的header和footer

現(xiàn)在已經(jīng)提供了設(shè)置默認(rèn)的Header、Footer的static方法,可在Application或者一個Activity中這樣設(shè)置:

//設(shè)置全局  可在Application或者一個Activity中這樣設(shè)置
TwinklingRefreshLayout.setDefaultHeader(SinaRefreshView.class.getName());
TwinklingRefreshLayout.setDefaultFooter(BallPulseView.class.getName());
//使用庫中已有的header和footer
mBinding.refreshLayout.setHeaderView(new SinaRefreshView(this));
mBinding.refreshLayout.setBottomView(new BallPulseView(this));
//仿android原生系統(tǒng)下拉刷新 SwipeRefreshLayout
ProgressLayout headerView = new ProgressLayout(this);
mBinding.refreshLayout.setHeaderView(headerView);

自定義Header

相關(guān)接口分別為IHeaderView,代碼如下:

public interface IHeaderView {
    View getView();

    void onPullingDown(float fraction,float maxHeadHeight,float headHeight);

    void onPullReleasing(float fraction,float maxHeadHeight,float headHeight);

    void startAnim(float maxHeadHeight,float headHeight);

    void reset();
}

CustomHeader

繼承自FrameLayout并實(shí)現(xiàn)IHeaderView方法。

public class CustomHeader extends FrameLayout implements IHeaderView {

    //圓形進(jìn)度條
    private int rotationSrc = R.drawable.ypc_footer_progress_small;

    //箭頭
    private int arrowSrc = R.mipmap.arrow;

    private boolean mIsBeingDragged = false;

    private long freshTime;

    private final int ROTATE_ANIM_DURATION = 180;
    //private RotateAnimation mRotateUpAnim;
    //private RotateAnimation mRotateDownAnim;

    private TextView headerTitle;
    private TextView headerTime;
    private ImageView headerArrow;
    private ProgressBar headerProgressbar;

    public CustomHeader(@NonNull Context context) {
        this(context, null);
    }

    public CustomHeader(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomHeader(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();

       /* mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
        mRotateUpAnim.setFillAfter(true);
        mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
        mRotateDownAnim.setFillAfter(true);*/
    }

    private void init() {
        View view = View.inflate(getContext(), R.layout.pull_down_refresh_header, null);
        headerTitle = view.findViewById(R.id.default_header_title);
        headerTime = view.findViewById(R.id.default_header_time);
        headerArrow = view.findViewById(R.id.default_header_arrow);
        headerProgressbar = view.findViewById(R.id.default_header_progressbar);
        headerProgressbar.setIndeterminateDrawable(ContextCompat.getDrawable(getContext(), rotationSrc));
        headerArrow.setImageResource(arrowSrc);
        addView(view);
    }


    /**
     * 用于在TwinklingRefreshLayout中獲取到實(shí)際的Header,因此不能返回null。
     *
     * @return
     */
    @Override
    public View getView() {
        return this;
    }

    /**
     * 正在下拉的過程
     *
     * @param fraction      其中fraction表示當(dāng)前下拉的距離與Header高度的比值(或者當(dāng)前上拉距離與Footer高度的比值)。
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void onPullingDown(float fraction, float maxHeadHeight, float headHeight) {
        if (!mIsBeingDragged) {
            mIsBeingDragged = true;
            if (freshTime == 0) {
                freshTime = System.currentTimeMillis();
            } else {
                int m = (int) ((System.currentTimeMillis() - freshTime) / 1000 / 60);
                if (m >= 1 && m < 60) {
                    headerTime.setText(String.format("%s分鐘前", m));
                } else if (m > 60 * 24) {
                    int d = m / (60 * 24);
                    headerTime.setText(String.format("%s天前", d));
                } else if (m >= 60) {
                    int h = m / 60;
                    headerTime.setText(String.format("%s小時前", h));
                } else if (m == 0) {
                    headerTime.setText("剛剛");
                }
            }
        }
        if (fraction > 1f) {
            headerTitle.setText("松開刷新");
        } else {
            headerTitle.setText("下拉刷新");
        }

        //表示當(dāng)前頭部滑動的距離,然后算出它和最大高度的比例,然后乘以180,可以使得在滑動到最大距離時Arrow恰好能旋轉(zhuǎn)180度。
        headerArrow.setRotation(fraction * headHeight / maxHeadHeight * 180);
    }

    /**
     * 上拉/下拉釋放時回調(diào)的狀態(tài)
     *
     * @param fraction
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void onPullReleasing(float fraction, float maxHeadHeight, float headHeight) {
        mIsBeingDragged = false;
    }

    /**
     * 在onRefresh/onLoadMore之后才會回調(diào)的過程(此處是顯示了加載中的小菊花)
     *
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void startAnim(float maxHeadHeight, float headHeight) {
        freshTime = System.currentTimeMillis();
        headerTitle.setText("正在刷新");
        headerArrow.setVisibility(View.INVISIBLE);
        headerArrow.clearAnimation();
        headerProgressbar.setVisibility(View.VISIBLE);
    }

    @Override
    public void onFinish(OnAnimEndListener animEndListener) {
        animEndListener.onAnimEnd();
        headerTitle.setVisibility(VISIBLE);
        headerArrow.setVisibility(View.VISIBLE);
        headerProgressbar.setVisibility(View.INVISIBLE);
    }

    @Override
    public void reset() {
        headerTitle.setVisibility(VISIBLE);
        headerArrow.setVisibility(View.VISIBLE);
        headerProgressbar.setVisibility(View.INVISIBLE);
    }
}

pull_down_refresh_header.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="bottom">

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="70dp">

        <LinearLayout
            android:id="@+id/default_header_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">

            <TextView
                android:id="@+id/default_header_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="下拉刷新"
                android:textColor="#777777" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="3dp">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="上次更新時間:"
                    android:textColor="#777777"
                    android:textSize="12sp" />

                <TextView
                    android:id="@+id/default_header_time"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="剛剛"
                    android:textColor="#777777"
                    android:textSize="12sp" />
            </LinearLayout>
        </LinearLayout>

        <ImageView
            android:id="@+id/default_header_arrow"
            android:layout_width="20dp"
            android:layout_height="40dp"
            android:layout_alignLeft="@id/default_header_text"
            android:layout_centerVertical="true"
            android:layout_marginLeft="-35dp" />

        <ProgressBar
            android:id="@+id/default_header_progressbar"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignLeft="@id/default_header_text"
            android:layout_centerVertical="true"
            android:layout_marginLeft="-40dp"
            android:visibility="invisible"
            tools:visibility="visible" />
    </RelativeLayout>

</LinearLayout>

設(shè)置頭部/底部個性化刷新效果,頭部需要實(shí)現(xiàn)IHeaderView,底部需要實(shí)現(xiàn)IBottomView

setHeaderView(IHeaderView headerView)、setBottomView(IBottomView bottomView)

  mBinding.refreshLayout.setHeaderView(new CustomHeader(this));

gif圖片作為header

public class GifHeader extends FrameLayout implements IHeaderView {

    private GifDrawable mGifDrawable;

    private ViewGroup.LayoutParams mLp;

    private int mLoadingW = 0, mLoadingH = 0;

    private GifImageView mGifImageView;

    private Context mContext;

    public GifHeader(@NonNull Context context) {
        this(context, null);
    }

    public GifHeader(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GifHeader(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }

    private void init() {
        View rootView = View.inflate(getContext(), R.layout.ypc_home_refresh_header3, null);
        mGifImageView = rootView.findViewById(R.id.gif_second);
        mGifDrawable = (GifDrawable) mGifImageView.getDrawable();
        mLoadingW = DensityUtils.dip2px(238f);
        mLoadingH = DensityUtils.dip2px(120f);
        mLp = mGifImageView.getLayoutParams();

        addView(rootView);
    }


    /**
     * 用于在TwinklingRefreshLayout中獲取到實(shí)際的Header,因此不能返回null。
     *
     * @return
     */
    @Override
    public View getView() {
        return this;
    }

    /**
     * 正在下拉的過程
     *
     * @param fraction      其中fraction表示當(dāng)前下拉的距離與Header高度的比值(或者當(dāng)前上拉距離與Footer高度的比值)。
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void onPullingDown(float fraction, float maxHeadHeight, float headHeight) {
        //下拉的時候不執(zhí)行動畫
        // mGifDrawable.reset();

        if (fraction > 1f) {
            changeLoadingWH(1);
        } else if (fraction <= 1f) {
            changeLoadingWH(fraction);
        }

    }

    /**
     * 上拉/下拉釋放時回調(diào)的狀態(tài)
     *
     * @param fraction
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void onPullReleasing(float fraction, float maxHeadHeight, float headHeight) {

    }

    /**
     * 在onRefresh/onLoadMore之后才會回調(diào)的過程(此處是顯示了加載中的小菊花)
     *
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void startAnim(float maxHeadHeight, float headHeight) {
        mGifDrawable.start();
    }

    @Override
    public void onFinish(OnAnimEndListener animEndListener) {
        animEndListener.onAnimEnd();
        mGifDrawable.stop();
    }

    @Override
    public void reset() {
        mGifDrawable.reset();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    private void changeLoadingWH(float fraction) {
        mLp.width = (int) (mLoadingW * fraction);
        mLp.height = (int) (mLoadingH * fraction);
        mGifImageView.setLayoutParams(mLp);
    }
}

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="horizontal"
    tools:ignore="MissingDefaultResource">

    <pl.droidsonroids.gif.GifImageView
        android:id="@+id/gif_second"
        android:layout_width="238dp"
        android:layout_height="120dp"
        android:layout_gravity="center"
        android:src="@drawable/refresh_header3" />

</LinearLayout>

自定義Footer

相關(guān)接口分別為IBottomView,代碼如下:

CustomFooter

public class CustomFooter  extends FrameLayout implements IBottomView {

    private int rotationSrc = R.drawable.ypc_footer_progress_small;
    private TextView footerTitle;
    private ProgressBar footerProgressbar;
    private boolean isLoading;

    public CustomFooter(Context context) {
        this(context,null,0);
    }

    public CustomFooter(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        View view = View.inflate(getContext(),R.layout.common_footer,  null);
        footerTitle = view.findViewById(R.id.default_footer_title);
        footerProgressbar = view.findViewById(R.id.default_footer_progressbar);
        footerProgressbar.setIndeterminateDrawable(ContextCompat.getDrawable(getContext(), rotationSrc));
        addView(view);
    }


    @Override
    public View getView() {
        return this;
    }

    @Override
    public void onPullingUp(float fraction, float maxBottomHeight, float bottomHeight) {
        if(Math.abs(fraction) < 1f){
            footerTitle.setText("查看更多");
        }else{
            footerTitle.setText("松開載入更多");
        }
        if(footerTitle.getVisibility() != VISIBLE){
            footerTitle.setVisibility(VISIBLE);
            footerProgressbar.setVisibility(View.INVISIBLE);
        }
    }

    @Override
    public void startAnim(float maxBottomHeight, float bottomHeight) {
        footerTitle.setVisibility(View.INVISIBLE);
        footerProgressbar.setVisibility(View.VISIBLE);
        isLoading = true;
    }

    @Override
    public void onPullReleasing(float fraction, float maxBottomHeight, float bottomHeight) {

    }

    @Override
    public void onFinish() {
        footerTitle.setText("查看更多");
        footerTitle.setVisibility(View.VISIBLE);
        footerProgressbar.setVisibility(View.INVISIBLE);
        isLoading =false;
    }

    @Override
    public void reset() {
        footerTitle.setText("查看更多");
        footerTitle.setVisibility(View.VISIBLE);
        footerProgressbar.setVisibility(View.INVISIBLE);
        isLoading =false;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ProgressBar
        android:id="@+id/default_footer_progressbar"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:padding="10dp"
        android:layout_centerInParent="true"
        android:visibility="invisible" />

    <TextView
        android:id="@+id/default_footer_title"
        android:gravity="center"
        android:padding="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="#777777"
        android:text="加載更多" />

</RelativeLayout>
  mBinding.refreshLayout.setBottomView(new CustomFooter(this));

封裝Header和Footer到基類

public abstract class BaseActivity<T extends ViewDataBinding> extends AppCompatActivity {

    public T mBinding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding= DataBindingUtil.setContentView(this,getLayoutId());

        initData();
    }

    public abstract int getLayoutId();

    public void initData() {

    }
}
public abstract class RefreshActivity<T extends ViewDataBinding> extends BaseActivity<T> {

    private IHeaderView mHeaderView;

    @Override
    public void initData() {
        super.initData();

        getRefreshView().setEnableRefresh(true);
        getRefreshView().setEnableLoadmore(false);
        mHeaderView = getHeaderView();
        getRefreshView().setHeaderView(mHeaderView);
    }

    /***
     * 獲取頭部布局,子類可重寫該方法設(shè)置頭布局
     */
    protected IHeaderView getHeaderView() {
        if (mHeaderView == null) {
            return new CustomHeader(this);
        }
        return mHeaderView;
    }


    protected abstract TwinklingRefreshLayout getRefreshView();

}
public abstract class LoadMoreActivity<T extends ViewDataBinding> extends RefreshActivity<T> {

    private IBottomView mBottomView;

    @Override
    public void initData() {
        super.initData();
        getRefreshView().setEnableLoadmore(true);
        mBottomView = getBottomView();
        getRefreshView().setBottomView(mBottomView);
    }

    protected IBottomView getBottomView() {
        if (mBottomView == null) {
            return new CustomFooter(this);
        }
        return mBottomView;
    }
}
public class MainActivity extends LoadMoreActivity<ActivityMainBinding> {

    private List<String> mDatas = new ArrayList<>();

    private RvAdapter mRvAdapter;

    @Override
    public int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected TwinklingRefreshLayout getRefreshView() {
        return mBinding.refreshLayout;
    }

    /**
     * 重寫該方法設(shè)置和基類不同的頭布局
     *
     * @return
     */
    @Override
    protected IHeaderView getHeaderView() {
        return super.getHeaderView();
        //return new CustomHeader2(this);
    }

    /**
     * 重寫該方法設(shè)置和基類不同的底布局
     *
     * @return
     */
    @Override
    protected IBottomView getBottomView() {
        return super.getBottomView();
        //return new CustomFooter2(this);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        mRvAdapter = new RvAdapter(mDatas);
        mBinding.recyclerView.setAdapter(mRvAdapter);

        loadData();

        //TwinklingRefreshLayout不會自動結(jié)束刷新或者加載更多,需要手動控制
        mBinding.refreshLayout.setOnRefreshListener(new RefreshListenerAdapter() {
            @Override
            public void onRefresh(TwinklingRefreshLayout refreshLayout) {
                super.onRefresh(refreshLayout);

                new Handler().postDelayed(() -> {
                    loadData();
                    Toast.makeText(MainActivity.this, "下拉刷新", Toast.LENGTH_SHORT).show();
                    refreshLayout.finishRefreshing();
                }, 2000);
            }

            @Override
            public void onLoadMore(TwinklingRefreshLayout refreshLayout) {
                super.onLoadMore(refreshLayout);

                new Handler().postDelayed(() -> {
                    loadMore();
                    Toast.makeText(MainActivity.this, "上拉加載", Toast.LENGTH_SHORT).show();
                    refreshLayout.finishLoadmore();
                }, 2000);
            }
        });
    }


    private void loadData() {
        mDatas.clear();
        for (int i = 1; i <= 30; i++) {
            mDatas.add("趙麗穎" + i);
        }
        mRvAdapter.notifyDataSetChanged();
    }

    private void loadMore() {
        for (int i = 1; i <= 10; i++) {
            mDatas.add("趙麗穎更多" + i);
        }
        mRvAdapter.notifyDataSetChanged();
    }
}

TwinklingRefreshLayout嵌套CoordinatorLayout

<?xml version="1.0" encoding="utf-8"?>
<com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coord_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:addStatesFromChildren="true"
        android:fitsSystemWindows="true">

        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clipChildren="false">

            <!--...-->

        </android.support.design.widget.AppBarLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    </android.support.design.widget.CoordinatorLayout>
</com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout>

讓refreshLayout能夠找到RecyclerView/ListView

refreshLayout.setTargetView(rv);

設(shè)置AppBarLayout的移動監(jiān)聽器,需要下拉顯示AppBarLayout時需設(shè)置setEnableRefresh(false),setEnableOverScroll(false);AppBarLayout隱藏后還原為原來設(shè)置的值即可:

AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        if (verticalOffset >= 0) {
            refreshLayout.setEnableRefresh(true);
            refreshLayout.setEnableOverScroll(false);
        } else {
            refreshLayout.setEnableRefresh(false);
            refreshLayout.setEnableOverScroll(false);
        }
    }
});

CoordinatorLayout嵌套TwinklingRefreshLayout

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coord_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:addStatesFromChildren="true"
    android:fitsSystemWindows="true">

    <com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout
        android:id="@+id/refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout>

</android.support.design.widget.CoordinatorLayout>

注意給TwinklingRefreshLayout設(shè)置一個layout_behavior="@string/appbar_scrolling_view_behavior"。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容