android 仿qq5.3,下拉刷新,自动加载更多,下拉回弹,仿IOS橡皮筋效果,通用版,效果完美

最近在做项目,用到了下拉刷新,本来这个效果应该是再普通不过的一个效果,自己懒得去写,所以就去网上找,找了很多很多,发现效果都不是很理想,要么不支持多点触控,要么不支持自动加载更多,一直觉得QQ5.3的下拉刷新是很人性化了,所以一直想找这样的下拉刷新和自动加载更多,找了很久才发现一个和QQ5.3接近的效果,http://blog.csdn.net/zhongkejingwang/article/details/38868463,大家可以去看下他的博客,不过这个拿过来貌似有一些bug,自动加载更多会出现bug,而且不能自己控制是否还有更多数据。经过我的修改已经可以自己随意控制是否还有更多数据。

先上图:




下面看修改后的PullToRefreshLayout代码

import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

/**
 * 如果不需要下拉刷新直接在canPullDown中返回false,这里的自动加载和下拉刷新没有冲突,通过增加在尾部的footerview实现自动加载,
 * 所以在使用中不要再动footerview了
 * 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38963177
 * @author chenjing
 * 
 */
public class PullableListView extends ListView implements Pullable
{
	public static final int INIT = 0;
	public static final int LOADING = 1;
	public static final int NO_MORE_DATA = 2;
	private OnLoadListener mOnLoadListener;
	private View mLoadmoreView;
	private ImageView mLoadingView;
	private TextView mStateTextView;
	private int state = INIT;
	private boolean canLoad = true;
	private boolean autoLoad = true;
	private boolean hasMoreData = true;
	private AnimationDrawable mLoadAnim;

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

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

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

	private void init(Context context)
	{
		mLoadmoreView = LayoutInflater.from(context).inflate(R.layout.load_more,
				null);
		mLoadingView = (ImageView) mLoadmoreView.findViewById(R.id.loading_icon);
		mLoadingView.setBackgroundResource(R.anim.loading_anim);
		mLoadAnim = (AnimationDrawable) mLoadingView.getBackground();
		mStateTextView = (TextView) mLoadmoreView.findViewById(R.id.loadstate_tv);
		mLoadmoreView.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				//点击加载
				if(state != LOADING && hasMoreData){
					load();
				}
			}
		});
		addFooterView(mLoadmoreView, null, false);
	}
	
	/**
	 * 是否开启自动加载
	 * @param enable true启用,false禁用
	 */
	public void enableAutoLoad(boolean enable){
		autoLoad = enable;
	}
	
	/**
	 * 是否显示加载更多
	 * @param v true显示,false不显示
	 */
	public void setLoadmoreVisible(boolean v){
		if(v)
			{
			if (getFooterViewsCount() == 0) {
				addFooterView(mLoadmoreView, null, false);
			}
			}
		else {
			removeFooterView(mLoadmoreView);
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev)
	{
		switch (ev.getActionMasked())
		{
		case MotionEvent.ACTION_DOWN:
			// 按下的时候禁止自动加载
			canLoad = false;
			break;
		case MotionEvent.ACTION_UP:
			// 松开手判断是否自动加载
			canLoad = true;
			checkLoad();
			break;
		}
		return super.onTouchEvent(ev);
	}

	@Override
	protected void onScrollChanged(int l, int t, int oldl, int oldt)
	{
		super.onScrollChanged(l, t, oldl, oldt);
		// 在滚动中判断是否满足自动加载条件
		checkLoad();
	}

	/**
	 * 判断是否满足自动加载条件
	 */
	private void checkLoad()
	{
		if (reachBottom() && mOnLoadListener != null && state != LOADING
				&& canLoad && autoLoad && hasMoreData)
			load();
	}
	
	private void load(){
		changeState(LOADING);
		mOnLoadListener.onLoad(this);
	}

	private void changeState(int state)
	{
		this.state = state;
		switch (state)
		{
		case INIT:
			mLoadAnim.stop();
			mLoadingView.setVisibility(View.INVISIBLE);
			mStateTextView.setText(R.string.more);
			break;

		case LOADING:
			mLoadingView.setVisibility(View.VISIBLE);
			mLoadAnim.start();
			mStateTextView.setText(R.string.loading);
			break;
			
		case NO_MORE_DATA:
			mLoadAnim.stop();
			mLoadingView.setVisibility(View.INVISIBLE);
			mStateTextView.setText("没有更多的数据了");
			break;
		}
	}

	/**
	 * 完成加载
	 */
	public void finishLoading()
	{
		changeState(INIT);
	}

	@Override
	public boolean canPullDown()
	{
		if (getCount() == 0)
		{
			// 没有item的时候也可以下拉刷新
			return true;
		} else if (getFirstVisiblePosition() == 0
				&& getChildAt(0).getTop() >= 0)
		{
			// 滑到ListView的顶部了
			return true;
		} else
			return false;
	}

	public void setOnLoadListener(OnLoadListener listener)
	{
		this.mOnLoadListener = listener;
	}

	/**
	 * @return footerview可见时返回true,否则返回false
	 */
	private boolean reachBottom()
	{
		if (getCount() == 0)
		{
			return true;
		} else if (getLastVisiblePosition() == (getCount() - 1))
		{
			// 滑到底部,且頂部不是第0个,也就是说item数超过一屏后才能自动加载,否则只能点击加载
			if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
					&& getChildAt(
							getLastVisiblePosition()
									- getFirstVisiblePosition()).getTop() < getMeasuredHeight() && !canPullDown())
				return true;
		}
		return false;
	}

	public boolean isHasMoreData() {
		return hasMoreData;
	}

	public void setHasMoreData(boolean hasMoreData) {
		this.hasMoreData = hasMoreData;
		if (!hasMoreData) {
			changeState(NO_MORE_DATA);
		}else{
			changeState(INIT);
		}
	}

	public interface OnLoadListener
	{
		void onLoad(PullableListView pullableListView);
	}

	@Override
	public boolean canPullUp() {
		// TODO Auto-generated method stub
		return false;
	}
}


