Android 开发 上拉加载更多功能实现

2023-03-14,,

实现思维

  开始之前先废话几句,Android系统没有提供上拉加载的控件,只提供了下拉刷新的SwipeRefreshLayout控件。这个控件我们就不废话,无法实现上拉刷新的功能。现在我们说说上拉加载更多的功能实现

 思维步骤:

    首先我们需要自定义重写RecyclerView,这个是重点.原因是,如果不重写RecyclerView会出现ItemView的点击与滑动的时候有事件分发冲突的问题(滑动无法得到down的触摸事件,或者点击无法得到up的触摸事件),我们重写RecyclerView目的就是重新分发事件,将down和up事件不被拦截,拦截我们需要的move事件.
    创建一个叫页尾的布局文件,它用来在列表的最后面显示使用
    接着我们需要想办法在RecyclerView.Adapter的适配器里导入这个页尾布局。你的列表内容适配器的普通item该如何实现还是如何实现。
    为了导入这个页尾布局,我们需要在导入的List长度+1,因为这个页尾布局是另外加入的,需要在getItemCount()这个重写方法里返回List长度上+1。
    现在就需要判断什么时候滚动到了列表的最后,这个时候我们需要重写一个之前写RecyclerView.Adapter适配器一般不触及的一个重写方法public int getItemViewType(int position) 。重写它根据position位置返回普通item和页尾item的ViewType。
    能判断什么时候滚动到最后面后,我们就需要在写RecyclerView.Adapter适配器一样在onCreateViewHolder方法里导入布局,这里我们可以根据ViewType判断应该导入普通item还是页尾item
    然后就是处理点击逻辑或者处理刷新逻辑了。在部分在activity中实现

  

代码部分

重写RecyclerView:

