我的Android进阶之旅------>Android之ListView实现下拉回弹刷新

简介: step1:新建项目 PullToRefresh step2:设计应用的UI界面 a./layout/pull_to_refresh.

step1:新建项目 PullToRefresh



step2:设计应用的UI界面

a./layout/pull_to_refresh.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<!-- The PullToRefreshListView replaces a standard ListView widget. -->
	<cn.roco.refresh.PullToRefreshListView
		android:id="@+id/android:list" android:layout_height="fill_parent"
		android:layout_width="fill_parent" />
</LinearLayout>

b./layout/pull_to_refresh_header.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent" android:layout_height="fill_parent"
	android:paddingTop="10dip" android:paddingBottom="15dip"
	android:gravity="center" android:id="@+id/pull_to_refresh_header">
	<ProgressBar android:id="@+id/pull_to_refresh_progress"
		android:indeterminate="true" android:layout_width="wrap_content"
		android:layout_height="wrap_content" android:layout_marginLeft="30dip"
		android:layout_marginRight="20dip" android:layout_marginTop="10dip"
		android:visibility="gone" android:layout_centerVertical="true"
		style="?android:attr/progressBarStyleSmall" />
	<ImageView android:id="@+id/pull_to_refresh_image"
		android:layout_width="wrap_content" android:layout_height="wrap_content"
		android:layout_marginLeft="30dip" android:layout_marginRight="20dip"
		android:visibility="gone" android:layout_gravity="center"
		android:gravity="center" android:src="@drawable/ic_pulltorefresh_arrow" />
	<TextView android:id="@+id/pull_to_refresh_text" android:text="@string/pull_to_refresh_tap_label"
		android:textAppearance="?android:attr/textAppearanceMedium"
		android:textStyle="bold" android:paddingTop="5dip"
		android:layout_width="fill_parent" android:layout_height="wrap_content"
		android:layout_gravity="center" android:gravity="center" />
	<TextView android:id="@+id/pull_to_refresh_updated_at"
		android:layout_below="@+id/pull_to_refresh_text" android:visibility="gone"
		android:textAppearance="?android:attr/textAppearanceSmall"
		android:layout_width="fill_parent" android:layout_height="wrap_content"
		android:layout_gravity="center" android:gravity="center" />
</RelativeLayout>

step3:cn.roco.refreshPullToRefreshListView.java  该类重写了ListView

package cn.roco.refresh;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;

public class PullToRefreshListView extends ListView implements OnScrollListener {

	private static final int TAP_TO_REFRESH = 1; // 初始状态
	private static final int PULL_TO_REFRESH = 2; // 拉动刷新
	private static final int RELEASE_TO_REFRESH = 3; // 释放刷新
	private static final int REFRESHING = 4; // 正在刷新

	private static final String TAG = "PullToRefreshListView";

	private OnRefreshListener mOnRefreshListener; // 刷新接口

	/**
	 * Listener that will receive notifications every time the list scrolls.
	 */
	private OnScrollListener mOnScrollListener; // listview 滚动监听器
	private LayoutInflater mInflater; // 视图索引器

	/**
	 * 头部视图 内容 -- start
	 */
	private RelativeLayout mRefreshView;
	private TextView mRefreshViewText;
	private ImageView mRefreshViewImage;
	private ProgressBar mRefreshViewProgress; // 刷新进度
	private TextView mRefreshViewLastUpdated;
	/**
	 * 头部视图 内容 -- end
	 */
	private int mCurrentScrollState; // 当前listivew 的滚动状态
	private int mRefreshState; // 当前listview 的刷新状态
	// 动画效果
	private RotateAnimation mFlipAnimation; // 变为向下的箭头
	private RotateAnimation mReverseFlipAnimation; // 变为逆向的箭头

	private int mRefreshViewHeight; // 头视图的高度
	private int mRefreshOriginalTopPadding; // 头视图 原始的top padding 属性值
	private int mLastMotionY; // 向下触屏事件时的手指起始y轴位置

