自定义ViewGroup (支持margin,gravity以及水平,垂直排列,滑动和点击事件)
2016-02-05 15:36
489 查看
自定义ViewGroup (1)支持margin,gravity以及水平,垂直排列
以下代码通过上面三篇博客整理而成
attrs.xml
同理,可重写LinearLayout实现垂直滑动
自定义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; } }
相关文章推荐
- hdu2850 2010.3.6
- eclipse 如何配制项目编译JDK版本
- 处理错误TypeError: initial_value must be unicode or None, not str
- SVProgressHUD控件使用 进度条
- hdu2863 2010.3.6
- 简单探讨可牛影像软件中具有肤质保留功能的磨皮算法及其实现细节
- Django 笔记 windows 安装PIP 以及 更新django
- Django 笔记 windows 安装PIP 以及 更新django
- 可以跟亲戚合作一个行业 但不能合伙一起做
- hdu3029 2010.3.6
- 适配模式
- Ceph Calamari 安装(Ubuntu14.04)
- hdu1041 2010.3.6
- hdu2275 2010.3.6
- 好用的排名函数~ROW_NUMBER(),RANK(),DENSE_RANK() 三兄弟
- Meteor
- hdu1754 2010.3.6
- hdu1753 2010.3.6
- 黄迪明9.11
- 答大一学生:英语成绩不好,能有多大发展空间