Android ListView 实现上拉加载的示例代码

作者:蒋志碧 时间:2021-09-16 18:04:17 

本文介绍了Android ListView 实现上拉加载的示例代码,分享给大家,具体如下:

Android ListView 实现上拉加载的示例代码

我们先分析一下如何实现 ListView 上拉加载。

  • 当我们上拉的时候,会出现一个提示界面,即 ListView 的 Footer 布局。

  • ListView 要实现滚动,所以要监听 ListView 滚动事件,即 OnScrollListener() 事件。

  • 当我们开始滚动时,Footer 布局才慢慢显示出来,所以需要监听 ListView 的 onTouch() 事件。

实现思路

  1. 首先判断 ListView 加载时机,当 ListView 的 lastVisibleItem == totalItemCount 时表示当前处于 ListView 最底端,此时允许下拉。

  2. 自定义一个 FooterView,将 FooterView 添加到 ListView 底部,在上拉时候的显示和完成时候的隐藏。

  3. 定义一个加载接口,当上拉动作完成时候回调,用于标记状态并加载最新数据进行展示。

1、定义 Footer

Footer 要实现的效果:

第一次上拉时,Footer 逐渐显示,文字显示为下拉可以加载,箭头向上,进度条隐藏。

当松开加载的时候,箭头隐藏,进度条展示,文字改为正在加载。

Android ListView 实现上拉加载的示例代码

1、Footer 加载时状态变化

定义一个如上图所示的 Footer 的 XML 文件 footer_layout.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:paddingBottom="10dp"
 android:paddingTop="10dp">

<LinearLayout
   android:id="@+id/layout"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_centerHorizontal="true"
   android:layout_marginTop="10dp"
   android:gravity="center"
   android:orientation="vertical">

<TextView
     android:id="@+id/tv_tip"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="下拉可以刷新"
     android:textSize="12sp" />
 </LinearLayout>

<ImageView
   android:id="@+id/img_arrow"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_marginRight="10dp"
   android:layout_toLeftOf="@+id/layout"
   android:src="@drawable/pull_to_refresh_arrow" />

<ProgressBar
   android:id="@+id/progress"
   style="@style/progressBar_custom_drawable"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_centerVertical="true"
   android:layout_marginRight="10dp"
   android:layout_toLeftOf="@+id/img_arrow"
   android:visibility="gone"
   tools:visibility="visible" />
</RelativeLayout>

2、初始化布局

定义一个 RefreshListView 类继承 ListView,重写构造函数,并将 Footer 添加到 ListView 中。


public class RefreshListView extends ListView {
 private View header;
 private int headerHeight;//顶部布局高度
 private int firstVisibleItem;//当前第一个 Item 可见位置
 private float startY;//按下时开始的Y值
 private int scrollState;//当前滚动状态

private View footer;
 private int footerHeight;//底部布局高度
 private float lastY;
 private boolean canLoadMoreEnabled;//是否允许加载更多

public RefreshListView(Context context) {
   super(context);
   initView(context);
 }

public RefreshListView(Context context, AttributeSet attrs) {
   super(context, attrs);
   initView(context);
 }

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
   super(context, attrs, defStyleAttr);
   initView(context);
 }

private void initView(Context context) {
   header = LayoutInflater.from(context).inflate(R.layout.header_layout, null);
   footer = LayoutInflater.from(context).inflate(R.layout.footer_layout, null);
   measureView(header);
   measureView(footer);
   //这里获取高度的时候需要先通知父布局header占用的空间
   headerHeight = header.getMeasuredHeight();
   footerHeight = footer.getMeasuredHeight();
   topPadding(-headerHeight);
   bottomPadding(-footerHeight);//用于隐藏 Footer
   this.addHeaderView(header);
   this.addFooterView(footer);
   this.setOnScrollListener(this);
 }

/**
  * 设置 Footer 布局的下边距
  * 以隐藏 Footer
  * @param topPadding
  */
 private void bottomPadding(int bottomPadding) {
   footer.setPadding(footer.getPaddingLeft(), footer.getPaddingTop(),
       footer.getPaddingRight(),
       bottomPadding);
   footer.invalidate();
 }
}

3、实现上拉加载

给 ListView 设置监听


