仿苏宁易购下拉刷新控件(支持ListView、ScrollView)

下拉刷新控件很常见,Android官方也推出了自己的下拉组件SwipeRefreshLayout。之前项目中有用到下拉刷新控件,要求根据手势播放动画效果,由于没有现成的,就参考第三方下拉控件,改造成自己项目所需的。改造过程还算比较顺利,现分享出来,仅供参考。

效果图


布局pull_to_refresh_header.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="#dadada">

    <ImageView
        android:id="@+id/cart"
        android:layout_width="39dp"
        android:layout_height="27dp"
        android:layout_marginTop="50dp"
        android:layout_alignParentLeft="true"
        android:visibility="visible"
        android:src="@drawable/loading_car"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10sp"
            android:drawableBottom="@drawable/loading_wenan"/>
        <TextView
            android:id="@+id/loading_middle"
            android:layout_width="120dp"
            android:layout_height="55dp"
            android:visibility="invisible"
            android:background="@drawable/refresh_anim"/>
        <TextView
            android:id="@+id/refresh_text"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:gravity="center"
            android:text="正在刷新..."
            android:textSize="10sp"
            android:textColor="#999"/>
    </LinearLayout>

</RelativeLayout>

PullToRefreshView主代码




package com.miloisbadboy.view;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;

import com.miloisbadboy.R;

public class PullToRefreshView extends LinearLayout {
	private static final String TAG = "PullToRefreshView";
	// refresh states
	private static final int PULL_TO_REFRESH = 2;
	private static final int RELEASE_TO_REFRESH = 3;
	private static final int REFRESHING = 4;
	// pull state
	private static final int PULL_UP_STATE = 0;
	private static final int PULL_DOWN_STATE = 1;
	private boolean enablePullTorefresh = true;
	private boolean enablePullLoadMoreDataStatus = true;
	/**
	 * last y
	 */
	private int mLastMotionY;
	/**
	 * lock
	 */
	private boolean mLock;
	/**
	 * header view
	 */
	private View mHeaderView;
	/**
	 * list or grid
	 */
	private AdapterView<?> mAdapterView;
	/**
	 * scrollview
	 */
	private ScrollView mScrollView;
	/**
	 * header view height
	 */
	private int mHeaderViewHeight;
	/**
	 * layout inflater
	 */
	private LayoutInflater mInflater;
	/**
	 * header view current state
	 */
	private int mHeaderState;
	/**
	 * footer view current state
	 */
	private int mFooterState;
	/**
	 * pull state,pull up or pull down;PULL_UP_STATE or PULL_DOWN_STATE
	 */
	private int mPullState;
	/**
	 * header refresh listener
	 */
	private OnHeaderRefreshListener mOnHeaderRefreshListener;
	/**
	 * 正在刷新动画
	 */
	protected AnimationDrawable animationMiddle;
	/**
	 * 刷新提示语
	 */
	protected TextView mLoddingTextView;
	/**
	 * 敞篷车
	 */
	protected ImageView mCart;
	/**
	 * 刷新提示
	 */
	protected TextView refreshText;
	/**
	 * 记录第一次按下的y坐标
	 */
	protected int mYDown;
	/**
	 * y方向移动的距离
	 */
	protected int moveDistance;
	/**
	 * 最小的有效滑动距离
	 */
	protected int mTouchSlop;
	/**
	 * 屏幕宽度
	 */
	protected int mScreenWidth;
	/**
	 * 屏幕高度
	 */
	protected int mScreenHeight;

	public PullToRefreshView(Context context, AttributeSet attrs) {
		this(context, attrs,0);
	}

	public PullToRefreshView(Context context) {
		this(context,null);
	}

	public PullToRefreshView(Context context, AttributeSet attributeSet, int defStyle) {
		super(context,attributeSet,defStyle);
		init(context);
	}

	private void init(Context context) {
		mInflater = LayoutInflater.from(getContext());
		setOrientation(VERTICAL);
		mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
		mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;
		mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;
		// header view 在此添加,保证是第一个添加到linearlayout的最上端
		addHeaderView();
	}

