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 逐渐显示,文字显示为下拉可以加载,箭头向上,进度条隐藏。

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

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 源码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • react-native ListView下拉刷新上拉加载实现代码

    本文介绍了react-native ListView下拉刷新上拉加载实现.分享给大家,具体如下: 先看效果图 下拉刷新 React Native提供了一个组件可以实现下拉刷新方法RefreshControl 使用方法 <ListView refreshControl={ <RefreshControl refreshing={this.state.refreshing} onRefresh={this._onRefresh.bind(this)} /> } //... </List

  • Android ListView实现上拉加载下拉刷新和滑动删除功能

    最近项目需要用到可以滑动删除并且带有上拉加载下拉刷新的Listview,查阅了一些资料,大多都是在SwipeMenuListView的基础上去添加头部和底部View,来扩展上拉加载和下拉刷新的功能,不过需要手动的去绘制UI及处理一些动画效果.用起来也不是特别方便.刚好项目中用到PulltorefreshLibrary库,就尝试着扩展了一个PullToRefreshSwipeMenuListView类来实现需求.先看一下效果: 实现步骤 一.组合Pulltorefresh与SwipeMenuLis

  • android使用SwipeRefreshLayout实现ListView下拉刷新上拉加载

    本文实例为大家分享了android实现ListView下拉刷新上拉加载的具体代码,供大家参考,具体内容如下 这次使用的是系统的SwipeRefreshLayout实现下拉刷新,和设置ListView的滑动监听判断是否滑动到最底部然后加载更多: 这个要比PullToRefreshListView简单很多,想PullToRefreshListView实现下拉刷新上拉加载的可以看这篇博客: android使用PullToRefresh框架实现ListView下拉刷新上拉加载更多 至于使用哪一种大家可以

  • Android实现上拉加载更多ListView(PulmListView)

    思路 今天带大家实现一个上拉加载更多的ListView.GitHub传送门:PulmListView, 欢迎大家fork&&star. 先带大家理一下思路, 如果我们要实现一个上拉加载更多的ListView, 我们需要实现的功能包括: 1.一个自定义的ListView, 并且该ListView能够判断当前是否已经处于最底部.  2.一个自定义的FooterView, 用于在ListView加载更多的过程中进行UI展示.  3.关联FooterView和ListView, 包括加载时机判断.

  • Android自定义listview布局实现上拉加载下拉刷新功能

    listview实现上拉加载以及下拉刷新的方式有很多.下面是我写的一种自定义的布局,复用性也比较的强.首先就是继承的listview的自定义view.   AutoListView.Java: package com.example.mic.testdemo.view; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.os.Bu

  • Android XListView下拉刷新和上拉加载更多

    市面上有好多的类比ListView刷新数据的开源框架,如:v4包自带的SwipeRefreshLayout ,以及集ListView.GridView甚至WebView于一身的Pulltorefresh等等.前述的两个开源框架目前使用也算频繁.有兴趣的读者可以自行搜索,当然有时间一定回来对所有的使用方式做一个汇总和比较.今天介绍的这款框架,专门针对ListView做下拉刷新与上拉加载的,如果单单是ListView就显得更加简单方便易于理解. 1.首先引入xListView_lib库到自己的Dem

  • Android通过XListView实现上拉加载下拉刷新功能

    本文实例为大家分享了XListView实现上拉加载下拉刷新的具体代码,供大家参考,具体内容如下 ## 导入XListVIew第三方库文件.通过LinkedList将刷新数据插入到集合头部,将加载的数据放入集合尾部 ## private Context context; private View view; private String path; private XListView xlv; private LinkedList<Data> listData; private Handler

  • Android中Listview下拉刷新和上拉加载更多的多种实现方案

    listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明. 方案一:添加头布局和脚布局 android系统为listview提供了addfootview和addheadview两个API.这样可以直接自定义一个View,以添加视图的形式实现下来刷新和上拉加载. 实现步骤    1.创建一个类继承ListView:class PullToRefreshListView extends ListView: 2.在构造方法中添加HeadView:addHeaderVi

  • android使用PullToRefresh框架实现ListView下拉刷新上拉加载更多

    本文实例为大家分享了Android实现ListView下拉刷新上拉加载更多的具体代码,供大家参考,具体内容如下 其实谷歌官方目前已经推出ListView下拉刷新框架SwipeRefreshLayout,想了解的朋友可以点击 android使用SwipeRefreshLayout实现ListView下拉刷新上拉加载了解一下: 大家不难发现当你使用SwipeRefreshLayout下拉的时候布局文件不会跟着手势往下滑,而且想要更改这个缺陷好像非常不容易. 虽然SwipeRefreshLayout非

  • XListView实现下拉刷新和上拉加载原理解析

    XListview是一个非常受欢迎的下拉刷新控件,但是已经停止维护了.之前写过一篇XListview的使用介绍,用起来非常简单,这两天放假无聊,研究了下XListview的实现原理,学到了很多,今天分享给大家. 提前声明,为了让代码更好的理解,我对代码进行了部分删减和重构,如果大家想看原版代码,请去github自行下载. Xlistview项目主要是三部分:XlistView,XListViewHeader,XListViewFooter,分别是XListView主体.header.footer

随机推荐