package com.cmeiyuan.widget; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; import com.nineoldandroids.animation.ValueAnimator; import com.nineoldandroids.animation.ValueAnimator.AnimatorUpdateListener; public class MenuScrollView extends FrameLayout { private static final float COEFFICIENT = 0.5f; private static final int GESTURE_SLIDE_NONE = 0; private static final int GESTURE_SLIDE_LEFT = 1; private static final int GESTURE_SLIDE_RIGHT = 2; // 显示菜单数量 private float mShowMenuCount = 4f; // 是否正在被拖动 private boolean mIsBeingDragged = false; // 上一次触摸的X点 private float mLastTouchX; // 激知点 private int mActivePointerId = -1; // 复位动画 private ValueAnimator mResumeAnimator; // 位移动画时间 private long mScrollDuration = 200; // 位移动画 private ValueAnimator mScrollAnimator; // 插值器1 private Interpolator mInterpolator1 = new AccelerateDecelerateInterpolator(); // 插值器2 private Interpolator mInterpolator2 = new LinearInterpolator(); // 是否有弹性 private boolean mElasticityEnable = false; protected int mSlideGesture = GESTURE_SLIDE_NONE; // 手势 private GestureDetector mGestureDetector; // menu页监听器 private OnPageChangeListener mListener; public MenuScrollView(Context context) { super(context); init(context); } public MenuScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MenuScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public void setShowMenuCount(float count) { this.mShowMenuCount = count; } public float getShowMenuCount() { return mShowMenuCount; } public OnPageChangeListener getOnPageChangeListener() { return mListener; } public void setOnPageChangeListener(OnPageChangeListener l) { this.mListener = l; } private void init(Context context) { mResumeAnimator = new ValueAnimator(); mResumeAnimator.setInterpolator(mInterpolator1); mResumeAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (Integer) animation.getAnimatedValue(); scrollTo(value, 0); } }); mScrollAnimator = new ValueAnimator(); mScrollAnimator.setInterpolator(mInterpolator2); mScrollAnimator.setDuration(mScrollDuration); mScrollAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (Integer) animation.getAnimatedValue(); scrollTo(value, 0); } }); mGestureDetector = new GestureDetector(context, mGistureListener); } private SimpleOnGestureListener mGistureListener = new SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { Log.d("MenuScrollView", "onFling:" + velocityX); if (velocityX > 0) { // mSlideGesture = GESTURE_SLIDE_RIGHT; } else if (velocityX < 0) { // mSlideGesture = GESTURE_SLIDE_LEFT; } else { // mSlideGesture = GESTURE_SLIDE_NONE; } return true; } }; public void showPage(int index) { int curX = getScrollX(); int endX = 0; if (index == 1) { endX = getNormalOverScrollX(); } // int centerX = 0; // int width = getWidth(); // // if(endX > curX){ // centerX = endX + (int) (0.2f * width); // }else{ // centerX = endX - (int) (0.2f * width); // } // mScrollAnimator.setIntValues(curX, centerX, endX); mScrollAnimator.setIntValues(curX, endX); mScrollAnimator.start(); } public int getCurrentPage() { return getScrollX() / getWidth(); } public int getMaxPage() { float showCount = mShowMenuCount; return (int) Math.ceil((double) getChildCount() / (double) showCount); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { // mIsBeingDragged = false; mActivePointerId = event.getPointerId(0); mLastTouchX = event.getX(); } if (event.getAction() == MotionEvent.ACTION_MOVE) { float eventX = event.getX(); int distance = (int) (mLastTouchX - eventX); // 有一定移动距离才拦截move事件,否则不拦截,让子view处理 if (Math.abs(distance) > 20) { mIsBeingDragged = true; } } return mIsBeingDragged; } @Override public boolean onTouchEvent(MotionEvent event) { mGestureDetector.onTouchEvent(event); switch (event.getAction() & MotionEvent.ACTION_MASK) { // 第一个手指触摸屏幕时 case MotionEvent.ACTION_DOWN: mActivePointerId = event.getPointerId(0); mLastTouchX = event.getX(); break; // 另一个手指触摸时 case MotionEvent.ACTION_POINTER_DOWN: int index = event.getActionIndex(); mLastTouchX = event.getX(index); mActivePointerId = event.getPointerId(index); break; // 一个手指离开屏幕,但至少还有一个手指在触摸屏幕 case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(event); if (mActivePointerId != -1) { mLastTouchX = event.getX(event .findPointerIndex(mActivePointerId)); } break; // 手指在屏幕上移动时 case MotionEvent.ACTION_MOVE: if(mElasticityEnable){ int activePointerIndex = event.findPointerIndex(mActivePointerId); if (activePointerIndex == -1) { break; } float eventX = event.getX(activePointerIndex); int distance = (int) (mLastTouchX - eventX); mIsBeingDragged = true; scrollBy(calculateScrollX(distance), 0); mLastTouchX = eventX; } break; // 最后一个手指离开屏幕时 case MotionEvent.ACTION_UP: mIsBeingDragged = false; mActivePointerId = -1; playResumeAnimation(); break; } return true; } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = ev.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastTouchX = (int) ev.getX(newPointerIndex); mActivePointerId = ev.getPointerId(newPointerIndex); } } protected int getCloseToPage() { int scrollWidth = getNormalOverScrollX(); if (scrollWidth > scrollWidth / 2) { return 1; } else { return 0; } } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (l > oldl) { mSlideGesture = GESTURE_SLIDE_LEFT; } else { mSlideGesture = GESTURE_SLIDE_RIGHT; } } private void playResumeAnimation() { ValueAnimator animator = mResumeAnimator; int scrollX = getScrollX(); int startX = 0; int endX = getNormalOverScrollX(); if (scrollX < startX) { animator.setIntValues(scrollX, startX); animator.setDuration(200); animator.start(); } else if (scrollX > startX && scrollX < endX) { if (mSlideGesture == GESTURE_SLIDE_NONE) { showPage(getCloseToPage()); } else { int index = 0; if (mSlideGesture == GESTURE_SLIDE_LEFT) { index = getCurrentPage() + 1; int maxPage = getMaxPage() - 1; index = index > maxPage ? maxPage : index; } else { index = getCurrentPage() - 1; index = index < 0 ? 0 : index; } showPage(index); if (getOnPageChangeListener() != null) { getOnPageChangeListener().onPageChanged(index); } mSlideGesture = GESTURE_SLIDE_NONE; } } else if (scrollX > endX) { animator.setIntValues(scrollX, endX); animator.setDuration(200); animator.start(); } } private int calculateScrollX(int distance) { int result = distance; int overScroll = 0; // 朝左滑 if (distance < 0) { int scrollLeft = getOverScrollLeft(); if (scrollLeft < 0) { overScroll = Math.abs(scrollLeft); } } // 朝右滑 if (distance > 0) { int scrollRight = getOverScrollRight(); if (scrollRight > 0) { overScroll = Math.abs(scrollRight); } } if (overScroll > 0 && overScroll < getWidth()) { float overScrollF = overScroll; float totalWidthF = getMeasuredWidth(); float scrollPercent = overScrollF / totalWidthF; float percent = 1 - scrollPercent; result = (int) (distance * percent * COEFFICIENT); } return result; } protected int getOverScrollLeft() { return getScrollX(); } protected int getOverScrollRight() { return getScrollX() - getNormalOverScrollX(); } protected int getNormalOverScrollX() { int result = getChildWidth() - getMeasuredWidth(); return result < 0 ? 0 : result; } protected boolean overScrollLeft() { return getScrollX() < 0; } protected boolean overScrollRight() { return getScrollX() > getChildWidth() - getMeasuredWidth(); } private int getChildWidth() { int childWidth = 0; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getVisibility() == View.VISIBLE) { childWidth += child.getMeasuredWidth(); } } return childWidth; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); int childCount = getChildCount(); float showMenuCount = mShowMenuCount; if (childCount == 0) { return; } int pLeft = getPaddingLeft(); int pTop = getPaddingTop(); int pRight = getPaddingRight(); int pBottom = getPaddingBottom(); int width = getMeasuredWidth(); int height = getMeasuredHeight(); int childWidth = (int) ((width - pLeft - pRight) / showMenuCount); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); child.layout(pLeft + (i * childWidth), pTop, pLeft + (i + 1) * childWidth, height - pBottom); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { float showMenuCount = mShowMenuCount; int width = 0; int height = 0; int maxHeight = 0; int maxWidth = 0; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); float widthPercent = 1f / showMenuCount; int childWidthSize = (int) (widthPercent * widthSize); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( childWidthSize, MeasureSpec.EXACTLY); child.measure(childWidthMeasureSpec, heightMeasureSpec); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); } if (widthMode == MeasureSpec.UNSPECIFIED) { width = widthSize; } else if (widthMode == MeasureSpec.AT_MOST) { width = Math.min(widthSize, maxWidth); } else if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } if (heightMode == MeasureSpec.UNSPECIFIED) { height = maxHeight; height += getPaddingTop() + getPaddingBottom(); } else if (heightMode == MeasureSpec.AT_MOST) { height = Math.min(heightSize, maxHeight); height += getPaddingTop() + getPaddingBottom(); } else if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } setMeasuredDimension(width, height); } public interface OnPageChangeListener { public void onPageChanged(int index); } }