自定义ViewGroup实现瀑布流样式

实现效果:

像ListView一样调用

实现View复用

可以设定列数

实现下拉刷新和上拉加载的接口,方便调用者自定义刷新View的样式和加载View的样式

瀑布流为了优化体验,在图片未加载前要先获取图片尺寸,因此最后服务端传回图片的尺寸,否则只能下载好所有图片之后再去添加到瀑布流中。

理论上说几千张图片没关系的,关键自己要控制图片的回收,就像ListView在显示很多图片那样。

想看效果的话可以直接应用市场搜App“猜猜我在哪儿”,下载地址http://gdown.baidu.com/data/wisegame/df72eeae87e4873b/caicaiwozainaer_9.apk,帐号和密码都为i,看看就行,不要乱发东西哦,基本和蘑菇街应用效果类似,有BUG,感兴趣自己找吧。PS(请不要反编译APK)


设计思路:

1. 瀑布流控件仿照ListView来搞,第一个就是View复用。这个ListView用的这么多了,道理我就不说了。实现过程就是所有的ChildView在滑动过程判断是否可见,不可见放到集合viewList中,下次Activity的Adapter中调用getView(View v, int position)时候,如果集合里viewList里有view,那么取出来一个传出去,否则new出来一个传出去。

2.瀑布流的子控件的位置计算,这个嘛一眼难尽。正着排序(手指往上滑动),那一行短排在那一行。而且数据的顺序也是按照正常顺序的。当倒着排序(手指往下滑动),当然也是那一行上边界最“短”排在那一行上面,不过这时候对应的数据可不是按照顺序排的。ao

3.可以像ListView那样可以添加HeadView,这里只能添加一个,需要添加多个可以整合成一个HeadView来实现,当然你也可以修改源码

4.不可以像ListView那样可以添加FootView,主要我觉得下面不齐,添加一个FootView会显得丑。

5.实现了下拉刷新和上拉加载更多的View的显示机制,View需要Activity中初始化好再传进来,这样样式可以方便自定义

其他,以后在补充,Demo有空再上传,下面是主要的类;

Adapter, WaterFall, Item, StateListener

package com.ilioili.waterfall;

