自定义listview实现上拉加载下拉刷新

原创 2016年06月01日 14:38:55

Android内自带的listview没有上拉加载下拉刷新的功能,可是在正常的开发过程中这一点是必不可少的。

首先是自定义listview,就是定义一个继承listview的类

package com.example.handerview;

import java.text.SimpleDateFormat;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;


public class RefreshListView extends ListView implements OnScrollListener{

	
	  private static final String TAG = "RefreshListView";
	  private int firstVisibleItemPosition; // 屏幕显示在第一个的item的索引
	  private int downY; // 按下时y轴的偏移量
	  private int headerViewHeight; // 头布局的高度
	  private View headerView; // 头布局的对象

	  private final int DOWN_PULL_REFRESH = 0; // 下拉刷新状态
	  private final int RELEASE_REFRESH = 1; // 松开刷新
	  private final int REFRESHING = 2; // 正在刷新中
	  private int currentState = DOWN_PULL_REFRESH; // 头布局的状态: 默认为下拉刷新状态

	  private Animation upAnimation; // 向上旋转的动画
	  private Animation downAnimation; // 向下旋转的动画

	  private ImageView ivArrow; // 头布局的箭头
	  private ProgressBar mProgressBar; // 头布局的进度条
	  private TextView tvState; // 头布局的状态
	  private TextView tvLastUpdateTime; // 头布局的最后更新时间

	  private OnRefreshListener mOnRefershListener;
	  private boolean isScrollToBottom; // 是否滑动到底部
	  private View footerView; // 脚布局的对象
	  private int footerViewHeight; // 脚布局的高度
	  private boolean isLoadingMore = false; // 是否正在加载更多中

	  public RefreshListView(Context context, AttributeSet attrs) {
	    super(context, attrs);
	    //初始化头部
	    initHeaderView();
	    
	    //初始化底部
	    initFooterView();
	    this.setOnScrollListener(this);
	  }

	  /**
	   * 初始化脚布局
	   */
	  private void initFooterView() {
		//获取布局文件
	    footerView = View.inflate(getContext(), R.layout.listview_footer, null);
	    
	 // 系统会帮我们测量出headerView的高度
	    footerView.measure(0, 0);
	    footerViewHeight = footerView.getMeasuredHeight();
	    footerView.setPadding(0, -footerViewHeight, 0, 0);
	    this.addFooterView(footerView);
	  }

	  /**
	   * 初始化头布局
	   */
	  private void initHeaderView() {
		//获取布局文件
	    headerView = View.inflate(getContext(), R.layout.listview_header, null);
	    
	    //获取布局文件内的箭头
	    ivArrow = (ImageView) headerView.findViewById(R.id.iv_listview_header_arrow);
	    
	    //获取布局文件内的进度条
	    mProgressBar = (ProgressBar) headerView.findViewById(R.id.pb_listview_header);
	    tvState = (TextView) headerView.findViewById(R.id.tv_listview_header_state);
	    tvLastUpdateTime = (TextView) headerView.findViewById(R.id.tv_listview_header_last_update_time);

	    // 设置最后刷新时间
	    tvLastUpdateTime.setText("最后刷新时间: " + getLastUpdateTime());

	    headerView.measure(0, 0); // 系统会帮我们测量出headerView的高度
	    headerViewHeight = headerView.getMeasuredHeight();
	    headerView.setPadding(0, -headerViewHeight, 0, 0);
	    this.addHeaderView(headerView); // 向ListView的顶部添加一个view对象
	    initAnimation();
	  }

	  /**
	   * 获得系统的最新时间
	   * 
	   * @return
	   */
	  private String getLastUpdateTime() {
	    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	    return sdf.format(System.currentTimeMillis());
	  }