public class UpLoadingRecyclerView extends RecyclerView {
private static final String TAG = "MyRecyclerView";
private boolean isScroll;
private float downY;
private int mHeightPixels;
private OnUpScrollListener mOnUpScrollListener; public UpLoadingRecyclerView(@NonNull Context context) {
super(context);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
mHeightPixels = displayMetrics.heightPixels;
} public UpLoadingRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
mHeightPixels = displayMetrics.heightPixels;
} public UpLoadingRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
mHeightPixels = displayMetrics.heightPixels;
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
} /**
* 重写拦截的目的是只拦截移动事件,不拦截其他触摸事件
* @param e
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
super.onInterceptTouchEvent(e);//一定要super.onInterceptTouchEvent(e);不要让RecyclerView的其他事件分发受到影响,否则会出现不滚动的问题
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
isScroll = false;//按下不拦截
downY = e.getY();
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(downY - e.getY()) >= ViewConfiguration.get(
getContext()).getScaledTouchSlop()) {//判断是否产生了最小滑动
isScroll = true;//移动了拦截触摸事件
} else {
isScroll = false;
}
break;
case MotionEvent.ACTION_UP:
isScroll = false;//松开不拦截
break;
} return isScroll;
} @Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (downY-e.getY() > mHeightPixels/3){//判断是不是从下往上滑动了屏幕的三分之一
if (mOnUpScrollListener!=null) {
mOnUpScrollListener.onScroll();
Log.e(TAG, "onTouchEvent:滚动了");
} }
break; }
return super.onTouchEvent(e);
} public void setOnUpScrollListener(OnUpScrollListener listener){
this.mOnUpScrollListener = listener; } public interface OnUpScrollListener{
void onScroll();
} }

页尾布局 pull_up_refresh.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/colorGray5"
android:layout_width="match_parent"
android:layout_height="30dp"> <ProgressBar
android:id="@+id/footer_progress"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginRight="10dp"
android:visibility="gone"
android:indeterminateDrawable="@anim/pull_up_ic"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@id/footer_text"
app:layout_constraintBottom_toBottomOf="@id/footer_text"
app:layout_constraintRight_toLeftOf="@id/footer_text"/> <TextView
android:id="@+id/footer_text"
android:text="@string/pull_up_load_more"
android:textColor="@color/fontBlack3"
android:textSize="@dimen/font_size_14"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/footer_progress"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
ProgressBar是刷新加载时候的动画图片  TextView就是文本内容

适配器代码

public class TNoticeListAdapter extends RecyclerView.Adapter<TNoticeListAdapter.ViewHolder> {
private List<TNoticeListBase.Notice> mList;
private static final int ITEM_VIEW = 1;
private static final int FOOTER_VIEW = 2;
public static final int FOOTER_TIPS = 0;//提示上拉加载更多
public static final int FOOTER_ING = 1;//加载中
public static final int FOOTER_ERROR = 2;//网络异常
public static final int FOOTER_FINISH = 3;//没有更多内容
private int footerState = 0;
private View mFooterView;
private OnFooterClickListener mFooterClickListener;
private OnItemClickListener mItemClickListener;
private int mScrollPosition;
public TNoticeListAdapter(List<TNoticeListBase.Notice> list){
this.mList = list; }
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ITEM_VIEW){
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.t_notice_item,parent,false);
final ViewHolder viewHolder = new ViewHolder(itemView);
itemView.setOnClickListener(new View.OnClickListener() { //传出item的点击
@Override
public void onClick(View v) {
if (mItemClickListener!=null){
int postion = viewHolder.getAdapterPosition();
mItemClickListener.onClick(mList.get(postion).code);
}
}
});
return viewHolder;
}else {
mFooterView = LayoutInflater.from(parent.getContext()).inflate(R.layout.pull_up_refresh,parent,false);
mFooterView.setOnClickListener(new View.OnClickListener() { //传出页尾View的点击
@Override
public void onClick(View v) {
if (getFooterState()==FOOTER_ERROR && mFooterClickListener!=null){ //页尾View只在网络异常的时候可以被点击
mFooterClickListener.onClick();
} }
});
return new ViewHolder(mFooterView); }
} @Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
setScrollPosition(position);
if (getItemViewType(position) == ITEM_VIEW) {
TNoticeListBase.Notice data = mList.get(position);
holder.itemTime.setText("今天 "+TimeUtil.getHour24(data.time)+":"+TimeUtil.getMinute(data.time));
holder.itemContent.setText("\t\t" + data.content);
return;
}
     switch (footerState){
case FOOTER_TIPS:
holder.footerText.setText(R.string.pull_up_load_more);
holder.footerProgress.setVisibility(View.GONE);
break;
case FOOTER_ING:
holder.footerText.setText(R.string.loading_more_for_you);
holder.footerProgress.setVisibility(View.VISIBLE);
break;
case FOOTER_ERROR:
holder.footerText.setText(R.string.network_exception_click_reload);
holder.footerProgress.setVisibility(View.GONE);
break;
case FOOTER_FINISH:
holder.footerText.setText(R.string.Theres_nothing_more);
holder.footerProgress.setVisibility(View.GONE);
break;
default:
holder.footerText.setText(R.string.pull_up_load_more);
holder.footerProgress.setVisibility(View.GONE);
break;
}
} @Override
public int getItemViewType(int position) {
//根据itemView的位置返回View的类型是普通还是页尾
if (position == getItemCount()-1){
return FOOTER_VIEW;
}else {
return ITEM_VIEW;
}
} @Override
public int getItemCount() {
return mList.size()==0?0:mList.size()+1;
} /**
* 得到页尾状态
* @return
*/
public int getFooterState() {
return footerState;
} /**
* 设置页尾状态
* @param footerState
*/
public void setFooterState(int footerState) {
this.footerState = footerState;
notifyDataSetChanged();
} /**
* 设置页尾点击监听
* @param listener
*/
public void setFooterClickListener(OnFooterClickListener listener){
this.mFooterClickListener = listener; }
/**
* 设置item的点击监听
* @param listener
*/
public void setItemClickListener(OnItemClickListener listener){
this.mItemClickListener = listener;
} /**
* 添加数据方法
* @param list
*/
public void addData(List<TNoticeListBase.Notice> list){
this.mList = list; } /**
* 得到滚动位置
* @return
*/
public int getScrollPosition() {
return mScrollPosition;
} /**
* 设置滚动位置 用于判断当前RecyclerView有没有滚动到最后
* @param position
*/
private void setScrollPosition(int position) {
this.mScrollPosition = position;
} public class ViewHolder extends RecyclerView.ViewHolder {
TextView itemTime;
TextView itemContent;
TextView footerText;
ProgressBar footerProgress;
public ViewHolder(@NonNull View itemView) {
super(itemView);
if (mFooterView!=null && itemView == mFooterView){
footerProgress = (ProgressBar)itemView.findViewById(R.id.footer_progress);
footerText = (TextView)itemView.findViewById(R.id.footer_text);
}else {
itemTime = (TextView) itemView.findViewById(R.id.time);
itemContent = (TextView) itemView.findViewById(R.id.content);
}
}
} public interface OnFooterClickListener{
void onClick(); } public interface OnItemClickListener{
void onClick(String code); }
}

