您的位置:首页 > 其它

自定义ViewGroup (支持margin,gravity以及水平,垂直排列,滑动和点击事件)

2016-02-05 15:36 489 查看
自定义ViewGroup (1)支持margin,gravity以及水平,垂直排列


自定义ViewGroup (2)支持滑动,并处理多指触摸可能产生的跳动问题


自定义ViewGroup (3) 与子View之间 Touch Event的拦截与处理

以下代码通过上面三篇博客整理而成

package com.example.touch;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

public class CustomViewGroup extends ViewGroup {
private static final String TAG = CustomViewGroup.class.getSimpleName();

private int desiredWidth;
private int desiredHeight;

private Orientation orientation;

private float lastX;
private float lastY;

private float downX;
private float downY;

private Scroller mScroller;
private VelocityTracker velocityTracker;

private int maxFlingVelocity;
private int minFlingVelocity;
private int mTouchSlop;

private int pointerId = 0;

public CustomViewGroup(Context context) {
this(context, null);
}

public CustomViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}

private void init(Context context,  AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Orientation);
orientation = Orientation.valueOf(ta.getInt(R.styleable.Orientation_orientation, 0));
ta.recycle();
if (BuildConfig.DEBUG) {
Log.d(TAG, "init(), orientation = " + orientation.getValue());

}
mScroller = new Scroller(context);
maxFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
minFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mTouchSlop = ViewConfiguration.getTouchSlop();

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
desiredWidth = 0;
desiredHeight = 0;

int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
if (orientation == Orientation.HORIZONTAL) {
desiredWidth += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
desiredHeight += Math.max(desiredHeight, child.getMeasuredHeight() + lp.topMargin
+ lp.bottomMargin);
} else {
desiredWidth += Math.max(desiredWidth, child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin);
desiredHeight += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
}

}
}

desiredWidth += getPaddingLeft() + getPaddingRight();
desiredHeight += getPaddingTop() + getPaddingBottom();

desiredWidth = Math.max(desiredWidth, getSuggestedMinimumWidth());
desiredHeight = Math.max(desiredHeight, getSuggestedMinimumHeight());
setMeasuredDimension(resolveSize(desiredWidth, widthMeasureSpec),
resolveSize(desiredHeight, heightMeasureSpec));
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int parentLeft = getPaddingLeft();
final int parentTop = getPaddingTop();
final int parentRight = r - l - getPaddingRight();
final int parentBottom = b - t - getPaddingBottom();
if (BuildConfig.DEBUG) {
Log.d(TAG,  "parentLeft = " + DisplayUtils.px2dip(getContext(), parentLeft) + ", parentTop = " + parentTop
+ ", parentRight = " + parentRight + ", parentBottom = " + parentBottom);
}

int left = parentLeft;
int top = parentTop;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final int gravity = lp.gravity;
final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

if (orientation == Orientation.HORIZONTAL) {
left += lp.leftMargin;
top += parentTop + lp.topMargin;
if (gravity != -1) {
switch (verticalGravity) {
case Gravity.TOP:
break;
case Gravity.CENTER_VERTICAL:
top = parentTop + (parentBottom - parentTop - childWidth) / 2
+ lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
top = parentBottom - childHeight - lp.bottomMargin;
break;

default:
break;
}
}

if (BuildConfig.DEBUG) {
Log.d(TAG, "child[width: " + childWidth
+ ", height: " + childHeight + "]");
Log.d(TAG, "child[left: " + left + ", top: "
+ top + ", right: " + (left + childWidth)
+ ", bottom: " + (top + childHeight));
}

child.layout(left, top, left + childWidth, top + childHeight);
left += childWidth + lp.rightMargin;
} else {
// layout vertical, and only consider horizontal gravity

left = parentLeft + lp.leftMargin;
top += lp.topMargin;
switch (horizontalGravity) {
case Gravity.LEFT:
break;
case Gravity.CENTER_HORIZONTAL:
left = parentLeft
+ (parentRight - parentLeft - childWidth) / 2
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
left = parentRight - childWidth - lp.rightMargin;
break;
}
child.layout(left, top, left + childWidth, top + childHeight);
top += childHeight + lp.bottomMargin;
}
}
}

}

