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