ExpandHelper.java revision 9b2cd15f0fed990f532f35590c2a2896b90dc7fc
16a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler/*
26a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * Copyright (C) 2012 The Android Open Source Project
36a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler *
46a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * Licensed under the Apache License, Version 2.0 (the "License");
56a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * you may not use this file except in compliance with the License.
66a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * You may obtain a copy of the License at
76a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler *
86a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler *      http://www.apache.org/licenses/LICENSE-2.0
96a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler *
106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * Unless required by applicable law or agreed to in writing, software
116a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * distributed under the License is distributed on an "AS IS" BASIS,
126a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * See the License for the specific language governing permissions and
146a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * limitations under the License.
156a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler */
166a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
176a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
186a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerpackage com.android.systemui;
196a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
208900e631940fdffe7b941b56dc0f17e55345441eRomain Guyimport android.animation.Animator;
218900e631940fdffe7b941b56dc0f17e55345441eRomain Guyimport android.animation.AnimatorListenerAdapter;
22ba925e8ecd9decff5701001a0190042d6797942dChris Wrenimport android.animation.AnimatorSet;
236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.animation.ObjectAnimator;
246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.content.Context;
256a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.util.Log;
269b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wrenimport android.view.Gravity;
276a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.MotionEvent;
286a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.ScaleGestureDetector;
296a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.View;
306a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.ViewGroup;
316a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.View.OnClickListener;
329b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wrenimport android.widget.ScrollView;
339b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wrenimport android.widget.FrameLayout;
346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
356a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerpublic class ExpandHelper implements Gefingerpoken, OnClickListener {
366a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public interface Callback {
375de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren        View getChildAtRawPosition(float x, float y);
386a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        View getChildAtPosition(float x, float y);
3980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        boolean canChildBeExpanded(View v);
408fd12657e353a4a6f6d875a0d86850426fec00e8Chris Wren        boolean setUserExpandedChild(View v, boolean userxpanded);
416a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
426a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
436a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private static final String TAG = "ExpandHelper";
449b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    protected static final boolean DEBUG = true;
456a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private static final long EXPAND_DURATION = 250;
46ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private static final long GLOW_DURATION = 150;
47ba925e8ecd9decff5701001a0190042d6797942dChris Wren
4889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Set to false to disable focus-based gestures (two-finger pull).
4989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private static final boolean USE_DRAG = true;
5089139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Set to false to disable scale-based gestures (both horizontal and vertical).
5189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private static final boolean USE_SPAN = true;
5289139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Both gestures types may be active at the same time.
5389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // At least one gesture type should be active.
5489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // A variant of the screwdriver gesture will emerge from either gesture type.
556a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
5680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // amount of overstretch for maximum brightness expressed in U
5780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
5880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private static final float STRETCH_INTERVAL = 2f;
5980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
6080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // level of glow for a touch, without overstretch
6180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // overstretch fills the range (GLOW_BASE, 1.0]
6280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private static final float GLOW_BASE = 0.5f;
6380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
646a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    @SuppressWarnings("unused")
656a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private Context mContext;
666a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
676a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private boolean mStretching;
685de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    private View mEventSource;
696a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private View mCurrView;
7080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private View mCurrViewTopGlow;
7180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private View mCurrViewBottomGlow;
726a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mOldHeight;
736a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mNaturalHeight;
7489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private float mInitialTouchFocusY;
756a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mInitialTouchSpan;
766a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private Callback mCallback;
776a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private ScaleGestureDetector mDetector;
786a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private ViewScaler mScaler;
79ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mScaleAnimation;
80ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private AnimatorSet mGlowAnimationSet;
81ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mGlowTopAnimation;
82ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mGlowBottomAnimation;
836a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
846a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private int mSmallSize;
856a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private int mLargeSize;
8680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private float mMaximumStretch;
876a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
889b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    private int mGravity;
899b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
906a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private class ViewScaler {
916a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        View mView;
929b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
936a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public ViewScaler() {}
946a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public void setView(View v) {
956a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView = v;
966a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
976a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public void setHeight(float h) {
9880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            if (DEBUG) Log.v(TAG, "SetHeight: setting to " + h);
996a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            ViewGroup.LayoutParams lp = mView.getLayoutParams();
1006a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = (int)h;
1016a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
10289139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            mView.requestLayout();
1036a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1046a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public float getHeight() {
1056a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            int height = mView.getLayoutParams().height;
1066a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            if (height < 0) {
1076a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                height = mView.getMeasuredHeight();
1086a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
1096a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            return (float) height;
1106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1116a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public int getNaturalHeight(int maximum) {
1126a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            ViewGroup.LayoutParams lp = mView.getLayoutParams();
11389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (DEBUG) Log.v(TAG, "Inspecting a child of type: " + mView.getClass().getName());
1146a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            int oldHeight = lp.height;
1156a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
1166a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
1176a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.measure(
1186a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                    View.MeasureSpec.makeMeasureSpec(mView.getMeasuredWidth(),
11989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                                                     View.MeasureSpec.EXACTLY),
1206a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                    View.MeasureSpec.makeMeasureSpec(maximum,
12189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                                                     View.MeasureSpec.AT_MOST));
1226a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = oldHeight;
1236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
1246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            return mView.getMeasuredHeight();
1256a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1266a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
1276a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1289b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    /**
1299b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * Handle expansion gestures to expand and contract children of the callback.
1309b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     *
1319b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param context application context
1329b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param callback the container that holds the items to be manipulated
1339b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param small the smallest allowable size for the manuipulated items.
1349b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param large the largest allowable size for the manuipulated items.
1359b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param scoller if non-null also manipulate the scroll position to obey the gravity.
1369b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     */
1376a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public ExpandHelper(Context context, Callback callback, int small, int large) {
1386a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mSmallSize = small;
13980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
1406a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mLargeSize = large;
1416a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mContext = context;
1426a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCallback = callback;
1436a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mScaler = new ViewScaler();
1449b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = Gravity.TOP;
145ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
146ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.setDuration(EXPAND_DURATION);
147ba925e8ecd9decff5701001a0190042d6797942dChris Wren
1488900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        AnimatorListenerAdapter glowVisibilityController = new AnimatorListenerAdapter() {
1498900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            @Override
1508900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            public void onAnimationStart(Animator animation) {
1518900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View target = (View) ((ObjectAnimator) animation).getTarget();
1528900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                if (target.getAlpha() <= 0.0f) {
1538900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    target.setVisibility(View.VISIBLE);
1548900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                }
1558900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            }
1568900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
1578900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            @Override
1588900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            public void onAnimationEnd(Animator animation) {
1598900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View target = (View) ((ObjectAnimator) animation).getTarget();
1608900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                if (target.getAlpha() <= 0.0f) {
1618900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    target.setVisibility(View.INVISIBLE);
1628900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                }
1638900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            }
1648900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        };
1658900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
166ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowTopAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
1678900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mGlowTopAnimation.addListener(glowVisibilityController);
168ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowBottomAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
1698900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mGlowBottomAnimation.addListener(glowVisibilityController);
170ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet = new AnimatorSet();
171ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation);
172ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet.setDuration(GLOW_DURATION);
173ba925e8ecd9decff5701001a0190042d6797942dChris Wren
1746a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mDetector =
1756a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                new ScaleGestureDetector(context,
1766a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                                         new ScaleGestureDetector.SimpleOnScaleGestureListener() {
1776a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            @Override
1786a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            public boolean onScaleBegin(ScaleGestureDetector detector) {
1796a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                if (DEBUG) Log.v(TAG, "onscalebegin()");
1805de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                float x = detector.getFocusX();
1815de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                float y = detector.getFocusY();
1825de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
1835de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                View v = null;
1845de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                if (mEventSource != null) {
1855de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                    int[] location = new int[2];
1865de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                    mEventSource.getLocationOnScreen(location);
1875de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                    x += (float) location[0];
1885de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                    y += (float) location[1];
1895de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                    v = mCallback.getChildAtRawPosition(x, y);
1905de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                } else {
1915de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                    v = mCallback.getChildAtPosition(x, y);
1925de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                }
1936a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1946a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                // your fingers have to be somewhat close to the bounds of the view in question
19589139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                mInitialTouchFocusY = detector.getFocusY();
19689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                mInitialTouchSpan = Math.abs(detector.getCurrentSpan());
19789139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                if (DEBUG) Log.d(TAG, "got mInitialTouchSpan: (" + mInitialTouchSpan + ")");
1986a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1996a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                mStretching = initScale(v);
2006a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                return mStretching;
2016a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
2026a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
2036a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            @Override
2046a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            public boolean onScale(ScaleGestureDetector detector) {
2056a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                if (DEBUG) Log.v(TAG, "onscale() on " + mCurrView);
20689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren
20789139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                // are we scaling or dragging?
20889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float span = Math.abs(detector.getCurrentSpan()) - mInitialTouchSpan;
20989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                span *= USE_SPAN ? 1f : 0f;
21089139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float drag = detector.getFocusY() - mInitialTouchFocusY;
21189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                drag *= USE_DRAG ? 1f : 0f;
2129b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren                drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
21389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float pull = Math.abs(drag) + Math.abs(span) + 1f;
21489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
21589139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                if (DEBUG) Log.d(TAG, "current span handle is: " + hand);
21689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                hand = hand + mOldHeight;
21789139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float target = hand;
21880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                if (DEBUG) Log.d(TAG, "target is: " + target);
21989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                hand = hand < mSmallSize ? mSmallSize : (hand > mLargeSize ? mLargeSize : hand);
22089139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                hand = hand > mNaturalHeight ? mNaturalHeight : hand;
22189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                if (DEBUG) Log.d(TAG, "scale continues: hand =" + hand);
22289139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                mScaler.setHeight(hand);
22389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren
22489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                // glow if overscale
22589139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float stretch = (float) Math.abs((target - hand) / mMaximumStretch);
22680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f)));
22780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                if (DEBUG) Log.d(TAG, "stretch: " + stretch + " strength: " + strength);
22880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                setGlow(GLOW_BASE + strength * (1f - GLOW_BASE));
2296a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                return true;
2306a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
2316a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
2326a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            @Override
2336a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            public void onScaleEnd(ScaleGestureDetector detector) {
2346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                if (DEBUG) Log.v(TAG, "onscaleend()");
2356a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                // I guess we're alone now
2366a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                if (DEBUG) Log.d(TAG, "scale end");
2376a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                finishScale(false);
2386a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
2396a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        });
2406a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
2415de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
2425de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    public void setEventSource(View eventSource) {
2435de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren        mEventSource = eventSource;
2445de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    }
2455de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
2469b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    public void setGravity(int gravity) {
2479b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = gravity;
2489b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    }
2499b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
25080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    public void setGlow(float glow) {
25189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        if (!mGlowAnimationSet.isRunning() || glow == 0f) {
25289139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (mGlowAnimationSet.isRunning()) {
25389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                mGlowAnimationSet.cancel();
25489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            }
255ba925e8ecd9decff5701001a0190042d6797942dChris Wren            if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) {
256ba925e8ecd9decff5701001a0190042d6797942dChris Wren                if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) {
257ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    // animate glow in and out
258ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowTopAnimation.setTarget(mCurrViewTopGlow);
259ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowBottomAnimation.setTarget(mCurrViewBottomGlow);
260ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowTopAnimation.setFloatValues(glow);
261ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowBottomAnimation.setFloatValues(glow);
262ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowAnimationSet.setupStartValues();
263ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowAnimationSet.start();
264ba925e8ecd9decff5701001a0190042d6797942dChris Wren                } else {
265ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    // set it explicitly in reponse to touches.
266ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mCurrViewTopGlow.setAlpha(glow);
267ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mCurrViewBottomGlow.setAlpha(glow);
2688900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    handleGlowVisibility();
269ba925e8ecd9decff5701001a0190042d6797942dChris Wren                }
270ba925e8ecd9decff5701001a0190042d6797942dChris Wren            }
27180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        }
27280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
2738900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
2748900e631940fdffe7b941b56dc0f17e55345441eRomain Guy    private void handleGlowVisibility() {
2758900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mCurrViewTopGlow.setVisibility(mCurrViewTopGlow.getAlpha() <= 0.0f ?
2768900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View.INVISIBLE : View.VISIBLE);
2778900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mCurrViewBottomGlow.setVisibility(mCurrViewBottomGlow.getAlpha() <= 0.0f ?
2788900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View.INVISIBLE : View.VISIBLE);
2798900e631940fdffe7b941b56dc0f17e55345441eRomain Guy    }
2808900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
2816a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onInterceptTouchEvent(MotionEvent ev) {
2826a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (DEBUG) Log.d(TAG, "interceptTouch: act=" + (ev.getAction()) +
2836a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                         " stretching=" + mStretching);
2846a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mDetector.onTouchEvent(ev);
2856a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        return mStretching;
2866a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
2876a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
2886a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onTouchEvent(MotionEvent ev) {
2896a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        final int action = ev.getAction();
2906a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (DEBUG) Log.d(TAG, "touch: act=" + (action) + " stretching=" + mStretching);
2916a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (mStretching) {
2925de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren            if (DEBUG) Log.d(TAG, "detector ontouch");
2936a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mDetector.onTouchEvent(ev);
2946a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
2956a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        switch (action) {
2966a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_UP:
2976a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_CANCEL:
2985de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                if (DEBUG) Log.d(TAG, "cancel");
2996a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                mStretching = false;
30080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                clearView();
3016a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                break;
3026a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
3036a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        return true;
3046a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
3056a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private boolean initScale(View v) {
3066a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (v != null) {
3076a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            if (DEBUG) Log.d(TAG, "scale begins on view: " + v);
3086a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mStretching = true;
30980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            setView(v);
31080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            setGlow(GLOW_BASE);
3116a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mScaler.setView(v);
3126a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mOldHeight = mScaler.getHeight();
31380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            if (mCallback.canChildBeExpanded(v)) {
31480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                if (DEBUG) Log.d(TAG, "working on an expandable child");
31580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
31680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            } else {
31780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                if (DEBUG) Log.d(TAG, "working on a non-expandable child");
31880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                mNaturalHeight = mOldHeight;
31980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            }
3206a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight +
3216a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                        " mNaturalHeight: " + mNaturalHeight);
3226a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            v.getParent().requestDisallowInterceptTouchEvent(true);
3236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
3246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        return mStretching;
3256a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
3266a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
3276a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private void finishScale(boolean force) {
3286a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        float h = mScaler.getHeight();
3296a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        final boolean wasClosed = (mOldHeight == mSmallSize);
3306a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (wasClosed) {
3316a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            h = (force || h > mSmallSize) ? mNaturalHeight : mSmallSize;
3326a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        } else {
3336a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            h = (force || h < mNaturalHeight) ? mSmallSize : mNaturalHeight;
3346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
33580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        if (DEBUG && mCurrView != null) mCurrView.setBackgroundColor(0);
336ba925e8ecd9decff5701001a0190042d6797942dChris Wren        if (mScaleAnimation.isRunning()) {
337ba925e8ecd9decff5701001a0190042d6797942dChris Wren            mScaleAnimation.cancel();
338ba925e8ecd9decff5701001a0190042d6797942dChris Wren        }
339ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.setFloatValues(h);
340ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.setupStartValues();
341ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.start();
3426a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mStretching = false;
34380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        setGlow(0f);
3448fd12657e353a4a6f6d875a0d86850426fec00e8Chris Wren        mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight);
34589139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        if (DEBUG) Log.d(TAG, "scale was finished on view: " + mCurrView);
34680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        clearView();
34780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
34880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
34980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void clearView() {
3506a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCurrView = null;
35180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mCurrViewTopGlow = null;
35280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mCurrViewBottomGlow = null;
35380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
35480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
35580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void setView(View v) {
35689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        mCurrView = v;
35780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        if (v instanceof ViewGroup) {
35880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            ViewGroup g = (ViewGroup) v;
35980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            mCurrViewTopGlow = g.findViewById(R.id.top_glow);
36080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            mCurrViewBottomGlow = g.findViewById(R.id.bottom_glow);
36189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (DEBUG) {
36280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                String debugLog = "Looking for glows: " +
36380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                        (mCurrViewTopGlow != null ? "found top " : "didn't find top") +
36480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                        (mCurrViewBottomGlow != null ? "found bottom " : "didn't find bottom");
36580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                Log.v(TAG,  debugLog);
36680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            }
36780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        }
3686a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
3696a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
3706a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    @Override
3716a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public void onClick(View v) {
3726a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        initScale(v);
3736a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        finishScale(true);
3746a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
3756a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
3766a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler}
377