import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class WaterFall extends ViewGroup implements Observer{
	
	
	
	private Adapter adapter;
	private GestureDetector detector;
	/**
	 * 最上面可见的位置,FreshView的上边界,如果没有FreshView则为headView的上边界,如果都没有则为ItemList的上边界,即0
	 */
	private int topBound;
	
	/**
	 * onScrll可以滑动的上边界
	 */
	private int scrollTopLimit;
	/**
	 * onScrll可以滑动的下边界
	 */
	private int scrollBottomLimit = Integer.MAX_VALUE;
	
	/**
	 * fling滑动的时候的curY的最下边界(最大值)
	 */
	private int flingBottomLimit = Integer.MAX_VALUE;
	/**
	 * fling滑动的时候的curY的最上边界(最小值)
	 */
	private int flingTopLimit;
	
	private Item[] bottomItems;
	/**
	 * 子空间高度Measure的参数
	 */
	private int childHeightMeasureSpec;
	/**
	 * 子空间宽度Measure的参数
	 */
	private int childWidthMeasureSpec;
	/**
	 * Item的View添加到此集合,不包含headView和footView,用于View复用
	 */
	private ArrayList<View> itemViewList;
	/**
	 * 当前滑动的位置
	 */
	private int curY = 0;
	
	public static final int DEFAULT_EXTRA_SIZE = 100;

	/**
	 * ACTION_DOWN的时候记录坐标,用于判断事件是否要拦截
	 */
	private float firstFingerY;
	
	/**
	 * ACTION_DOWN的时候记录坐标,用于判断事件是否要拦截
	 */
	private float fisrtFingerX;
	
	/**
	 * 是否可以刷新,当可以刷新时,flingTopLimit的�?和onScrollTopLimit�?��,可以看见headView
	 */
	private boolean freshable = true;
	
	private View freshView;
	
	private OnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener(){
		@Override
		public boolean onDown(MotionEvent e) {
			return true;
		}
		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {
			if(scroller.isFinished()){
				scroller.fling(0, curY, 0, (int)-velocityY, 0, 0, curY-5000, curY+5000);
				flingStarted = true;
				postInvalidate();//会调用computScroll()
				return true;
			}else{
				return false;
			}
			
		}
		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,float distanceY) {
			scrollTo(0, (int)(curY+distanceY));//会调用onScrollChanged()
			computePreState();
			return true;
		}
		
	};
	/**
	 * 回调当前手指抬起后将要进行的操作
	 */
	private void computePreState(){
		if(stateListener==null) return;
		if(!isLoading){
			if(curY+viewHeight>=itemsBottomBound+loadMoreView.getMeasuredHeight()){
				if(isLoading){
					stateListener.getPreState(StateListener.UP_TO_LOAD_MORE);
				}
			}else if(curY+viewHeight>itemsBottomBound){
				if(isLoading)
				stateListener.getPreState(StateListener.UP_TO_CANCEL_LOAD_MORE);
			}
		}
		if(!isFreshing){
			if(curY <= topBound){
				stateListener.getPreState(StateListener.UP_TO_FRESH);
			}else if(curY<=headViewHeight){
				stateListener.getPreState(StateListener.UP_TO_CANCEL_FRESH);
			}
		}
		
	}
	
	public boolean isOnFling(){
		if(flingStarted && !scroller.isFinished()){
			return true;
		}else{
			flingStarted = false;
			return false;
		}
	}
	
	private View headView;
	
	private int headViewHeight;
	
	private int headViewTop;

	private ArrayList<Item> idleItemList;
	/**
	 * �?��不可见的View添加到此集合里面,用于View复用
	 */
	private ArrayList<View> itemRecycler;
	/**
	 */
	private boolean flingStarted;
	/**
	 * 是否向下滑动
	 */
	private boolean isDownward = true;
	/**
	 * 是否正在刷新,在OnTouchEnvent()中,如果在调用stateListener.startFresh()之前置为true,对滑动边界的设定有影响
	 */
	private boolean isFreshing;
	
	private boolean isFreshViewMeasured;
	
	/**
	 * 调用完initItems()后,设置为true,用于在onMeasure(int agr0, int agr1)里防止重复测�?
	 */
	private boolean isInitItemsDone;
	
	/**
	 * 是否正在刷新,在OnTouchEnvent()中,如果在调用stateListener.startLoadMore()之前置为true,对滑动边界的设定有影响
	 */
	private boolean isLoading;
	
	private boolean isLoadMoreViewMeasured;
	/**
	 * 程序在调用measureChildren时此值为true
	 */
	private ArrayList<Item> itemList;
	
	private int itemWidth;
	/**
	 * 是否可以上拉加载
	 */
	private boolean loadable = true;
	/**
	 * 下拉刷新的View
	 */
	private View loadMoreView;
	/**
	 * 瀑布流的列数
	 */
	private int N = 2;
	
	/**
	 * ItemList的最下边界
	 */
	private int itemsBottomBound;
	
	private Scroller scroller;
	
	private StateListener stateListener;
	
	private Item[] topItems;
	
	
	private int viewHeight;
	
	private int viewWidth;
	
	
	public WaterFall(Context context) {
		super(context);
		init();
	}
	
	
	public WaterFall(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}
	/**
	 * 取消刷新
	 */
	public void cancelFreshing(){
		isFreshing = false;
		if(freshable){
			scrollTopLimit = topBound;
		}else{
			scrollTopLimit = headViewTop;
		}
		flingTopLimit = headViewTop;
		flingBottomLimit = scrollBottomLimit - loadMoreView.getMeasuredHeight();
		if(curY<headViewTop){
			scroller.startScroll(0, curY, 0, headViewTop-curY, 1000);
			postInvalidate();
		}
	}
	
	/**
	 * 如果loadMoreView可见,则滑动至不可见,如果loadMoreView不可见,则不处理
	 * 修改fling的下边界
	 */
	public void cancelLoading(){
		if(loadMoreView==null) return;
		isLoading = false;
		flingBottomLimit = scrollBottomLimit-loadMoreView.getMeasuredHeight();
		if(curY+viewHeight>itemsBottomBound){
			scroller.startScroll(0, curY, 0, itemsBottomBound-viewHeight-curY, 1000);
			postInvalidate();
		}
		loadMoreView.setVisibility(View.GONE);
	}
	

	
	/**
	 * 添加新的Item之后要计算ItemList的下边界
	 */
	final private void computeBottom(){
		for(int i=0; i<N; i++){
			if(bottomItems[i]!=null){
				if(bottomItems[i].bottom>itemsBottomBound){
					itemsBottomBound=bottomItems[i].bottom;
				}
			}else{
				break;
			}
		}
	}
	private void computeChildVisibity(){
//		long start = System.currentTimeMillis();
		Item item = null;
		int col = 0;
		if(itemList.isEmpty()){
			return;
		}else if(isDownward){//向下滑动
			col = getNextDownWhenDown();
			while(bottomItems[col].top>curY+viewHeight && bottomItems[col].upItem!=null){
				p("回收Item"+bottomItems[col].index);
				bottomItems[col].v.setVisibility(View.GONE);
				itemRecycler.add(bottomItems[col].v);
				bottomItems[col] = bottomItems[col].upItem;
				col = getNextDownWhenDown();
			}
			col = getNextUpWhenDown();
			while(topItems[col].top>curY && topItems[col].upItem!=null){
				p("添加Item"+topItems[col].index);
				item = topItems[col].upItem;
				topItems[col] = item;
				item.v = obtainView(item.index);
				item.v.layout(item.left, item.top, item.right, item.bottom);
			}
			
		}else{//向上滑动
			col = getNextUpWhenUp();
			while(topItems[col].bottom<curY && topItems[col].downItem!=null){
//				Log.e("","回收Item"+topItems[col].index);
				topItems[col].v.setVisibility(View.GONE);
				itemRecycler.add(topItems[col].v);
				topItems[col] = topItems[col].downItem;
				col = getNextUpWhenUp();
			}
			col = getNextDownWhenUp();
			while(bottomItems[col].bottom<curY+viewHeight){//�?���?��下边界小于底部,有空缺出来了
				p("添加Item"+bottomItems[col].index);
				item = bottomItems[col].downItem;
				if(null==item){
					if(itemList.size()<adapter.getCount()){
						item = obtainItem();
						itemList.add(item);
						item.index = itemList.size()-1;
						item.v = obtainView(item.index);
						item.top = bottomItems[col].bottom;
						item.upItem = bottomItems[col];
						bottomItems[col].downItem = item;
						bottomItems[col] = item;
						item.left = col*itemWidth;
						item.right = item.left+itemWidth;
						item.bottom = item.top+item.v.getMeasuredHeight();
						
						if(itemList.size()==adapter.getCount()){
							computeBottom();
							setBottomConfigs();
						}
					}else{
						break;
					}
				}else{
					item.v = obtainView(item.index);
					bottomItems[col] = item;
				}
				item.v.layout(item.left, item.top, item.right, item.bottom);
				col = getNextDownWhenUp();
				computeBottom();
			}
		}
	}
	/**
	 * �?��的Item加载完成后,设置LoadView为可见,并且放置好位置(分是否慢�?��内容�?
	 * 设置fling的下边界
	 */
	private void setBottomConfigs(){
		p("setBottomConfigs");
		if(null==loadMoreView&&loadable){
			if(headViewHeight+itemsBottomBound>viewHeight){//超出�?��
				scrollBottomLimit = itemsBottomBound-viewHeight;
			}else{
				scrollBottomLimit = 0;
			}
			flingBottomLimit = scrollBottomLimit;
		}else{
			loadMoreView.setVisibility(View.VISIBLE);
			if(headViewHeight+itemsBottomBound>viewHeight){//超出�?��
				loadMoreView.layout(0, itemsBottomBound, viewWidth, itemsBottomBound+loadMoreView.getMeasuredHeight());
				loadMoreView.invalidate();
				scrollBottomLimit = itemsBottomBound+loadMoreView.getMeasuredHeight()-viewHeight;
				if(isLoading){
					flingBottomLimit = scrollBottomLimit;
				}else{
					flingBottomLimit = scrollBottomLimit-loadMoreView.getMeasuredHeight();
				}
			}else{
				loadMoreView.layout(0, viewHeight-headViewHeight, viewWidth, viewHeight-headViewHeight+loadMoreView.getMeasuredHeight());
				scrollBottomLimit = loadMoreView.getMeasuredHeight();
				flingBottomLimit = 0;
			}
		}
		p("flingBottomLimit"+flingBottomLimit);
		p("scrollBottomLimit"+scrollBottomLimit);
	}
	
	private void initBottomConfigs(){
		p("initBottomConfigs");
		flingBottomLimit = Integer.MAX_VALUE;
		scrollBottomLimit = Integer.MAX_VALUE;
		if(null!=loadMoreView){
			loadMoreView.setVisibility(View.GONE);
		}
	}
	
	@Override
	public void computeScroll() {
		super.computeScroll();
		if(scroller.computeScrollOffset()){
			scrollTo(0, scroller.getCurrY());
		}
	}
	
	public void freshHeadView() {
		int widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
		measureHeadView(widthMeasureSpec);
	}
	
	public int getColumn(){
		return N;
	}
	final private int getNextDownWhenDown(){
		int index = 0;
		for(int i=1; i<N; i++){
			if(bottomItems[i].top>bottomItems[index].top){
				index = i;
			}
		}
		return index;
	}
	final private int getNextDownWhenUp(){
		for(int i=0; i<N; i++){
			if(bottomItems[i]==null){
				return i;
			}
		}
		int index = 0;
		for(int i=1; i<N; i++){
			if(bottomItems[i].bottom<bottomItems[index].bottom){
				index = i;
			}
		}
		return index;
	}
	final private int getNextUpWhenDown(){
		int index = 0;
		for(int i=1; i<N; i++){
			if(topItems[i].top>topItems[index].top){
				index = i;
			}
		}
		return index;
	}

	final private int getNextUpWhenUp(){
		int index = 0;
		for(int i=1; i<N; i++){
			if(topItems[i].bottom<topItems[index].bottom){
				index = i;
			}
		}
		return index;
	}
	
	@SuppressLint("NewApi")
	private void init(){
		p("init");
		itemRecycler = new ArrayList<View>();
		itemViewList = new ArrayList<View>();
		itemList = new ArrayList<Item>();
		idleItemList = new ArrayList<Item>();
		detector = new GestureDetector(getContext(), gestureListener);
		if(android.os.Build.VERSION.SDK_INT>10){
			scroller = new Scroller(getContext(),null,true);
		}else{
			scroller = new Scroller(getContext(),null);
		}
	}
	
	private void initItems(){
		p("initItems()");
		if(isInitItemsDone || adapter.getCount()==0){
			return;
		}else{
			p("initItems() data loading");
			int num = adapter.getCount();
			for(int i=0; i<num; i++){
				isInitItemsDone = true;
				int colum = getNextDownWhenUp();
				Item item = null;
				if(bottomItems[colum]==null){
					item = obtainItem();
					topItems[colum] = item;
					item.top = 0;
				}else{
					if(bottomItems[colum].bottom>curY+viewHeight){
						break;
					}else{
						item = obtainItem();
						item.top = bottomItems[colum].bottom;
						item.upItem = bottomItems[colum];
						bottomItems[colum].downItem = item;
					}
				}
				itemList.add(item);
				item.v = obtainView(i);
				item.left = colum*itemWidth;
				item.right = item.left+itemWidth;
				item.bottom = item.top+item.v.getMeasuredHeight();
				bottomItems[colum] = item;
				item.v.layout(item.left, item.top, item.right, item.bottom);
				item.index = i;
				p("添加Item"+item.index);
			}
			itemsBottomBound = 0;
			computeBottom();
			if(itemList.size()==adapter.getCount()){
				setBottomConfigs();
			}
		}
	}
	
	final private boolean isAllItemsLoaded(){
		if(adapter.getCount()==0){
			return false;
		}else{
			return itemList.size()==adapter.getCount();
		}
		
	}
	
	/**
	 * @return true表示向下滑动,否则认为是向上滑动(优化回收策略)
	 */
	public boolean isDownWard(){
		return isDownward;
	}
	public boolean isFreshing(){
		return isFreshing;
	}

	/**
	 * @param position
	 * @param extraBound 超出边界在extraBound之内的认为可�?
	 * @return
	 */
	public boolean isItemVisible(int position, int extraBound){
		if(position>=itemList.size()){
			return true;
		}
		Item item = itemList.get(position);
		if(item.bottom==0){
			return true;
		}else{
			return item.isVisible(curY, viewHeight, DEFAULT_EXTRA_SIZE);
		}
	}
	
	public boolean isLoaing(){
		return isLoading;
	}

	private void measureFreshViewIfNotMeasured(int widthMeasureSpec) {
		if(null!=freshView && !isFreshViewMeasured){
			int heightMeasureSpec = MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED);
			freshView.measure(widthMeasureSpec, heightMeasureSpec);
			topBound = headViewTop-freshView.getMeasuredHeight();
			if(freshable){
				scrollTopLimit = topBound;
			}else{
				scrollTopLimit = headViewTop;
			}
			freshView.layout(0, topBound, freshView.getMeasuredWidth(), headViewTop);
			isFreshViewMeasured = true;
		}
	}
	
	private void measureHeadView(int widthMeasureSpec) {
		p("measureHeadView()");
		if(null==headView){
			return;
		}
		
		LayoutParams params = headView.getLayoutParams();
		if(null==headView.getLayoutParams()){
			params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
			headView.setLayoutParams(params);
		}
		int heightSpec = 0;
		if(params.height==LayoutParams.WRAP_CONTENT){
			heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
		}else{
			heightSpec = MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY);
		}
		int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
		headView.measure(widthSpec, heightSpec);
		if(headViewHeight != headView.getMeasuredHeight()){
			headViewHeight = headView.getMeasuredHeight();
			headViewTop = -headViewHeight;
			flingTopLimit = headViewTop;
			topBound = headViewTop-freshView.getMeasuredHeight();
			scrollTopLimit = topBound;
			headView.layout(0, headViewTop, headView.getMeasuredWidth(), 0);
			freshView.layout(0, topBound, freshView.getMeasuredWidth(), headViewTop);
			scrollTo(0, headViewTop);
		}
	}
	
	private void measureLoadMoreViewIfNotMeasured(int widthMeasureSpec) {
		if(null!=loadMoreView && !isLoadMoreViewMeasured){
			int heightMeasureSpec = MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED);
			loadMoreView.measure(widthMeasureSpec, heightMeasureSpec);
			isLoadMoreViewMeasured = true;
		}
	}
	/**
	 * 如果没有滑动到可以触发刷新的位置则返回到刷新不可见位�?
	 * 如果没有滑动到可以触发加载更多的位置则返回到LoadMoreView不可见位�?
	 */
	private void modifyOverBound() {
		if(null!=freshView){
			if(!isFreshing){
				if(curY <= topBound){
					if(null!=stateListener){
						isFreshing = true;
						flingTopLimit = scrollTopLimit;
						stateListener.startFresh();
					}
				}else if(curY<headViewTop && curY>topBound){
					flingTopLimit = headViewHeight;
					scroller.forceFinished(true);
					scroller.startScroll(0, curY, 0, headViewTop-curY, 500);
					postInvalidate();
				}
			}
		}
		if(null!=loadMoreView){
			if(!isLoading && curY+viewHeight >= itemsBottomBound+loadMoreView.getMeasuredHeight()){
				if(null!=stateListener){
					isLoading = true;
					flingBottomLimit = scrollBottomLimit;
					scrollTo(0, flingBottomLimit);
					stateListener.startLoadMore();
				}
			}else if(curY+viewHeight>itemsBottomBound){//loadMoreView可见,动画弹�?
				flingBottomLimit = scrollBottomLimit-loadMoreView.getMeasuredHeight();
				scroller.forceFinished(true);
				scroller.startScroll(0, curY, 0, itemsBottomBound-viewHeight-curY, 500);
				postInvalidate();
			}
		}
	}
	
	final private Item obtainItem(){
		Item item = null;
		if(idleItemList.isEmpty()){
			item =  new Item();
		}else{
			item = idleItemList.get(0);
			item.reset();
			idleItemList.remove(0);
		}
		return item;
	}
	
	
	final private View obtainView(int i) {
		View v = null;
		if(itemRecycler.isEmpty()){
			v = adapter.getView(null,this,i);
			itemViewList.add(v);
			addView(v);
		}else{
			v = itemRecycler.get(0);
			v = adapter.getView(v, this, i);
			v.setVisibility(View.VISIBLE);
			itemRecycler.remove(0);
		}
		v.forceLayout();
		v.measure(childWidthMeasureSpec, childHeightMeasureSpec);
		return v;
	}
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		if(ev.getAction()==MotionEvent.ACTION_DOWN){
			firstFingerY = ev.getY();
			fisrtFingerX = ev.getX();
			detector.onTouchEvent(ev);//让Detector记录Down事件,否则onScroll的最后一次事件是上次的Up事件的位�?
			scroller.forceFinished(true);
		}
		//判断滑动方向为竖直方向则拦截事件
		if(ev.getAction()==MotionEvent.ACTION_MOVE
			&& Math.abs(ev.getY()-firstFingerY)+Math.abs(ev.getX()-fisrtFingerX)>6 
					&& Math.abs(ev.getY()-firstFingerY)>Math.abs(ev.getX()-fisrtFingerX)){
			return true;
		}
		return false;
	}
	
	@Override
	protected void onLayout(boolean change, int l, int t, int r, int b) {
		p("onLayout()");
//		headView.layout(0, headViewTop, viewWidth, 0);
	}
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		p("onMeasure");
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		viewHeight = getMeasuredHeight();
		viewWidth = getMeasuredWidth();
		itemWidth = viewWidth/N;
		childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY);
		childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
		measureFreshViewIfNotMeasured(widthMeasureSpec);
		measureLoadMoreViewIfNotMeasured(widthMeasureSpec);
		measureHeadView(widthMeasureSpec);
		initItems();
	}
	private int scrollDistance;
	
	@Override
	/**
	 * 调用scrollTo(int x, inty)和scrollBy(int x,int y)会执行到这里
	 */
	protected void onScrollChanged(int l, int t, int oldl, int oldt) {
		if(t==oldt) return;
		scrollDistance = t-curY;
		curY = t;
		isDownward = t<oldt;
		if(isDownward){//�?���?
			int topLimit = 0;
			if(isOnFling()){
				topLimit = flingTopLimit;
			}else if(scroller.isFinished()){
				topLimit = scrollTopLimit;
			}
			if(t<topLimit){
				scrollDistance = topLimit-curY;
				curY = topLimit;
				scrollTo(0, curY);
				if(!scroller.isFinished()){
					scroller.forceFinished(true);
				}
			}
		}else{//�?���?
			if(isAllItemsLoaded()){
				int bottomLimit = 0;
				if(isOnFling()){
					bottomLimit = flingBottomLimit;
				}else{
					bottomLimit = scrollBottomLimit;
				}
				if(curY>bottomLimit){
					scrollDistance = bottomLimit-curY;
					curY = bottomLimit;
					if(!scroller.isFinished()){
						scroller.forceFinished(true);
					}
				}
				scrollTo(0, curY);
			}
		}
		if(scrollDistance!=0){
			computeChildVisibity();
		}
		if(null!=stateListener){
			stateListener.onScroll(curY);
		}
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent e) {
		if(e.getAction()==MotionEvent.ACTION_UP){
			modifyOverBound();
		}
		return detector.onTouchEvent(e);
	}
	
	final private void p(String s){
//		System.out.println(s);
	}

	public void setAdapter(Adapter adapter){
		adapter.addObserver(this);
		this.adapter = adapter;
		requestLayout();
	}
	
	public void setCanFresh(boolean b){
		freshable = b;
	}
	
	public void setCanLoad(boolean b){
		loadable = b;
	}
	
	public void setColumn(int n){
		this.N = n;
		topItems = new Item[N];
		bottomItems = new Item[N];
	}
	/**
	 * 下拉刷新显示的View
	 */
	public void setFreshView(View v){
		freshView = v;
		addView(freshView);
	}
	/**
	 * 像ListView那样添加�?��HeadView,不过只能添加一�?
	 * @param headerView
	 */
	public void setHeadView(View v){
		headView = v;
		addView(headView);
	}
	/**
	 * 加载更多显示的View
	 */
	public void setLoadMoreView(View v){
		loadMoreView = v;
		addView(loadMoreView);
	}
	public void setStateListener(StateListener listener){
		this.stateListener = listener;
	}
	public void startFresh(){
		if(!isFreshing){
			scrollTopLimit = topBound;
			scroller.startScroll(0, curY, 0, topBound-curY);
			
			isFreshing = true;
			flingTopLimit = topBound;
			stateListener.startFresh();
			postInvalidate();
		}
	}
	@Override
	public void update(Observable observable, Object data){
		p("update()");
		if(null!=data){
			int position = (Integer) data;
			p("update() fresh item : position="+position);
			if(position<itemList.size()){
				if(isItemVisible(position, DEFAULT_EXTRA_SIZE)){
					Item item = itemList.get(position);
					adapter.getView(item.v, this, position);
				}
			}
		}else{
			switch(adapter.getUpdateMode()){
			case Adapter.MODE_UPDATE_APPEND:
				if(adapter.getCount()!=itemList.size()){
					initBottomConfigs();
					scrollBy(0, 1);//触发onScrollChanged()
				}
				break;
			case Adapter.MODE_UPDATE_ALL:
				p("update() WaterFallAdapter.MODE_UPDATE_ALL");
				reset();
				break;
			}
		}
	}


	private void reset() {
		itemRecycler.clear();
		for(View v : itemViewList){
			v.setVisibility(View.GONE);
			itemRecycler.add(v);
		}
		idleItemList.addAll(itemList);
		itemList.clear();
		isInitItemsDone = false;
		for(int i=0; i<N; i++){
			topItems[i] = null;
			bottomItems[i] = null;
		}
		initBottomConfigs();
		requestLayout();
	}
	
	
}

