Android自定义控件--下拉刷新的实现

Posted qq_20889581

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义控件--下拉刷新的实现相关的知识,希望对你有一定的参考价值。

我们在使用ListView的时候,很多情况下需要用到下拉刷新的功能。为了了解下拉刷新的底层实现原理,我采用自定义ListView控件的方式来实现效果。

实现的基本原理是:自定义ListView,给ListView加载头布局,然后动态的控制头布局的现实与隐藏。ListView初始化的时候,头布局是隐藏的,当手指往下拉的时候,根据手指移动的距离与头布局的高度的关系来控制头布局的显示。具体的控制思路详见后边的代码,代码中的注释很详细。先来看一下效果图:

                          

头布局的实现代码如下:

<?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="match_parent"
    android:orientation="horizontal" >

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="5dp"
        android:padding="5dp" >

        <ImageView
            android:id="@+id/iv_arror"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:padding="10dp"
            android:src="@drawable/refresh_listview_arrow" />

        <ProgressBar
            android:id="@+id/pb_list"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:indeterminateDrawable="@drawable/custom_progress"
            android:visibility="invisible" >
        </ProgressBar>
    </RelativeLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginLeft="25dp"
        android:layout_marginTop="12dp"
        android:orientation="vertical"
        android:padding="5dp" >

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="12dp"
            android:text="下拉刷新"
            android:textColor="#FF0000"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="3dp"
            android:text="2015-03-10 17:07:07"
            android:textColor="@android:color/darker_gray"
            android:textSize="16sp" />
    </LinearLayout>

</LinearLayout>

接着定义一个RefreshListView,继承自ListView,重写它的三个构造方法,在RefreshlistView初始化的时候,加载一个头布局,这个头布局默认是隐藏的。

<span style="white-space:pre">	</span>// 初始化头布局
	private void initHeaderView() {
		// 给当前的ListView添加下拉刷新的头布局
		mHeaderView = View.inflate(getContext(), R.layout.list_refresh_header,
				null);
		tvTitle = (TextView) mHeaderView.findViewById(R.id.tv_title);
		tvTime = (TextView) mHeaderView.findViewById(R.id.tv_time);
		// 下拉箭头
		ivArror = (ImageView) mHeaderView.findViewById(R.id.iv_arror);
		// 进度条
		pb = (ProgressBar) mHeaderView.findViewById(R.id.pb_list);

		this.addHeaderView(mHeaderView);
		// 初始化这个下拉刷新的ListView的时候,县隐藏掉下拉刷新的头布局,然后通过代码动态的控制头布局的显示与隐藏
		// 隐藏头布局
		// 先测量一把头布局
		mHeaderView.measure(0, 0);
		// 获得测量得到的高度
		mHeaderViewHeight = mHeaderView.getMeasuredHeight();
		// 设置paddingtop为负高度,隐藏头布局
		mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);

		// 初始化完成布局,初始化两个动画效果

		initAnimation();
		// 设置当前时间
		tvTime.setText("最后刷新时间:" + getCurrentTime());
	}

当手指触摸屏幕时,触发OnTouchEvent事件,根据手指在Y轴方向上的移动偏移量来动态的控制头布局的显示与隐藏。并即时更新下拉框的显示的状态。具体的代码如下:

