开源中国控件ScrollLayout分析

来源:互联网 发布:淘宝有人恶意下单付款 编辑:程序博客网 时间:2024/06/07 10:26
本文参考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()方法刷新控件;
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();}@Overrideprotected 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;}}}@Overrideprotected 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 scrollLayoutfinal 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 pagewhichScreen = 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动画结束。 */@Overridepublic void computeScroll() {/** * 当startScroll执行过程中即在duration时间内,computeScrollOffset方法会一直返回true,但当动画执行完成后会返回返加false.  */if (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), mScroller.getCurrY());postInvalidate();}}@Overridepublic 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;}@Overridepublic 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);}}

原创粉丝点击