用法也很简单:

pullToRefreshLayout.refreshFinish(PullToRefreshLayout.SUCCEED);
listView.setHasMoreData(true);//是否有更多数据

加载完成后自己设置true或者false就可以了!


后来又发现QQ5.3每一个界面貌似都可以下拉回弹,这样的设计很人性化,模仿的IOS的下拉回弹橡皮筋效果,于是自己又借助上面的代码修改了下做成了和QQ一样的效果:OverScrollView

/**
 * 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38868463
 * 
 * @author 陈靖
 */
public class OverScrollView extends ScrollView {
	public static final String TAG = "PullToRefreshLayout";
	 //触发事件的高度默认阀值
    private static final int TRIGGER_HEIGHT = 120;
    //滑动的总距离
    private float overScrollDistance;
	 //触发事件的高度阀值,最小值为30
	private int mOverScrollTrigger = TRIGGER_HEIGHT;
	private OverScrollTinyListener mOverScrollTinyListener;
	private OverScrollListener mOverScrollListener;
	// 按下Y坐标,上一个事件点Y坐标
	private float downY, lastY;
	// 下拉的距离。注意:pullDownY和pullUpY不可能同时不为0
	public float pullDownY = 0;
	// 上拉的距离
	private float pullUpY = 0;
	private MyTimer timer;
	// 回滚速度
	public float MOVE_SPEED = 8;
	// 第一次执行布局
	private boolean isLayout = false;
	// 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化
	private float radio = 2;
	// 实现了Pullable接口的View
	private View pullableView;
	// 过滤多点触碰
	private int mEvents;
	// 这两个变量用来控制pull的方向,如果不加控制,当情况满足可上拉又可下拉时没法下拉
	private boolean canPullDown = true;
	private boolean canPullUp = true;