<span style="white-space:pre">	</span>/**
	 * 下拉屏幕的时候,将头布局展示出来,重写一下ListView的触摸屏幕的事件
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 手指按下的时候获得当前屏幕的Y坐标
			startY = (int) ev.getRawY();

			break;
		case MotionEvent.ACTION_MOVE:
			// 手指移动的时候,获得当前屏幕的Y坐标,与开始的Y坐标做比较

			if (startY == -1) {
				// 确保startY有效
				startY = (int) ev.getRawY();
			}

			int endY = (int) ev.getRawY();
			// 屏幕Y方向移动的距离
			int dy = endY - startY;
			// 只有屏幕向下滑,也就是dy>0,并且当前的ListView显示的是第一个条目,那个隐藏的下拉刷新的头布局才慢慢的随手指滑出来
			if (dy > 0 && this.getFirstVisiblePosition() == 0) {
				// 允许下拉刷新框出来
				// 下拉刷新框出来其实就是给头布局设置padding,首先根据手指移动的距离计算一下padding
				int padding = dy - mHeaderViewHeight;//
				mHeaderView.setPadding(0, padding, 0, 0);
				// 更新刷新框的状态
				// 一共有三种状态,下拉刷新、正在刷新、松开刷新
				// 定义这三种状态
				// 如果padding大于0说明下拉刷新的框已经全部拉出来了,将状态改为松开刷新
				if (padding > 0 && mCurrentState != STATE_RELEASE_REFRESH) {
					// 状态改为松开刷新,将下拉框显示的状态更新一下
					mCurrentState = STATE_RELEASE_REFRESH;
					// 更新当前下拉框显示的状态
					updateState();
				}
				if (padding < 0 && mCurrentState != STATE_PULL_REFRESH) {
					// 状态改为下拉刷新
					mCurrentState = STATE_PULL_REFRESH;
					updateState();
				}

			}

			break;
		case MotionEvent.ACTION_UP:
			// 手指抬起来的时候重置startY。将状态改为正在刷新
			startY = -1;
			if (mCurrentState == STATE_RELEASE_REFRESH) {
				// 如果当前为松开刷新,则手指一抬起来就将状态改为正在
				mCurrentState = STATE_REFRESHING;
				// 头布局的padding设置为0000
				mHeaderView.setPadding(0, 0, 0, 0);
				updateState();
			} else {
				// 没有完全拉出来,就将头布局隐藏
				mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);

			}

			break;

		}

		return super.onTouchEvent(ev);
	}

	/**
	 * 更新当前下拉框显示的状态
	 */
	private void updateState() {
		// 判断当前状态是什么,根据当前状态更新下拉框显示
		switch (mCurrentState) {
		case STATE_PULL_REFRESH:
			// 下拉刷新
			tvTitle.setText("下拉刷新");
			ivArror.setVisibility(View.VISIBLE);
			pb.setVisibility(View.INVISIBLE);
			// 给箭头设置动画
			ivArror.startAnimation(animDown);
			break;

		case STATE_REFRESHING:
			// 正在刷新
			tvTitle.setText("正在刷新...");
			ivArror.clearAnimation();// 必须先清除动画,才能隐藏
			ivArror.setVisibility(View.INVISIBLE);
			pb.setVisibility(View.VISIBLE);
			// 正在刷新的时候调用接口中的方法
			if (mListener != null) {
				// 正在刷新
				mListener.onRefresh();
			}

			break;

		case STATE_RELEASE_REFRESH:
			// 松开刷新
			tvTitle.setText("松开刷新");
			ivArror.setVisibility(View.VISIBLE);
			pb.setVisibility(View.INVISIBLE);
			ivArror.startAnimation(animUp);
			break;
		}

	}

为了监听下拉刷新的动作,自定义一个监听接口,当正在刷新的时候执行onRefresh方法。对外提供一个设置监听的方法,监听器由子类实现,具体的正在刷新执行的逻辑由子类在onRefresh中实现。

// 下拉刷新逻辑的实现方式:给当前的ListView设置下拉刷新的监听器,监听器的本质就是一个接口
	/**
	 * 下拉刷新的监听器
	 * 
	 * @author ZHY
	 * 
	 */
	public interface onRefreshListener {
		// 刷新的时候执行的方法,谁实现这个接口。谁执行此方法
		public void onRefresh();

	}

	onRefreshListener mListener;

	// 有了监听器之后,提供一个设置监听的方法
	public void setOnRefreshListener(onRefreshListener listener) {
		// 这个Listneer是从子类中闯过来的,是子类定义的监听
		mListener = listener;
	}

刷新完成之后,改变下拉刷新的状态,并且隐藏下拉框

<span style="white-space:pre">	</span>// 刷新完成的时候,将下拉框隐藏
	/**
	 * 调用服务器的数据刷新,刷新完成之后调用的方法 加载完成之后的回调
	 * 
	 * @param isSuccess
	 */
	public void onRefreshCompleted(boolean isSuccess) {

		// 将状态改成下拉刷新,然后隐藏下拉框
		mCurrentState = STATE_PULL_REFRESH;
		tvTitle.setText("下拉刷新");
		ivArror.setVisibility(View.VISIBLE);
		pb.setVisibility(View.INVISIBLE);

		mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);// 隐藏
		// 如果刷新成功,则更新最后一次刷新时间,否则不更新时间
		if (isSuccess) {
			tvTime.setText("最后刷新时间:" + getCurrentTime());

		}

	}

这一部分工作做完之后,一个可以实现下拉刷新功能的ListView就定义好了。在MainActivity中就可以直接使用了

源码下载


以上是关于Android自定义控件--下拉刷新的实现的主要内容,如果未能解决你的问题,请参考以下文章

iOS常用刷新控件(下拉、上拉)详解

Android自定义组合控件---教你如何自定义下拉刷新和左滑删除

Android自定义组合控件---教你如何自定义下拉刷新和左滑删除

怎么自定义android 下拉刷新动画

Android自定义控件

探索SwipeRefreshLayout配合自定义ListView完成下拉刷新滑到底部自动加载更多