ListView 仿微信加载数据后保持位置不变

像微信那样,滚动到顶部后在顶部加载数据,数据加载完后保持位置不变(位置没有跳动),其中关键代码如下所示,

positon表示加载的数据数,targetY为加载前第一项的y坐标。

listView.setSelectionFromTop(position, targetY);


其中下拉刷新的view如下:



public class RefreshListView extends LinearLayout {
	
	private static final int FRICTION = 2;			
	private LoadingLayout header;
	private ListView listView;
	private OnRefreshListener refreshListener;
	private SmoothScrollRunnable mSmoothScrollRunnable;
	private onRLClickListener clickListener;
	private boolean hasMoreData = true;
	
	public RefreshListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.setOrientation(LinearLayout.VERTICAL);
		header = new LoadingLayout(context);
		LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		this.addView(header, 0, lp);
		listView = new ListView(context);
		listView.setCacheColorHint(R.color.white);
		listView.setFadingEdgeLength(0);
//        android:cacheColorHint="#00000000"
//        android:fadingEdge="none"
		lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
		this.addView(listView, -1, lp);
		listView.setOnScrollListener(new OnScrollListener() {
			@Override
			public void onScrollStateChanged(AbsListView view, int scrollState) {
				//判断listview触顶刷新
				if(OnScrollListener.SCROLL_STATE_IDLE == scrollState) {		//当状态从滚动到停止滚动时触发。
//					final Adapter adapter = listView.getAdapter();
					final View firstVisibleChild = listView.getChildAt(0);
					final int firstVisibleItem = listView.getFirstVisiblePosition();
					if (firstVisibleChild != null) {
						if(firstVisibleItem == 0 && firstVisibleChild.getTop() >= view.getTop()) {
							if(header.getVisibility() == View.VISIBLE && hasMoreData) {
								RefreshListView.this.smoothScrollTo(-header.getHeight(), scrollFinishedListener);
								mState = State.REFRESHING;
							}
						}
					}
				}
			}
			
			@Override
			public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { }
		});
		listView.setDividerHeight(0);
		listView.setDivider(null);
	}
	
	public void setRLClickListener(onRLClickListener listener) {
		clickListener = listener;
	}

	public interface onRLClickListener {
		public void click();
	}
	
	public void setOnItemClickListener(OnItemClickListener listener) {
		listView.setOnItemClickListener(listener);
	}
	
	public void setAdapter(ListAdapter adapter) {
		this.listView.setAdapter(adapter);
	}
	
	/**  
	* @Title: scrollToBottom
	* @Description: 滚动到底部
	*/  
	public void scrollToBottom() {
		ListAdapter adapter = listView.getAdapter();
		if(adapter != null && adapter.getCount() > 1) {
			this.listView.setSelection(adapter.getCount() - 1);
		}
	}
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		LogPrint.mark();
		if(h < oldh) {
			LogPrint.mark();
			this.postDelayed(new Runnable() {
				@Override
				public void run() {
					scrollToBottom();
				}
			}, 100);
		}
		int pTop = getPaddingTop();