public class RefreshListView extends ListView implements AbsListView.OnScrollListener {
 private int firstVisibleItem;//当前第一个 Item 可见位置
 private int scrollState;//当前滚动状态

private void initView(Context context) {
   header = LayoutInflater.from(context).inflate(R.layout.header_layout, null);
   footer = LayoutInflater.from(context).inflate(R.layout.footer_layout, null);
   measureView(header);
   measureView(footer);
   //这里获取高度的时候需要先通知父布局header占用的空间
   headerHeight = header.getMeasuredHeight();
   footerHeight = footer.getMeasuredHeight();
   topPadding(-headerHeight);
   bottomPadding(-footerHeight);
   this.addHeaderView(header);
   this.addFooterView(footer);
   this.setOnScrollListener(this);
 }

@Override
 public void onScrollStateChanged(AbsListView view, int scrollState) {
   this.scrollState = scrollState;
 }

@Override
 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
   this.firstVisibleItem = firstVisibleItem;
   this.lastVisibleItem = firstVisibleItem + visibleItemCount;
   this.totalItemCount = totalItemCount;
 }
}

加载的时机是判断lastVisibleItem == totalItemCount,而上拉事件我们需要重写 onTouchEvent() 事件,首先定义几个状态。


private float lastY;

private static int state;//当前状态
private final static int NONE = 0;//正常状态
private final static int PULL = 1;//下拉状态
private final static int RELEASE = 2;//释放状态
private final static int REFRESHING = 3;//正在刷新状态

在 onTouchEvent 中,在 ACTION_DOWN 时,记录最开始的 Y 值,然后在 ACTION_MOVE 事件中实时记录移动距离 space,不断刷新 FooterView 的 bootomPadding,让它跟随滑动距离进行显示,继续滑动,当 space 大于了 FooterHeight 时,状态给为 RELEASE,表示可以释放进行刷新操作。


@Override
 public boolean onTouchEvent(MotionEvent ev) {
   switch (ev.getAction()) {
     case MotionEvent.ACTION_DOWN:
       //最顶部
       if (firstVisibleItem == 0) {//刷新
         canRefreshEnabled = true;
         startY = ev.getY();
       } else if (lastVisibleItem == totalItemCount) {//加载更多
         canLoadMoreEnabled = true;
         lastY = ev.getY();
       }
       break;
     case MotionEvent.ACTION_MOVE:
       onMove(ev);
       break;
     case MotionEvent.ACTION_UP:
       if (state == RELEASE) {//如果已经释放,则可以提示刷新数据
         state = REFRESHING;
         if (iRefreshListener != null) {
           iRefreshListener.onRefresh();
         }
         if (iLoadMoreListener != null) {
           iLoadMoreListener.onLoadMore();
         }
       } else if (state == PULL) {//如果是在下拉状态,不刷新数据
         state = NONE;
       }
       if (canRefreshEnabled) {
         refreshViewByState();
       }
       if (canLoadMoreEnabled) {
         loadViewByState();
       }
       canLoadMoreEnabled = false;
       canRefreshEnabled = false;
       break;
   }
   return super.onTouchEvent(ev);
 }

/**
  * 判断移动过程中的操作
  *
  * @param ev
  */
 private void onMove(MotionEvent ev) {
   int tempY = (int) ev.getY();
   int refreshSpace = (int) (tempY - startY);//向下移动的距离
   int topPadding = refreshSpace - headerHeight;//在移动过程中不断设置 topPadding
   int loadSpace = (int) (lastY - tempY);//向上移动的距离
   int bottomPadding = loadSpace - footerHeight;//在移动过程中不断设置 bottomPadding
   switch (state) {
     case NONE:
       //下拉移动距离大于0
       if (refreshSpace > 0) {
         state = PULL; //状态变成下拉状态
         refreshViewByState();
       }
       //上拉移动距离大于0
       if (loadSpace > 0) {
         state = PULL;//状态变成下拉状态
         loadViewByState();
       }
       break;
     case PULL:
       if (canRefreshEnabled) {
         topPadding(topPadding);//在移动过程中不断设置 topPadding,让 Header 随着下拉动作慢慢显示
       }
       if (canLoadMoreEnabled) {
         bottomPadding(bottomPadding);//在移动过程中不断设置 bottomPadding,让 Footer 随着上拉动作慢慢显示
       }
       //移动距离大于headerHeight并且正在滚动
       if (canRefreshEnabled && refreshSpace > (headerHeight + 30) && scrollState == SCROLL_STATE_TOUCH_SCROLL) {
         state = RELEASE;//提示释放
         refreshViewByState();
       }
       //移动距离大于footerHeight并且正在滚动
       if (canLoadMoreEnabled && loadSpace > footerHeight + 30 && scrollState == SCROLL_STATE_TOUCH_SCROLL) {
         state = RELEASE;//提示释放
         loadViewByState();//刷新footer布局
       }
       break;
     case RELEASE:
       if (canRefreshEnabled) {
         topPadding(topPadding);
         //移动距离小于headerHeight
         if (refreshSpace < headerHeight + 30) {
           state = PULL;//提示下拉
         } else if (refreshSpace <= 0) {
           state = NONE;
         }
         refreshViewByState();//更新header
       }
       if (canLoadMoreEnabled) {
         bottomPadding(bottomPadding);
         //移动距离小于footerHeight
         if (loadSpace < footerHeight + 30) {
           state = PULL;//提示下拉
         } else if (loadSpace <= 0) {
           state = NONE;
         }
         loadViewByState();//更新footer
       }
       break;
   }
 }