@Override
protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}

@Override
public android.view.ViewGroup.LayoutParams generateLayoutParams(
AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}

public static class LayoutParams extends MarginLayoutParams {
public int gravity = -1;

public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
}

public LayoutParams(int width, int height) {
super(width, height);
}

public LayoutParams(android.view.ViewGroup.LayoutParams source) {
super(source);
}

public LayoutParams(MarginLayoutParams source) {
super(source);
}

public LayoutParams(int width, int height, int gravity) {
this(width, height);
this.gravity = gravity;
}

}

public static enum Orientation {
HORIZONTAL(0), VERTICAL(1);

private int value;
private Orientation(int value) {
this.value = value;
}

public int getValue() {
return value;
}

public static Orientation valueOf(int value) {
switch (value) {
case 0:
return HORIZONTAL;
case 1:
return VERTICAL;

default:
throw new RuntimeException("0 -> HORIZONTAL, 1 -> VERTICAL");
}
}

}

@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
if (BuildConfig.DEBUG) {
Log.d(TAG, "onTouchEvent(), action = " + action);
}
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
switch (action) {
case MotionEvent.ACTION_DOWN:
pointerId = event.getPointerId(0);
final int pointerIndex = event.findPointerIndex(pointerId);
lastX = event.getX(pointerIndex);
lastY = event.getY(pointerIndex);
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;

case MotionEvent.ACTION_MOVE:
final float  moveX = event.getX();
final float  moveY = event.getY();
moveBy((int) (lastX - moveX), (int) (lastY - moveY));
lastX = moveX;
lastY = moveY;
break;
case MotionEvent.ACTION_UP:
final int pi = event.findPointerIndex(pointerId);
if ((isClickable() || isLongClickable())
&& ((event.getX(pi) - downX) < mTouchSlop
|| (event.getY(pi) - downY) < mTouchSlop)) {
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
requestFocus();
}
if (event.getEventTime() - event.getDownTime() >= ViewConfiguration.getLongPressTimeout()
&& isLongClickable()) {
performLongClick();
} else {
performClick();
}

} else {
velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);
float velocityX = velocityTracker.getXVelocity();
float velecityY = velocityTracker.getYVelocity();
computeMove(-velocityX, -velecityY);
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
solvePointerUp(event);
break;

default:
break;
}
return true;
}

private void moveBy(int deltaX, int deltaY) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "moveBy(), deltaX = " + deltaX + ", deltaY = " + deltaY);
}
if (orientation == Orientation.HORIZONTAL) {
if (Math.abs(deltaX) >= Math.abs(deltaY)) {
scrollBy(deltaX, 0);
}
} else {
if (Math.abs(deltaY) >= Math.abs(deltaX)) {
scrollBy(0, deltaY);
}
}

}

private void computeMove(float velocityX, float velocityY) {
if (orientation == Orientation.HORIZONTAL) {
final int scrollX = getScrollX();
int maxX = desiredWidth - getWidth();
if (scrollX > maxX) {
mScroller.startScroll(scrollX, 0, maxX - scrollX , 0);
invalidate();
} else if (scrollX < 0) {
mScroller.startScroll(scrollX, 0, -scrollX , 0);
invalidate();
} else if (Math.abs(velocityX) >= minFlingVelocity && maxX > 0) {
mScroller.fling(scrollX, 0, (int) velocityX, 0, 0, maxX, 0, 0);
invalidate();
}
} else {
int scrollY = getScrollY();
int maxY = desiredHeight - getHeight();
if (scrollY > maxY) {
mScroller.startScroll(0, scrollY, 0, maxY - scrollY);
invalidate();
} else if (scrollY < 0) {
mScroller.startScroll(0, scrollY, 0, -scrollY);
invalidate();
} else if (Math.abs(velocityY) >= minFlingVelocity && maxY > 0) {
mScroller.fling(0, scrollY, 0, (int) velocityY, 0, 0, 0, maxY);
invalidate();
}
}

}