	/**
	 * 执行自动回滚的handler
	 */
	Handler updateHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			// 回弹速度随下拉距离moveDeltaY增大而增大
			MOVE_SPEED = (float) (5 + 15 * Math.tan(Math.PI / 2
					/ getMeasuredHeight() * (pullDownY + Math.abs(pullUpY))));
			if (pullDownY > 0)
				pullDownY -= MOVE_SPEED;
			else if (pullUpY < 0)
				pullUpY += MOVE_SPEED;
			if (pullDownY < 0) {
				// 已完成回弹
				pullDownY = 0;
				timer.cancel();
			}
			if (pullUpY > 0) {
				// 已完成回弹
				pullUpY = 0;
				timer.cancel();
			}
			// 刷新布局,会自动调用onLayout
			requestLayout();
		}

	};

	public OverScrollView(Context context) {
		super(context);
		initView(context);
	}

	public OverScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initView(context);
	}

	public OverScrollView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initView(context);
	}

	private void initView(Context context) {
		timer = new MyTimer(updateHandler);
	}

	private void hide() {
		timer.schedule(5);
	}

	/**
	 * 不限制上拉或下拉
	 */
	private void releasePull() {
		canPullDown = true;
		canPullUp = true;
	}

	/*
	 * (非 Javadoc)由父控件决定是否分发事件,防止事件冲突
	 * 
	 * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent)
	 */
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		switch (ev.getActionMasked()) {
		case MotionEvent.ACTION_DOWN:
			downY = ev.getY();
			lastY = downY;
			timer.cancel();
			mEvents = 0;
			releasePull();
			break;
		case MotionEvent.ACTION_POINTER_DOWN:
		case MotionEvent.ACTION_POINTER_UP:
			// 过滤多点触碰
			mEvents = -1;
			break;
		case MotionEvent.ACTION_MOVE:
			float deltaY = ev.getY() - lastY;
			if (mEvents == 0) {
				if (canPullDown && isCanPullDown()) {
					// 可以下拉,正在加载时不能下拉
					// 对实际滑动距离做缩小,造成用力拉的感觉
					pullDownY = pullDownY + deltaY / radio;
					if (ev.getY() - lastY  < 0) {
						pullDownY = pullDownY + deltaY ;
					}
					if (pullDownY < 0) {
						pullDownY = 0;
						canPullDown = false;
						canPullUp = true;
					}
					if (pullDownY > getMeasuredHeight())
						pullDownY = getMeasuredHeight();
					overScrollDistance = pullDownY;
				} else if (canPullUp && isCanPullUp()) {
					// 可以上拉,正在刷新时不能上拉
					pullUpY = pullUpY + deltaY / radio;
					if (ev.getY() - lastY > 0) {
						pullUpY = pullUpY + deltaY ;
					}
					if (pullUpY > 0) {
						pullUpY = 0;
						canPullDown = true;
						canPullUp = false;
					}
					if (pullUpY < -getMeasuredHeight())
						pullUpY = -getMeasuredHeight();
					overScrollDistance = pullUpY;
				} else
					releasePull();
			} else
				mEvents = 0;
			lastY = ev.getY();
			// 根据下拉距离改变比例
			radio = (float) (2 + 3 * Math.tan(Math.PI / 2 / getMeasuredHeight()
					* (pullDownY + Math.abs(pullUpY))));
			requestLayout();
			// 因为刷新和加载操作不能同时进行,所以pullDownY和pullUpY不会同时不为0,因此这里用(pullDownY +
			// Math.abs(pullUpY))就可以不对当前状态作区分了
			if ((pullDownY + Math.abs(pullUpY)) > 8) {
				// 防止下拉过程中误触发长按事件和点击事件
				ev.setAction(MotionEvent.ACTION_CANCEL);
			}
			if(mOverScrollTinyListener != null){
				mOverScrollTinyListener.scrollDistance((int)deltaY, (int)overScrollDistance);
			}
			break;
		case MotionEvent.ACTION_UP:
			hide();
			overScrollTrigger();
			if(mOverScrollTinyListener != null && (isCanPullDown() || isCanPullUp())){
				mOverScrollTinyListener.scrollLoosen();
			}
			break;
		case MotionEvent.ACTION_CANCEL:
			if(mOverScrollTinyListener != null && (isCanPullDown() || isCanPullUp())){
				mOverScrollTinyListener.scrollLoosen();
			}
			break;
		default:
			break;
		}
		// 事件分发交给父类
		try {
			super.dispatchTouchEvent(ev);
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return true;
	}


	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (!isLayout) {
			// 这里是第一次进来的时候做一些初始化
			pullableView = getChildAt(0);
			isLayout = true;
		}
		
		pullableView.layout(0, (int) (pullDownY + pullUpY),
				pullableView.getMeasuredWidth(), (int) (pullDownY + pullUpY)
						+ pullableView.getMeasuredHeight());
	}

	class MyTimer {
		private Handler handler;
		private Timer timer;
		private MyTask mTask;

		public MyTimer(Handler handler) {
			this.handler = handler;
			timer = new Timer();
		}

		public void schedule(long period) {
			if (mTask != null) {
				mTask.cancel();
				mTask = null;
			}
			mTask = new MyTask(handler);
			timer.schedule(mTask, 0, period);
		}

		public void cancel() {
			if (mTask != null) {
				mTask.cancel();
				mTask = null;
			}
		}

		class MyTask extends TimerTask {
			private Handler handler;

			public MyTask(Handler handler) {
				this.handler = handler;
			}

			@Override
			public void run() {
				handler.obtainMessage().sendToTarget();
			}

		}
	}
	
	/** 
     * 判断是否滚动到顶部 
     */  
    private boolean isCanPullDown() {  
        return getScrollY() == 0 ||   
        		pullableView.getHeight() < getHeight() + getScrollY();  
    }  
      
    /** 
     * 判断是否滚动到底部 
     */  
    private boolean isCanPullUp() {  
        return  pullableView.getHeight() <= getHeight() + getScrollY();  
    } 
    
    private boolean isOnTop(){
		return getScrollY() == 0;
	}
	
	private boolean isOnBottom(){
		return getScrollY() + getHeight() == pullableView.getHeight();
	}
	
	/**
	 * 当OverScroll超出一定值时,调用此监听
	 * 
	 * @author King
	 * @since 2014-4-9 下午4:36:29
	 */
	public interface OverScrollListener {

		/**
		 * 顶部
		 */
		void headerScroll();
		
		/**
		 * 底部
		 */
		void footerScroll();
		
	}
	
	/**
	 * 每当OverScroll时,都能触发的监听
	 * @author King
	 * @since 2014-4-9 下午4:39:06
	 */
	public interface OverScrollTinyListener{
		
		/**
		 * 滚动距离
		 * @param tinyDistance 当前滚动的细小距离
		 * @param totalDistance 滚动的总距离
		 */
		void scrollDistance(int tinyDistance, int totalDistance);

        /**
         * 滚动松开
         */
        void scrollLoosen();
	}
	
	/**
	 * 设置OverScrollListener出发阀值
	 * @param height
	 */
	public void setOverScrollTrigger(int height){
		if(height >= 30){
			mOverScrollTrigger = height;
		}
	}
	
	private void overScrollTrigger(){
		if(mOverScrollListener == null){
			return;
		}
		
		if(overScrollDistance > mOverScrollTrigger && overScrollDistance >= 0){
			mOverScrollListener.headerScroll();
		}
		
		if(overScrollDistance < -mOverScrollTrigger && overScrollDistance < 0){
			mOverScrollListener.footerScroll();
		}
	}
	
	public OverScrollTinyListener getOverScrollTinyListener() {
		return mOverScrollTinyListener;
	}

	public void setOverScrollTinyListener(OverScrollTinyListener OverScrollTinyListener) {
		this.mOverScrollTinyListener = OverScrollTinyListener;
	}

	public OverScrollListener getOverScrollListener() {
		return mOverScrollListener;
	}

	public void setOverScrollListener(OverScrollListener OverScrollListener) {
		this.mOverScrollListener = OverScrollListener;
	}
}

上面注意的是我加了两个接口函数,一个是可以在每次滑动的时候触发,一个只是在头部和尾部的时候触发。

其实利用这两个接口就可以实现QQ空间,或者新浪个人中心那个下拉刷新头像伸缩的效果。

上效果:


用法很简单和Scrollview一样,Scollview怎么用这个就怎么用,把Scrollview换成OverScollview就可以了

OverScrollView由于继承了ScrollView那么自然里面不能嵌套listview和其ExpandListView,如果非要嵌套用,那也可以,使你的listview或者expandListview继承


import android.content.Context;
import android.util.AttributeSet;
import android.widget.ExpandableListView;
import android.widget.ListView;

public class CustomerListView extends ListView {  
  
    public CustomerListView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        // TODO Auto-generated constructor stub  
    }  
  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        // TODO Auto-generated method stub  
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,  MeasureSpec.AT_MOST);  
  
        super.onMeasure(widthMeasureSpec, expandSpec);  
    }  
}

这个就可以了。写的比较粗糙,有啥问题请留言!

好了最后提供下demo:

http://download.csdn.net/detail/wondaymh/8314885



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值