	  /**
	   * 初始化动画
	   */
	  private void initAnimation() {
	    upAnimation = new RotateAnimation(0f, -180f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f);
	    upAnimation.setDuration(500);
	    upAnimation.setFillAfter(true); // 动画结束后, 停留在结束的位置上

	    downAnimation = new RotateAnimation(-180f, -360f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f);
	    downAnimation.setDuration(500);
	    downAnimation.setFillAfter(true); // 动画结束后, 停留在结束的位置上
	  }

	  
	  /**
	   * listview的触摸事件
	   */
	  @Override
	  public boolean onTouchEvent(MotionEvent ev) {
	    switch (ev.getAction()) {
	      case MotionEvent.ACTION_DOWN :
	        downY = (int) ev.getY();
	        break;
	      case MotionEvent.ACTION_MOVE :
	    	  //移动中的Y
	        int moveY = (int) ev.getY();
	        
	        // 移动中的y - 按下的y = 间距.
	        int diff = (moveY - downY) / 2;
	        
	        // -头布局的高度 + 间距 = paddingTop
	        int paddingTop = -headerViewHeight + diff;
	        
	        // 如果: -头布局的高度 > paddingTop的值 执行super.onTouchEvent(ev);
	        if (firstVisibleItemPosition == 0&& -headerViewHeight < paddingTop) {
	        	
	          if (paddingTop > 0 && currentState == DOWN_PULL_REFRESH) {
	        	  // 完全显示了.
	            Log.i(TAG, "松开刷新");
	            currentState = RELEASE_REFRESH;
	            refreshHeaderView();
	          } else if (paddingTop < 0
	              && currentState == RELEASE_REFRESH) { // 没有显示完全
	            Log.i(TAG, "下拉刷新");
	            currentState = DOWN_PULL_REFRESH;
	            refreshHeaderView();
	          }
	          // 下拉头布局
	          headerView.setPadding(0, paddingTop, 0, 0);
	          return true;
	        }
	        break;
	      case MotionEvent.ACTION_UP :
	        // 判断当前的状态是松开刷新还是下拉刷新
	        if (currentState == RELEASE_REFRESH) {
	          Log.i(TAG, "刷新数据.");
	          // 把头布局设置为完全显示状态
	          headerView.setPadding(0, 0, 0, 0);
	          // 进入到正在刷新中状态
	          currentState = REFRESHING;
	          refreshHeaderView();

	          if (mOnRefershListener != null) {
	            mOnRefershListener.onDownPullRefresh(); // 调用使用者的监听方法
	          }
	        } else if (currentState == DOWN_PULL_REFRESH) {
	          // 隐藏头布局
	          headerView.setPadding(0, -headerViewHeight, 0, 0);
	        }
	        break;
	      default :
	        break;
	    }
	    return super.onTouchEvent(ev);
	  }

	  /**
	   * 根据currentState刷新头布局的状态
	   */
	  private void refreshHeaderView() {
	    switch (currentState) {
	      case DOWN_PULL_REFRESH : // 下拉刷新状态
	        tvState.setText("下拉刷新");
	        ivArrow.startAnimation(downAnimation); // 执行向下旋转
	        break;
	      case RELEASE_REFRESH : // 松开刷新状态
	        tvState.setText("松开刷新");
	        ivArrow.startAnimation(upAnimation); // 执行向上旋转
	        break;
	      case REFRESHING : // 正在刷新中状态
	        ivArrow.clearAnimation();
	        ivArrow.setVisibility(View.GONE);
	        mProgressBar.setVisibility(View.VISIBLE);
	        tvState.setText("正在刷新中...");
	        break;
	      default :
	        break;
	    }
	  }

