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