一、前言
最近有一个需求就是上拉、下拉整体刷新一页数据,并且将下一页数据整体划出显示,上一页数据则消失。这个类似于选股宝的头条选股的效果,先上图。
二、实现心路历程
拿到这个需求,我就想,我靠,没搞过啊,这个怎么弄!仔细看,这好像就是上拉刷新、下拉加载呀,怎么实现?套两层recycleView?里面负责显示,外面一层负责动画?添加header 和 footer?然后增加动画,还有阻尼效果?不行不行,实现时间太长,不允许!找找网上有没有实现好的开源代码,然后:“仿选股宝选股头条上拉加载”等等,都没有。那只能自己实现了,然后自己再细看这玩意,越看越像之前ListView时代的PullToRefresh,然后我就想,找一个这样的上下组件,然后这种入场方式就自己实现:先上拉,数据出来之后,记录新加载的第一条数据,然后从该条开始,向上或者向下移动。最后和大佬说这个方案的时候,他给我提意见:你这样就弄得整个recycleView的逻辑很复杂,你还不如利用Fragment显示,让它处理数据显示,动画,进出场,recycleView只负责列表显示。瞬间茅塞顿开。
三、上代码
上下拉加载,我就自己不造*了,有很多大牛写的很好,找了几个,最终选中了https://www.cnblogs.com/zhujiabin/p/7425535.html 这个,我对他的代码做了小小的改动,增加下拉加载时,没有数据,显示到顶了,类似于上拉没有数据的那种效果,具体修改思路按照代码中底部加载完成字段isMore 增加isPreviousData字段控制,代码就不贴了,比较简单,稍微改下就行。
3.1具体使用
3.1.1 用一个Activity承接Fragment
PullRefreshActivity.java
public class PullRefreshActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pull_refresh);
BaseFragment fragment = new BaseFragment();
BaseFragment fragment2 = new BaseFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fl_root, PullRefreshFragment.newInstance(it))
.setCustomAnimations(R.anim.fragment_top_in, R.anim.fragment_bottom_out, R.anim.fragment_bottom_in, R.anim.fragment_top_out)
.commit();
}
}
其XML文件很简单,就一个FrameLayout,这里就不贴出了,说明一下,这个代码仅用于demo,具体项目中,这Activity得加载一次数据,在数据加载成功回调中切换Fragment,然后在Fragment中,Fragment代码如下:
PullRefreshFragment.kt
class PullRefreshFragment: Fragment() {
private var list: PullRefreshRecyclerView? = null
private var pageNum: Int = 0
private var adapter: MyAdapter? = null
private val data = ArrayList<String>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_six, container, false)
list = view.findViewById<PullRefreshRecyclerView>(R.id.list)
PullRefreshUtil.setRefresh(list as PullRefreshView, true, true)
adapter = MyAdapter(activity, data)
list?.setLayoutManager(LinearLayoutManager(activity))
list?.setAdapter(adapter)
loadData()
list?.setOnPullDownRefreshListener {
list?.isPreviousData(true)
pageNum = 1
loadData()
list?.refreshFinish()
}
list?.setOnPullUpRefreshListener {
pageNum++
list?.isMore(true)
loadData()
list?.refreshFinish()
}
list?.setOnHeadStateListener(object : PullRefreshView.OnHeadStateListener{
override fun onRetractHead(head: View?) {
activity?.supportFragmentManager?.beginTransaction()
?..setCustomAnimations(R.anim.fragment_top_in, R.anim.fragment_bottom_out, R.anim.fragment_bottom_in, R.anim.fragment_top_out)
?.replace(R.id.fl_root, PullRefreshFragment(), TAG2)?.commit()
(head as HeadView).onRetractHead(head)
}
override fun onRefreshHead(head: View?) {
(head as HeadView).onRefreshHead(head)
}
override fun onScrollChange(head: View?, scrollOffset: Int, scrollRatio: Int) {
(head as HeadView).onScrollChange(head, scrollOffset, scrollRatio)
}
override fun onNotMore(head: View?) {
(head as HeadView).onNotMore(head)
}
override fun onHasMore(head: View?) {
(head as HeadView).onHasMore(head)
}
})
//以下两个监听,为了切换Fragment,实际开发中我们不需要这两个监听,我们只要在数据加载成功回调中处理即可 ,下面会有实际中的运用的代码
list?.setOnTailStateListener(object : PullRefreshView.OnTailStateListener{
override fun onScrollChange(tail: View?, scrollOffset: Int, scrollRatio: Int) {
(tail as TailView).onScrollChange(tail, scrollOffset, scrollRatio)
}
override fun onRefreshTail(tail: View?) {
(tail as TailView).onRefreshTail(tail)
}
override fun onRetractTail(tail: View?) {
activity?.supportFragmentManager?.beginTransaction()
?..setCustomAnimations(R.anim.fragment_top_in, R.anim.fragment_bottom_out, R.anim.fragment_bottom_in, R.anim.fragment_top_out)
?.replace(R.id.fl_root, PullRefreshFragment())?.commit()
(tail as TailView).onRetractTail(tail)
}
override fun onNotMore(tail: View?) {
(tail as TailView).onNotMore(tail)
}
override fun onHasMore(tail: View?) {
(tail as TailView).onHasMore(tail)
}
})
return view
}
private fun loadData() {
for (i in 0..14) {
data.add(i.toString() + "")
}
adapter?.setData(data)
adapter?.notifyDataSetChanged()
}
var mOnLoadSuccess: onl oadSuccess? = null
fun setOnLoadSucess(onLoadSuccess: onl oadSuccess) {
this.mOnLoadSuccess = onl oadSuccess
}
interface onl oadSuccess {
fun loadSuccess()
}
}
接上实际开发中的代码:
mViewModel.headLineData.observe(this, Observer {
var enter: Int?
var exit: Int?
var popEnter: Int?
var popExit: Int?
if (isUp) {
enter = R.anim.fragment_bottom_in
exit = R.anim.fragment_top_out
popEnter = R.anim.fragment_top_in
popExit = R.anim.fragment_bottom_out
} else {
enter = R.anim.fragment_top_in
exit = R.anim.fragment_bottom_out
popEnter = R.anim.fragment_bottom_in
popExit = R.anim.fragment_top_out
}
mBinding?.recycleView?.refreshFinish()
activity?.supportFragmentManager?.beginTransaction()
?.setCustomAnimations(enter, exit, popEnter, popExit)
?.replace(R.id.fl_root, newInstance(it))?.commit()
})
MyAdapter.java
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> mData;
private Context mContext;
public MyAdapter(Context context, ArrayList<String> dataList) {
this.mContext = context;
this.mData = dataList;
}
public void setData(List<String> data) {
this.mData = data;
}
@Override
public int getItemCount() {
return mData.size();
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.tv.setText(mData.get(position));
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_adapter, parent, false);
return new ViewHolder(view);
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView tv;
public ViewHolder(View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tv);
}
}
}
其中item_adapter.xml文件很简单,只有一个TextView,这里就不贴出来了,另外Fragment进出场的四个动画如下:
fragment_bottom_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="100%p"
android:toYDelta="0%p"
android:duration="300"/>
<alpha
android:fromAlpha="0.5"
android:toAlpha="1.0"
android:duration="300"/>
</set>
fragment_top_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="0%p"
android:toYDelta="-100%p"
android:duration="300"/>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.5"
android:duration="300"/>
</set>
fragment_top_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="-100%p"
android:toYDelta="0%p"
android:duration="300"/>
<alpha
android:fromAlpha="0.5"
android:toAlpha="1.0"
android:duration="300"/>
</set>
fragment_bottom_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="0%p"
android:toYDelta="100%p"
android:duration="300"/>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.5"
android:duration="300"/>
</set>
具体效果如下: