水平“选项卡”在视图之间滚动

时间:2010-03-23 15:44:30

标签: android layout user-interface android-widget

我有兴趣创建一个“捕捉”到所查看项目的水平滚动视图,因此一次只显示一个项目。用户可以左/右触摸拖动并看到上一个/下一个视图,如果有足够的速度则切换到它。这种互动就像Nexus One附带的新天气/新闻小部件在其“标签”之间导航一样。

是否有任何现有的视图窗口小部件可以执行此操作?

更新:找到了新闻/天气小部件(GenieWidget)的副本,他们似乎已经实现了自己的小部件来实现这一点,他们称之为com.google.android.apps.genie.geniewidget.ui.FlingableLinearLayout,这是他们自己的自定义com.google.android.apps.genie.geniewidget.ui.TabView的一部分。由于这个来源不可用,所以看起来并不太有希望。

6 个答案:

答案 0 :(得分:12)

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

我在github上克隆了Eric Taix的http://code.google.com/p/andro-views/

https://github.com/olibye/AndroViews

然后应用上面的补丁:

  • JonO的补丁

  • Tom de Waard的补丁

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

我会在上面发表这个评论,但我似乎无法评论JonO的回答

答案 1 :(得分:5)

不要看新闻和天气实施,它有一些缺陷。但是,您可以在android.git.kernel.org上使用Home应用程序的源代码(称为Launcher或Launcher2)。我们用于在Home上滚动的小部件位于Workspace.java。

答案 2 :(得分:4)

Eric Taix完成了将Workspace剥离到可以重用的WorkspaceView的大部分工作。可在此处找到: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;
        }

    }

答案 3 :(得分:0)

我不知道Nexus,但我可以向你推荐画廊视图。根据您的上述解释,它非常适合您的要求。

答案 4 :(得分:0)

答案 5 :(得分:0)

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

实际上我并排了几个ScrollView,屏幕只显示一个。当用户垂直滚动时,我希望ScrollView滚动;当用户水平滚动时,我希望ViewFlow(我正在使用的全局布局)捕捉到上一个/下一个ScrollView

我得到的东西几乎接近我想要的东西,但现在这样做。我知道很久以前就回答了这个问题,但我想分享一下我所做的,所以就是这样。

这只是一个派生ScrollView并实现onInterceptTouchEventonTouchEvent的类。

/**
 * 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;
}

代码并不完美,需要更多的润色,但它的功能。几乎像Genie Widget。 (我希望这个人是开源的!)特别是在滚动检测周围,它并没有完全使用触摸斜率,而是在比较X和Y轴之间的移动。但这很容易调整。

我正在为ViewFlowhttps://github.com/pakerfeldt/android-viewflow

使用这个非常好的代码