//		int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);  
//		int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); 
//		header.measure(w, h);
		//计算header的高度,并把它隐藏起来。
		pTop = -header.getMeasuredHeight();
		setPadding(getPaddingLeft(), pTop, getPaddingRight(), getPaddingBottom());
	}

	private int mLastMotionY = 0;				//上一次触摸点的Y坐标
	private int mTouchDownY = 0;				//触摸点的Y坐标
	private boolean isBeingDragged = false;		//是否被拖拽中
	private State mState = State.RESET;
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		
		if(clickListener != null) {
			clickListener.click();
		}
		
		if(isRefreshing()) {
			//如果正在刷新就把触摸事件拦截,不让拖拽。
			return true;
		}
		
		final int action = event.getAction();
		final int posY = (int) event.getY();

		if(action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
			if(isBeingDragged) {
				isBeingDragged = false;
				setState(mState);
				return false;
			}
		}
		
		switch (action) {
		case MotionEvent.ACTION_MOVE:
			//得到两次触摸点的Y坐标差
			int diff = posY - mLastMotionY;
			if(isReadyForPull() && !isBeingDragged) {
//				LogPrint.debug("mLastMotionY: " + mLastMotionY + "; posY: " + posY + "; diff: " + diff + "; TOUCH_SLOP: " + TOUCH_SLOP);
				//当达到可以被拖拽并且当前不是被拖拽的条件时,获取最初的触摸点Y坐标值
				if(diff > 1) {
					isBeingDragged = true;
					mTouchDownY = mLastMotionY = posY;
				}
			}

			if(isBeingDragged) {
				mLastMotionY = posY;
				//如果正在拖拽中,那么每次移动都刷新拖拽距离
				pullEvent();
				return true;
			}
			
			mLastMotionY = posY;
			break;
		case MotionEvent.ACTION_DOWN:
			if(isReadyForPull()) {
				mTouchDownY = mLastMotionY = posY;
				isBeingDragged = false;
			}
			break;
		}
		return super.dispatchTouchEvent(event);
	}
	
	/**  
	* @Title: pullEvent
	* @Description: 刷新拖拽的距离,并且通过距离判断是否到达“松手刷新”的状态
	*/  
	private void pullEvent() {
		final int lastMotionY = mLastMotionY;
		final int touchDownY = mTouchDownY;
		final int DISTANCE = Math.round(Math.min(touchDownY - lastMotionY, 0)) / FRICTION;
		if(DISTANCE == 0) {
			mTouchDownY = mLastMotionY;
		}
		scrollTo(0, DISTANCE);
		if(DISTANCE < 0) {
			mState = State.RELEASE_TO_REFRESH;
		} else {
			mState = State.PULL_TO_REFRESH;
		}
//		LogPrint.debug("State: " + mState);
	}

	/**
	 * @Title: isRefreshing
	 * @Description: 判断当前是否正在刷新
	 * @return boolean 
	 */
	private boolean isRefreshing() {
		return mState == State.REFRESHING;
	}

	/**
	 * @Title: isReadyForPull
	 * @Description: 判断是否可以开始被拖拽
	 * @return boolean 
	 */
	private boolean isReadyForPull() {
		final Adapter adapter = listView.getAdapter();

		if (null == adapter || adapter.isEmpty()) {
			return true;
		} else {
			/**
			 * This check should really just be:
			 * mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
			 * internally use a HeaderView which messes the positions up. For
			 * now we'll just add one to account for it and rely on the inner
			 * condition which checks getTop().
			 */
			if (listView.getFirstVisiblePosition() <= 1) {
				final View firstVisibleChild = listView.getChildAt(0);
				if (firstVisibleChild != null) {
					return firstVisibleChild.getTop() >= listView.getTop();
				}
			}
		}
		return false;
	}

	/**  
	* @Title: setState
	* @Description: 通过状态执行相应的动作
	* @param state void
	*/  
	public void setState(State state) {
		switch (state) {
		case RESET:
		case PULL_TO_REFRESH:
			smoothScrollTo(0);
			break;
		case RELEASE_TO_REFRESH:
			int height = header.getHeight();
			mState = State.REFRESHING;
			if(hasMoreData) {
				RefreshListView.this.smoothScrollTo(-height, 800, scrollFinishedListener);
			} else {
				RefreshListView.this.smoothScrollTo(0, 800, scrollFinishedListener);
			}
			break;
		case REFRESHING:
			break;
		}
	}

	public static enum State {
		RESET,
		PULL_TO_REFRESH,		//继续下拉可以刷新
		RELEASE_TO_REFRESH,		//松手刷新
		REFRESHING,				//正在刷新
	}
	
	public void setOnRefreshListener(OnRefreshListener listener) {
		this.refreshListener = listener;
	}
	
	private void smoothScrollTo(int toY) {
		smoothScrollTo(toY, null);
	}

	private void smoothScrollTo(int toY, OnSmoothScrollFinishedListener listener) {
		smoothScrollTo(toY, 0, listener);
	}

	private void smoothScrollTo(int toY, int duration, OnSmoothScrollFinishedListener listener) {
		final int fromY = getScrollY();
		if(fromY != toY) {
			mSmoothScrollRunnable = new SmoothScrollRunnable(fromY, toY, duration, listener);
			post(mSmoothScrollRunnable);
		}
	}
	
	final class SmoothScrollRunnable implements Runnable {
		private final int mScrollFromY;
		private final int mScrollToY;
		private final int mScrollDuration;
		private OnSmoothScrollFinishedListener scrollFinishedListener;

		private float mCurrentY = 0;
		private float deltaY;
		private final int PERIOD = 10;
		
		public SmoothScrollRunnable(int fromY, int toY, int duration, OnSmoothScrollFinishedListener listener) {
			mCurrentY = mScrollFromY = fromY;
			mScrollToY = toY;
			mScrollDuration = duration == 0 ? 400 : duration;
			scrollFinishedListener = listener;
			final float distance = (float) (mScrollToY - mScrollFromY);
			final float timeEverage = (float) mScrollDuration / (float) PERIOD;
			final float scrollStep = distance / timeEverage;
			deltaY = scrollStep == 0 ? 4 : scrollStep;
			deltaY = deltaY >= 0 ? deltaY + 10 : deltaY - 10;
//			LogPrint.debug("mCurrentY: " + mCurrentY + "DeltaY: " + deltaY + "; distance: " + (mScrollToY - mScrollFromY));
		}

		@Override
		public void run() {
//			LogPrint.prinstTime();
			mCurrentY += deltaY;
//			LogPrint.debug("mCurrentY: " + mCurrentY + "; mScrollToY: " + mScrollToY);
			if((deltaY > 0 && mCurrentY >= mScrollToY) || (deltaY < 0 && mCurrentY <= mScrollToY)) {
				RefreshListView.this.scrollTo(0, mScrollToY);
				if(scrollFinishedListener != null) {
					scrollFinishedListener.OnSmoothScrollFinished();
				}
				return;
			}
//			LogPrint.mark();
			RefreshListView.this.scrollTo(0, (int) mCurrentY);
			RefreshListView.this.postDelayed(this, PERIOD);
		}
	}

