下拉刷新框架TwinklingRefreshLayout的使用

TwinklingRefreshLayout

TwinklingRefreshLayout介绍

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

  1. 支持RecyclerView、ScrollView、AbsListView系列(ListView、GridView)、WebView以及其它可以获取到scrollY的控件
  2. 支持加载更多
  3. 默认支持 越界回弹,随手势速度有不同的效果
  4. 可开启没有刷新控件的纯净越界回弹模式
  5. setOnRefreshListener中拥有大量可以回调的方法
  6. 将Header和Footer抽象成了接口,并回调了滑动过程中的系数,方便实现个性化的Header和Footer
  7. 支持NestedScroll,嵌套CoordinatorLayout

目前已经支持了所有的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系统为了跟iOS不一样,当界面OverScroll的时候会显示一个阴影。为了达到更好的显示效果,最好禁用系统的overScroll,如上给RecyclerView添加android:overScrollMode="never"。

在Activity或者Fragment中配置

TwinklingRefreshLayout不会自动结束刷新或者加载更多,需要手动控制

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


        //是否需要下拉刷新,默认需要true
        mBinding.refreshLayout.setEnableRefresh(true);

        //是否需要加载更多,默认需要true
        mBinding.refreshLayout.setEnableLoadmore(true);

        //是否自动加载更多, 滑到底部默认加载更多,默认false
        mBinding.refreshLayout.setAutoLoadMore(true);

        //如果你想进入到界面的时候主动调用下刷新,可以调用startRefresh()/startLoadmore()方法。
        mBinding.refreshLayout.startRefresh();
        // mBinding.refreshLayout.startLoadMore();

        //是否允许进入越界回弹模式,默认true
        mBinding.refreshLayout.setEnableOverScroll(true);

        //是否开启悬浮刷新模式(支持切换到像SwipeRefreshLayout一样的悬浮刷新模式),默认false
        mBinding.refreshLayout.setFloatRefresh(false);


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

        loadData();


        //TwinklingRefreshLayout不会自动结束刷新或者加载更多,需要手动控制
        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()方法结束刷新,finishLoadmore()方法结束加载更多。此处OnRefreshListener还有其它方法,可以选择需要的来重写。

扩展属性

image.png

动态设置相关属性

如果你想进入到界面的时候主动调用下刷新

如果你想进入到界面的时候主动调用下刷新,可以调用startRefresh()/startLoadmore()方法。

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

开启纯净的越界回弹模式,也就是所有刷新相关的View都不显示,只显示越界回弹效果

setPureScrollModeOn()

灵活的设置是否禁用上下拉

setEnableRefresh、setEnableLoadmore

是否在底部越界的时候自动切换到加载更多模式

setAutoLoadMore

是否允许越界回弹,默认支持越界回弹

这一点很多类似SwipeRefreshLayout的刷新控件都没有做到(包括SwipeRefreshLayout),因为没有拦截下来的时间会传递给列表控件,而列表控件的滚动状态很难获取。解决方案就是给列表控件设置了OnTouchListener并把事件交给GestureDetector处理,然后在列表控件的OnScrollListener中监听View是否滚动到了顶部(没有OnScrollListener的则采用延时监听策略)。

setEnableOverScroll

支持切换到像SwipeRefreshLayout一样的悬浮刷新模式了

setFloatRefresh(boolean)

设置头部/底部个性化刷新效果,头部需要实现IHeaderView,底部需要实现IBottomView

setHeaderView(IHeaderView headerView)、setBottomView(IBottomView bottomView)

setDefaultHeader、setDefaultFooter
现在已经提供了设置默认的Header、Footer的static方法,可在Application或者一个Activity中这样设置:

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

添加一个固定在顶部的Header(效果还需要优化)

addFixedExHeader

设置滚动事件的作用对象。

setTargetView(View view)

是否允许在越界的时候显示刷新控件,默认是允许的,也就是Fling越界的时候Header或Footer照常显示,反之就是不显示;可能有特殊的情况,刷新控件会影响显示体验才设立了这个状态。