@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
if (orientation == Orientation.HORIZONTAL) {
scrollTo(mScroller.getCurrX(), 0);
postInvalidate();
} else {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
if (BuildConfig.DEBUG) {
Log.d(TAG, "onInterceptTouchEvent(), action = " + action);
}
if (action == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// 该事件可能不是我们的
return true;
}
boolean isIntercept = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
isIntercept = !mScroller.isFinished();
pointerId = ev.getPointerId(0);
downX = lastX = ev.getX();
downY = lastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = ev.findPointerIndex(pointerId);
float moveX = ev.getX(pointerIndex);
float moveY = ev.getY(pointerIndex);
if (orientation == Orientation.HORIZONTAL) {
if (Math.abs(lastX = moveX) >= mTouchSlop) {
isIntercept = true;
}
} else {
if (Math.abs(lastY - moveY) >= mTouchSlop) {
isIntercept = true;
}
}
if (isIntercept) {
lastX = moveX;
lastY = moveY;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// 这是触摸的最后一个事件,无论如何都不会拦截
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
break;
case MotionEvent.ACTION_POINTER_UP:
solvePointerUp(ev);
break;
default:
break;
}
return isIntercept;
}

private void solvePointerUp(MotionEvent event) {
// 获取离开屏幕的手指的索引
int pointerIndexLeave = event.getActionIndex();
int pointerIdLeave = event.getPointerId(pointerIndexLeave);
if (pointerId == pointerIdLeave) {
// 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker
int reIndex = pointerIndexLeave == 0 ? 1 : 0;
pointerId = event.getPointerId(reIndex);
// 调整触摸位置,防止出现跳动
lastX = event.getX(reIndex);
lastY = event.getY(reIndex);
if (velocityTracker != null)
velocityTracker.clear();
}
}

}


attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

<attr name="layout_gravity">

<!-- Push object to the top of its container, not changing its size. -->
<flag name="top" value="0x30" />
<!-- Push object to the bottom of its container, not changing its size. -->
<flag name="bottom" value="0x50" />
<!-- Push object to the left of its container, not changing its size. -->
<flag name="left" value="0x03" />
<!-- Push object to the right of its container, not changing its size. -->
<flag name="right" value="0x05" />
<!-- Place object in the vertical center of its container, not changing its size. -->
<flag name="center_vertical" value="0x10" />
<!-- Place object in the horizontal center of its container, not changing its size. -->
<flag name="center_horizontal" value="0x01" />
</attr>

<declare-styleable name="CustomViewgroup">
<attr name="layout_gravity" />
</declare-styleable>

<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>

<declare-styleable name="Orientation">
<attr name="orientation" />
</declare-styleable>

</resources>


同理,可重写LinearLayout实现垂直滑动
package com.example.touch;

import com.example.touch.CustomViewGroup.LayoutParams;
import com.example.touch.CustomViewGroup.Orientation;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.Scroller;

public class CustomLinearLayout extends LinearLayout {
private static String TAG = CustomLinearLayout.class.getSimpleName();

private int desiredWidth;
private int desiredHeight;

private float lastX;
private float lastY;

private float downX;
private float downY;

private Scroller mScroller;
private VelocityTracker velocityTracker;

private int maxFlingVelocity;
private int minFlingVelocity;
private int mTouchSlop;

private int pointerId = 0;

public CustomLinearLayout(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}

public CustomLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CustomLinearLayout(Context context) {
this(context, null);
}

private void init(Context context,  AttributeSet attrs) {
mScroller = new Scroller(context);
maxFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
minFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mTouchSlop = ViewConfiguration.getTouchSlop();

}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
desiredWidth = 0;
desiredHeight = 0;

int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
desiredWidth += child.getMeasuredWidth() + lp.leftMargin+ lp.rightMargin;
desiredHeight += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

}
}