	private boolean mBounceHack; // 是否反弹

	public PullToRefreshListView(Context context) {
		super(context);
		init(context);
	}

	public PullToRefreshListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	public PullToRefreshListView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}

	private void init(Context context) {
		// Load all of the animations we need in code rather than through XML
		// 初始化动画
		mFlipAnimation = new RotateAnimation(0, -180,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		mFlipAnimation.setInterpolator(new LinearInterpolator());
		mFlipAnimation.setDuration(250);
		mFlipAnimation.setFillAfter(true);
		mReverseFlipAnimation = new RotateAnimation(-180, 0,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
		mReverseFlipAnimation.setDuration(250);
		mReverseFlipAnimation.setFillAfter(true);

		mInflater = (LayoutInflater) context
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		mRefreshView = (RelativeLayout) mInflater.inflate(
				R.layout.pull_to_refresh_header, this, false);
		mRefreshViewText = (TextView) mRefreshView
				.findViewById(R.id.pull_to_refresh_text);
		mRefreshViewImage = (ImageView) mRefreshView
				.findViewById(R.id.pull_to_refresh_image);
		mRefreshViewProgress = (ProgressBar) mRefreshView
				.findViewById(R.id.pull_to_refresh_progress);
		mRefreshViewLastUpdated = (TextView) mRefreshView
				.findViewById(R.id.pull_to_refresh_updated_at);

		mRefreshViewImage.setMinimumHeight(50);
		mRefreshView.setOnClickListener(new OnClickRefreshListener());
		mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();

		mRefreshState = TAP_TO_REFRESH;

		addHeaderView(mRefreshView);

		super.setOnScrollListener(this);

		measureView(mRefreshView);
		mRefreshViewHeight = mRefreshView.getMeasuredHeight(); // 获取头文件的测量高度
	}

	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();
		setSelection(1);
	}

	@Override
	public void setAdapter(ListAdapter adapter) {
		super.setAdapter(adapter);

		setSelection(1);
	}

	/**
	 * Set the listener that will receive notifications every time the list
	 * scrolls.
	 * 
	 * @param l
	 *            The scroll listener.
	 */
	@Override
	public void setOnScrollListener(AbsListView.OnScrollListener l) {
		mOnScrollListener = l;
	}

	/**
	 * Register a callback to be invoked when this list should be refreshed.
	 * 
	 * @param onRefreshListener
	 *            The callback to run.
	 */
	public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
		mOnRefreshListener = onRefreshListener;
	}

	/**
	 * Set a text to represent when the list was last updated.
	 * 
	 * @param lastUpdated
	 *            Last updated at.
	 */
	public void setLastUpdated(CharSequence lastUpdated) {
		if (lastUpdated != null) {
			mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
			mRefreshViewLastUpdated.setText(lastUpdated);
		} else {
			mRefreshViewLastUpdated.setVisibility(View.GONE);
		}
	}

	/**
	 * 触摸事件
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		final int y = (int) event.getY(); // 当前手指的Y值
		mBounceHack = false; // 不反弹

		switch (event.getAction()) {
		case MotionEvent.ACTION_UP:
			// 将垂直滚动条设置为可用状态
			if (!isVerticalScrollBarEnabled()) {
				setVerticalScrollBarEnabled(true);
			}
			// 如果头部刷新条出现,并且不是正在刷新状态
			if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
				if ((mRefreshView.getBottom() >= mRefreshViewHeight || mRefreshView
						.getTop() >= 0) && mRefreshState == RELEASE_TO_REFRESH) { // 如果头部视图处于拉离顶部的情况
					// Initiate the refresh
					mRefreshState = REFRESHING; // 将标量设置为,正在刷新
					prepareForRefresh(); // 准备刷新
					onRefresh(); // 刷新
				} else if (mRefreshView.getBottom() < mRefreshViewHeight
						|| mRefreshView.getTop() <= 0) {
					// Abort refresh and scroll down below the refresh view
					// 停止刷新,并且滚动到头部刷新视图的下一个视图
					resetHeader();
					setSelection(1);// 定位在第二个列表项
				}
			}
			break;
		case MotionEvent.ACTION_DOWN:
			mLastMotionY = y; // 跟踪手指的Y值
			break;
		case MotionEvent.ACTION_MOVE:
			applyHeaderPadding(event); // 更行头视图的toppadding 属性
			break;
		}
		return super.onTouchEvent(event);
	}

	/****
	 * 不断的头部的top padding 属性
	 * 
	 * @param ev
	 */
	private void applyHeaderPadding(MotionEvent ev) {
		// getHistorySize has been available since API 1
		int pointerCount = ev.getHistorySize(); // 获取累积的动作数

		for (int p = 0; p < pointerCount; p++) {
			if (mRefreshState == RELEASE_TO_REFRESH) { // 如果是释放将要刷新状态
				if (isVerticalFadingEdgeEnabled()) {
					setVerticalScrollBarEnabled(false);
				}

				int historicalY = (int) ev.getHistoricalY(p); // 历史累积的高度

				// Calculate the padding to apply, we divide by 1.7 to
				// simulate a more resistant effect during pull.
				int topPadding = (int) (((historicalY - mLastMotionY) - mRefreshViewHeight) / 1.7);

				mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
						topPadding, mRefreshView.getPaddingRight(),
						mRefreshView.getPaddingBottom());
			}
		}
	}

	/**
	 * Sets the header padding back to original size. 使头部视图的toppadding 恢复到初始值
	 */
	private void resetHeaderPadding() {
		mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
				mRefreshOriginalTopPadding, mRefreshView.getPaddingRight(),
				mRefreshView.getPaddingBottom());
	}

	/**
	 * Resets the header to the original state. 初始化头部视图 状态
	 */
	private void resetHeader() {
		if (mRefreshState != TAP_TO_REFRESH) {
			mRefreshState = TAP_TO_REFRESH; // 初始刷新状态

			resetHeaderPadding(); // 使头部视图的toppadding 恢复到初始值

			// Set refresh view text to the pull label
			mRefreshViewText.setText(R.string.pull_to_refresh_tap_label); // 将文字初始化
			// Replace refresh drawable with arrow drawable
			mRefreshViewImage
					.setImageResource(R.drawable.ic_pulltorefresh_arrow); // 设置初始图片
			// Clear the full rotation animation
			mRefreshViewImage.clearAnimation(); // 清除动画
			// Hide progress bar and arrow.
			mRefreshViewImage.setVisibility(View.GONE); // 隐藏头视图
			mRefreshViewProgress.setVisibility(View.GONE); // 隐藏进度条
		}
	}

	// 测量视图的高度
	private void measureView(View child) {
		// 获取头部视图属性
		ViewGroup.LayoutParams p = child.getLayoutParams();
		if (p == null) {
			p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
					ViewGroup.LayoutParams.WRAP_CONTENT);
		}

		int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
		int lpHeight = p.height;
		int childHeightSpec;
		if (lpHeight > 0) {// 如果视图的高度大于0
			childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
					MeasureSpec.EXACTLY);
		} else {
			childHeightSpec = MeasureSpec.makeMeasureSpec(0,
					MeasureSpec.UNSPECIFIED);
		}
		child.measure(childWidthSpec, childHeightSpec);
	}

	/****
	 * 滑动事件 滑动时被调用
	 */
	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		// When the refresh view is completely visible, change the text to say
		// "Release to refresh..." and flip the arrow drawable.
		// 如果是接触滚动状态,并且不是正在刷新的状态
		if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
				&& mRefreshState != REFRESHING) {
			if (firstVisibleItem == 0) { // 如果显示出来了第一个列表项
				// 显示刷新图片
				mRefreshViewImage.setVisibility(View.VISIBLE);
				if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20 || mRefreshView
						.getTop() >= 0) && mRefreshState != RELEASE_TO_REFRESH) { // 如果下拉了listiview,则显示上拉刷新动画
					mRefreshViewText
							.setText(R.string.pull_to_refresh_release_label);
					mRefreshViewImage.clearAnimation();
					mRefreshViewImage.startAnimation(mFlipAnimation);
					mRefreshState = RELEASE_TO_REFRESH;
				} else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
						&& mRefreshState != PULL_TO_REFRESH) { // 如果没有到达,下拉刷新距离,则回归原来的状态
					mRefreshViewText
							.setText(R.string.pull_to_refresh_pull_label);
					if (mRefreshState != TAP_TO_REFRESH) {
						mRefreshViewImage.clearAnimation();
						mRefreshViewImage.startAnimation(mReverseFlipAnimation);
					}
					mRefreshState = PULL_TO_REFRESH;
				}
			} else {
				mRefreshViewImage.setVisibility(View.GONE); // 隐藏刷新图片
				resetHeader(); // 初始化,头部
			}
		}
		// 如果是自己滚动状态+ 第一个视图已经显示+ 不是刷新状态
		else if (mCurrentScrollState == SCROLL_STATE_FLING
				&& firstVisibleItem == 0 && mRefreshState != REFRESHING) {
			setSelection(1);
			mBounceHack = true; // 状态为回弹
		} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
			setSelection(1);
		}

		if (mOnScrollListener != null) {
			mOnScrollListener.onScroll(view, firstVisibleItem,
					visibleItemCount, totalItemCount);
		}
	}

	/**
	 * 滑动状态改变时被调用
	 */
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		mCurrentScrollState = scrollState;

		if (mCurrentScrollState == SCROLL_STATE_IDLE) { // 如果滚动停顿
			mBounceHack = false;
		}

		if (mOnScrollListener != null) {
			mOnScrollListener.onScrollStateChanged(view, scrollState);
		}
	}

	// 准备刷新
	public void prepareForRefresh() {
		resetHeaderPadding(); // 初始化,头部文件

		mRefreshViewImage.setVisibility(View.GONE);
		// We need this hack, otherwise it will keep the previous drawable.
		mRefreshViewImage.setImageDrawable(null);
		mRefreshViewProgress.setVisibility(View.VISIBLE);

		// Set refresh view text to the refreshing label
		mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);

		mRefreshState = REFRESHING;
	}

	// 刷新
	public void onRefresh() {
		Log.d(TAG, "onRefresh");

		if (mOnRefreshListener != null) {
			mOnRefreshListener.onRefresh();
		}
	}

	/**
	 * 刷新完成 的回调函数 Resets the list to a normal state after a refresh.
	 * 
	 * @param lastUpdated
	 *            Last updated at.
	 */
	public void onRefreshComplete(CharSequence lastUpdated) {
		setLastUpdated(lastUpdated);
		onRefreshComplete();
	}

	/**
	 * 刷新完成回调函数 Resets the list to a normal state after a refresh.
	 */
	public void onRefreshComplete() {
		Log.d(TAG, "onRefreshComplete");

		resetHeader();

		// If refresh view is visible when loading completes, scroll down to
		// the next item.
		if (mRefreshView.getBottom() > 0) {
			invalidateViews();
			setSelection(1);
		}
	}

	/**
	 * Invoked when the refresh view is clicked on. This is mainly used when
	 * there's only a few items in the list and it's not possible to drag the
	 * list.
	 */
	private class OnClickRefreshListener implements OnClickListener {

		@Override
		public void onClick(View v) {
			if (mRefreshState != REFRESHING) {
				prepareForRefresh();
				onRefresh();
			}
		}

	}

	/**
	 * 刷新方法接口 Interface definition for a callback to be invoked when list should
	 * be refreshed.
	 */
	public interface OnRefreshListener {
		/**
		 * Called when the list should be refreshed.
		 * <p>
		 * A call to {@link PullToRefreshListView #onRefreshComplete()} is
		 * expected to indicate that the refresh has completed.
		 */
		public void onRefresh();
	}
}

