如何给ListView & RecyclerView加上拉刷新 & 下拉加载更多机制
这道题想考察什么?
考察同学是否对列表下拉刷新与上拉加载的实现熟悉。
考生应该如何回答
ListView
下面我们通过自定义ListView,实现下拉刷新与上拉加载,实现的关键点:
- 为ListView添加头布局和底布局。
- 通过改变头布局的paddingTop值,来控制控件的显示和隐藏
- 根据我们滑动的状态,动态修改头部布局和底部布局。
代码如下:
public class CustomRefreshListView extends ListView implements OnScrollListener{
/**
* 头布局
*/
private View headerView;
/**
* 头部布局的高度
*/
private int headerViewHeight;
/**
* 头部旋转的图片
*/
private ImageView iv_arrow;
/**
* 头部下拉刷新时状态的描述
*/
private TextView tv_state;
/**
* 下拉刷新时间的显示控件
*/
private TextView tv_time;
/**
* 底部布局
*/
private View footerView;
/**
* 底部旋转progressbar
*/
private ProgressBar pb_rotate;
/**
* 底部布局的高度
*/
private int footerViewHeight;
/**
* 按下时的Y坐标
*/
private int downY;
private final int PULL_REFRESH = 0;//下拉刷新的状态
private final int RELEASE_REFRESH = 1;//松开刷新的状态
private final int REFRESHING = 2;//正在刷新的状态
/**
* 当前下拉刷新处于的状态
*/
private int currentState = PULL_REFRESH;
/**
* 头部布局在下拉刷新改变时,图标的动画
*/
private RotateAnimation upAnimation,downAnimation;
/**
* 当前是否在加载数据
*/
private boolean isLoadingMore = false;
public CustomRefreshListView(Context context) {
this(context,null);
}
public CustomRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
//设置滑动监听
setOnScrollListener(this);
//初始化头布局
initHeaderView();
//初始化头布局中图标的旋转动画
initRotateAnimation();
//初始化为尾布局
initFooterView();
}
/**
* 初始化headerView
*/
private void initHeaderView() {
headerView = View.inflate(getContext(), R.layout.head_custom_listview, null);
iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);
tv_state = (TextView) headerView.findViewById(R.id.tv_state);
tv_time = (TextView) headerView.findViewById(R.id.tv_time);
//测量headView的高度
headerView.measure(0, 0);
//获取高度,并保存
headerViewHeight = headerView.getMeasuredHeight();
//设置paddingTop = -headerViewHeight;这样,该控件被隐藏
headerView.setPadding(0, -headerViewHeight, 0, 0);
//添加头布局
addHeaderView(headerView);
}
/**
* 初始化旋转动画
*/
private void initRotateAnimation() {
upAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
upAnimation.setDuration(300);
upAnimation.setFillAfter(true);
downAnimation = new RotateAnimation(-180, -360,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
downAnimation.setDuration(300);
downAnimation.setFillAfter(true);
}
//初始化底布局,与头布局同理
private void initFooterView() {
footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null);
footerView.measure(0, 0);
footerViewHeight = footerView.getMeasuredHeight();
footerView.setPadding(0, -footerViewHeight, 0, 0);
addFooterView(footerView);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//获取按下时y坐标
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if(currentState==REFRESHING){
//如果当前处在滑动状态,则不做处理
break;
}
//手指滑动偏移量
int deltaY = (int) (ev.getY() - downY);
//获取新的padding值
int paddingTop = -headerViewHeight + deltaY;
if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0){
//向下滑,且处于顶部,设置padding值,该方法实现了顶布局慢慢滑动显现
headerView.setPadding(0, paddingTop, 0, 0);
if(paddingTop>=0 && currentState==PULL_REFRESH){
//从下拉刷新进入松开刷新状态
currentState = RELEASE_REFRESH;
//刷新头布局
refreshHeaderView();
}else if (paddingTop<0 && currentState==RELEASE_REFRESH) {
//进入下拉刷新状态
currentState = PULL_REFRESH;
refreshHeaderView();
}
return true;//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
}
break;
case MotionEvent.ACTION_UP:
if(currentState==PULL_REFRESH){
//仍处于下拉刷新状态,未滑动一定距离,不加载数据,隐藏headView
headerView.setPadding(0, -headerViewHeight, 0, 0);
}else if (currentState==RELEASE_REFRESH) {
//滑倒一定距离,显示无padding值得headcView
headerView.setPadding(0, 0, 0, 0);
//设置状态为刷新
currentState = REFRESHING;
//刷新头部布局
refreshHeaderView();
if(listener!=null){
//接口回调加载数据
listener.onPullRefresh();
}
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 根据currentState来更新headerView
*/
private void refreshHeaderView(){
switch (currentState) {
case PULL_REFRESH:
tv_state.setText("下拉刷新");
iv_arrow.startAnimation(downAnimation);
break;
case RELEASE_REFRESH:
tv_state.setText("松开刷新");
iv_arrow.startAnimation(upAnimation);
break;
case REFRESHING:
iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完
iv_arrow.setVisibility(View.INVISIBLE);
pb_rotate.setVisibility(View.VISIBLE);
tv_state.setText("正在刷新...");
break;
}
}
/**
* 完成刷新操作,重置状态,在你获取完数据并更新完adater之后,去在UI线程中调用该方法
*/
public void completeRefresh(){
if(isLoadingMore){
//重置footerView状态
footerView.setPadding(0, -footerViewHeight, 0, 0);
isLoadingMore = false;
}else {
//重置headerView状态
headerView.setPadding(0, -headerViewHeight, 0, 0);
currentState = PULL_REFRESH;
pb_rotate.setVisibility(View.INVISIBLE);
iv_arrow.setVisibility(View.VISIBLE);
tv_state.setText("下拉刷新");
tv_time.setText("最后刷新:"+getCurrentTime());
}
}
/**
* 获取当前系统时间,并格式化
* @return
*/
private String getCurrentTime(){
SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
return format.format(new Date());
}
private OnRefreshListener listener;
public void setOnRefreshListener(OnRefreshListener listener){
this.listener = listener;
}
public interface OnRefreshListener{
void onPullRefresh();
void onLoadingMore();
}
/**
* SCROLL_STATE_IDLE:闲置状态,就是手指松开
* SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动
* SCROLL_STATE_FLING:快速滑动后松开
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(scrollState==OnScrollListener.SCROLL_STATE_IDLE
&& getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){
isLoadingMore = true;
footerView.setPadding(0, 0, 0, 0);//显示出footerView
setSelection(getCount());//让listview最后一条显示出来,在页面完全显示出底布局
if(listener!=null){
listener.onLoadingMore();
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
}
1.下拉刷新的实现逻辑如下:
下拉刷新是通过设置setOnTouchListener()方法,监听触摸事件,通过手指滑动的不同处理实现相应逻辑。
实现比较复杂,分为了三个情况,初始状态(显示下拉刷新),释放刷新状态,刷新状态。
其中下拉刷新状态和释放状态的改变,是由于手指滑动的不同距离,是在MotionEvent.ACTION_MOVE中进行判断,该判断不处理任何数据逻辑,只是根据手指滑动的偏移量来确定该表UI的显示。
刷新状态的判断是在MotionEvent.ACTION_UP手指抬起时判断的。这很容易理解,因为最终下拉刷新是否加载数据的确定,是由我们手指离开屏幕时与初始值的偏移量确定的。如果我们的偏移量小于了头布局的高度,代表不刷新,继续隐藏头布局。如果偏移量大于了头布局的高度,则意味着刷新,修改UI,同时通过接口回调,让其持有者进行加载数据。
2.上拉加载的实现逻辑如下:
上拉加载和下拉刷新不同,它的实现十分简单,我们通过ListView的滚动监听进行处理相应逻辑。即setOnScrollListener(this)。
该方法需要实现两个回调方法:
- public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount):滚动监听的调用。
- public void onScrollStateChanged(AbsListView view, int scrollState):滑动状态改变的回调。其中scrollState为回调的状态,可能值为
- SCROLL_STATE_IDLE:闲置状态,手指松开后的状态回调
- SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动的状态回调
- SCROLL_STATE_FLING:手指松开后惯性滑动的回调
我们在onScrollStateChanged中进行判断,主要判断一下条件;
- 是否是停止状态
- 是否滑倒最后
- 是否正在加载数据
如果符合条件,则开始加载数据,通过接口回调。
RecyclerView
使用官方的刷新控件SwipeRefreshLayout来实现下拉刷新,当RecyclerView滑到底部实现下拉加载(进度条效果用RecyclerView加载一个布局实现)
需要完成控件的下拉监听和上拉监听,其中,下拉监听通过SwipRefreshLayout的setOnRefreshListener()方法监听,而上拉刷新,需要通过监听列表的滚动,当列表滚动到底部时触发事件,具体代码如下:
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
private SwipeRefreshLayout refreshLayout;
private RecyclerView recyclerView;
private LinearLayoutManager layoutManager;
private RecyclerAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_layout);
recyclerView = (RecyclerView) findViewById(R.id.recycler_list);
layoutManager = new LinearLayoutManager(this);
// 设置刷新时进度条颜色,最多四种
refreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary);
// 设置监听,需要重写onRefresh()方法,顶部下拉时会调用这个方法
refreshLayout.setOnRefreshListener(this);
mAdapter = new RecyclerAdapter();//自定义的适配器
recyclerView.setAdapter(mAdapter);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addOnScrollListener(new OnRecyclerScrollListener());
}
/**
* 用于下拉刷新
*/
@Override
public void onRefresh() {
}
/**
* 用于上拉加载更多
*/
public class OnRecyclerScrollListener extends RecyclerView.OnScrollListener {
int lastVisibleItem = 0;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (mAdapter != null && newState == RecyclerView.SCROLL_STATE_IDLE
&& lastVisibleItem + 1 == mAdapter.getItemCount()) {
//滚动到底部了,可以进行数据加载等操作
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = layoutManager.findLastVisibleItemPosition();
}
}
}
下面是实现上拉时进度条转动的效果
<?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="match_parent">
<TextView
android:id="@+id/tv_item_footer_load_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center"
android:text="上拉加载更多"
/>
<ProgressBar
android:id="@+id/pb_item_footer_loading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:visibility="gone"/>
</RelativeLayout>
public class RecyclerAdapter extends RecyclerView.Adapter<ViewHolder> {
private static final int TYPE_CONTENT = 0;
private static final int TYPE_FOOTER = 1;
private ArrayList<DataBean> dataList;
private ProgressBar pbLoading;
private TextView tvLoadMore;
public RecyclerAdapter() {
dataList = new ArrayList<>();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_CONTENT) {
return new ContentViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_list_content, parent, false));
} else if (viewType == TYPE_FOOTER) {//加载进度条的布局
return new FooterViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_list_footer, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int type = getItemViewType(position);
if (type == TYPE_CONTENT) {
DataBean bean = dataList.get(position);
((ContentViewHolder) holder).tvId.setText("" + bean.getId());
((ContentViewHolder) holder).tvName.setText(bean.getName());
} else if (type == TYPE_FOOTER) {
pbLoading = ((FooterViewHolder) holder).pbLoading;
tvLoadMore = ((FooterViewHolder) holder).tvLoadMore;
}
}
/**
* 获取数据集加上一个footer的数量
*/
@Override
public int getItemCount() {
return dataList.size() + 1;
}
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_CONTENT;
}
}
/**
* 获取数据集的大小
*/
public int getListSize() {
return dataList.size();
}
/**
* 内容的ViewHolder
*/
public static class ContentViewHolder extends ViewHolder {
private TextView tvId, tvName;
public ContentViewHolder(View itemView) {
super(itemView);
tvId = (TextView) itemView.findViewById(R.id.tv_item_id);
tvName = (TextView) itemView.findViewById(R.id.tv_item_name);
}
}
/**
* footer的ViewHolder
*/
public static class FooterViewHolder extends ViewHolder {
private TextView tvLoadMore;
private ProgressBar pbLoading;
public FooterViewHolder(View itemView) {
super(itemView);
tvLoadMore = (TextView) itemView.findViewById(R.id.tv_item_footer_load_more);
pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_item_footer_loading);
}
}
/**
* 显示正在加载的进度条,滑动到底部时,调用该方法,上拉就显示进度条,隐藏"上拉加载更多"
*/
public void showLoading() {
if (pbLoading != null && tvLoadMore != null) {
pbLoading.setVisibility(View.VISIBLE);
tvLoadMore.setVisibility(View.GONE);
}
}
/**
* 显示上拉加载的文字,当数据加载完毕,调用该方法,隐藏进度条,显示“上拉加载更多”
*/
public void showLoadMore() {
if (pbLoading != null && tvLoadMore != null) {
pbLoading.setVisibility(View.GONE);
tvLoadMore.setVisibility(View.VISIBLE);
}
}
}
有需要以上面试题的朋友可以关注一下哇哇,以上都可以分享!!!
网友评论