本文参考http://hi.baidu.com/mysoft_tongmg/item/9b65c898ed7d2030326eeb76和http://www.cnblogs.com/wader2011/archive/2011/10/10/2205142.html
具体思路为:首先,自定义一个控件继承于View,添加一个Scroller类型的成员变量mScroller;
其次,调用mScroller的startScroll()去产生一个偏移控制,手动调用invalid()方法去重新绘制控件;
最后,重写的computeScroll()方法,通过mScroller的computeScrollOffset()方法,根据当前已经逝去的时间,获取当前应该偏移的坐标;调用scrollBy()方法去逐步偏移,调用postInvalidate()方法刷新控件;
具体思路为:首先,自定义一个控件继承于View,添加一个Scroller类型的成员变量mScroller;
其次,调用mScroller的startScroll()去产生一个偏移控制,手动调用invalid()方法去重新绘制控件;
最后,重写的computeScroll()方法,通过mScroller的computeScrollOffset()方法,根据当前已经逝去的时间,获取当前应该偏移的坐标;调用scrollBy()方法去逐步偏移,调用postInvalidate()方法刷新控件;
package net.oschina.app.widget;
import android.content.Context;
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.view.ViewGroup;
import android.widget.Scroller;
/**
* 左右滑动切换屏幕控件
* @author Yao.GUET date: 2011-05-04
* @modify liux (http://my.oschina.net/liux)
*/
public class ScrollLayout extends ViewGroup {
private static final String TAG = "ScrollLayout";
private Scroller mScroller;
/*
* 速度追踪器,主要是为了通过当前滑动速度判断当前滑动是否为fling
*/
private VelocityTracker mVelocityTracker;
/*
* 记录当前屏幕下标,取值范围是:0 到 getChildCount()-1
*/
private int mCurScreen;
private int mDefaultScreen = 0;
/*
* Touch状态值 0:静止 1:滑动
*/
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_SCROLLING = 1;
private static final int SNAP_VELOCITY = 600;
//当前touch事件状态
private int mTouchState = TOUCH_STATE_REST;
/*
* 记录touch事件中被认为是滑动事件前的最大可滑动距离
*/
private int mTouchSlop;
/*
* 记录滑动时上次手指所处的位置
*/
private float mLastMotionX;
private float mLastMotionY;
private OnViewChangeListener mOnViewChangeListener;
/**
* 设置是否可左右滑动
* @author liux
*/
private boolean isScroll = true;
public void setIsScroll(boolean b) {
this.isScroll = b;
}
public ScrollLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrollLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mScroller = new Scroller(context);
mCurScreen = mDefaultScreen;
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeft, 0, childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Log.e(TAG, "onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"ScrollLayout only canmCurScreen run at EXACTLY mode!");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"ScrollLayout only can run at EXACTLY mode!");
}
// The children are given the same width and height as the scrollLayout
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
// Log.e(TAG, "moving to screen "+mCurScreen);
scrollTo(mCurScreen * width, 0);
}
/**
* According to the position of current layout scroll to the destination
* page.处理当屏幕拖动到一个位置松手后的处理
* 根据当前x坐标位置确定切换到第几屏
*/
public void snapToDestination() {
final int screenWidth = getWidth();
final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth;
snapToScreen(destScreen);
}
public void snapToScreen(int whichScreen) {
//是否可滑动
if(!isScroll) {
this.setToScreen(whichScreen);
return;
}
scrollToScreen(whichScreen);
}
public void scrollToScreen(int whichScreen) {
// get the valid layout page
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
if (getScrollX() != (whichScreen * getWidth())) {
final int delta = whichScreen * getWidth() - getScrollX();
mScroller.startScroll(getScrollX(), 0, delta, 0,
Math.abs(delta) * 1);//持续滚动时间 以毫秒为单位
mCurScreen = whichScreen;
invalidate(); // Redraw the layout
if (mOnViewChangeListener != null)
{
mOnViewChangeListener.OnViewChange(mCurScreen);
}
}
}
public void setToScreen(int whichScreen) {
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
mCurScreen = whichScreen;
scrollTo(whichScreen * getWidth(), 0);
if (mOnViewChangeListener != null)
{
mOnViewChangeListener.OnViewChange(mCurScreen);
}
}
public int getCurScreen() {
return mCurScreen;
}
/**
* View绘制流程draw()过程中会调用computeScroll()方法
* 即当我们在在mScroller.startScroll()后调用invalidate()后对导致这个方法的执行(即第一次时,我们需要手动去调用一次invalidate才会去调用 )
* 而这个方法里再去调postInvalidate,
* 这样就可以不断地去调用scrollTo方法了,直到mScroller动画结束。
*/
@Override
public void computeScroll() {
/**
* 当startScroll执行过程中即在duration时间内,computeScrollOffset方法会一直返回true,但当动画执行完成后会返回返加false.
*/
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//是否可滑动
if(!isScroll) {
return false;
}
/**
* 当你需要跟踪触摸屏事件的速度的时候,使用obtain()方法来获得VelocityTracker类的一个实例对象
在onTouchEvent回调函数中,使用addMovement(MotionEvent)函数将当前的移动事件传递给VelocityTracker对象
使用computeCurrentVelocity (int units)函数来计算当前的速度,使用 getXVelocity ()、 getYVelocity ()函数来获得当前的速度
*/
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
//Log.e(TAG, "event down!");
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
mLastMotionX = x;
//---------------New Code----------------------
mLastMotionY = y;
//---------------------------------------------
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) (mLastMotionX - x);
//---------------New Code----------------------
int deltaY = (int) (mLastMotionY - y);
if(Math.abs(deltaX) < 200 && Math.abs(deltaY) > 10)
break;
mLastMotionY = y;
//-------------------------------------
mLastMotionX = x;
scrollBy(deltaX, 0);
break;
case MotionEvent.ACTION_UP:
//Log.e(TAG, "event : up");
// if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) velocityTracker.getXVelocity();
//Log.e(TAG, "velocityX:" + velocityX);
if (velocityX > SNAP_VELOCITY && mCurScreen > 0) {
// Fling enough to move left
//Log.e(TAG, "snap left");
snapToScreen(mCurScreen - 1);
} else if (velocityX < -SNAP_VELOCITY
&& mCurScreen < getChildCount() - 1) {
// Fling enough to move right
//Log.e(TAG, "snap right");
snapToScreen(mCurScreen + 1);
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
// }
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
break;
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop);
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE)
&& (mTouchState != TOUCH_STATE_REST))
{
return true;
}
final float x = ev.getX();
final float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_MOVE:
/**
* 我们需在滑动时做一个简单但很重要的判断,即我们需要简单的判断用户的意图——想横向滑动还是想纵向滑动
* 只有当用户在横向滑动时才将mTouchState = TOUCH_STATE_SCROLLING
*/
final int xDiff = (int) Math.abs(mLastMotionX - x);
if (xDiff > mTouchSlop) {
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mLastMotionY = y;
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
: TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchState = TOUCH_STATE_REST;
break;
}
return mTouchState != TOUCH_STATE_REST;
}
/**
* 设置屏幕切换监听器
* @param listener
*/
public void SetOnViewChangeListener(OnViewChangeListener listener)
{
mOnViewChangeListener = listener;
}
/**
* 屏幕切换监听器
* @author liux
*/
public interface OnViewChangeListener {
public void OnViewChange(int view);
}
}