step4:PullToRefreshActivity.java

package cn.roco.refresh;

import java.util.Arrays;
import java.util.LinkedList;

import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import cn.roco.refresh.PullToRefreshListView.OnRefreshListener;

public class PullToRefreshActivity extends ListActivity {
	private LinkedList<String> mListItems;
	private String[] mStrings;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.pull_to_refresh);
		mStrings = initString();
		// Set a listener to be invoked when the list should be refreshed.
		((PullToRefreshListView) getListView())
				.setOnRefreshListener(new OnRefreshListener() {
					@Override
					public void onRefresh() {
						// Do work to refresh the list here.
						new GetDataTask().execute();
					}
				});

		mListItems = new LinkedList<String>();
		mListItems.addAll(Arrays.asList(mStrings));

		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
				android.R.layout.simple_list_item_1, mListItems);
		setListAdapter(adapter);
	}

	private class GetDataTask extends AsyncTask<Void, Void, String[]> {
		@Override
		protected String[] doInBackground(Void... params) {
			// Simulates a background job.
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				;
			}
			return mStrings;
		}

		@Override
		protected void onPostExecute(String[] result) {
			mListItems.addFirst("Added after refresh...");
			// Call onRefreshComplete when the list has been refreshed.
			((PullToRefreshListView) getListView()).onRefreshComplete();
			super.onPostExecute(result);
		}
	}

	private String[] initString() {
		String[] initStrings = new String[20];
		for (int i = 0; i < initStrings.length; i++) {
			initStrings[i] = "Roco_" + i;
		}
		return initStrings;
	}
}