加载数据的时候,要根据状态不断改变 FooterView 的显示,箭头定义一个旋转动画让其跟随滑动距离实现旋转,进度条也设置了逐帧动画实现自定义进度条。


private void loadViewByState() {
   TextView tip = footer.findViewById(R.id.tv_tip);
   ImageView arrow = footer.findViewById(R.id.img_arrow);
   ProgressBar progressBar = footer.findViewById(R.id.progress);
   progressBar.setBackgroundResource(R.drawable.custom_progress_bar);
   AnimationDrawable animationDrawable = (AnimationDrawable) progressBar.getBackground();
   //给箭头设置动画
   RotateAnimation anim = new RotateAnimation(0, 180,
       RotateAnimation.RELATIVE_TO_SELF, 0.5f,
       RotateAnimation.RELATIVE_TO_SELF, 0.5f);
   RotateAnimation anim1 = new RotateAnimation(180, 0,
       RotateAnimation.RELATIVE_TO_SELF, 0.5f,
       RotateAnimation.RELATIVE_TO_SELF, 0.5f);
   anim.setDuration(200);
   anim.setFillAfter(true);
   anim1.setDuration(200);
   anim1.setFillAfter(true);
   switch (state) {
     case NONE://正常,footer不显示
       bottomPadding(-footerHeight);
       arrow.clearAnimation();
       break;
     case PULL://下拉状态
       arrow.setVisibility(VISIBLE);//箭头显示,进度条隐藏
       progressBar.setVisibility(GONE);
       if (animationDrawable.isRunning()) {
         animationDrawable.stop();
       }
       tip.setText("上拉可以加载");
       arrow.clearAnimation();
       arrow.setAnimation(anim);//箭头向下
       break;
     case RELEASE://释放状态
       arrow.setVisibility(VISIBLE);//箭头显示,进度条隐藏
       progressBar.setVisibility(GONE);
       if (animationDrawable.isRunning()) {
         //停止动画播放
         animationDrawable.stop();
       }
       tip.setText("松开开始加载");
       arrow.clearAnimation();
       arrow.setAnimation(anim);//箭头向上
       break;
     case REFRESHING://刷新状态
       bottomPadding(50);
       arrow.setVisibility(GONE);//箭头显示,进度条隐藏
       progressBar.setVisibility(VISIBLE);
       animationDrawable.start();
       tip.setText("正在加载...");
       arrow.clearAnimation();
       break;
   }
 }

4、下拉刷新完成回调

当上拉加载完成时,我们需要实现数据的刷新,并且要通知 Adapter 刷新数据,这里我们定义一个监听接口实现回调即可。回调在 ACTION_UP 的 RELEASE 状态下进行注册。


private ILoadMoreListener iLoadMoreListener;

public void setILoadMoreListener(ILoadMoreListener iLoadMoreListener) {
   this.iLoadMoreListener = iLoadMoreListener;
 }

public interface ILoadMoreListener {
   void onLoadMore();
 }

5、测试


package com.dali.refreshandloadmorelistview;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;

import java.util.ArrayList;

