Home | History | Annotate | Download | only in phototable
      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 
     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 */
     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 
     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 
     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
    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 
    241     private static void log(String message) {
    242         if (DEBUG) {
    243             Log.i(TAG, message);
    244         }
    245     }
    246 }
    247