step5:部署应用,查看运行效果

        

        


==================================下面看一个gif动画===========================================

                        


注:(原作者:johannilsson 选自:https://github.com/johannilsson/android-pulltorefresh)



==================================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址http://blog.csdn.net/ouyang_peng

==================================================================================================


相关文章
|
7月前
|
API Android开发 开发者
Android UI设计: 什么是RecyclerView?为什么它比ListView更好?
Android UI设计: 什么是RecyclerView?为什么它比ListView更好?
90 2
|
6月前
|
前端开发 Android开发 Windows
27. 【Android教程】下拉选择框 Spinner
27. 【Android教程】下拉选择框 Spinner
227 2
|
7月前
|
API Android开发
Android高手进阶教程(十五)之---通过Location获取Address的使用!
Android高手进阶教程(十五)之---通过Location获取Address的使用!
67 1
|
3月前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
205 9
|
6月前
|
API Android开发 开发者
`RecyclerView`是Android API 21引入的UI组件,用于替代ListView和GridView
【6月更文挑战第26天】`RecyclerView`是Android API 21引入的UI组件,用于替代ListView和GridView。它提供高效的数据视图复用,优化的布局管理,支持多种布局(如线性、网格),并解耦数据、适配器和视图。RecyclerView的灵活性、性能(如局部刷新和动画支持)和扩展性使其成为现代Android开发的首选,特别是在处理大规模数据集时。
66 2
|
7月前
|
XML Java Android开发
Android Studio App入门之列表视图ListView的讲解及实战(附源码 超详细必看)
Android Studio App入门之列表视图ListView的讲解及实战(附源码 超详细必看)
701 1
|
6月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
6月前
|
前端开发 API Android开发
25. 【Android教程】列表控件 ListView
25. 【Android教程】列表控件 ListView
181 2
|
6月前
|
存储 算法 Java
Android 进阶——代码插桩必知必会&ASM7字节码操作
Android 进阶——代码插桩必知必会&ASM7字节码操作
252 0
|
7月前
|
缓存 网络协议 Java
挑战全网,史上最全Android开发进阶,跳槽复习指南(1),掌握这6大技能体系
挑战全网,史上最全Android开发进阶,跳槽复习指南(1),掌握这6大技能体系