代码之家  ›  专栏  ›  技术社区  ›  Steve Pomeroy

在视图之间水平滚动“tab”

  •  14
  • Steve Pomeroy  · 技术社区  · 15 年前

    我对创建一个水平滚动视图很感兴趣,它可以“捕捉”到所查看的项目,因此一次只显示一个项目。用户可以触按左/右拖动,并将看到上一个/下一个视图,如果速度足够,则切换到该视图。这种交互与NexusOne随附的新天气/新闻小部件在其“选项卡”之间导航所做的完全相同。

    有没有现有的视图小部件可以做到这一点?

    更新:找到新闻/天气小部件(geniewidget)的副本,他们似乎已经实现了自己的小部件来完成这一任务,他们称之为 com.google.android.apps.genie.geniewidget.ui.FlingableLinearLayout 这是他们自己的习惯 com.google.android.apps.genie.geniewidget.ui.TabView . 由于该消息来源不可用,所以这并不是一个太有希望的方向。

    6 回复  |  直到 13 年前
        1
  •  11
  •   Andrew Whitaker    13 年前

    (更新20110905:官方 android tools 现在做得更好)

    我克隆了埃里克·泰克斯的 http://code.google.com/p/andro-views/ 在Github上

    https://github.com/olibye/AndroViews

    然后从上面应用补丁:

    • 乔诺的补丁

    • 汤姆·德沃德的补丁

    • 分成一个库和一个示例,允许简单地包含在其他项目中

    我本可以在上面发表这一评论,但我似乎无法对乔诺的回答发表评论。

        2
  •  5
  •   Romain Guy    15 年前

    不要看新闻和天气的实施,它有几个缺陷。但是,您可以在android.git.kernel.org上使用home应用程序的源代码(称为launcher或launcher2)。我们用来在主页上滚动的小部件在workspace.java中。

        3
  •  4
  •   Jon O    14 年前

    EricTaix已经完成了将工作区剥离到可重用的工作区视图中的大部分工作。可以在这里找到: http://code.google.com/p/andro-views/

    发布后的版本做了在模拟器中应该做的事情,但是在实际的硬件上,有时候它会被卡在视图之间而不是回击——我已经给他发了一个补丁(他在提交之前正在测试,到发布这个的日期为止),它应该使它的行为完全符合工作区有。

    如果补丁没有很快出现,我会单独发布。


    正如所承诺的,由于它还没有出现,这里是我的补丁版本:

        /**
         * Copyright 2010 Eric Taix (eric.taix@gmail.com) Licensed under the Apache License, Version 2.0 (the "License"); you
         * may not use this file except in compliance with the License. You may obtain a copy of the License at
         * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
         * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
         * either express or implied. See the License for the specific language governing permissions and limitations under the
         * License.
         */
    
        import android.content.Context;
        import android.graphics.Bitmap;
        import android.graphics.Canvas;
        import android.graphics.Paint;
        import android.graphics.RectF;
        import android.os.Parcel;
        import android.os.Parcelable;
        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.view.ViewParent;
        import android.view.animation.Interpolator;
        import android.widget.Scroller;
    
        /**
         * The workspace is a wide area with a infinite number of screens. Each screen contains a view. A workspace is meant to
         * be used with a fixed width only.<br/>
         * <br/>
         * This code has been done by using com.android.launcher.Workspace.java
         */
        public class WorkspaceView extends ViewGroup {
    
            private static final int INVALID_POINTER = -1;
    
            private int mActivePointerId = INVALID_POINTER;
            private static final int INVALID_SCREEN = -1;
    
            // The velocity at which a fling gesture will cause us to snap to the next screen
            private static final int SNAP_VELOCITY = 500;
    
            // the default screen index
            private int defaultScreen;
            // The current screen index
            private int currentScreen;
            // The next screen index
            private int nextScreen = INVALID_SCREEN;
            // Wallpaper properties
            private Bitmap wallpaper;
            private Paint paint;
            private int wallpaperWidth;
            private int wallpaperHeight;
            private float wallpaperOffset;
            private boolean wallpaperLoaded;
            private boolean firstWallpaperLayout = true;
            private static final int TAB_INDICATOR_HEIGHT_PCT = 2;
            private RectF selectedTab;
    
    
            // The scroller which scroll each view
            private Scroller scroller;
            // A tracker which to calculate the velocity of a mouvement
            private VelocityTracker mVelocityTracker;
    
            // Tha last known values of X and Y
            private float lastMotionX;
            private float lastMotionY;
    
            private final static int TOUCH_STATE_REST = 0;
            private final static int TOUCH_STATE_SCROLLING = 1;
    
            // The current touch state
            private int touchState = TOUCH_STATE_REST;
            // The minimal distance of a touch slop
            private int touchSlop;
    
            // An internal flag to reset long press when user is scrolling
            private boolean allowLongPress;
            // A flag to know if touch event have to be ignored. Used also in internal
            private boolean locked;
    
            private WorkspaceOvershootInterpolator mScrollInterpolator;
    
            private int mMaximumVelocity;
    
            private Paint selectedTabPaint;
            private Canvas canvas;
    
            private RectF bar;
    
            private Paint tabIndicatorBackgroundPaint;
    
            private static class WorkspaceOvershootInterpolator implements Interpolator {
                private static final float DEFAULT_TENSION = 1.3f;
                private float mTension;
    
                public WorkspaceOvershootInterpolator() {
                    mTension = DEFAULT_TENSION;
                }
    
                public void setDistance(int distance) {
                    mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
                }
    
                public void disableSettle() {
                    mTension = 0.f;
                }
    
                public float getInterpolation(float t) {
                    // _o(t) = t * t * ((tension + 1) * t + tension)
                    // o(t) = _o(t - 1) + 1
                    t -= 1.0f;
                    return t * t * ((mTension + 1) * t + mTension) + 1.0f;
                }
            }
    
            /**
             * Used to inflate the Workspace from XML.
             * 
             * @param context The application's context.
             * @param attrs The attribtues set containing the Workspace's customization values.
             */
            public WorkspaceView(Context context, AttributeSet attrs) {
                this(context, attrs, 0);
            }
    
            /**
             * Used to inflate the Workspace from XML.
             * 
             * @param context The application's context.
             * @param attrs The attribtues set containing the Workspace's customization values.
             * @param defStyle Unused.
             */
            public WorkspaceView(Context context, AttributeSet attrs, int defStyle) {
                super(context, attrs, defStyle);
                defaultScreen = 0;
                initWorkspace();
            }
    
            /**
             * Initializes various states for this workspace.
             */
            private void initWorkspace() {
                mScrollInterpolator = new WorkspaceOvershootInterpolator();
                scroller = new Scroller(getContext(),mScrollInterpolator);
                currentScreen = defaultScreen;
    
                paint = new Paint();
                paint.setDither(false);
    
                // Does this do anything for me?
                final ViewConfiguration configuration = ViewConfiguration.get(getContext());
                touchSlop = configuration.getScaledTouchSlop();
                mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    
                selectedTabPaint = new Paint();
                selectedTabPaint.setColor(getResources().getColor(R.color.RED));
                selectedTabPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    
                tabIndicatorBackgroundPaint = new Paint();
                tabIndicatorBackgroundPaint.setColor(getResources().getColor(R.color.GRAY));
                tabIndicatorBackgroundPaint.setStyle(Paint.Style.FILL);
            }
    
            /**
             * Set a new distance that a touch can wander before we think the user is scrolling in pixels slop<br/>
             * 
             * @param touchSlopP
             */
            public void setTouchSlop(int touchSlopP) {
                touchSlop = touchSlopP;
            }
    
            /**
             * Set the background's wallpaper.
             */
            public void loadWallpaper(Bitmap bitmap) {
                wallpaper = bitmap;
                wallpaperLoaded = true;
                requestLayout();
                invalidate();
            }
    
            boolean isDefaultScreenShowing() {
                return currentScreen == defaultScreen;
            }
    
            /**
             * Returns the index of the currently displayed screen.
             * 
             * @return The index of the currently displayed screen.
             */
            int getCurrentScreen() {
                return currentScreen;
            }
    
            /**
             * Sets the current screen.
             * 
             * @param currentScreen
             */
            public void setCurrentScreen(int currentScreen) {
    
                if (!scroller.isFinished()) scroller.abortAnimation();
                currentScreen = Math.max(0, Math.min(currentScreen, getChildCount()));
                scrollTo(currentScreen * getWidth(), 0);
                Log.d("workspace", "setCurrentScreen: width is " + getWidth());
                invalidate();
            }
    
            /**
             * Shows the default screen (defined by the firstScreen attribute in XML.)
             */
            void showDefaultScreen() {
                setCurrentScreen(defaultScreen);
            }
    
            /**
             * Registers the specified listener on each screen contained in this workspace.
             * 
             * @param l The listener used to respond to long clicks.
             */
            @Override
            public void setOnLongClickListener(OnLongClickListener l) {
                final int count = getChildCount();
                for (int i = 0; i < count; i++) {
                    getChildAt(i).setOnLongClickListener(l);
                }
            }
    
    
            @Override
            public void computeScroll() {
                if (scroller.computeScrollOffset()) {
                    scrollTo(scroller.getCurrX(), scroller.getCurrY());
                    postInvalidate();
                } else if (nextScreen != INVALID_SCREEN) {
                    currentScreen = Math.max(0, Math.min(nextScreen, getChildCount() - 1));
                    nextScreen = INVALID_SCREEN;
                }
            }
    
            /**
             * ViewGroup.dispatchDraw() supports many features we don't need: clip to padding, layout animation, animation
             * listener, disappearing children, etc. The following implementation attempts to fast-track the drawing dispatch by
             * drawing only what we know needs to be drawn.
             */
            @Override
            protected void dispatchDraw(Canvas canvas) {
                // First draw the wallpaper if needed
    
                if (wallpaper != null) {
                    float x = getScrollX() * wallpaperOffset;
                    if (x + wallpaperWidth < getRight() - getLeft()) {
                        x = getRight() - getLeft() - wallpaperWidth;
                    }
                    canvas.drawBitmap(wallpaper, x, (getBottom() - getTop() - wallpaperHeight) / 2, paint);
                }
    
                // Determine if we need to draw every child or only the current screen
                boolean fastDraw = touchState != TOUCH_STATE_SCROLLING && nextScreen == INVALID_SCREEN;
                // If we are not scrolling or flinging, draw only the current screen
                if (fastDraw) {
                    View v = getChildAt(currentScreen);
                    drawChild(canvas, v, getDrawingTime());
                }
                else {
                    final long drawingTime = getDrawingTime();
                    // If we are flinging, draw only the current screen and the target screen
                    if (nextScreen >= 0 && nextScreen < getChildCount() && Math.abs(currentScreen - nextScreen) == 1) {
                        drawChild(canvas, getChildAt(currentScreen), drawingTime);
                        drawChild(canvas, getChildAt(nextScreen), drawingTime);
                    }
                    else {
                        // If we are scrolling, draw all of our children
                        final int count = getChildCount();
                        for (int i = 0; i < count; i++) {
                            drawChild(canvas, getChildAt(i), drawingTime);
                        }
                    }
                }
                updateTabIndicator();
                canvas.drawBitmap(bitmap, getScrollX(), getMeasuredHeight()*(100-TAB_INDICATOR_HEIGHT_PCT)/100, paint);
    
            }
    
    
            /**
             * Measure the workspace AND also children
             */
            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
                final int width = MeasureSpec.getSize(widthMeasureSpec);
                final int height = MeasureSpec.getSize(heightMeasureSpec);
        //      Log.d("workspace","Height is " + height);
                final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
                if (widthMode != MeasureSpec.EXACTLY) {
                    throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
                }
    
                final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
                if (heightMode != MeasureSpec.EXACTLY) {
                    throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
                }
    
                // The children are given the same width and height as the workspace
                final int count = getChildCount();
                for (int i = 0; i < count; i++) {
                    int adjustedHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height*(100-TAB_INDICATOR_HEIGHT_PCT)/100, heightMode);
                    getChildAt(i).measure(widthMeasureSpec,adjustedHeightMeasureSpec);
    
                }
    
                // Compute wallpaper
                if (wallpaperLoaded) {
                    wallpaperLoaded = false;
                    wallpaper = centerToFit(wallpaper, width, height, getContext());
                    wallpaperWidth = wallpaper.getWidth();
                    wallpaperHeight = wallpaper.getHeight();
                }
                wallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) / ((count - 1) * (float) width) : 1.0f;
                if (firstWallpaperLayout) {
                    scrollTo(currentScreen * width, 0);
                    firstWallpaperLayout = false;
                }
    
        //      Log.d("workspace","Top is "+getTop()+", bottom is "+getBottom()+", left is "+getLeft()+", right is "+getRight());
    
                updateTabIndicator();
                invalidate();
            }
    
            Bitmap bitmap;
    
            private OnLoadListener load;
    
    
        private int lastEvHashCode;
    
            private void updateTabIndicator(){
                int width = getMeasuredWidth();
                int height = getMeasuredHeight();
    
                //For drawing in its own bitmap:
                bar = new RectF(0, 0, width, (TAB_INDICATOR_HEIGHT_PCT*height/100));
    
                int startPos = getScrollX()/(getChildCount());
                selectedTab = new RectF(startPos, 0, startPos+width/getChildCount(), (TAB_INDICATOR_HEIGHT_PCT*height/100));
    
                bitmap = Bitmap.createBitmap(width, (TAB_INDICATOR_HEIGHT_PCT*height/100), Bitmap.Config.ARGB_8888);
                canvas = new Canvas(bitmap);
                canvas.drawRoundRect(bar,0,0, tabIndicatorBackgroundPaint);
                canvas.drawRoundRect(selectedTab, 5,5, selectedTabPaint);
            }
    
            /**
             * Overrided method to layout child
             */
            @Override
            protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
                int childLeft = 0;
                final int count = getChildCount();
                for (int i = 0; i < count; i++) {
                    final View child = getChildAt(i);
                    if (child.getVisibility() != View.GONE) {
                        final int childWidth = child.getMeasuredWidth();
                        child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
                        childLeft += childWidth;
                    }
                }
                load.onLoad();
            }
    
            @Override
            public boolean dispatchUnhandledMove(View focused, int direction) {
                if (direction == View.FOCUS_LEFT) {
                    if (getCurrentScreen() > 0) {
                        scrollToScreen(getCurrentScreen() - 1);
                        return true;
                    }
                }
                else if (direction == View.FOCUS_RIGHT) {
                    if (getCurrentScreen() < getChildCount() - 1) {
                        scrollToScreen(getCurrentScreen() + 1);
                        return true;
                    }
                }
                return super.dispatchUnhandledMove(focused, direction);
            }
    
            /**
             * This method JUST determines whether we want to intercept the motion. If we return true, onTouchEvent will be called
             * and we do the actual scrolling there.
             */
            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                Log.d("workspace","Intercepted a touch event");
                if (locked) {
                    return true;
                }
    
                /*
                 * Shortcut the most recurring case: the user is in the dragging state and he is moving his finger. We want to
                 * intercept this motion.
                 */
                final int action = ev.getAction();
                if ((action == MotionEvent.ACTION_MOVE) && (touchState != TOUCH_STATE_REST)) {
                    return true;
                }
    
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain();
                }
                mVelocityTracker.addMovement(ev);
    
        //      switch (action & MotionEvent.ACTION_MASK) {
                switch (action) {
                case MotionEvent.ACTION_MOVE:
    
        //          Log.d("workspace","Intercepted a move event");
                    /*
                     * Locally do absolute value. mLastMotionX is set to the y value of the down event.
                     */
                    handleInterceptMove(ev);
                    break;
    
                case MotionEvent.ACTION_DOWN:
                    // Remember location of down touch
                    final float x1 = ev.getX();
                    final float y1 = ev.getY();
                    lastMotionX = x1;
                    lastMotionY = y1;
                    allowLongPress = true;
                    mActivePointerId = ev.getPointerId(0);
    
                    /*
                     * If being flinged and user touches the screen, initiate drag; otherwise don't. mScroller.isFinished should be
                     * false when being flinged.
                     */
                    touchState = scroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
                    break;
    
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    mActivePointerId = INVALID_POINTER;
                    allowLongPress = false;
    
                    if (mVelocityTracker != null) {
                        mVelocityTracker.recycle();
                        mVelocityTracker = null;
                    }
                    touchState = TOUCH_STATE_REST;
                    break;
    
                case MotionEvent.ACTION_POINTER_UP:
                    onSecondaryPointerUp(ev);
                    break;
                }
    
                /*
                 * The only time we want to intercept motion events is if we are in the drag mode.
                 */
                return touchState != TOUCH_STATE_REST;
            }
    
            private void handleInterceptMove(MotionEvent ev) {
                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                final float x = ev.getX(pointerIndex);
                final float y = ev.getY(pointerIndex);
                final int xDiff = (int) Math.abs(x - lastMotionX);
                final int yDiff = (int) Math.abs(y - lastMotionY);
                boolean xMoved = xDiff > touchSlop;
                boolean yMoved = yDiff > touchSlop;
    
                if (xMoved || yMoved) {
                    //Log.d("workspace","Detected move.  Checking to scroll.");
                    if (xMoved && !yMoved) {
                        //Log.d("workspace","Detected X move.  Scrolling.");
                        // Scroll if the user moved far enough along the X axis
                        touchState = TOUCH_STATE_SCROLLING;
                        lastMotionX = x;
                    }
                    // Either way, cancel any pending longpress
                    if (allowLongPress) {
                        allowLongPress = false;
                        // Try canceling the long press. It could also have been scheduled
                        // by a distant descendant, so use the mAllowLongPress flag to block
                        // everything
                        final View currentView = getChildAt(currentScreen);
                        currentView.cancelLongPress();
                    }
                }
            }
    
            private void onSecondaryPointerUp(MotionEvent ev) {
                final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >>
                        MotionEvent.ACTION_POINTER_ID_SHIFT;
                final int pointerId = ev.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                    // This was our active pointer going up. Choose a new
                    // active pointer and adjust accordingly.
                    // TODO: Make this decision more intelligent.
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    lastMotionX = ev.getX(newPointerIndex);
                    lastMotionY = ev.getY(newPointerIndex);
                    mActivePointerId = ev.getPointerId(newPointerIndex);
                    if (mVelocityTracker != null) {
                        mVelocityTracker.clear();
                    }
                }
            }
    
            /**
             * Track the touch event
             */
            @Override
            public boolean onTouchEvent(MotionEvent ev) {
        //      Log.d("workspace","caught a touch event");
                if (locked) {
                    return true;
                }
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain();
                }
                mVelocityTracker.addMovement(ev);
    
                final int action = ev.getAction();
                final float x = ev.getX();
    
                switch (action) {
                case MotionEvent.ACTION_DOWN:
    
                    //We can still get here even if we returned false from the intercept function.
                    //That's the only way we can get a TOUCH_STATE_REST (0) here.
                    //That means that our child hasn't handled the event, so we need to 
        //          Log.d("workspace","caught a down touch event and touchstate =" + touchState);
    
                    if(touchState != TOUCH_STATE_REST){
                        /*
                         * If being flinged and user touches, stop the fling. isFinished will be false if being flinged.
                         */
                        if (!scroller.isFinished()) {
                            scroller.abortAnimation();
                        }
    
                        // Remember where the motion event started
                        lastMotionX = x;
                        mActivePointerId = ev.getPointerId(0);
                    } 
                    break;
                case MotionEvent.ACTION_MOVE:
    
                    if (touchState == TOUCH_STATE_SCROLLING) {
                        handleScrollMove(ev);
                    } else {
        //              Log.d("workspace","caught a move touch event but not scrolling");
                        //NOTE:  We will never hit this case in Android 2.2.  This is to fix a 2.1 bug.
                        //We need to do the work of interceptTouchEvent here because we don't intercept the move
                        //on children who don't scroll.
    
                        Log.d("workspace","handling move from onTouch");
    
                        if(onInterceptTouchEvent(ev) && touchState == TOUCH_STATE_SCROLLING){
                            handleScrollMove(ev);
                        }
    
                    }
    
                    break;
                case MotionEvent.ACTION_UP:
        //          Log.d("workspace","caught an up touch event");
                    if (touchState == TOUCH_STATE_SCROLLING) {
                        final VelocityTracker velocityTracker = mVelocityTracker;
                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                        int velocityX = (int) velocityTracker.getXVelocity();
    
                        if (velocityX > SNAP_VELOCITY && currentScreen > 0) {
                            // Fling hard enough to move left
                            scrollToScreen(currentScreen - 1);
                        }
                        else if (velocityX < -SNAP_VELOCITY && currentScreen < getChildCount() - 1) {
                            // Fling hard enough to move right
                            scrollToScreen(currentScreen + 1);
                        }
                        else {
                            snapToDestination();
                        }
    
                        if (mVelocityTracker != null) {
                            mVelocityTracker.recycle();
                            mVelocityTracker = null;
                        }
                    }
                    touchState = TOUCH_STATE_REST;
                    mActivePointerId = INVALID_POINTER;
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Log.d("workspace","caught a cancel touch event");
                    touchState = TOUCH_STATE_REST;
                    mActivePointerId = INVALID_POINTER;
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    Log.d("workspace","caught a pointer up touch event");
                    onSecondaryPointerUp(ev);
                    break;
                }
    
                return true;
            }
    
            private void handleScrollMove(MotionEvent ev){
                // Scroll to follow the motion event
                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                final float x1 = ev.getX(pointerIndex);
                final int deltaX = (int) (lastMotionX - x1);
                lastMotionX = x1;
    
                if (deltaX < 0) {
                    if (getScrollX() > 0) {
                        //Scrollby invalidates automatically
                        scrollBy(Math.max(-getScrollX(), deltaX), 0);
                    }
                }
                else if (deltaX > 0) {
                    final int availableToScroll = getChildAt(getChildCount() - 1).getRight() - getScrollX() - getWidth();
                    if (availableToScroll > 0) {
                        //Scrollby invalidates automatically
                        scrollBy(Math.min(availableToScroll, deltaX), 0);
                    }
                } else {
                    awakenScrollBars();
                }
            }
    
            /**
             * Scroll to the appropriated screen depending of the current position
             */
            private void snapToDestination() {
                final int screenWidth = getWidth();
                final int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;
                Log.d("workspace", "snapToDestination");
                scrollToScreen(whichScreen);
            }
    
            /**
             * Scroll to a specific screen
             * 
             * @param whichScreen
             */
            public void scrollToScreen(int whichScreen) {
                scrollToScreen(whichScreen, false);
            }
    
            private void scrollToScreen(int whichScreen, boolean immediate){
                Log.d("workspace", "snapToScreen=" + whichScreen);
    
                boolean changingScreens = whichScreen != currentScreen;
    
                nextScreen = whichScreen;
    
                View focusedChild = getFocusedChild();
                if (focusedChild != null && changingScreens && focusedChild == getChildAt(currentScreen)) {
                    focusedChild.clearFocus();
                }
    
                final int newX = whichScreen * getWidth();
                final int delta = newX - getScrollX();
                Log.d("workspace", "newX=" + newX + " scrollX=" + getScrollX() + " delta=" + delta);
                scroller.startScroll(getScrollX(), 0, delta, 0, immediate ? 0 : Math.abs(delta) * 2);
                invalidate();
            }
    
            public void scrollToScreenImmediate(int whichScreen){
                scrollToScreen(whichScreen, true);
            }
    
            /**
             * Return the parceable instance to be saved
             */
            @Override
            protected Parcelable onSaveInstanceState() {
                final SavedState state = new SavedState(super.onSaveInstanceState());
                state.currentScreen = currentScreen;
                return state;
            }
    
            /**
             * Restore the previous saved current screen
             */
            @Override
            protected void onRestoreInstanceState(Parcelable state) {
                SavedState savedState = (SavedState) state;
                super.onRestoreInstanceState(savedState.getSuperState());
                if (savedState.currentScreen != -1) {
                    currentScreen = savedState.currentScreen;
                }
            }
    
            /**
             * Scroll to the left right screen
             */
            public void scrollLeft() {
                if (nextScreen == INVALID_SCREEN && currentScreen > 0 && scroller.isFinished()) {
                    scrollToScreen(currentScreen - 1);
                }
            }
    
            /**
             * Scroll to the next right screen
             */
            public void scrollRight() {
                if (nextScreen == INVALID_SCREEN && currentScreen < getChildCount() - 1 && scroller.isFinished()) {
                    scrollToScreen(currentScreen + 1);
                }
            }
    
            /**
             * Return the screen's index where a view has been added to.
             * 
             * @param v
             * @return
             */
            public int getScreenForView(View v) {
                int result = -1;
                if (v != null) {
                    ViewParent vp = v.getParent();
                    int count = getChildCount();
                    for (int i = 0; i < count; i++) {
                        if (vp == getChildAt(i)) {
                            return i;
                        }
                    }
                }
                return result;
            }
    
            /**
             * Return a view instance according to the tag parameter or null if the view could not be found
             * 
             * @param tag
             * @return
             */
            public View getViewForTag(Object tag) {
                int screenCount = getChildCount();
                for (int screen = 0; screen < screenCount; screen++) {
                    View child = getChildAt(screen);
                    if (child.getTag() == tag) {
                        return child;
                    }
                }
                return null;
            }
    
            /**
             * Unlocks the SlidingDrawer so that touch events are processed.
             * 
             * @see #lock()
             */
            public void unlock() {
                locked = false;
            }
    
            /**
             * Locks the SlidingDrawer so that touch events are ignores.
             * 
             * @see #unlock()
             */
            public void lock() {
                locked = true;
            }
    
            /**
             * @return True is long presses are still allowed for the current touch
             */
            public boolean allowLongPress() {
                return allowLongPress;
            }
    
            /**
             * Move to the default screen
             */
            public void moveToDefaultScreen() {
                scrollToScreen(defaultScreen);
                getChildAt(defaultScreen).requestFocus();
            }
    
            // ========================= INNER CLASSES ==============================
    
            /**
             * A SavedState which save and load the current screen
             */
            public static class SavedState extends BaseSavedState {
                int currentScreen = -1;
    
                /**
                 * Internal constructor
                 * 
                 * @param superState
                 */
                SavedState(Parcelable superState) {
                    super(superState);
                }
    
                /**
                 * Private constructor
                 * 
                 * @param in
                 */
                private SavedState(Parcel in) {
                    super(in);
                    currentScreen = in.readInt();
                }
    
                /**
                 * Save the current screen
                 */
                @Override
                public void writeToParcel(Parcel out, int flags) {
                    super.writeToParcel(out, flags);
                    out.writeInt(currentScreen);
                }
    
                /**
                 * Return a Parcelable creator
                 */
                public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
                    public SavedState createFromParcel(Parcel in) {
                        return new SavedState(in);
                    }
    
                    public SavedState[] newArray(int size) {
                        return new SavedState[size];
                    }
                };
            }
    
            //Added for "flipper" compatibility
            public int getDisplayedChild(){
                return getCurrentScreen();
            }
    
            public void setDisplayedChild(int i){
                //    setCurrentScreen(i);
                scrollToScreen(i);
                getChildAt(i).requestFocus();
            }
    
            public void setOnLoadListener(OnLoadListener load){
                this.load = load;
            }
    
            public void flipLeft(){
                scrollLeft();
            }
    
            public void flipRight(){
                scrollRight();
            }
    
            // ======================== UTILITIES METHODS ==========================
    
            /**
             * Return a centered Bitmap
             * 
             * @param bitmap
             * @param width
             * @param height
             * @param context
             * @return
             */
            static Bitmap centerToFit(Bitmap bitmap, int width, int height, Context context) {
                final int bitmapWidth = bitmap.getWidth();
                final int bitmapHeight = bitmap.getHeight();
    
                if (bitmapWidth < width || bitmapHeight < height) {
                    // Normally should get the window_background color of the context
                    int color = Integer.valueOf("FF191919", 16);
                    Bitmap centered = Bitmap.createBitmap(bitmapWidth < width ? width : bitmapWidth, bitmapHeight < height ? height
                            : bitmapHeight, Bitmap.Config.RGB_565);
                    Canvas canvas = new Canvas(centered);
                    canvas.drawColor(color);
                    canvas.drawBitmap(bitmap, (width - bitmapWidth) / 2.0f, (height - bitmapHeight) / 2.0f, null);
                    bitmap = centered;
                }
                return bitmap;
            }
    
        }
    
        4
  •  0
  •   Kantesh    15 年前

    我不知道NexusOne,但是,我可以建议你去画廊看看。根据你上述的解释,这完全适合你的要求。

        5
  •  0
  •   Vasile Surdu    14 年前
        6
  •  0
  •   Benoit Duffez    14 年前

    我尝试了很多我们可以在网上找到的代码示例,即使几乎所有的代码都是好的,但它们并没有正确地处理我的想法。

    实际上我有几个 ScrollView s并排,屏幕仅显示一个。当用户垂直滚动时,我希望 卷轴视图 滚动;当用户水平滚动时,我希望 ViewFlow (我使用的全局布局)捕捉到上一个/下一个 卷轴视图 .

    我几乎得到了我想要的东西,但现在就可以了。我知道这是很久以前的答案,但我想分享我所做的,所以在这里。

    它只是一个派生的类 卷轴视图 和工具 onInterceptTouchEvent onTouchEvent .

    /**
     * Try to know if the move will be an horizontal drag
     * If so, we want to intercept the touch event (return true)
     * otherwise, we don't want to intercept this event
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction() & MotionEvent.ACTION_MASK;
    
        // If scrolling (over X or Y), we want to intercept: process
        // it within the ScrollView & send it to the ViewFlow
        if (action == MotionEvent.ACTION_MOVE && mState != NO_SCROLL) {
            return true;
        }
    
        // Try to detect the motion
        switch (action) {
        case MotionEvent.ACTION_MOVE:
            float deltaX = Math.abs(ev.getX()-lastX);
            float deltaY = Math.abs(ev.getY()-lastY);
    
            boolean xMoved = deltaX > 0.5* mTouchSlop && deltaX > deltaY;
            boolean yMoved = deltaY > 0.5*mTouchSlop;
    
            if (xMoved || yMoved) {
                if (xMoved && !yMoved) {
                    mState = SCROLL_X;
                } else {
                    mState = SCROLL_Y;
                }
            } else {
                mState = NO_SCROLL;
            }
    
            lastX = ev.getX();
            lastY = ev.getY();
            break;
    
        case MotionEvent.ACTION_DOWN:
            // Remember location of down touch
            lastX = ev.getX();
            lastY = ev.getY();
            mState = NO_SCROLL;
            sendTouchEvent(ev);
            break;
    
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            if (mState == NO_SCROLL) {
                // Thats a tap!
            }
            mState = NO_SCROLL;
            break;
    
        }
    
        if (mState != SCROLL_X) {
            super.onInterceptTouchEvent(ev);
        }
    
        return mState == SCROLL_X;// Intercept only dragging over X axis
    }
    
    /**
     * Handles touch events. Basically only horizontal drag.
     * Such events are handled locally by the scrollview
     * _AND_ sent to the {@link ViewFlow} in order to make it snap
     * horizontally
     * @param ev the MotionEvent 
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        super.onTouchEvent(ev);
    
        switch (action) {
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            mState = NO_SCROLL;
            sendTouchEvent(ev);
            super.onTouchEvent(ev);
            break;
    
        case MotionEvent.ACTION_DOWN:
            super.onTouchEvent(ev);
            break;
    
        case MotionEvent.ACTION_MOVE:
            lastY = ev.getX();
            lastY = ev.getY();
            if (mState == SCROLL_X) {
                sendTouchEvent(ev);
            } else if (mState == SCROLL_Y) {
                super.onTouchEvent(ev);
            }
            break;
        }
    
        return false;
    }
    

    这段代码并不完美,需要稍加润色,但它是功能性的。就像精灵小部件。(我希望这家伙是开源的!)特别是在滚动检测周围,它并没有使用精确的触摸斜坡,而是比较X轴和Y轴之间的移动。但这很容易调整。

    我用这个很好的代码 视图流 : https://github.com/pakerfeldt/android-viewflow