最近在学习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和动画结合的效果了。