最近在学习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的冲突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(); } } } } }