	private void addHeaderView() {
		// header view
		mHeaderView = mInflater.inflate(R.layout.pull_to_refresh_header, this, false);
		mCart = (ImageView) mHeaderView.findViewById(R.id.cart);
		mLoddingTextView = (TextView) mHeaderView.findViewById(R.id.loading_middle);
		animationMiddle = (AnimationDrawable) mLoddingTextView.getBackground();
		refreshText = (TextView) mHeaderView.findViewById(R.id.refresh_text);
		// header layout
		measureView(mHeaderView);
		mHeaderViewHeight = mHeaderView.getMeasuredHeight();
		LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mHeaderViewHeight);
		// 设置topMargin的值为负的header View高度,即将其隐藏在最上方
		params.topMargin = -(mHeaderViewHeight);
		// mHeaderView.setLayoutParams(params1);
		addView(mHeaderView, params);
	}

	/**
	 * init AdapterView like ListView,GridView and so on;or init ScrollView
	 */
	private void initContentAdapterView() {
		int count = getChildCount();
		if (count < 2) {
			throw new IllegalArgumentException("this layout must contain 2 child views,and AdapterView or ScrollView must in the second position!");
		}
		View view = null;
		for (int i = 0; i < count; ++i) {
			view = getChildAt(i);
			if (view instanceof AdapterView<?>) {
				mAdapterView = (AdapterView<?>) view;
			}
			if (view instanceof ScrollView) {
				// finish later
				mScrollView = (ScrollView) view;
			}
		}
		if (mAdapterView == null && mScrollView == null) {
			throw new IllegalArgumentException("must contain a AdapterView or ScrollView in this layout!");
		}
	}

	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) {
			childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
		} else {
			childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
		}
		child.measure(childWidthSpec, childHeightSpec);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent e) {
		int y = (int) e.getRawY();
		switch (e.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 首先拦截down事件,记录y坐标
			mLastMotionY = y;
			mYDown = y;
			break;
		case MotionEvent.ACTION_MOVE:
			// deltaY > 0 是向下运动,< 0是向上运动
			int deltaY = y - mLastMotionY;
			if (isRefreshViewScroll(deltaY)) {
				return true;
			}
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			break;
		}
		return false;
	}

	/*
	 * 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return
	 * false)则由PullToRefreshView 的子View来处理;否则由下面的方法来处理(即由PullToRefreshView自己来处理)
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (mLock) {
			return true;
		}
		int y = (int) event.getRawY();
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// onInterceptTouchEvent已经记录
			// mLastMotionY = y;
			break;
		case MotionEvent.ACTION_MOVE:
			int deltaY = y - mLastMotionY;
			moveDistance = (int) event.getRawY() - mYDown;
			if(Math.abs(moveDistance) > mTouchSlop) {
				if (mPullState == PULL_DOWN_STATE) {
					// PullToRefreshView执行下拉
					Log.i(TAG, " pull down!parent view move!");
					headerPrepareToRefresh(deltaY);
					// setHeaderPadding(-mHeaderViewHeight);
				} else if (mPullState == PULL_UP_STATE) {
					// PullToRefreshView执行上拉
					Log.i(TAG, "pull up!parent view move!");
//				footerPrepareToRefresh(deltaY);
				}
				mLastMotionY = y;
			}
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			int topMargin = getHeaderTopMargin();
			if (mPullState == PULL_DOWN_STATE) {
				if (mHeaderState == RELEASE_TO_REFRESH) {
					// 开始刷新
					headerRefreshing();
				} else {
					// 还没有执行刷新,重新隐藏
					setHeaderTopMargin(-mHeaderViewHeight);
				}
			} else if (mPullState == PULL_UP_STATE) {
				if (Math.abs(topMargin) >= mHeaderViewHeight) {

				} else {
					// 还没有执行刷新,重新隐藏
					setHeaderTopMargin(-mHeaderViewHeight);
				}
			}
			break;
		}
		return super.onTouchEvent(event);
	}

	/**
	 * 是否应该到了父View,即PullToRefreshView滑动
	 * @param deltaY > 0 是向下运动,< 0是向上运动
	 */
	private boolean isRefreshViewScroll(int deltaY) {
		if (mHeaderState == REFRESHING || mFooterState == REFRESHING) {
			return false;
		}
		// 对于ListView和GridView
		if (mAdapterView != null) {
			// 子view(ListView or GridView)滑动到最顶端
			if (deltaY > 0) {
				// 判断是否禁用下拉刷新操作
				if (!enablePullTorefresh) {
					return false;
				}
				View child = mAdapterView.getChildAt(0);
				if (child == null) {
					// 如果mAdapterView中没有数据,不拦截
					return false;
				}
				if (mAdapterView.getFirstVisiblePosition() == 0 && child.getTop() == 0) {
					mPullState = PULL_DOWN_STATE;
					return true;
				}
				int top = child.getTop();
				int padding = mAdapterView.getPaddingTop();
				if (mAdapterView.getFirstVisiblePosition() == 0 && Math.abs(top - padding) <= 11) {// 这里之前用3可以判断,但现在不行,还没找到原因
					mPullState = PULL_DOWN_STATE;
					return true;
				}

			} else if (deltaY < 0) {
				// 判断是否禁用上拉加载更多操作
				if (!enablePullLoadMoreDataStatus) {
					return false;
				}
				View lastChild = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1);
				if (lastChild == null) {
					// 如果mAdapterView中没有数据,不拦截
					return false;
				}
				// 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view,
				// 等于父View的高度说明mAdapterView已经滑动到最后
				if (lastChild.getBottom() <= getHeight() && mAdapterView.getLastVisiblePosition() == mAdapterView.getCount() - 1) {
					mPullState = PULL_UP_STATE;
					return true;
				}
			}
		}
		// 对于ScrollView
		if (mScrollView != null) {
			// 子scroll view滑动到最顶端
			View child = mScrollView.getChildAt(0);
			if (deltaY > 0 && mScrollView.getScrollY() == 0) {
				mPullState = PULL_DOWN_STATE;
				return true;
			} else if (deltaY < 0 && child.getMeasuredHeight() <= getHeight() + mScrollView.getScrollY()) {
				mPullState = PULL_UP_STATE;
				return true;
			}
		}
		return false;
	}

	/**
	 * header 准备刷新,手指移动过程,还没有释放
	 * @param deltaY,手指滑动的距离
	 */
	private void headerPrepareToRefresh(int deltaY) {
		// 计算车的偏移量
		int distance = (int)((moveDistance / (float)mScreenHeight) * mScreenWidth);
		if(distance > mScreenWidth/2 - mCart.getMeasuredWidth()) {
			distance = mScreenWidth/2 - mCart.getMeasuredWidth();
		}
		// 设置车的x轴偏移量
		mCart.setTranslationX(distance);
		int newTopMargin = changingHeaderViewTopMargin(deltaY);
		// 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态
		if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {
			refreshText.setText(R.string.pull_to_refresh_release_label);
			mHeaderState = RELEASE_TO_REFRESH;
		} else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放
			refreshText.setText(R.string.pull_to_refresh_pull_label);
			mHeaderState = PULL_TO_REFRESH;
		}
	}
	/**
	 * 修改Header view top margin的值
	 */
	private int changingHeaderViewTopMargin(int deltaY) {
		LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
		float newTopMargin = params.topMargin + deltaY * 0.7f;
		params.topMargin = (int) newTopMargin;
		mHeaderView.setLayoutParams(params);
		invalidate();
		return params.topMargin;
	}

	/**
	 * header refreshing
	 */
	public void headerRefreshing() {
		mHeaderState = REFRESHING;
		setHeaderTopMargin(0);
		mCart.setVisibility(GONE);
		mLoddingTextView.setVisibility(VISIBLE);
		animationMiddle.start();
		refreshText.setText(R.string.pull_to_refresh_refreshing_label);
		if (mOnHeaderRefreshListener != null) {
			mOnHeaderRefreshListener.onHeaderRefresh(this);
		}
	}

	/**
	 * 设置header view 的topMargin的值
	 * @param topMargin 为0时,说明header view 刚好完全显示出来;为-mHeaderViewHeight时,说明完全隐藏了
	 */
	private void setHeaderTopMargin(int topMargin) {
		LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
		params.topMargin = topMargin;
		mHeaderView.setLayoutParams(params);
		invalidate();
	}

	/**
	 * header view 完成更新后恢复初始状态
	 */
	public void onHeaderRefreshComplete() {
		mCart.setVisibility(VISIBLE);
		ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mCart,"translationX",mScreenWidth/2,mScreenWidth);
		objectAnimator.setDuration(300);
		objectAnimator.start();
		mLoddingTextView.setVisibility(INVISIBLE);
		animationMiddle.stop();
		refreshText.setText("");
		mHeaderState = PULL_TO_REFRESH;
		postDelayed(new Runnable() {
			@Override
			public void run() {
				setHeaderTopMargin(-mHeaderViewHeight);
			}
		},200);
	}

	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		initContentAdapterView();
	}

	/**
	 * Resets the list to a normal state after a refresh.
	 */
	public void onHeaderRefreshComplete(CharSequence lastUpdated) {
		onHeaderRefreshComplete();
	}

	/**
	 * 获取当前header view 的topMargin
	 */
	private int getHeaderTopMargin() {
		LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
		return params.topMargin;
	}

	/**
	 * set headerRefreshListener
	 */
	public void setOnHeaderRefreshListener(OnHeaderRefreshListener headerRefreshListener) {
		mOnHeaderRefreshListener = headerRefreshListener;
	}

	/**
	 * Interface definition for a callback to be invoked when list/grid header
	 * view should be refreshed.
	 */
	public interface OnHeaderRefreshListener {
		public void onHeaderRefresh(PullToRefreshView view);
	}

	public boolean isEnablePullTorefresh() {
		return enablePullTorefresh;
	}

	public void setEnablePullTorefresh(boolean enablePullTorefresh) {
		this.enablePullTorefresh = enablePullTorefresh;
	}

}

没什么高深的东西,相信大家都能看得懂,我就不多解释。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值