//	private OnScrollToTopListener listener = new OnScrollToTopListener() {
//		@Override
//		public void onScrollToTop() {
//			Toast.makeText(getContext(), "ScrollToTop", Toast.LENGTH_SHORT).show();
//			RefreshListView.this.smoothScrollTo(-header.getHeight(), scrollFinishedListener);
//			mState = State.REFRESHING;
//		}
//	};
	
	private OnSmoothScrollFinishedListener scrollFinishedListener = new OnSmoothScrollFinishedListener() {
		@Override
		public void OnSmoothScrollFinished() {
			int targetY = 0;
			int position = 0;
			if(refreshListener != null && hasMoreData) {
				position = refreshListener.refreshData();
				targetY = header.getMeasuredHeight();
				RefreshListView.this.scrollTo(0, 0);
				listView.setSelectionFromTop(position, targetY);
			}
			mState = State.RESET;
		}
	};
	
	interface OnScrollToTopListener {
		void onScrollToTop();
	}

	public interface OnSmoothScrollFinishedListener {
		void OnSmoothScrollFinished();
	}
	
	public interface OnRefreshListener {
		int refreshData();
	}

	public void setMoreData(boolean moreData) {
		hasMoreData = moreData;
		if(moreData) {
			this.header.setVisibility(View.VISIBLE);
		} else {			
			this.header.setVisibility(View.INVISIBLE);
		}
		LogPrint.debug("has more data " + hasMoreData);
	}
	
}






评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值