	  /**
	   * 当滚动状态改变时回调
	   */
	  @Override
	  public void onScrollStateChanged(AbsListView view, int scrollState) {

	    if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_FLING) {
	      // 判断当前是否已经到了底部
	      if (isScrollToBottom && !isLoadingMore) {
	        isLoadingMore = true;
	        // 当前到底部
	        Log.i(TAG, "加载更多数据");
	        footerView.setPadding(0, 0, 0, 0);
	        this.setSelection(this.getCount());

	        if (mOnRefershListener != null) {
	          mOnRefershListener.onLoadingMore();
	        }
	      }
	    }
	  }


	  /**
	   * 设置刷新监听事件
	   * 
	   * @param listener
	   */
	  public void setOnRefreshListener(OnRefreshListener listener) {
	    mOnRefershListener = listener;
	  }

	  /**
	   * 隐藏头布局
	   */
	  public void hideHeaderView() {
	    headerView.setPadding(0, -headerViewHeight, 0, 0);
	    ivArrow.setVisibility(View.VISIBLE);
	    mProgressBar.setVisibility(View.GONE);
	    tvState.setText("下拉刷新");
	    tvLastUpdateTime.setText("最后刷新时间: " + getLastUpdateTime());
	    currentState = DOWN_PULL_REFRESH;
	  }

	  /**
	   * 隐藏脚布局
	   */
	  public void hideFooterView() {
	    footerView.setPadding(0, -footerViewHeight, 0, 0);
	    isLoadingMore = false;
	  }
	  public interface OnRefreshListener {

		  /**
		   * 下拉刷新
		   */
		  void onDownPullRefresh();

		  /**
		   * 上拉加载更多
		   */
		  void onLoadingMore();

		void onRefresh();
		}
	/**
	 * 当滚动时调用
	 * 
	 * @param firstVisibleItem
	 *            当前屏幕显示在顶部的item的position
	 * @param visibleItemCount
	 *            当前屏幕显示了多少个条目的总数
	 * @param totalItemCount
	 *            ListView的总条目的总数
	 */
	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
			firstVisibleItemPosition = firstVisibleItem;
		    if (getLastVisiblePosition() == (totalItemCount - 1)) {
		      isScrollToBottom = true;
		    } else {
		      isScrollToBottom = false;
		    }
		
	}
	  
	  
}
这就是上边代码中有用到的布局

<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">

    <include 
        layout="@layout/listview_header"
        android:visibility="gone"/>
    
    <com.example.handerview.RefreshListView
        android:id="@+id/refreshlistview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
    </com.example.handerview.RefreshListView>
    
    <include 
        layout="@layout/listview_footer"
        android:visibility="gone"/>
    
    
</LinearLayout>


上拉加载和下拉刷新都需要有一个布局来占位和显示内容,包括刷新或加载过程中的动态图片

导入的头布局

<span style="font-size:18px;"><?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:orientation="horizontal" >
    
    <FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="10dip" >

    <ImageView
      android:id="@+id/iv_listview_header_arrow"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:minWidth="30dip"
      android:src="@drawable/ic_launcher" />

    <ProgressBar
      android:id="@+id/pb_listview_header"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:indeterminateDrawable="@anim/common_progressbar"
      android:visibility="gone" />
  </FrameLayout>

  <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:gravity="center_horizontal"
    android:orientation="vertical" >

    <TextView
      android:id="@+id/tv_listview_header_state"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="下拉刷新"
      android:textColor="#FF0000"
      android:textSize="18sp" />

    <TextView
      android:id="@+id/tv_listview_header_last_update_time"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginTop="5dip"
      android:text="最后刷新时间: 2014-10-10 12:56:12"
      android:textColor="@android:color/white"
      android:textSize="14sp" />
  </LinearLayout>

</LinearLayout>
</span>
导入的脚布局

<span style="font-size:18px;"><?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:orientation="vertical" >
    
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:layout_margin="10dip"
    android:gravity="center_vertical"
    android:orientation="horizontal" >

    <ProgressBar
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:indeterminateDrawable="@anim/common_progressbar"
       />

    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginLeft="10dip"
      android:text="加载更多..."
      android:textColor="#FF0000"
      android:textSize="18sp" />

</LinearLayout>
</LinearLayout>
</span>