在activity里的实现

public class TNoticeListActivity extends BaseActivity {
private UpLoadingRecyclerView mRecyclerView;
private SwipeRefreshLayout mSwitchRefresh;
private TNoticeListAdapter mAdapter;
private LinearLayoutManager mLinearLayoutManager;
private List<TNoticeListBase.Notice> mNoticeList; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initData();
initClick(); } @Override
public int getLayout() {
return R.layout.activity_t_notice;
} @Override
public void initView() {
mRecyclerView = (RecyclerView)findViewById(R.id.recyclerview);
mLinearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLinearLayoutManager); } /**
* 模拟数据
*/
public void initData(){
mNoticeList = new ArrayList<>();
for (int i=0;i<2;i++){
TNoticeListBase.Notice notice = new TNoticeListBase().new Notice();
notice.time = 1547604994000L+i;
notice.content = "这是一个测试用的通知内容这是一个测试用的通知内容这是一个测试用的通知内容"+i;
notice.code = "dsfa13c1z616v5"+i;
mNoticeList.add(notice);
}
mAdapter = new TNoticeListAdapter(mNoticeList);
mRecyclerView.setAdapter(mAdapter); } /**
* 添加加载模拟数据
*/
public void addData(){
for (int i=0;i<10;i++){
TNoticeListBase.Notice notice = new TNoticeListBase().new Notice();
notice.time = 1547604994000L+i;
notice.content = "这是二个测试用的通知内容"+i;
notice.code = "dsfa13c1z616v5"+i;
mNoticeList.add(notice);
}
mAdapter.addData(mNoticeList);
mAdapter.setFooterState(TNoticeListAdapter.FOOTER_TIPS); } public void initClick(){
mRecyclerView.setOnUpScrollListener(new UpLoadingRecyclerView.OnUpScrollListener() {
@Override
public void onScroll() {
if (mAdapter.getItemCount()-1 == mAdapter.getPosition() && mAdapter.getFooterState() == TNoticeListAdapter.FOOTER_TIPS){
mAdapter.setFooterState(TNoticeListAdapter.FOOTER_ING);
//加载中的逻辑
L.e("正在加载中");
new Thread(new Runnable() {//模拟数据加载
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
addData(); }
});
}
}).start(); }
}
});
mAdapter.setFooterClickListener(new TNoticeListAdapter.OnFooterClickListener() {
@Override
public void onClick() {
L.e("网络异常点击了"); }
});
mAdapter.setItemClickListener(new TNoticeListAdapter.OnItemClickListener() {
@Override
public void onClick(String code) {
L.e("item被点击了");
}
});
mSwitchRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
Toast.makeText(TNoticeListActivity.this, "没有更多数据了", Toast.LENGTH_SHORT).show();
mSwitchRefresh.setRefreshing(false);//刷新事件结束
}
});
} }

判断数据长度不够,提示已经是最新数据的办法,此方法可以在Activity里创建:

    private final int PAGE_SIZE = 20;
private void downRefresh(List<TNoticeListBase.Notice> noticeList) {
mAdapter.addData(noticeList);
if (noticeList.size() < PAGE_SIZE) { //如果从服务器上获取的数据长度小于发送过去的一页长度,说明服务器没有数据了.页尾就显示没有更多数据
mAdapter.setFooterState(TNoticeListAdapter.FOOTER_FINISH);
} else {
mAdapter.setFooterState(TNoticeListAdapter.FOOTER_TIPS);
}
}

Android 开发 上拉加载更多功能实现的相关教程结束。

《Android 开发 上拉加载更多功能实现.doc》

下载本文的Word格式文档,以方便收藏与打印。