1f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein/*
2f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Copyright (C) 2011 Google Inc.
3f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Licensed to The Android Open Source Project.
4f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein *
5f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License");
6f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * you may not use this file except in compliance with the License.
7f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * You may obtain a copy of the License at
8f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein *
9f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein *      http://www.apache.org/licenses/LICENSE-2.0
10f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein *
11f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Unless required by applicable law or agreed to in writing, software
12f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS,
13f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * See the License for the specific language governing permissions and
15f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * limitations under the License.
16f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */
17f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
18f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinpackage com.android.ex.photo;
19f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
20f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.content.Context;
21f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.support.v4.view.MotionEventCompat;
22f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.support.v4.view.ViewPager;
23f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.util.AttributeSet;
24f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.view.MotionEvent;
25d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sappersteinimport android.view.View;
26f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
27f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein/**
28f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * View pager for photo view fragments. Define our own class so we can specify the
29f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * view pager in XML.
30f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */
31f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinpublic class PhotoViewPager extends ViewPager {
32f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
33f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * A type of intercept that should be performed
34f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
35f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public static enum InterceptType { NONE, LEFT, RIGHT, BOTH }
36f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
37f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
38f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Provides an ability to intercept touch events.
39f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * <p>
40f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * {@link ViewPager} intercepts all touch events and we need to be able to override this
41d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein     * behavior. Instead, we could perform a similar function by declaring a custom
423102e82e0187363d453f4bcde00e396ef5420a46Adam Copp     * {@link android.view.ViewGroup} to contain the pager and intercept touch events at a higher
433102e82e0187363d453f4bcde00e396ef5420a46Adam Copp     * level.
44f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
45f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public static interface OnInterceptTouchListener {
46f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
47f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Called when a touch intercept is about to occur.
48f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         *
49f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * @param origX the raw x coordinate of the initial touch
50f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * @param origY the raw y coordinate of the initial touch
51f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * @return Which type of touch, if any, should should be intercepted.
52f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
53f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public InterceptType onTouchIntercept(float origX, float origY);
54f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
55f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
56f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static final int INVALID_POINTER = -1;
57f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
58f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private float mLastMotionX;
59f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private int mActivePointerId;
60f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The x coordinate where the touch originated */
61f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private float mActivatedX;
62f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The y coordinate where the touch originated */
63f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private float mActivatedY;
64f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private OnInterceptTouchListener mListener;
65f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
66f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public PhotoViewPager(Context context) {
67f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        super(context);
68d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein        initialize();
69f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
70f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
71f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public PhotoViewPager(Context context, AttributeSet attrs) {
72f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        super(context, attrs);
73d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein        initialize();
74d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein    }
75d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein
76d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein    private void initialize() {
77d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein        // Set the page transformer to perform the transition animation
78d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein        // for each page in the view.
79d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein        setPageTransformer(true, new PageTransformer() {
80d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein            @Override
81d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein            public void transformPage(View page, float position) {
821f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project
831f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                // The >= 1 is needed so that the page
841f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                // (page A) that transitions behind the newly visible
851f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                // page (page B) that comes in from the left does not
861f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                // get the touch events because it is still on screen
871f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                // (page A is still technically on screen despite being
881f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                // invisible). This makes sure that when the transition
891f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                // has completely finished, we revert it to its default
901f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                // behavior and move it off of the screen.
911f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project                if (position < 0 || position >= 1.f) {
92d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    page.setTranslationX(0);
93d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    page.setAlpha(1.f);
94d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    page.setScaleX(1);
95d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    page.setScaleY(1);
96d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                } else {
97d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    page.setTranslationX(-position * page.getWidth());
98d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    page.setAlpha(Math.max(0,1.f - position));
99d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    final float scale = Math.max(0, 1.f - position * 0.3f);
100d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    page.setScaleX(scale);
101d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                    page.setScaleY(scale);
102d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein                }
103d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein            }
104d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein        });
105f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
106f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
107f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
108f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * {@inheritDoc}
109f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * <p>
110f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * We intercept touch event intercepts so we can prevent switching views when the
111f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * current view is internally scrollable.
112f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
113f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
114f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onInterceptTouchEvent(MotionEvent ev) {
115f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final InterceptType intercept = (mListener != null)
116f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                ? mListener.onTouchIntercept(mActivatedX, mActivatedY)
117f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                : InterceptType.NONE;
118f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final boolean ignoreScrollLeft =
119f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                (intercept == InterceptType.BOTH || intercept == InterceptType.LEFT);
120f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final boolean ignoreScrollRight =
121f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                (intercept == InterceptType.BOTH || intercept == InterceptType.RIGHT);
122f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
123f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // Only check ability to page if we can't scroll in one / both directions
124f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
125f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
126f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
127f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mActivePointerId = INVALID_POINTER;
128f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
129f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
130f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        switch (action) {
131f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            case MotionEvent.ACTION_MOVE: {
132f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                if (ignoreScrollLeft || ignoreScrollRight) {
133f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    final int activePointerId = mActivePointerId;
134f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    if (activePointerId == INVALID_POINTER) {
135f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        // If we don't have a valid id, the touch down wasn't on content.
136f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        break;
137f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    }
138f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
139f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    final int pointerIndex =
140f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                            MotionEventCompat.findPointerIndex(ev, activePointerId);
141f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    final float x = MotionEventCompat.getX(ev, pointerIndex);
142f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
143f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    if (ignoreScrollLeft && ignoreScrollRight) {
144f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        mLastMotionX = x;
145f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        return false;
146f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    } else if (ignoreScrollLeft && (x > mLastMotionX)) {
147f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        mLastMotionX = x;
148f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        return false;
149f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    } else if (ignoreScrollRight && (x < mLastMotionX)) {
150f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        mLastMotionX = x;
151f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        return false;
152f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    }
153f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                }
154f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                break;
155f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
156f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
157f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            case MotionEvent.ACTION_DOWN: {
158f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mLastMotionX = ev.getX();
159f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // Use the raw x/y as the children can be located anywhere and there isn't a
160f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // single offset that would be meaningful
161f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mActivatedX = ev.getRawX();
162f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mActivatedY = ev.getRawY();
163f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
164f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                break;
165f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
166f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
167f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            case MotionEventCompat.ACTION_POINTER_UP: {
168f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                final int pointerIndex = MotionEventCompat.getActionIndex(ev);
169f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
170f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                if (pointerId == mActivePointerId) {
171f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    // Our active pointer going up; select a new active pointer
172f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
173f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
174f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
175f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                }
176f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                break;
177f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
178f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
179f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
180f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return super.onInterceptTouchEvent(ev);
181f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
182f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
183f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
184f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * sets the intercept touch listener.
185f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
186f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void setOnInterceptTouchListener(OnInterceptTouchListener l) {
187f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mListener = l;
188f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
189f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein}
190