setOverScrollTopShow、setOverScrollBottomShow、setOverScrollRefreshShow

setWaveHeight、setHeaderHeight、setBottomHeight、setOverScrollHeight

setMaxHeadHeight 设置头部可拉伸的最大高度。
setHeaderHeight 头部固定高度(在此高度上显示刷新状态)
setMaxBottomHeight
setBottomHeight 底部高度
setOverScrollHeight 设置最大的越界高度

setOnRefreshListener大量可以回调的方法

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表示当前下拉的距离与Header高度的比值(或者当前上拉距离与Footer高度的比值)。

纯净的越界回弹模式

开启纯净的越界回弹模式,也就是所有刷新相关的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

现在已经提供了设置默认的Header、Footer的static方法,可在Application或者一个Activity中这样设置:

//设置全局  可在Application或者一个Activity中这样设置
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原生系统下拉刷新 SwipeRefreshLayout
ProgressLayout headerView = new ProgressLayout(this);
mBinding.refreshLayout.setHeaderView(headerView);

自定义Header

相关接口分别为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并实现IHeaderView方法。

public class CustomHeader extends FrameLayout implements IHeaderView {

    //圆形进度条
    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中获取到实际的Header,因此不能返回null。
     *
     * @return
     */
    @Override
    public View getView() {
        return this;
    }

    /**
     * 正在下拉的过程
     *
     * @param fraction      其中fraction表示当前下拉的距离与Header高度的比值(或者当前上拉距离与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("下拉刷新");
        }

        //表示当前头部滑动的距离,然后算出它和最大高度的比例,然后乘以180,可以使得在滑动到最大距离时Arrow恰好能旋转180度。
        headerArrow.setRotation(fraction * headHeight / maxHeadHeight * 180);
    }

    /**
     * 上拉/下拉释放时回调的状态
     *
     * @param fraction
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void onPullReleasing(float fraction, float maxHeadHeight, float headHeight) {
        mIsBeingDragged = false;
    }

    /**
     * 在onRefresh/onLoadMore之后才会回调的过程(此处是显示了加载中的小菊花)
     *
     * @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>

设置头部/底部个性化刷新效果,头部需要实现IHeaderView,底部需要实现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中获取到实际的Header,因此不能返回null。
     *
     * @return
     */
    @Override
    public View getView() {
        return this;
    }

    /**
     * 正在下拉的过程
     *
     * @param fraction      其中fraction表示当前下拉的距离与Header高度的比值(或者当前上拉距离与Footer高度的比值)。
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void onPullingDown(float fraction, float maxHeadHeight, float headHeight) {
        //下拉的时候不执行动画
        // mGifDrawable.reset();

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

    }

    /**
     * 上拉/下拉释放时回调的状态
     *
     * @param fraction
     * @param maxHeadHeight
     * @param headHeight
     */
    @Override
    public void onPullReleasing(float fraction, float maxHeadHeight, float headHeight) {

    }

    /**
     * 在onRefresh/onLoadMore之后才会回调的过程(此处是显示了加载中的小菊花)
     *
     * @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

相关接口分别为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);
    }

    /***
     * 获取头部布局,子类可重写该方法设置头布局
     */
    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;
    }

    /**
     * 重写该方法设置和基类不同的头布局
     *
     * @return
     */
    @Override
    protected IHeaderView getHeaderView() {
        return super.getHeaderView();
        //return new CustomHeader2(this);
    }

    /**
     * 重写该方法设置和基类不同的底布局
     *
     * @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不会自动结束刷新或者加载更多,需要手动控制
        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);

设置AppBarLayout的移动监听器,需要下拉显示AppBarLayout时需设置setEnableRefresh(false),setEnableOverScroll(false);AppBarLayout隐藏后还原为原来设置的值即可:

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设置一个layout_behavior="@string/appbar_scrolling_view_behavior"。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容