1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.gallery3d.photoeditor.actions; 18 19 import android.content.Context; 20 import android.util.AttributeSet; 21 import android.view.MotionEvent; 22 23 /** 24 * View that handles touch-events to track flipping directions and angles. 25 */ 26 class FlipView extends FullscreenToolView { 27 28 /** 29 * Listens to flip changes. 30 */ 31 public interface OnFlipChangeListener { 32 33 void onAngleChanged(float horizontalDegrees, float verticalDegrees, boolean fromUser); 34 35 void onStartTrackingTouch(); 36 37 void onStopTrackingTouch(); 38 } 39 40 private static final float FIXED_DIRECTION_THRESHOLD = 20; 41 42 private OnFlipChangeListener listener; 43 private float maxFlipSpan; 44 private float touchStartX; 45 private float touchStartY; 46 private float currentHorizontalDegrees; 47 private float currentVerticalDegrees; 48 private float lastHorizontalDegrees; 49 private float lastVerticalDegrees; 50 private boolean fixedDirection; 51 private boolean fixedDirectionHorizontal; 52 53 public FlipView(Context context, AttributeSet attrs) { 54 super(context, attrs); 55 } 56 57 public void setOnFlipChangeListener(OnFlipChangeListener listener) { 58 this.listener = listener; 59 } 60 61 public void setFlippedAngles(float horizontalDegrees, float verticalDegrees) { 62 refreshAngle(horizontalDegrees, verticalDegrees, false); 63 } 64 65 /** 66 * Sets allowed degrees for every flip before flipping the view. 67 */ 68 public void setFlipSpan(float degrees) { 69 // Flip-span limits allowed flipping degrees of every flip for usability purpose; the max. 70 // flipped angles could be accumulated and larger than allowed flip-span. 71 maxFlipSpan = degrees; 72 } 73 74 private float calculateAngle(boolean flipHorizontal, float x, float y) { 75 // Use partial length along the moving direction to calculate the flip angle. 76 float maxDistance = (flipHorizontal ? getWidth() : getHeight()) * 0.35f; 77 float moveDistance = flipHorizontal ? (x - touchStartX) : (touchStartY - y); 78 79 if (Math.abs(moveDistance) > maxDistance) { 80 moveDistance = (moveDistance > 0) ? maxDistance : -maxDistance; 81 82 if (flipHorizontal) { 83 touchStartX = x - moveDistance; 84 } else { 85 touchStartY = moveDistance + y; 86 } 87 } 88 return (moveDistance / maxDistance) * maxFlipSpan; 89 } 90 91 @Override 92 public boolean onTouchEvent(MotionEvent ev) { 93 super.onTouchEvent(ev); 94 95 if (isEnabled()) { 96 switch (ev.getAction()) { 97 case MotionEvent.ACTION_DOWN: 98 fixedDirection = false; 99 lastHorizontalDegrees = currentHorizontalDegrees; 100 lastVerticalDegrees = currentVerticalDegrees; 101 touchStartX = ev.getX(); 102 touchStartY = ev.getY(); 103 104 if (listener != null) { 105 listener.onStartTrackingTouch(); 106 } 107 break; 108 109 case MotionEvent.ACTION_MOVE: 110 // Allow only one direction for flipping during movements, and make the 111 // direction fixed once it exceeds threshold. 112 float x = ev.getX(); 113 float y = ev.getY(); 114 boolean flipHorizontal = fixedDirection ? fixedDirectionHorizontal 115 : (Math.abs(x - touchStartX) >= Math.abs(y - touchStartY)); 116 float degrees = calculateAngle(flipHorizontal, x, y); 117 if (!fixedDirection && (Math.abs(degrees) > FIXED_DIRECTION_THRESHOLD)) { 118 fixedDirection = true; 119 fixedDirectionHorizontal = flipHorizontal; 120 } 121 122 if (flipHorizontal) { 123 refreshAngle(lastHorizontalDegrees + degrees, lastVerticalDegrees, true); 124 } else { 125 refreshAngle(lastHorizontalDegrees, lastVerticalDegrees + degrees, true); 126 } 127 break; 128 129 case MotionEvent.ACTION_CANCEL: 130 case MotionEvent.ACTION_UP: 131 if (listener != null) { 132 listener.onStopTrackingTouch(); 133 } 134 break; 135 } 136 } 137 return true; 138 } 139 140 private void refreshAngle(float horizontalDegrees, float verticalDegrees, boolean fromUser) { 141 currentHorizontalDegrees = horizontalDegrees; 142 currentVerticalDegrees = verticalDegrees; 143 if (listener != null) { 144 listener.onAngleChanged(horizontalDegrees, verticalDegrees, fromUser); 145 } 146 } 147 } 148