public class MainActivity extends Activity implements RefreshListView.IRefreshListener, RefreshListView.ILoadMoreListener {

private ArrayList<ApkEntity> apk_list;
 private ListAdapter adapter;
 private RefreshListView listView;

@Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   setData();
   showList(apk_list);
 }

private void showList(ArrayList<ApkEntity> apk_list) {
   if (adapter == null) {
     listView = (RefreshListView) findViewById(R.id.listview);
     listView.setIRefreshListener(this);
     listView.setILoadMoreListener(this);
     adapter = new ListAdapter(this, apk_list);
     listView.setAdapter(adapter);
   } else {
     adapter.onDateChange(apk_list);
   }
 }

private void setData() {
   apk_list = new ArrayList<ApkEntity>();
   for (int i = 0; i < 10; i++) {
     ApkEntity entity = new ApkEntity();
     entity.setName("默认数据" + i);
     entity.setDes("这是一个神奇的应用");
     entity.setInfo("50w用户");
     apk_list.add(entity);
   }
 }

private void setRefreshData() {
   for (int i = 0; i < 2; i++) {
     ApkEntity entity = new ApkEntity();
     entity.setName("默认数据 + 刷新" + i);
     entity.setDes("这是一个神奇的应用");
     entity.setInfo("50w用户");
     apk_list.add(0, entity);
   }
 }

private void setLoadData() {
   for (int i = 0; i < 2; i++) {
     ApkEntity entity = new ApkEntity();
     entity.setName("默认数据 + 加载" + (adapter.getCount() + i));
     entity.setDes("这是一个神奇的应用");
     entity.setInfo("50w用户");
     apk_list.add(entity);
   }
 }

@Override
 public void onRefresh() {
   //添加刷新动画效果
   Handler handler = new Handler();
   handler.postDelayed(new Runnable() {
     @Override
     public void run() {
       //获取最新数据
       setRefreshData();
       //通知界面显示数据
       showList(apk_list);
       //通知 ListView 刷新完成
       listView.refreshComplete();
     }
   }, 2000);
 }

@Override
 public void onLoadMore() {
   Handler handler = new Handler();
   handler.postDelayed(new Runnable() {
     @Override
     public void run() {
       //获取最新数据
       setLoadData();
       //通知界面显示数据
       showList(apk_list);
       //通知 ListView 刷新完成
       listView.loadMoreComplete();
     }
   }, 2000);
 }
}

GitHub 源码

来源:https://www.jianshu.com/p/e8ee83fe1215

标签:ListView,上拉加载
0
投稿

猜你喜欢

  • springboot跨域CORS处理代码解析

    2022-07-29 21:12:20
  • 使用java实现http多线程断点下载文件(一)

    2023-11-23 15:45:51
  • Mybatis Log Plugin的使用方式

    2021-08-27 19:02:31
  • Android 中使用RadioGroup和Fragment实现底部导航栏的功能

    2022-07-17 16:11:04
  • SpringMVC实现表单验证功能详解

    2023-09-24 09:07:28
  • C#实现将窗体固定在显示器的左上角且不能移动的方法

    2022-03-04 18:34:17
  • Java基础之二叉搜索树的基本操作

    2023-07-08 10:07:07
  • C# String常用函数的使用详解

    2022-04-05 05:29:02
  • Android基础之startActivityForResult()的用法详解

    2022-05-28 03:37:29
  • java中日期格式化的大坑

    2021-07-02 09:42:59
  • 详解如何在SpringBoot中自定义参数解析器

    2023-07-24 16:06:51
  • 解决Weblogic部署war找不到spring配置文件的问题

    2022-12-29 07:03:08
  • Spring Boot2中如何优雅地个性化定制Jackson实现示例

    2021-09-27 12:21:15
  • springboot添加https服务器的方法

    2022-08-19 06:14:31
  • java开发MVC三层架构上再加一层Manager层原理详解

    2023-06-14 06:10:51
  • java读取其他服务接口返回的json数据示例代码

    2023-11-10 14:05:29
  • 用Android studio实现简易计算器功能

    2021-07-31 21:06:13
  • Android实现手机振动设置的方法

    2021-08-02 23:46:21
  • SpringBoot使用Thymeleaf自定义标签的实例代码

    2023-11-24 21:41:29
  • C#使用Matrix执行缩放的方法

    2022-05-03 15:46:58
  • asp之家 软件编程 m.aspxhome.com