• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 package com.android.dreams.phototable;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.util.Log;
21 import android.view.MotionEvent;
22 import android.view.View;
23 import android.view.ViewConfiguration;
24 
25 /**
26  * Touch listener that implements phototable interactions.
27  */
28 public class PhotoTouchListener implements View.OnTouchListener {
29     private static final String TAG = "PhotoTouchListener";
30     private static final boolean DEBUG = false;
31     private static final int INVALID_POINTER = -1;
32     private static final int MAX_POINTER_COUNT = 10;
33     private final int mTouchSlop;
34     private final int mTapTimeout;
35     private final PhotoTable mTable;
36     private final float mBeta;
37     private final boolean mEnableFling;
38     private final boolean mManualImageRotation;
39     private long mLastEventTime;
40     private float mLastTouchX;
41     private float mLastTouchY;
42     private float mInitialTouchX;
43     private float mInitialTouchY;
44     private float mInitialTouchA;
45     private long mInitialTouchTime;
46     private float mInitialTargetX;
47     private float mInitialTargetY;
48     private float mInitialTargetA;
49     private float mDX;
50     private float mDY;
51     private int mA = INVALID_POINTER;
52     private int mB = INVALID_POINTER;
53     private float[] pts = new float[MAX_POINTER_COUNT];
54 
PhotoTouchListener(Context context, PhotoTable table)55     public PhotoTouchListener(Context context, PhotoTable table) {
56         mTable = table;
57         final ViewConfiguration configuration = ViewConfiguration.get(context);
58         mTouchSlop = configuration.getScaledTouchSlop();
59         mTapTimeout = ViewConfiguration.getTapTimeout();
60         final Resources resources = context.getResources();
61         mBeta = resources.getInteger(R.integer.table_damping) / 1000000f;
62         mEnableFling = resources.getBoolean(R.bool.enable_fling);
63         mManualImageRotation = resources.getBoolean(R.bool.enable_manual_image_rotation);
64     }
65 
66     /** Get angle defined by first two touches, in degrees */
getAngle(View target, MotionEvent ev)67     private float getAngle(View target, MotionEvent ev) {
68         float alpha = 0f;
69         int a = ev.findPointerIndex(mA);
70         int b = ev.findPointerIndex(mB);
71         if (a >=0 && b >=0) {
72             alpha = (float) (Math.atan2(pts[2*a + 1] - pts[2*b + 1],
73                                         pts[2*a] - pts[2*b]) *
74                              180f / Math.PI);
75         }
76         return alpha;
77     }
78 
resetTouch(View target)79     private void resetTouch(View target) {
80         mInitialTouchX = -1;
81         mInitialTouchY = -1;
82         mInitialTouchA = 0f;
83         mInitialTargetX = (float) target.getX();
84         mInitialTargetY = (float) target.getY();
85         mInitialTargetA = (float) target.getRotation();
86     }
87 
onFling(View target, float dX, float dY)88     public void onFling(View target, float dX, float dY) {
89         if (!mEnableFling) {
90             return;
91         }
92         log("fling " + dX + ", " + dY);
93 
94         // convert to pixel per frame
95         dX /= 60f;
96         dY /= 60f;
97 
98         // starting position compionents in global corrdinate frame
99         final float x0 = pts[0];
100         final float y0 = pts[1];
101 
102         // velocity
103         final float v = (float) Math.hypot(dX, dY);
104 
105         if (v == 0f) {
106             return;
107         }
108 
109         // number of steps to come to a stop
110         final float n = (float) Math.max(1.0, (- Math.log(v) / Math.log(mBeta)));
111         // distance travelled before stopping
112         final float s = (float) Math.max(0.0, (v * (1f - Math.pow(mBeta, n)) / (1f - mBeta)));
113 
114         // ending posiiton after stopping
115         final float x1 = x0 + s * dX / v;
116         final float y1 = y0 + s * dY / v;
117 
118         mTable.fling(target, x1 - x0, y1 - y0, (int) (1000f * n / 60f), false);
119     }
120 
121     @Override
onTouch(View target, MotionEvent ev)122     public boolean onTouch(View target, MotionEvent ev) {
123         final int action = ev.getActionMasked();
124 
125         // compute raw coordinates
126         for(int i = 0; i < 10 && i < ev.getPointerCount(); i++) {
127             pts[i*2] = ev.getX(i);
128             pts[i*2 + 1] = ev.getY(i);
129         }
130         target.getMatrix().mapPoints(pts);
131 
132         switch (action) {
133         case MotionEvent.ACTION_DOWN:
134             mTable.moveToTopOfPile(target);
135             mInitialTouchTime = ev.getEventTime();
136             mA = ev.getPointerId(ev.getActionIndex());
137             resetTouch(target);
138             break;
139 
140         case MotionEvent.ACTION_POINTER_DOWN:
141             if (mB == INVALID_POINTER) {
142                 mB = ev.getPointerId(ev.getActionIndex());
143                 mInitialTouchA = getAngle(target, ev);
144             }
145             break;
146 
147         case MotionEvent.ACTION_POINTER_UP:
148             if (mB == ev.getPointerId(ev.getActionIndex())) {
149                 mB = INVALID_POINTER;
150                 mInitialTargetA = (float) target.getRotation();
151             }
152             if (mA == ev.getPointerId(ev.getActionIndex())) {
153                 log("primary went up!");
154                 mA = mB;
155                 resetTouch(target);
156                 mB = INVALID_POINTER;
157             }
158             break;
159 
160         case MotionEvent.ACTION_MOVE: {
161                 if (mA != INVALID_POINTER) {
162                     int idx = ev.findPointerIndex(mA);
163                     float x = pts[2 * idx];
164                     float y = pts[2 * idx + 1];
165                     if (mInitialTouchX == -1 && mInitialTouchY == -1) {
166                         mInitialTouchX = x;
167                         mInitialTouchY = y;
168                     } else {
169                         float dt = (float) (ev.getEventTime() - mLastEventTime) / 1000f;
170                         float tmpDX = (x - mLastTouchX) / dt;
171                         float tmpDY = (y - mLastTouchY) / dt;
172                         if (dt > 0f && (Math.abs(tmpDX) > 5f || Math.abs(tmpDY) > 5f)) {
173                             // work around odd bug with multi-finger flings
174                             mDX = tmpDX;
175                             mDY = tmpDY;
176                         }
177                         log("move " + mDX + ", " + mDY);
178 
179                         mLastEventTime = ev.getEventTime();
180                         mLastTouchX = x;
181                         mLastTouchY = y;
182                     }
183 
184                     if (!mTable.hasSelection()) {
185                         float rotation = target.getRotation();
186                         if (mManualImageRotation && mB != INVALID_POINTER) {
187                             float a = getAngle(target, ev);
188                             rotation = mInitialTargetA + a - mInitialTouchA;
189                         }
190                         mTable.move(target,
191                                     mInitialTargetX + x - mInitialTouchX,
192                                     mInitialTargetY + y - mInitialTouchY,
193                                     rotation);
194                     }
195                 }
196             }
197             break;
198 
199         case MotionEvent.ACTION_UP: {
200                 if (mA != INVALID_POINTER) {
201                     int idx = ev.findPointerIndex(mA);
202                     float x0 = pts[2 * idx];
203                     float y0 = pts[2 * idx + 1];
204                     if (mInitialTouchX == -1 && mInitialTouchY == -1) {
205                         mInitialTouchX = x0;
206                         mInitialTouchY = y0;
207                     }
208                     double distance = Math.hypot(x0 - mInitialTouchX,
209                                                  y0 - mInitialTouchY);
210                     if (mTable.hasSelection()) {
211                         if (distance < mTouchSlop) {
212                           mTable.clearSelection();
213                         } else {
214                           if ((x0 - mInitialTouchX) > 0f) {
215                             mTable.selectPrevious();
216                           } else {
217                             mTable.selectNext();
218                           }
219                         }
220                     } else if ((ev.getEventTime() - mInitialTouchTime) < mTapTimeout &&
221                                distance < mTouchSlop) {
222                         // tap
223                         mTable.setSelection(target);
224                     } else {
225                         onFling(target, mDX, mDY);
226                     }
227                     mA = INVALID_POINTER;
228                     mB = INVALID_POINTER;
229                 }
230             }
231             break;
232 
233         case MotionEvent.ACTION_CANCEL:
234             log("action cancel!");
235             break;
236         }
237 
238         return true;
239     }
240 
log(String message)241     private static void log(String message) {
242         if (DEBUG) {
243             Log.i(TAG, message);
244         }
245     }
246 }
247