desiredWidth += getPaddingLeft() + getPaddingRight();
desiredHeight += getPaddingTop() + getPaddingBottom();

}

@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
if (BuildConfig.DEBUG) {
Log.d(TAG, "onTouchEvent(), action = " + action);
}
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
switch (action) {
case MotionEvent.ACTION_DOWN:
pointerId = event.getPointerId(0);
final int pointerIndex = event.findPointerIndex(pointerId);
lastX = event.getX(pointerIndex);
lastY = event.getY(pointerIndex);
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;

case MotionEvent.ACTION_MOVE:
final float  moveX = event.getX();
final float  moveY = event.getY();
moveBy((int) (lastX - moveX), (int) (lastY - moveY));
lastX = moveX;
lastY = moveY;
break;
case MotionEvent.ACTION_UP:
final int pi = event.findPointerIndex(pointerId);
if ((isClickable() || isLongClickable())
&& ((event.getX(pi) - downX) < mTouchSlop
|| (event.getY(pi) - downY) < mTouchSlop)) {
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
requestFocus();
}
if (event.getEventTime() - event.getDownTime() >= ViewConfiguration.getLongPressTimeout()
&& isLongClickable()) {
performLongClick();
} else {
performClick();
}

} else {
velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);
float velocityX = velocityTracker.getXVelocity();
float velecityY = velocityTracker.getYVelocity();
computeMove(-velocityX, -velecityY);
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
solvePointerUp(event);
break;

default:
break;
}
return true;
}

private void moveBy(int deltaX, int deltaY) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "moveBy(), deltaX = " + deltaX + ", deltaY = " + deltaY);
}
if (Math.abs(deltaY) >= Math.abs(deltaX)) {
scrollBy(0, deltaY);
}

}

private void computeMove(float velocityX, float velocityY) {
int scrollY = getScrollY();
int maxY = desiredHeight - getHeight();
if (scrollY > maxY) {
mScroller.startScroll(0, scrollY, 0, maxY - scrollY);
invalidate();
} else if (scrollY < 0) {
mScroller.startScroll(0, scrollY, 0, -scrollY);
invalidate();
} else if (Math.abs(velocityY) >= minFlingVelocity && maxY > 0) {
mScroller.fling(0, scrollY, 0, (int) velocityY, 0, 0, 0, maxY);
invalidate();
}

}

@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}

private void solvePointerUp(MotionEvent event) {
// 获取离开屏幕的手指的索引
int pointerIndexLeave = event.getActionIndex();
int pointerIdLeave = event.getPointerId(pointerIndexLeave);
if (pointerId == pointerIdLeave) {
// 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker
int reIndex = pointerIndexLeave == 0 ? 1 : 0;
pointerId = event.getPointerId(reIndex);
// 调整触摸位置,防止出现跳动
lastX = event.getX(reIndex);
lastY = event.getY(reIndex);
if (velocityTracker != null)
velocityTracker.clear();
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
if (BuildConfig.DEBUG) {
Log.d(TAG, "onInterceptTouchEvent(), action = " + action);
}
if (action == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// 该事件可能不是我们的
return true;
}
boolean isIntercept = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
isIntercept = !mScroller.isFinished();
pointerId = ev.getPointerId(0);
downX = lastX = ev.getX();
downY = lastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = ev.findPointerIndex(pointerId);
float moveX = ev.getX(pointerIndex);
float moveY = ev.getY(pointerIndex);
if (Math.abs(lastY - moveY) >= mTouchSlop) {
isIntercept = true;
}
if (isIntercept) {
lastX = moveX;
lastY = moveY;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// 这是触摸的最后一个事件,无论如何都不会拦截
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
break;
case MotionEvent.ACTION_POINTER_UP:
solvePointerUp(ev);
break;
default:
break;
}
return isIntercept;
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: