ListView下拉刷新

本文介绍了一种自定义的下拉刷新ListView组件,该组件不是复合控件,而是通过继承ListView并重写实现。文章提供了详细的代码示例,包括如何隔离下拉刷新与列表滚动的操作,以及如何设置自定义属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是一个下拉刷新的ListView,它不是一个复合控件,而是继承了ListView然后进行了改写.

这里要说明一点,其实用复合控件去实现下拉刷新是最简单的方式,但由于复合控件继承的通常是layout,所以并不是真正意义上的ListView.而且多个相同的复合控件在同一个fragment页面里是会出问题的.

所以说,直接继承ListView去制作一个下拉控件,是相对比较复杂,但比较安全的方式.它的重用性更强.


现在大部分下拉刷新控件是不隔离下拉操作和列表滚动操作的,也就是说当列表拉到顶部之后,如果继续拉就直接刷新了.

我个人不是很喜欢这样的操作,我希望滚到顶的时候能停住,就像一个普通的ListView一样.只有松开手指再次下拉,才能刷新.这也是我写这个控件的初衷.


源码及Demo在这里:

http://download.youkuaiyun.com/detail/qyc898/7556559

源码里"加载更多"功能没有更新进去,但下面的代码是更新了的.可以直接复制下面ListPullDown类的代码,然后替换掉Demo里的代码.


 

头部ITEM的XML布局,注意,这个布局一定要LinearLayout,用相对布局在拖动时会被自动排版,失去效果

pulldownlist_top.xml

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

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dp"
        android:layout_marginTop="5dp" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="下拉刷新"
            android:textColor="#000000"
            android:textSize="14sp" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/textView1"
            android:layout_centerHorizontal="true"
            android:text="上一次更新时间:未更新"
            android:textSize="12sp" />

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="70dp"
            android:src="@drawable/refresh_down" />

        <ProgressBar
            android:id="@+id/progressBar1"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="70dp" />
    </RelativeLayout>

</LinearLayout>


底部ITEM布局

pulldownlist_bottom.xml

<?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="wrap_content"
    android:background="@drawable/st_pulldownlist_click"
    android:paddingBottom="6dp"
    android:paddingTop="6dp" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="#FF000000"
        android:textSize="16sp" />

    <ProgressBar
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="70dp" />

</RelativeLayout>


 

自定义属性attrs.xml

    <declare-styleable name="PullDownList">
        <attr name="loadingMoreTextColor" format="color" />
        <attr name="loadingMoreTextSize" format="dimension" />
        <attr name="loadingMoreBKColor" format="reference|color" />
        <attr name="refreshTextColor" format="color" />
        <attr name="refreshTextSize" format="dimension" />
        <attr name="refreshTimeColor" format="color" />
        <attr name="refreshTimeSize" format="dimension" />
        <attr name="refreshArrowUp" format="reference" />
        <attr name="refreshArrowDown" format="reference" />
    </declare-styleable>


代码:

package qyc.library.control.list_pulldown;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import qyc.library.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
 * <p>
 * 作者:QYC
 * <p>
 * 邮件:68452263@qq.com
 * <p>
 * */
public class ListPullDown extends ListView {

	public ListPullDown(Context context) {
		super(context);
		if (isInEditMode()) {
			inEditMode();
		} else {
			init(null);
		}
	}

	public ListPullDown(Context context, AttributeSet attrs) {
		super(context, attrs);
		if (isInEditMode()) {
			inEditMode();
		} else {
			init(attrs);
		}
	}

	public ListPullDown(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		if (isInEditMode()) {
			inEditMode();
		} else {
			init(attrs);
		}
	}

	// 在可视化编辑器里面显示的内容
	private void inEditMode() {
		if (isInEditMode()) {
			setBackgroundColor(Color.rgb(134, 154, 190));
			ArrayAdapter<String> adapter = new ArrayAdapter<String>(
					getContext(), android.R.layout.simple_list_item_1);
			adapter.add("PullDownList");
			adapter.add("作者:QYC");
			adapter.add("邮箱:68452263@qq.com");
			setAdapter(adapter);
			return;
		}
	}

	/*
	 * ==================列表顶部的控件集合==================
	 */
	private ProgressBar progressBar_top = null;
	private ImageView imageView_top = null;
	private TextView txtTime_top, txtRefresh_top;
	/** 列表顶部的视图 */
	private View topViewItem = null;
	/** 列表顶部视图的高 */
	private int topViewItemHeight = 0;
	/** 刷新向下箭头 */
	private int imgRefreshDown = R.drawable.png_listpulldown_refreshdown;
	/** 刷新向上箭头 */
	private int imgRefreshUp = R.drawable.png_listpulldown_refreshup;
	/*
	 * =====================================================
	 */

	/*
	 * ==================列表底部的控件集合==================
	 */
	private View bottomViewItem = null;
	private ProgressBar progressBar_bottom = null;
	private TextView txtBottom = null;
	/*
	 * =====================================================
	 */

	/*
	 * ==================用于逻辑判断的4个变量==================
	 */
	/** 第一行是否在屏幕上 */
	private boolean isFirstItemShow = false;// 第一行即加载行,只要把列表滚动到顶,不管加载行被压缩了还是被拉开了,都会认为显示在了屏幕上
	/** 是否有加载意图,即加载第一行的意图 */
	private boolean isWantToExtrude = true;
	/** 是否已经拉开 */
	private boolean isExtruded = false;
	/** 是否已经在加载 */
	private boolean isLoading = false;
	/*
	 * =====================================================
	 */

	/** 时间格式化 */
	private static final SimpleDateFormat sdf = new SimpleDateFormat(
			"HH:mm:ss", Locale.CHINA);

	/*
	 * =====================================================
	 */

