package com.roboo.like.netease.view; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.Scroller; public class ScrollerViewGroup extends ViewGroup { private Scroller mScroller; private int mCurrentScreen; // 两种状态: 是否处于滑屏状态 private static final int TOUCH_STATE_REST = 0; // 什么都没做的状态 private static final int TOUCH_STATE_SCROLLING = 1; // 开始滑屏的状态 private int mTouchState = TOUCH_STATE_REST; // 默认是什么都没做的状态 // 处理触摸事件 ~ public static int SNAP_VELOCITY = 600; // 最小的滑动速率 private int mTouchSlop = 0; // 最小滑动距离,超过了,才认为开始滑动 private float mLastionMotionX = 0; // 记住上次触摸屏的位置 private static int mWidth; private static int mHeight; // 处理触摸的速率 private VelocityTracker mVelocityTracker = null; /** * 触发滚动到下一屏的距离 */ private int mToggleScrollDistance; public ScrollerViewGroup(Context context) { this(context, null); } public ScrollerViewGroup(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ScrollerViewGroup(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { this.mToggleScrollDistance = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mHeight = getResources().getDisplayMetrics().heightPixels; mWidth = getResources().getDisplayMetrics().widthPixels; this.mScroller = new Scroller(getContext()); LinearLayout firstLinear, secondLinear, thirdLinear; firstLinear = new LinearLayout(getContext()); firstLinear.setBackgroundColor(Color.parseColor("#88FF2340")); secondLinear = new LinearLayout(getContext()); secondLinear.setBackgroundColor(Color.parseColor("#8800FF30")); thirdLinear = new LinearLayout(getContext()); thirdLinear.setBackgroundColor(Color.parseColor("#880000FF")); this.addView(firstLinear); this.addView(secondLinear); this.addView(thirdLinear); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); for (int i = 0; i < getChildCount(); i++) { getChildAt(i).measure(mWidth, mHeight); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 0; i < getChildCount(); i++) { getChildAt(i).layout(mWidth * i, 0, mWidth * (i + 1), mHeight); // getChildAt(i).layout(160*i, 0, 160*(i+1), mHeight); } } @Override public void computeScroll() { super.computeScroll(); // 如果返回true,表示动画还没有结束 // 因为前面startScroll,所以只有在startScroll完成时 才会为false if (mScroller.computeScrollOffset()) { // 产生了动画效果,根据当前值 每次滚动一点 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); // 此时同样也需要刷新View ,否则效果可能有误差 postInvalidate(); } } public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); // 获得VelocityTracker对象,并且添加滑动对象 if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); // 触摸点 float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 如果屏幕的动画还没结束,你就按下了,我们就结束上一次动画,即开始这次新ACTION_DOWN的动画 if (mScroller != null) { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } } mLastionMotionX = x; // 记住开始落下的屏幕点 break; case MotionEvent.ACTION_MOVE: int detaX = (int) (mLastionMotionX - x); // 每次滑动屏幕,屏幕应该移动的距离 scrollBy(detaX, 0);// 开始缓慢滑屏咯。 detaX > 0 向右滑动 , detaX < 0 向左滑动 , mLastionMotionX = x; break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000); // 计算速率 int velocityX = (int) velocityTracker.getXVelocity(); // 滑动速率达到了一个标准(快速向右滑屏,返回上一个屏幕) 马上进行切屏处理 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { snapToScreen(mCurrentScreen - 1); } // 快速向左滑屏,返回下一个屏幕) else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < (getChildCount() - 1)) { snapToScreen(mCurrentScreen + 1); } // 以上为快速移动的 ,强制切换屏幕 else { // 我们是缓慢移动的,因此先判断是保留在本屏幕还是到下一屏幕 snapToDestination(); } // 回收VelocityTracker对象 if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } // 修正mTouchState值 mTouchState = TOUCH_STATE_REST; break; case MotionEvent.ACTION_CANCEL: mTouchState = TOUCH_STATE_REST; break; } return true; } // //我们是缓慢移动的,因此需要根据偏移值判断目标屏是哪个? private void snapToDestination() { // 当前的偏移位置 int scrollX = getScrollX(); int scrollY = getScrollY(); // 判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕 // 直接使用这个公式判断是哪一个屏幕 前后或者自己 // 判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕 // 这样的一个简单公式意思是:假设当前滑屏偏移值即 scrollCurX 加上每个屏幕一半的宽度,除以每个屏幕的宽度就是 // 我们目标屏所在位置了。 假如每个屏幕宽度为320dip, 我们滑到了500dip处,很显然我们应该到达第二屏 int destScreen = (getScrollX() + mWidth / 2) / mWidth; snapToScreen(destScreen); } // 真正的实现跳转屏幕的方法 private void snapToScreen(int whichScreen) { // 简单的移到目标屏幕,可能是当前屏或者下一屏幕 // 直接跳转过去,不太友好 // scrollTo(mLastScreen * MultiScreenActivity.screenWidth, 0); // 为了友好性,我们在增加一个动画效果 // 需要再次滑动的距离 屏或者下一屏幕的继续滑动距离 mCurrentScreen = whichScreen; // 防止屏幕越界,即超过屏幕数 if (mCurrentScreen > getChildCount() - 1) { // mCurrentScreen = getChildCount() - 1; // 循环到第一屏 mCurrentScreen = 0; int dx = getChildCount() * getWidth() - getScrollX(); mScroller.startScroll(-480, 0, 480, 0, Math.abs(dx) * 2); } else if (mCurrentScreen <= 0) { // mCurrentScreen = getChildCount() -1; // 循环到最后一屏 // int dx = getScrollX(); // mScroller.startScroll(getChildCount() * getWidth()+dx, 0, // (getChildCount()-1) * getWidth(), 0, Math.abs(dx) * 2); } // 为了达到下一屏幕或者当前屏幕,我们需要继续滑动的距离.根据dx值,可能向左滑动,也可能向右滑动 int dx = mCurrentScreen * getWidth() - getScrollX(); mScroller.startScroll(getScrollX(), 0, dx, 0, Math.abs(dx) * 2); // 由于触摸事件不会重新绘制View,所以此时需要手动刷新View 否则没效果 invalidate(); } }