Android自定义控件——ListView的下拉刷新与上拉加载

无疑,在Android开发中,ListView是使用非常频繁的控件之一,ListView提供一个列表的容易,允许我们以列表的形式将数据展示到界面上,但是Google给我们提供的原生ListView的控...
  • lee_tianya
  • lee_tianya
  • 2014年10月10日 20:14
  • 17202

下拉刷新、上拉加载实战:带你理解自定义View整个过程

下拉刷新、上拉加载实战:带你理解自定义View整个过程@(Android)下拉刷新上拉加载实战带你理解自定义View整个过程 参考文章 写在前面的话 效果图 Github地址 正式开始 第一步添加He...
  • u013647382
  • u013647382
  • 2017年02月27日 15:18
  • 2399

ListView上拉加载和下拉刷新多种实现方式

ListView上拉加载和下拉刷新多种实现方式该篇为ListView下拉刷新和上拉加载实现的各种方法大合集。可能在具体的细节逻辑上处理不太到位,但基本上完成逻辑的实现。细节方面,个人可以根据自己的需求...
  • lisdye2
  • lisdye2
  • 2016年05月19日 01:05
  • 20896

ListView下拉刷新,上拉自动加载更多

下拉刷新,Android中非常普遍的功能。为了方便便重写的ListView来实现下拉刷新,同时添加了上拉自动加载更多的功能。设计最初是参考开源中国的Android客户端源码。先看示例图。 重写后的...
  • limb99
  • limb99
  • 2014年02月03日 23:16
  • 62882

Android ListView下拉/上拉刷新:设计原理与实现

 《Android ListView下拉/上拉刷新:设计原理与实现》 Android上ListView的第三方开源的下拉刷新框架很多,应用场景很多很普遍,几乎成为现在APP的通用设计典范,甚至...
  • zhangphil
  • zhangphil
  • 2015年07月24日 09:45
  • 5334

Android之ListView下拉刷新和上拉加载实现详解(含源码Demo)

之前有一段时间没写博客了,忙课程设计用了一周,搞其他有的没的也浪费了很多时间.. 惭愧惭愧..这两天整理了一下listview的下拉刷新和上拉加载的使用方法,自己做了一遍以后感觉受益良多,也慢慢...
  • qq_22770457
  • qq_22770457
  • 2016年03月02日 11:50
  • 1263

上拉加载更多,下拉刷新的弹性ListView的实现

本文主要的是介绍如何实现弹性的listview,以及上拉和下拉功能的实现,其实对一般的View也是适用的,稍微修改一下就可以啦。里面涉及一些对事件分发的处理,有兴趣的可以看一下这个链接,http://...
  • newhope1106
  • newhope1106
  • 2016年12月06日 20:44
  • 861

SwipeRefreshLayout完美实现ListView网络数据上拉加载以及下拉刷新

最近才有时间将上拉加载与下拉刷新进行总结,SwipeRefreshLayout是Android -V4包下自带的一个刷新控件,但是不足的是它只有下拉刷新的功能,没有需要的上拉加载功能,所以我自己添加了...
  • ruirui_xiaomin
  • ruirui_xiaomin
  • 2017年01月18日 15:00
  • 1139

Android MVP设计框架模板 之 漂亮ListView上拉刷新下拉加载更多

Android MVP设计框架模板 之 漂亮ListView上拉刷新下拉加载更多 (本人自行攒写,难免有错误,请谅解)mvp的全称为Model-View-Presenter,Mo...
  • u010507199
  • u010507199
  • 2015年05月14日 09:56
  • 1124

实现下拉刷新,上拉加载可自定义各种动画

优雅的实现RecyclerView的各种动画的下拉刷新,上拉加载
  • q924284946
  • q924284946
  • 2017年07月07日 17:04
  • 275
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:自定义listview实现上拉加载下拉刷新
举报原因:
原因补充:

(最多只允许输入30个字)