package com.ilioili.waterfall;

import java.util.Observable;
import android.view.View;
import android.view.ViewGroup;

public abstract class Adapter extends Observable{
	/**
	 * justMeasure如果有图片先不要加载,设置好图片ImageView的LayoutParams即可,
	 * 在return v 之前请调�?v.forceLayout()
	 */
	public abstract View getView(View v,ViewGroup vp, int position);
	
	public abstract int getCount();
	
	/**
	 * @param updateMode
	 */
	public void notifyDataSetChanged(int updateMode){
		this.updateMode = updateMode;
		notifyObservers();
	}
	public void notifyDataSetChanged(){
		updateMode = MODE_UPDATE_ALL;
		notifyObservers();
	}
	public void freshItemIfVisible(int position){
		notifyObservers(Integer.valueOf(position));
	}
	public void notifyDataAdded(){
		notifyObservers();
	}
	@Override
	public boolean hasChanged() {
		return true;
	}
	public int getUpdateMode(){
		return updateMode;
	}
	
	final static public int MODE_UPDATE_ALL = 0;
	final static public int MODE_UPDATE_APPEND = 1;
	private int updateMode = MODE_UPDATE_ALL;
}
package com.ilioili.waterfall;


import android.view.View;

public class Item {
	View v;
	Item upItem;
	Item downItem;
	int index;
	int top;
	int left;
	int right;
	int bottom;
	/**
	 * 是否可见(超出可视边�?000个像素认为不可见,否则认为可见)
	 * @param scrollPosition
	 * @param parentHeight
	 * @return
	 */
	boolean isVisible(int scrollPosition,int parentHeight, int offset){
		if(scrollPosition<=bottom+offset && scrollPosition+parentHeight+offset>=top){
			return true;
		}else{
			return false;
		}
	}
	final void reset() {
		left = 0;
		bottom = 0;
		right = 0;
		bottom = 0;
		upItem = null;
		downItem = null;
		v = null;
		index = 0;
	}
	@Override
	public String toString() {
		return "top:"+top+"&bottom:"+bottom;
	}
	
}
package com.ilioili.waterfall;

public interface StateListener{
	/**
	 * 获取当前滑动的位�?
	 * @param y 当前滑动的到的位�?
	 */
	void onScroll(int y);
	/**
	 * 缓慢滑动
	 */
	void scrollToBottomOfLongestColumn();
	/**
	 * 滑动到最短一列的底部
	 */
	void scrollToBottomOfShortestColumn();
	/**
	 * 滑动到顶部,继续下拉刷新的View就可见了
	 */
	void scrollToTop();
	/**
	 * 开始刷新,刷新完成需调用cancelFreshing()可将刷新的View关掉
	 */
	void startFresh();
	/**
	 *开始加载更多,加载完成后需调用用cancelLoading()可以将加载更多关掉
	 */
	void startLoadMore();
	
	void getPreState(int state);
	
	public static int UP_TO_LOAD_MORE = 1;
	public static int UP_TO_CANCEL_LOAD_MORE = 2;
	public static int UP_TO_FRESH = 3;
	public static int UP_TO_CANCEL_FRESH = 4;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值