Android瀑布流的实现

    最近在学习android的ViewGroup,然后就想弄个例子练练手。看到网上的瀑布流挺有意思的,就尝试着去做。弄了一天终于弄好。

    android的自定义ViewGroup不多说,继承ViewGroup并覆写onLayout和onMeasure方法。在onLayout方法中设置child的布局位置,在onMeasure中测量child的宽高。以下是瀑布流的实现代码:(WaterFall写成WaterFlow,  = =!....)

package com.example.waterflowdemo;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

public class WaterFlow extends ViewGroup implements View.OnClickListener, OnGestureListener {
	
	private static final String TAG = WaterFlow.class.getSimpleName();
	
	private static final int MARGIN = 2;
	
	private Context context;
	
	private GestureDetector gestureDetector;
	
	/*
	 * this view group's height
	 */
	private int viewGroupHeight = 0;
	
	/*
	 * position needed to scroll
	 */
	private float position = 0;
	
	/*
	 * last down position
	 */
	private float downPosition = 0;
	
	private static final int COLUMNS = 2;
	
	public WaterFlow(Context context) {
		super(context);
		Log.d(TAG, "WaterFlow constructor");
		this.context = context;
		init();
	}

	public WaterFlow(Context context, AttributeSet attrs) {
		super(context, attrs);
		Log.d(TAG, "WaterFlow constructor");
		this.context = context;
		init();
	}
	
	private void init() {
		gestureDetector = new GestureDetector(context, this);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		Log.d(TAG, "WaterFlow.onLayout()");
		final int width = r - l;
		final int height = b - t;
		
		final int childCount = getChildCount();
		
		int y1 = t;
		int y2 = t;
		
		for(int i = 0; i < childCount; i ++) {
			final View child = getChildAt(i);
			child.setTag(i);
			child.setOnClickListener(this);
			final int childWidth = child.getMeasuredWidth();
			final int childHeight = child.getMeasuredHeight();
//			Log.d(TAG, "childWidth: " + childWidth + ", childHeight: " + childHeight);
			double scale = ((double)childWidth) / (width / COLUMNS);
//			Log.d(TAG, "scale: " + scale);
			final int w = width / COLUMNS;
			int h = 0;
			if(scale != 0) {
				h = (int) (childHeight / scale);
			} else {
				h = childHeight;
			}
//			Log.d(TAG, "w: " + w + ", h: " + h);
			if(y1 <= y2) {
				child.layout(l + MARGIN, y1 + MARGIN, l + w - MARGIN, y1 + h - MARGIN);
				y1 += h;
//				Log.d(TAG, "y1: " + y1);
			} else {
				child.layout(l + w + MARGIN, y2 + MARGIN, r - MARGIN, y2 + h - MARGIN);
				y2 += h;
//				Log.d(TAG, "y2: " + y2);
			}
		}
		viewGroupHeight = y1 > y2 ? y1 : y2;
		Log.d(TAG, "ViewGroupHeight: " + viewGroupHeight);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		Log.d(TAG, "WaterFlow.onMeasure()");
		final int childCount = getChildCount();
		int arg0 = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED);
		int arg1 = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED);
		for(int i = 0; i < childCount; i ++) {
			View child = getChildAt(i);
			if(child.getVisibility() != View.GONE) {
				child.measure(arg0, arg1);
			}
		}
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int height = getMeasuredHeight();
		if(viewGroupHeight <= height) {
			return false;
		}
		switch(event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			downPosition = event.getRawY();
			break;
		case MotionEvent.ACTION_MOVE:
			float transLength = event.getRawY() - downPosition;
			Log.d(TAG, "trans: " + transLength);
			if(transLength != 0) {
				position -= transLength;
				if(position < 0) {
					position = 0;
				} else if(position > viewGroupHeight - height) {
					position = viewGroupHeight - height;
				}
				Log.d(TAG, "position: " + position);
				this.scrollTo(0, (int) position);
			}
			downPosition = event.getRawY();
			break;
			default:
		}
		return gestureDetector.onTouchEvent(event);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		
		switch(ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			onTouchEvent(ev);
			return false;
		case MotionEvent.ACTION_MOVE:
			return true;
		case MotionEvent.ACTION_UP:
			return false;
		}
		return false;
	}

	@Override
	public void onClick(View v) {
		int i = (Integer) v.getTag();
		Toast.makeText(context, "Number " + i, Toast.LENGTH_SHORT).show();
	}

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

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {
		Log.d(TAG, "onFiling");
		return false;
	}

	@Override
	public void onLongPress(MotionEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
		Log.d(TAG, "onScroll");
		return true;
	}

	@Override
	public void onShowPress(MotionEvent e) {
		// TODO Auto-generated method stub
		
	}

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

}

onTouchEvent处理了瀑布流的滚动。如果做效果好一点可以添加手势操作,在onFiling中加入快速滑动等。可以在onTouchEvent中将MotionEvent交由GestureDetector处理。onClick响应了子view的点击事件,在这里只是简单地弹出一个toast并标注是第几个。

解决onTouch和onClick的冲突
昨天在网上看个一哥们的博文,看到了android关于onClick、onLongClick和onTouch的机制,在此多谢那位兄弟了。onClick是在onTouch中的MotionEvent.ACTION_DWON和MotionEvent.ACTION_UP中处理,在UP中会判断touch位置是否改变,touch的位置是否还在同一个view上,然后决定是否响应onClick时间;onLongClick是直接在MotionEvent.ACTION_DOWN中判断,当超过一定时间后就会响应onLongClick。所以在 onIntercepTouchEvent 中在DOWN中直接执行一次本view的onTouchEvent,然后返回false;在MOVE中返回true由本view拦截消耗事件;在UP中返回false将事件传递到子view中。

下面是测试的Activity代码,
package com.example.waterflowdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.graphics.Color;
import android.view.Menu;
import android.view.ViewGroup;
import android.widget.ImageView;

public class MainActivity extends Activity {
	
	private static final String TAG = "MainActivity";
	
	private WaterFlow waterFlow;
	
	private AddViewThread addViewThread;
	
	static Handler handler = new Handler() {
		private int index = 0;

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			if(msg.what == 0 && msg.obj != null && msg.obj instanceof WaterFlow) {
				WaterFlow waterFlow = (WaterFlow) msg.obj;
				ImageView image = new ImageView(waterFlow.getContext());
				image.setImageResource(R.drawable.d1 + (index % 22));
				image.setBackgroundColor(Color.GRAY);
				ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
				lp.setMargins(2, 2, 2, 2);
				image.setLayoutParams(lp);
				image.setTag(index);
				waterFlow.addView(image);
				index ++;
			}
		}
		
	};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		waterFlow = (WaterFlow) findViewById(R.id.waterflow);
		setContentView(waterFlow = (WaterFlow) findViewById(R.id.waterflow));
		
		(addViewThread = new AddViewThread()).start();
	}
	
	
	@Override
	protected void onDestroy() {
		addViewThread.flag = false;
		super.onDestroy();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	
	class AddViewThread extends Thread {
		
		boolean flag = true;
		
		public void run() {
			while(flag) {
				Message msg = handler.obtainMessage(0, waterFlow);
				handler.sendMessage(msg);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

}

最后图片过多的话,应该就会产生OOM的问题。这个就要做图片的及时回收了,这个可以参照google官方的那个Demo解决。以上就这么多了,接下来去研究研究ViewGroup和动画结合的效果了。









你可能感兴趣的:(android,瀑布流,ViewGroup)