blob: 6141eadbcf15ed746eafb9cecf8944c9bd47a922 [file] [log] [blame]
Daniel Sandler6a858c32012-03-12 14:38:58 -04001/*
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
17
18package com.android.systemui;
19
20import android.animation.ObjectAnimator;
21import android.content.Context;
22import android.graphics.RectF;
23import android.util.Log;
24import android.view.MotionEvent;
25import android.view.ScaleGestureDetector;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.View.OnClickListener;
29import com.android.internal.widget.SizeAdaptiveLayout;
30
31public class ExpandHelper implements Gefingerpoken, OnClickListener {
32 public interface Callback {
33 View getChildAtPosition(MotionEvent ev);
34 View getChildAtPosition(float x, float y);
Chris Wren80a76272012-04-18 10:52:18 -040035 boolean canChildBeExpanded(View v);
Daniel Sandler6a858c32012-03-12 14:38:58 -040036 }
37
38 private static final String TAG = "ExpandHelper";
39 protected static final boolean DEBUG = false;
40 private static final long EXPAND_DURATION = 250;
41
Chris Wren80a76272012-04-18 10:52:18 -040042 // amount of overstretch for maximum brightness expressed in U
43 // 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
44 private static final float STRETCH_INTERVAL = 2f;
45
46 // level of glow for a touch, without overstretch
47 // overstretch fills the range (GLOW_BASE, 1.0]
48 private static final float GLOW_BASE = 0.5f;
49
Daniel Sandler6a858c32012-03-12 14:38:58 -040050 @SuppressWarnings("unused")
51 private Context mContext;
52
53 private boolean mStretching;
54 private View mCurrView;
Chris Wren80a76272012-04-18 10:52:18 -040055 private View mCurrViewTopGlow;
56 private View mCurrViewBottomGlow;
Daniel Sandler6a858c32012-03-12 14:38:58 -040057 private float mOldHeight;
58 private float mNaturalHeight;
59 private float mInitialTouchSpan;
60 private Callback mCallback;
61 private ScaleGestureDetector mDetector;
62 private ViewScaler mScaler;
63 private ObjectAnimator mAnimation;
64
65 private int mSmallSize;
66 private int mLargeSize;
Chris Wren80a76272012-04-18 10:52:18 -040067 private float mMaximumStretch;
Daniel Sandler6a858c32012-03-12 14:38:58 -040068
69 private class ViewScaler {
70 View mView;
71 public ViewScaler() {}
72 public void setView(View v) {
73 mView = v;
74 }
75 public void setHeight(float h) {
Chris Wren80a76272012-04-18 10:52:18 -040076 if (DEBUG) Log.v(TAG, "SetHeight: setting to " + h);
Daniel Sandler6a858c32012-03-12 14:38:58 -040077 ViewGroup.LayoutParams lp = mView.getLayoutParams();
78 lp.height = (int)h;
79 mView.setLayoutParams(lp);
80 mView.requestLayout();
81 }
82 public float getHeight() {
83 int height = mView.getLayoutParams().height;
84 if (height < 0) {
85 height = mView.getMeasuredHeight();
86 }
87 return (float) height;
88 }
89 public int getNaturalHeight(int maximum) {
90 ViewGroup.LayoutParams lp = mView.getLayoutParams();
91 if (DEBUG) Log.v(TAG, "Inspecting a child of type: " + mView.getClass().getName());
92 int oldHeight = lp.height;
93 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
94 mView.setLayoutParams(lp);
95 mView.measure(
96 View.MeasureSpec.makeMeasureSpec(mView.getMeasuredWidth(),
97 View.MeasureSpec.EXACTLY),
98 View.MeasureSpec.makeMeasureSpec(maximum,
99 View.MeasureSpec.AT_MOST));
100 lp.height = oldHeight;
101 mView.setLayoutParams(lp);
102 return mView.getMeasuredHeight();
103 }
104 }
105
106 public ExpandHelper(Context context, Callback callback, int small, int large) {
107 mSmallSize = small;
Chris Wren80a76272012-04-18 10:52:18 -0400108 mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400109 mLargeSize = large;
110 mContext = context;
111 mCallback = callback;
112 mScaler = new ViewScaler();
113 mDetector =
114 new ScaleGestureDetector(context,
115 new ScaleGestureDetector.SimpleOnScaleGestureListener() {
116 @Override
117 public boolean onScaleBegin(ScaleGestureDetector detector) {
118 if (DEBUG) Log.v(TAG, "onscalebegin()");
119 View v = mCallback.getChildAtPosition(detector.getFocusX(), detector.getFocusY());
120
121 // your fingers have to be somewhat close to the bounds of the view in question
122 mInitialTouchSpan = Math.abs(detector.getCurrentSpanY());
123 if (DEBUG) Log.d(TAG, "got mInitialTouchSpan: " + mInitialTouchSpan);
124
125 mStretching = initScale(v);
126 return mStretching;
127 }
128
129 @Override
130 public boolean onScale(ScaleGestureDetector detector) {
131 if (DEBUG) Log.v(TAG, "onscale() on " + mCurrView);
132 float h = Math.abs(detector.getCurrentSpanY());
133 if (DEBUG) Log.d(TAG, "current span is: " + h);
134 h = h + mOldHeight - mInitialTouchSpan;
Chris Wren80a76272012-04-18 10:52:18 -0400135 float target = h;
136 if (DEBUG) Log.d(TAG, "target is: " + target);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400137 h = h<mSmallSize?mSmallSize:(h>mLargeSize?mLargeSize:h);
138 h = h>mNaturalHeight?mNaturalHeight:h;
139 if (DEBUG) Log.d(TAG, "scale continues: h=" + h);
140 mScaler.setHeight(h);
Chris Wren80a76272012-04-18 10:52:18 -0400141 float stretch = (float) Math.abs((target - h) / mMaximumStretch);
142 float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f)));
143 if (DEBUG) Log.d(TAG, "stretch: " + stretch + " strength: " + strength);
144 setGlow(GLOW_BASE + strength * (1f - GLOW_BASE));
Daniel Sandler6a858c32012-03-12 14:38:58 -0400145 return true;
146 }
147
148 @Override
149 public void onScaleEnd(ScaleGestureDetector detector) {
150 if (DEBUG) Log.v(TAG, "onscaleend()");
151 // I guess we're alone now
152 if (DEBUG) Log.d(TAG, "scale end");
153 finishScale(false);
154 }
155 });
156 }
Chris Wren80a76272012-04-18 10:52:18 -0400157 public void setGlow(float glow) {
158 if (mCurrViewTopGlow != null) {
159 mCurrViewTopGlow.setAlpha(glow);
160 }
161 if (mCurrViewBottomGlow != null) {
162 mCurrViewBottomGlow.setAlpha(glow);
163 }
164 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400165
166 public boolean onInterceptTouchEvent(MotionEvent ev) {
167 if (DEBUG) Log.d(TAG, "interceptTouch: act=" + (ev.getAction()) +
168 " stretching=" + mStretching);
169 mDetector.onTouchEvent(ev);
170 return mStretching;
171 }
172
173 public boolean onTouchEvent(MotionEvent ev) {
174 final int action = ev.getAction();
175 if (DEBUG) Log.d(TAG, "touch: act=" + (action) + " stretching=" + mStretching);
176 if (mStretching) {
177 mDetector.onTouchEvent(ev);
178 }
179 switch (action) {
180 case MotionEvent.ACTION_UP:
181 case MotionEvent.ACTION_CANCEL:
182 mStretching = false;
Chris Wren80a76272012-04-18 10:52:18 -0400183 clearView();
Daniel Sandler6a858c32012-03-12 14:38:58 -0400184 break;
185 }
186 return true;
187 }
188 private boolean initScale(View v) {
189 if (v != null) {
190 if (DEBUG) Log.d(TAG, "scale begins on view: " + v);
191 mStretching = true;
Chris Wren80a76272012-04-18 10:52:18 -0400192 setView(v);
193 setGlow(GLOW_BASE);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400194 mScaler.setView(v);
195 mOldHeight = mScaler.getHeight();
Chris Wren80a76272012-04-18 10:52:18 -0400196 if (mCallback.canChildBeExpanded(v)) {
197 if (DEBUG) Log.d(TAG, "working on an expandable child");
198 mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
199 } else {
200 if (DEBUG) Log.d(TAG, "working on a non-expandable child");
201 mNaturalHeight = mOldHeight;
202 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400203 if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight +
204 " mNaturalHeight: " + mNaturalHeight);
205 v.getParent().requestDisallowInterceptTouchEvent(true);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400206 }
207 return mStretching;
208 }
209
210 private void finishScale(boolean force) {
211 float h = mScaler.getHeight();
212 final boolean wasClosed = (mOldHeight == mSmallSize);
213 if (wasClosed) {
214 h = (force || h > mSmallSize) ? mNaturalHeight : mSmallSize;
215 } else {
216 h = (force || h < mNaturalHeight) ? mSmallSize : mNaturalHeight;
217 }
Chris Wren80a76272012-04-18 10:52:18 -0400218 if (DEBUG && mCurrView != null) mCurrView.setBackgroundColor(0);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400219 mAnimation = ObjectAnimator.ofFloat(mScaler, "height", h).setDuration(EXPAND_DURATION);
220 mAnimation.start();
221 mStretching = false;
Chris Wren80a76272012-04-18 10:52:18 -0400222 setGlow(0f);
223 clearView();
224 }
225
226 private void clearView() {
Daniel Sandler6a858c32012-03-12 14:38:58 -0400227 mCurrView = null;
Chris Wren80a76272012-04-18 10:52:18 -0400228 mCurrViewTopGlow = null;
229 mCurrViewBottomGlow = null;
230 }
231
232 private void setView(View v) {
233 mCurrView = null;
234 if (v instanceof ViewGroup) {
235 ViewGroup g = (ViewGroup) v;
236 mCurrViewTopGlow = g.findViewById(R.id.top_glow);
237 mCurrViewBottomGlow = g.findViewById(R.id.bottom_glow);
238 if (DEBUG) {
239 String debugLog = "Looking for glows: " +
240 (mCurrViewTopGlow != null ? "found top " : "didn't find top") +
241 (mCurrViewBottomGlow != null ? "found bottom " : "didn't find bottom");
242 Log.v(TAG, debugLog);
243 }
244 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400245 }
246
247 @Override
248 public void onClick(View v) {
249 initScale(v);
250 finishScale(true);
251
252 }
253}