TwinklingRefreshLayout介紹
TwinklingRefreshLayout延伸了Google的SwipeRefreshLayout的思想,不在列表控件上動刀,而是使用一個ViewGroup來包含列表控件,以保持其較低的耦合性和較高的通用性。其主要特性有:
- 支持RecyclerView、ScrollView、AbsListView系列(ListView、GridView)、WebView以及其它可以獲取到scrollY的控件
- 支持加載更多
- 默認(rèn)支持 越界回彈,隨手勢速度有不同的效果
- 可開啟沒有刷新控件的純凈越界回彈模式
- setOnRefreshListener中擁有大量可以回調(diào)的方法
- 將Header和Footer抽象成了接口,并回調(diào)了滑動過程中的系數(shù),方便實(shí)現(xiàn)個性化的Header和Footer
- 支持NestedScroll,嵌套CoordinatorLayout
目前已經(jīng)支持了所有的View,比如是一個FrameLayout,LinearLayout,AnyView。

依賴
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ò)展屬性

動態(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"。