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 
18 package com.android.ex.photo;
19 
20 import android.content.Context;
21 import androidx.core.view.MotionEventCompat;
22 import androidx.viewpager.widget.ViewPager;
23 import android.util.AttributeSet;
24 import android.view.MotionEvent;
25 import 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  */
31 public 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          */
onTouchIntercept(float origX, float origY)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 
PhotoViewPager(Context context)66     public PhotoViewPager(Context context) {
67         super(context);
68         initialize();
69     }
70 
PhotoViewPager(Context context, AttributeSet attrs)71     public PhotoViewPager(Context context, AttributeSet attrs) {
72         super(context, attrs);
73         initialize();
74     }
75 
initialize()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
onInterceptTouchEvent(MotionEvent ev)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      */
setOnInterceptTouchListener(OnInterceptTouchListener l)186     public void setOnInterceptTouchListener(OnInterceptTouchListener l) {
187         mListener = l;
188     }
189 }
190