	/** 初始化 */
	private void init(AttributeSet attrs) {
		setOnScrollListener(onScrollListener);
		LayoutInflater layoutInflater = (LayoutInflater) getContext()
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		// 初始化列表底部工具条
		bottomViewItem = layoutInflater.inflate(R.layout.listpulldown_bottom,
				this, false);
		progressBar_bottom = (ProgressBar) bottomViewItem
				.findViewById(R.id.progressBar1);
		txtBottom = (TextView) bottomViewItem.findViewById(R.id.textView1);
		bottomViewItem.setOnClickListener(onClickListener);// 设置点击监听
		bottomViewItem.setVisibility(View.GONE);// 默认状态下不显示加载更多的动作条
		// 加入底部工具条(加载更多)
		addFooterView(bottomViewItem);

		// 初始化列表顶部工具条
		topViewItem = layoutInflater.inflate(R.layout.listpulldown_top, this,
				false);
		topViewItem.setOnClickListener(null);// 使它不能作为list的一个item来点击
		progressBar_top = (ProgressBar) topViewItem
				.findViewById(R.id.progressBar1);
		progressBar_top.setVisibility(View.INVISIBLE);
		imageView_top = (ImageView) topViewItem.findViewById(R.id.imageView1);
		txtTime_top = (TextView) topViewItem.findViewById(R.id.textView2);
		txtTime_top.setText("上一次更新时间:无");
		txtRefresh_top = (TextView) topViewItem.findViewById(R.id.textView1);

		// 根据兹定于属性,对控件样式进行调整
		setResValue(attrs);

		/*
		 * 3步隐藏该topViewItem: 1.测出其加入屏幕后的长和宽,因为现在它没加入屏幕,直接获取其长宽是无效的 2.得到测出的高度
		 * 3.将上边距设置为负高度,相当于把高度变成了0,达到隐藏的效果(顺带一提:topViewItem即使通过padding设置其高度为0
		 * ,在列表中,它仍然会占据一行的像素,列表仍然认为它是第0行)
		 */
		// 第一步
		measureView(topViewItem);
		// 第二步
		topViewItemHeight = topViewItem.getMeasuredHeight();
		// 第三步
		topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);
		// 加入到列表中(点击刷新)
		addHeaderView(topViewItem);
	}

	/** 根据自定义属性,对控件样式进行调整 */
	private void setResValue(AttributeSet attrs) {
		// 获取控件自定义属性值
		TypedArray a = getContext().obtainStyledAttributes(attrs,
				R.styleable.ListPullDown);
		int N = a.getIndexCount();// 自定义属性被使用的数量
		for (int i = 0; i < N; i++) {
			int attr = a.getIndex(i);
			switch (attr) {
			case R.styleable.ListPullDown_loadingMoreBKColor:
				// 底部工具条背景色
				int loadingMoreBKColorID = a.getResourceId(attr, 0);
				if (loadingMoreBKColorID != 0) {
					bottomViewItem.setBackgroundResource(loadingMoreBKColorID);
				} else {
					int loadingMoreBKColor = a.getColor(attr, 0);
					if (loadingMoreBKColor != 0) {
						bottomViewItem.setBackgroundColor(loadingMoreBKColor);
					}
				}
				break;
			case R.styleable.ListPullDown_loadingMoreTextColor:
				// 底部工具条文字颜色
				int loadingMoreTextColor = a.getColor(attr, 0);
				if (loadingMoreTextColor != 0) {
					txtBottom.setTextColor(loadingMoreTextColor);
				}
				break;
			case R.styleable.ListPullDown_loadingMoreTextSize:
				// 底部工具条文字大小
				float loadingMoreTextSize = a.getDimension(attr, 0);
				if (loadingMoreTextSize != 0) {
					txtBottom.setTextSize(TypedValue.COMPLEX_UNIT_PX,
							loadingMoreTextSize);
				}
				break;
			case R.styleable.ListPullDown_refreshTextColor:
				// 顶部工具条提示文字颜色
				int refreshTextColor = a.getColor(attr, 0);
				if (refreshTextColor != 0) {
					txtRefresh_top.setTextColor(refreshTextColor);
				}
				break;
			case R.styleable.ListPullDown_refreshTextSize:
				// 顶部工具条提示文字大小
				float refreshTextSize = a.getDimension(attr, 0);
				if (refreshTextSize != 0) {
					txtRefresh_top.setTextSize(TypedValue.COMPLEX_UNIT_PX,
							refreshTextSize);
				}
				break;
			case R.styleable.ListPullDown_refreshTimeColor:
				// 顶部工具条时间文字颜色
				int refreshTimeColor = a.getColor(attr, 0);
				if (refreshTimeColor != 0) {
					txtTime_top.setTextColor(refreshTimeColor);
				}
				break;
			case R.styleable.ListPullDown_refreshTimeSize:
				// 顶部工具条时间文字大小
				float refreshTimeSize = a.getDimension(attr, 0);
				if (refreshTimeSize != 0) {
					txtTime_top.setTextSize(TypedValue.COMPLEX_UNIT_PX,
							refreshTimeSize);
				}
				break;
			case R.styleable.ListPullDown_refreshArrowUp:
				// 顶部工具条向上箭头
				int refreshArrowUp = a.getResourceId(attr, 0);
				if (refreshArrowUp != 0) {
					this.imgRefreshUp = refreshArrowUp;
				}
				break;
			case R.styleable.ListPullDown_refreshArrowDown:
				// 顶部工具条向下箭头
				int refreshArrowDown = a.getResourceId(attr, 0);
				if (refreshArrowDown != 0) {
					this.imgRefreshDown = refreshArrowDown;
				}
				break;
			default:
				break;
			}
		}
		a.recycle();
	}

	// 列表滚动事件
	private android.widget.AbsListView.OnScrollListener onScrollListener = new OnScrollListener() {
		/*
		 * 当列表以任何形式(自动滚或者手拖着滚)停止滚动后,如果第一行已经出现了,那么认为已经有了加载的意图
		 * 如果第一行没有出现,那么认为没有加载的意图
		 */
		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {
			if (isLoading)// 已经在加载则不作任何处理
			{
				return;
			}
			// 当列表以任何形式(自动滚或者手拖着滚)停止滚动时
			if (scrollState == SCROLL_STATE_IDLE) {
				if (isFirstItemShow) {
					isWantToExtrude = true;
					// System.out.println("意图被onScrollStateChanged置为true");
				} else {
					isWantToExtrude = false;
					// System.out.println("意图被onScrollStateChanged置为false");
				}
			}
		}

		/*
		 * 在列表滚动过程中,根据屏幕最顶行是不是第0行,来判断:第0行是否出现在屏幕上,是否存在加载的意图
		 */
		@Override
		public void onScroll(AbsListView view, int firstVisibleItem,
				int visibleItemCount, int totalItemCount) {
			if (isLoading)// 已经在加载则不作任何处理
			{
				return;
			}
			if (firstVisibleItem == 0) {
				isFirstItemShow = true;
				// System.out.println("isFirstItemShow被onScroll置为true");
			} else {
				isFirstItemShow = false;
				isWantToExtrude = false;
				// System.out.println("意图被onScroll置为false");
			}
		}
	};

	/** 上一次手指触摸屏幕的Y轴坐标 */
	private float lastActionDown_Y = 0;

	// 触摸事件
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (isLoading)// 已经在加载则不作任何处理
		{
			return super.onTouchEvent(ev);
		}

		int action = ev.getAction();
		if (action == MotionEvent.ACTION_DOWN) {
			// 按下手指时,获得按下y轴位置,用于在拖动时计算手指拖动距离
			lastActionDown_Y = ev.getY();
		} else if (action == MotionEvent.ACTION_MOVE) {
			/*
			 * 1.如果有加载的意图,则根据触摸屏Y轴的移动来判断.
			 * 2.Y轴移动值为正说明在有拉伸意图的情况下,操作者进行了拉伸,所以应该根据拉伸的幅度来拉伸第一行,并且屏蔽其他控件的触摸屏事件。
			 * 3.Y轴移动值变成负,说明操作者已经顶回去,第一行也已经被顶没..这时不再屏蔽触摸屏事件,直到ListView将加载意图至为负值。
			 * 或操作者再次拉伸. 4.当不再有加载的意图,则有两种可能。一种是平常的触摸,不用作任何处理。
			 * 另一种是当时已经拉开了,用户又不想拉了,所以又向上推了回去 推回去后,加载意图被onScroll函数制为false。
			 * 在这种情况下,应该把第一行重新压缩回原状。
			 */
			// 手指滑动的距离
			float gapValue_y = ev.getY() - lastActionDown_Y;
			gapValue_y = gapValue_y * 2 / 3;// 减慢下拉速度,比真实的手指滑动速度略慢
			if (isWantToExtrude) {
				if (gapValue_y >= 0) {
					isExtruded = true;
					// 新的PaddingTop应该是滑动的距离减去总高,才会使加载行有慢慢拉宽的效果
					int newPaddingTop = (int) (gapValue_y - topViewItemHeight);
					topViewItem.setPadding(0, newPaddingTop, 0, 0);
					// 如果新的PaddingTop大于等于20,说明已经正常显示的情况下还多拉了20像素,这就足够了,提示用户松开的话是可以更新的
					if (newPaddingTop >= 20) {
						setTopViewControl(2);
					} else {
						setTopViewControl(1);
					}
					// 屏蔽
					return false;
				}
			} else {
				// 用户此刻已经不想加载更多
				// 可能顶部工具条已经消失不见,也可能拖到一半
				if (isExtruded) {// 拖到一半,复原
					isExtruded = false;
					topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);
				}
			}
		} else if (action == MotionEvent.ACTION_UP) {
			if (isExtruded) {
				if (topViewItem.getPaddingTop() >= 20) {
					if (this.onPDListListener != null) {
						isLoading = true;
						topViewItem.setPadding(0, 0, 0, 0);
						setTopViewControl(3);
						this.onPDListListener.onRefresh();
					} else {
						topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);
						isExtruded = false;
					}
				} else {
					topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);
					isExtruded = false;
				}
			}
		}
		return super.onTouchEvent(ev);
	}

	// 提升性能,防止无意义的重复赋值
	private int controlState = -1;

	/**
	 * 用户是否正在往下拉
	 * 
	 * @return
	 */
	public boolean isExtruded() {
		return isExtruded;
	}

	/*
	 * ==================顶部工具条部分的私有函数=================
	 */
	/**
	 * 设置顶部工具条控件的显示内容
	 * 
	 * @param state
	 *            0表示被用户重置,1表示下拉刷新,2表示松开刷新,3表示正在刷新
	 */
	private void setTopViewControl(int state) {
		if (controlState != state) {
			System.out.println("state=" + state);
			controlState = state;
			if (state == 0) {
				imageView_top.setVisibility(View.VISIBLE);
				progressBar_top.setVisibility(View.INVISIBLE);
			} else if (state == 1) {
				txtRefresh_top.setText("下拉更新");
				imageView_top.setImageResource(imgRefreshDown);
			} else if (state == 2) {
				txtRefresh_top.setText("释放立即更新");
				imageView_top.setImageResource(imgRefreshUp);
			} else if (state == 3) {
				imageView_top.setVisibility(View.INVISIBLE);
				progressBar_top.setVisibility(View.VISIBLE);
				txtRefresh_top.setText("正在更新...");
			}
		}
	}

	/*
	 * ==============底部工具条部分的私有函数=================
	 */

	// View的点击事件
	private OnClickListener onClickListener = new OnClickListener() {

		@Override
		public void onClick(View v) {
			if (v.equals(bottomViewItem)) {
				if (onPDListListener != null) {
					onPDListListener.onloadMore();
					loadingMoreView_State(1);
				}
			}
		}
	};

	/**
	 * <p>
	 * 设置加载更多工具条的状态
	 * <p>
	 * -1=隐藏 0=显示并还原为待点击状态 1=处于加载中
	 * */
	private void loadingMoreView_State(int state) {
		if (state == -1) {
			bottomViewItem.setVisibility(View.GONE);
		} else if (state == 0) {
			bottomViewItem.setVisibility(View.VISIBLE);
			progressBar_bottom.setVisibility(View.INVISIBLE);
			txtBottom.setText("点击加载更多");
			bottomViewItem.setEnabled(true);
		} else if (state == 2) {
			bottomViewItem.setVisibility(View.VISIBLE);
			progressBar_bottom.setVisibility(View.VISIBLE);
			txtBottom.setText("正在努力加载...");
			bottomViewItem.setEnabled(false);
		}
	}

	/* =======================监听部分的公开函数====================== */
	/** 设置刷新监听 */
	public void setOnPDListen(OnPDListListener onPDListListener) {
		this.onPDListListener = onPDListListener;
	}

	/** 移除刷新监听 */
	public void removePDListen() {
		this.onPDListListener = null;
	}

	/* ===============底部工具条部分的公开函数============= */

	/** 是否显示加载更多 */
	public void loadingMoreView_IsEnabled(boolean isEnabled) {
		if (isEnabled) {
			loadingMoreView_State(0);
		} else {
			loadingMoreView_State(-1);
		}
	}

	/* ====================顶部工具条部分的公开函数==================== */
	/** 弹出刷新条 并触发onRefresh接口 */
	public void startRefresh() {
		if (!isLoading && this.onPDListListener != null) {
			isLoading = true;
			topViewItem.setPadding(0, 0, 0, 0);
			setTopViewControl(3);
			setSelection(0);// 选中首行
			this.onPDListListener.onRefresh();
		}
	}

	/**
	 * 结束刷新
	 * 
	 * @param isUpdateTime
	 *            是否更新时间,刷新失败可以不更新
	 */
	public void stopRefresh(boolean isUpdateTime) {
		// 还原所有变量,首行重置回初始值,选中首行
		if (isLoading) {
			// 重置所有变量
			isFirstItemShow = false;
			isWantToExtrude = true;
			isExtruded = false;
			isLoading = false;
			// 重置下拉行控件的内容
			setTopViewControl(0);
			// 更新时间
			if (isUpdateTime) {
				txtTime_top.setText("上一次更新时间:"
						+ sdf.format(Calendar.getInstance().getTime()));
			}
			topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);
			setSelection(0);
		}
	}

	/* ================测量长宽========================== */
	/** 为View测量长宽 因为View在加入到界面之前,直接去获取长宽是无效的,跟在Activity的onCreat时无法获取控件长宽一个道理 */
	private static void measureView(View child) {
		ViewGroup.LayoutParams p = child.getLayoutParams();
		if (p == null) {
			p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
					ViewGroup.LayoutParams.WRAP_CONTENT);
		}
		int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
		int lpHeight = p.height;
		int childHeightSpec;
		if (lpHeight > 0) {
			childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
					MeasureSpec.EXACTLY);
		} else {
			childHeightSpec = MeasureSpec.makeMeasureSpec(0,
					MeasureSpec.UNSPECIFIED);
		}
		child.measure(childWidthSpec, childHeightSpec);
	}

	private OnPDListListener onPDListListener = null;

	public interface OnPDListListener {
		/** 下拉刷新 */
		public void onRefresh();

		/** 加载更多 */
		public void onloadMore();
	}

}





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值