需求:
达到像LoadMoreListView那样的效果,实现分页时上拉加载等待时间有个footerView
要达到的效果
PullRefreshLayout+RecyclerView相关代码:
xml--Activity的布局:
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent"
android:orientation="vertical"
tools:context="xxx.xxx.xxx.xxx.activity.XXXActivity">
<com.baoyz.widget.PullRefreshLayout
android:id="@+id/pullRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
</com.baoyz.widget.PullRefreshLayout>
</LinearLayout>
xml--上拉加载的footerView的布局(R.layout.load_more_footer):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="46dp"
android:background="@color/globalBackGround"
android:gravity="center"
android:id="@+id/loadmore"
android:orientation="horizontal">
<ProgressBar
style="?android:attr/progressBarStyleSmall"
android:layout_width="35dp"
android:layout_height="35dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="46dp"
android:gravity="center"
android:text="正在加载..." />
</LinearLayout>
xml--adapter用到的item布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/touch_bg"
android:clickable="true"
android:id="@+id/msg_layout"
android:orientation="vertical">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dip">
<ImageView
android:id="@+id/bell_logo"
android:layout_width="50dp"
android:src="@drawable/xtxx_icon"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_gravity="center"
android:layout_marginBottom="10dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="10dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/bell_logo"
android:layout_toRightOf="@id/bell_logo"
android:ellipsize="end"
android:gravity="left|center_vertical"
android:lines="1"
android:text="系统消息"
android:textColor="#1e1e1e"
android:textSize="16sp" />
<TextView
android:id="@+id/msg_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/bell_logo"
android:layout_toLeftOf="@+id/red_point"
android:layout_toRightOf="@id/bell_logo"
android:ellipsize="end"
android:gravity="left|center_vertical"
android:lines="1"
android:text="content content......." />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignTop="@id/bell_logo"
android:layout_marginRight="10dp"
android:text="2017-04-20"
android:id="@+id/msg_time"
android:textColor="@color/color_aaaaaa_content_text"
android:textSize="10sp" />
</LinearLayout>
网上找的HeaderViewRecyclerAdapter:
package xxx.xxx.xxx.xxx.adapter;
/*
* Copyright (C) 2014 darnmason
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* RecyclerView adapter designed to wrap an existing adapter allowing the addition of
* header views and footer views.
* </p>
* <p>
* I implemented it to aid with the transition from ListView to RecyclerView where the ListView's
* addHeaderView and addFooterView methods were used. Using this class you may initialize your
* header views in the Fragment/Activity and add them to the adapter in the same way you used to
* add them to a ListView.
* </p>
* <p>
* I also required to be able to swap out multiple adapters with different content, therefore
* setAdapter may be called multiple times.
* </p>
* Created by darnmason on 07/11/2014.
*/
public class HeaderViewRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int HEADERS_START = Integer.MIN_VALUE;
private static final int FOOTERS_START = Integer.MIN_VALUE + 10;
private static final int ITEMS_START = Integer.MIN_VALUE + 20;
private static final int ADAPTER_MAX_TYPES = 100;
private RecyclerView.Adapter mWrappedAdapter;
private List<View> mHeaderViews, mFooterViews;
private Map<Class, Integer> mItemTypesOffset;
/**
* Construct a new header view recycler adapter
* @param adapter The underlying adapter to wrap
*/
public HeaderViewRecyclerAdapter(RecyclerView.Adapter adapter) {
mHeaderViews = new ArrayList<View>();
mFooterViews = new ArrayList<View>();
mItemTypesOffset = new HashMap<Class, Integer>();
setWrappedAdapter(adapter);
}
/**
* Replaces the underlying adapter, notifying RecyclerView of changes
* @param adapter The new adapter to wrap
*/
public void setAdapter(RecyclerView.Adapter adapter) {
if(mWrappedAdapter != null && mWrappedAdapter.getItemCount() > 0) {
notifyItemRangeRemoved(getHeaderCount(), mWrappedAdapter.getItemCount());
}
setWrappedAdapter(adapter);
notifyItemRangeInserted(getHeaderCount(), mWrappedAdapter.getItemCount());
}
@Override
public int getItemViewType(int position) {
int hCount = getHeaderCount();
if (position < hCount) return HEADERS_START + position;
else {
int itemCount = mWrappedAdapter.getItemCount();
if (position < hCount + itemCount) {
return getAdapterTypeOffset() + mWrappedAdapter.getItemViewType(position - hCount);
}
else return FOOTERS_START + position - hCount - itemCount;
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
if (viewType < HEADERS_START + getHeaderCount())
return new StaticViewHolder(mHeaderViews.get(viewType - HEADERS_START));
else if (viewType < FOOTERS_START + getFooterCount())
return new StaticViewHolder(mFooterViews.get(viewType - FOOTERS_START));
else {
return mWrappedAdapter.onCreateViewHolder(viewGroup, viewType - getAdapterTypeOffset());
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
int hCount = getHeaderCount();
if (position >= hCount && position < hCount + mWrappedAdapter.getItemCount())
mWrappedAdapter.onBindViewHolder(viewHolder, position - hCount);
}
/**
* Add a static view to appear at the start of the RecyclerView. Headers are displayed in the
* order they were added.
* @param view The header view to add
*/
public void addHeaderView(View view) {
mHeaderViews.add(view);
}
/**
* Add a static view to appear at the end of the RecyclerView. Footers are displayed in the
* order they were added.
* @param view The footer view to add
*/
public void addFooterView(View view) {
mFooterViews.add(view);
}
@Override
public int getItemCount() {
return getHeaderCount() + getFooterCount() + getWrappedItemCount();
}
/**
* @return The item count in the underlying adapter
*/
public int getWrappedItemCount() {
return mWrappedAdapter.getItemCount();
}
/**
* @return The number of header views added
*/
public int getHeaderCount() {
return mHeaderViews.size();
}
/**
* @return The number of footer views added
*/
public int getFooterCount() {
return mFooterViews.size();
}
private void setWrappedAdapter(RecyclerView.Adapter adapter) {
if (mWrappedAdapter != null) mWrappedAdapter.unregisterAdapterDataObserver(mDataObserver);
mWrappedAdapter = adapter;
Class adapterClass = mWrappedAdapter.getClass();
if(!mItemTypesOffset.containsKey(adapterClass)) putAdapterTypeOffset(adapterClass);
mWrappedAdapter.registerAdapterDataObserver(mDataObserver);
}
private void putAdapterTypeOffset(Class adapterClass) {
mItemTypesOffset.put(adapterClass, ITEMS_START + mItemTypesOffset.size() * ADAPTER_MAX_TYPES);
}
private int getAdapterTypeOffset() {
return mItemTypesOffset.get(mWrappedAdapter.getClass());
}
private RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
notifyItemRangeChanged(positionStart + getHeaderCount(), itemCount);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
notifyItemRangeInserted(positionStart + getHeaderCount(), itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
notifyItemRangeRemoved(positionStart + getHeaderCount(), itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
int hCount = getHeaderCount();
// TODO: No notifyItemRangeMoved method?
notifyItemRangeChanged(fromPosition + hCount, toPosition + hCount + itemCount);
}
};
private static class StaticViewHolder extends RecyclerView.ViewHolder {
public StaticViewHolder(View itemView) {
super(itemView);
}
}
//下边这个方法是我自己拷贝另一个网上的adpter的,作用是获得footerview,好在加载完的时候直接隐藏掉footerview
public View getFooterView() {
return getFooterCount()>0 ? mFooterViews.get(0) : null;
}
}
网上找的自定义onScrollListener:
package xxx.xxx.xxx.common.listener;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
public abstract class SysMsgListScrollListener extends RecyclerView.OnScrollListener{
private LinearLayoutManager mLinearLayoutManager;
int lastVisibleItemPosition;
public SysMsgListScrollListener(LinearLayoutManager linearLayoutManager) {
this.mLinearLayoutManager = linearLayoutManager;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//获取最后一个显示 的item的位置
lastVisibleItemPosition = mLinearLayoutManager.findLastVisibleItemPosition();
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//这一段是关键,如果显示的item总数大于0,并且滑动状态符合,并且最后一个位置大于等于item总数量-1的话(代表最后一个) 就执行加载方法onLoadMore();
int currentScrollState = newState;
int visibleItemCount = mLinearLayoutManager.getChildCount();
int totalItemCount = mLinearLayoutManager.getItemCount();
if ((visibleItemCount > 0 && currentScrollState == RecyclerView.SCROLL_STATE_IDLE && (lastVisibleItemPosition) >= totalItemCount - 1)) {
onLoadMore();
}
}
public abstract void onLoadMore();
}
自己写的一个接口,用作item点击监听:
package xxx.xxx.xxx.common.listener;
/**
* Created by Hello我的World on 2017/4/20.
*/
public interface SysMsgOnItemClickListener {
void sysMsgItemClick(long id);
}
ViewHolder:
package xxx.xxx.xxx.xxx.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import xxx.xxx.xxx.R;
import xxx.xxx.xxx.common.bean.SysMsgInfo;
import xxx.xxx.xxx.common.listener.SysMsgOnItemClickListener;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by Hello我的World on 2017/4/20.
*/
public class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.msg_layout)
LinearLayout msg_layout;
@BindView(R.id.msg_title)
TextView msg_title;
@BindView(R.id.msg_time)
TextView msg_time;
SysMsgOnItemClickListener listener;
List<SysMsgInfo> data;
public ViewHolder(View itemView, SysMsgOnItemClickListener listener, List<SysMsgInfo>data) {
super(itemView);
ButterKnife.bind(this,itemView);
this.listener = listener;
this.data = data;
}
@OnClick(R.id.msg_layout)
public void onClick(){
listener.sysMsgItemClick(data.get(getLayoutPosition()).getId());
}
public void setContent(SysMsgInfo info){
msg_title.setText(info.getTitle());
msg_time.setText(info.getCreateTime());
}
}
真正加载数据的Adapter(用来放到上边那个“网上找的adapter”里):
package xxx.xxx.xxx.common.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import xxx.xxx.xxx.R;
import xxx.xxx.xxx.common.bean.SysMsgInfo;
import xxx.xxx.xxx.common.listener.SysMsgOnItemClickListener;
import java.util.List;
/**
* Created by Hello我的World on 2017/4/20.
*/
public class SysMsgListAdapter extends RecyclerView.Adapter<ViewHolder> {
Context context;
List<SysMsgInfo> data;//这个list里的对象,是自己写的数据databean,按自己实际需求实现
SysMsgOnItemClickListener onItemClickListener;//这个监听器是自己写的接口,就一个方法,让item收到click事件时可以回调
public SysMsgListAdapter(Context context,List<SysMsgInfo>data,SysMsgOnItemClickListener listener){
this.context = context;
this.data = data;
this.onItemClickListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.sysmsg_item, parent, false);
return new ViewHolder(view,onItemClickListener,data);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setContent(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
public List<SysMsgInfo> getData() {
return data;
}
}
使用:
package xxx.xxx.xxx.common.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.baoyz.widget.PullRefreshLayout;
import xxx.xxx.xxx.R;
import xxx.xxx.xxx.base.BaseActivity;
import xxx.xxx.xxx.base.utils.CommonUtil;
import xxx.xxx.xxx.common.adapter.HeaderViewRecyclerAdapter;
import xxx.xxx.xxx.common.adapter.SysMsgListAdapter;
import xxx.xxx.xxx.common.bean.GetSysMessageListReturnInfo;
import xxx.xxx.xxx.common.contract.SysMsgListContract;
import xxx.xxx.xxx.common.listener.SysMsgListScrollListener;
import xxx.xxx.xxx.common.presenter.SysMsgListPresenter;
import butterknife.BindView;
import butterknife.ButterKnife;
public class SysMsgListActivity extends BaseActivity implements SysMsgListContract.View {
@BindView(R.id.pullRefreshLayout)
PullRefreshLayout pullRefreshLayout;
@BindView(R.id.tv_title)
TextView tv_title;
@BindView(R.id.listView)
RecyclerView listView;
SysMsgListContract.Presenter presenter;
HeaderViewRecyclerAdapter hAdapter;
SysMsgListAdapter adapter;
View footer;
int pageIndex = 1;
int pageSize = 20;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sys_msg_list);
ButterKnife.bind(this);
tv_title.setText("系统消息中心");
//这是这个activity的presenter,loadSysMsgList方法是加载数据的方法
presenter = new SysMsgListPresenter(this);
presenter.loadSysMsgList(pageIndex, pageSize);
//布局管理器
LinearLayoutManager l = new LinearLayoutManager(this);
listView.setLayoutManager(l);
listView.addOnScrollListener(new SysMsgListScrollListener(l) {
@Override
public void onLoadMore() {
//这个是RecyclerView的滑动监听,如果滑动到最后 就重新加载,先把footerView显示出来
if(footer!=null) footer.setVisibility(View.VISIBLE);
//执行加载
presenter.loadSysMsgList(pageIndex, pageSize);
}
});
pullRefreshLayout.setOnRefreshListener(() -> {
//下拉刷新操作,直接将pageIndex还原
pageIndex = 1;
//如果adapter有数据,直接清掉,并且将“网上找来的adapter”更新
if (adapter != null) {
adapter.getData().clear();
if (hAdapter != null) {
hAdapter.notifyDataSetChanged();
}
}
//加载数据
presenter.loadSysMsgList(pageIndex, pageSize);
});
}
@Override
public void showProgress(String message) {
}
@Override
public void dismissProgress() {
}
//下边这个方法是Adapter的关键代码段
@Override
public void showMsgList(GetSysMessageListReturnInfo data, int pageIndex) {
pullRefreshLayout.setRefreshing(false);//数据返回后,将下拉刷新状态设置为false
if (data.getMessageList().size() == 0) return;//如果数据为0就不用显示了
this.pageIndex = pageIndex;//分页加载的页码
//如果adapter为空,就新建adapter,如果不为空代表之前有过加载,直接在adapter的数组上addAll好了
if (adapter == null) {
adapter = new SysMsgListAdapter(this, data.getMessageList(), id -> {
//这个就是SysMsgOnItemClickListener的回调,然后做出自己需要的逻辑处理即可
Intent intent = new Intent(SysMsgListActivity.this,SysMsgDetailActivity.class);
intent.putExtra("MsgId",id);
startActivity(intent);
});
} else {
adapter.getData().addAll(data.getMessageList());
if (hAdapter != null) hAdapter.notifyDataSetChanged();
}
//如果“在网上找的Adapter”为空,就新创建一个,新建对象时将我们自己展示数据的adapter传进去
if (hAdapter == null) {
hAdapter = new HeaderViewRecyclerAdapter(adapter);
listView.setAdapter(hAdapter);
//isLastPage==0代表不是最后一页,isLastPage==1代表最后一页
if (data.getIsLastPage() != 1) {
//如果加载返回的数据中isLastPage表示不是最后一页,就要添加footerView
footer = LayoutInflater.from(this).inflate(R.layout.load_more_footer, listView, false);
hAdapter.addFooterView(footer);
}
}
//数据加载完成时将footerView隐藏
if (footer != null) footer.setVisibility(View.GONE);
}
@Override
public void loadDone() {
//加载到最后数据,当服务器返回已经没有数据了,presenter中会回调这个方法,在这里将footerView隐藏掉
CommonUtil.showToast("数据已加载到最后...");
if (footer != null) footer.setVisibility(View.GONE);
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
}
}
实现思路:
- Activity的布局(要包含PullRefreshLayout和RecyclerView)
这里用的是com.baoyz.widget.PullRefreshLayout,然后这个布局中包一个RecyclerView - 创建footerView的布局
- 创建adapter加载数据要用到的item
- 要有一个可以加footerView的adapter,网上找的HeaderViewRecyclerAdapter 点击这里看这个adapter的源码
- 实现onItemClick的监听器(其实就是个接口,在item监听到点击着自己了就回调这个接口的方法)
- 要知道RecyclerView滑动了没呀,要根据滑动状态判断加载不加载啊(onScrollListener RecyclerView滑动监听,ps.这个监听的源码地址找不到了)
- 要有一个ViewHolder
- 要有一个放数据的Adapter
- Activity里的一系列使用
以上是笔记,其实去年已经写过类似需求,也找到一套方法,没有记笔记,现在用到时发现全忘了,重新找资料看的,所以这次一定要记录好备用!
BTW:方法千千万,还有很多更简洁更快的方法----->不信就去看啊
再看啊