Loading packages/SystemUI/res/layout/status_bar_expanded.xml +1 −1 Original line number Diff line number Diff line Loading @@ -67,7 +67,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fadingEdge="none" android:overScrollMode="always" android:overScrollMode="ifContentScrolls" > <com.android.systemui.statusbar.policy.NotificationRowLayout android:id="@+id/latestItems" Loading packages/SystemUI/res/values/config.xml +3 −0 Original line number Diff line number Diff line Loading @@ -65,5 +65,8 @@ <!-- Vibration duration for MultiWaveView used in SearchPanelView --> <integer translatable="false" name="config_search_panel_view_vibration_duration">20</integer> <!-- The length of the vibration when the notificaiotn pops open. --> <integer name="one_finger_pop_duration_ms">10</integer> </resources> packages/SystemUI/res/values/dimens.xml +3 −0 Original line number Diff line number Diff line Loading @@ -150,4 +150,7 @@ <!-- Height of the carrier/wifi name label --> <dimen name="carrier_label_height">24dp</dimen> <!-- The distance you can pull a notificaiton before it pops open --> <dimen name="one_finger_pop_limit">32dp</dimen> </resources> packages/SystemUI/res/values/ids.xml +1 −0 Original line number Diff line number Diff line Loading @@ -18,4 +18,5 @@ <resources> <item type="id" name="expandable_tag" /> <item type="id" name="user_expanded_tag" /> <item type="id" name="user_lock_tag" /> </resources> packages/SystemUI/src/com/android/systemui/ExpandHelper.java +223 −51 Original line number Diff line number Diff line Loading @@ -22,24 +22,31 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.os.Vibrator; import android.util.Slog; import android.view.Gravity; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.View.OnClickListener; import java.util.Stack; public class ExpandHelper implements Gefingerpoken, OnClickListener { public interface Callback { View getChildAtRawPosition(float x, float y); View getChildAtPosition(float x, float y); boolean canChildBeExpanded(View v); boolean setUserExpandedChild(View v, boolean userxpanded); boolean setUserExpandedChild(View v, boolean userExpanded); boolean setUserLockedChild(View v, boolean userLocked); } private static final String TAG = "ExpandHelper"; protected static final boolean DEBUG = false; protected static final boolean DEBUG_SCALE = false; protected static final boolean DEBUG_GLOW = false; private static final long EXPAND_DURATION = 250; private static final long GLOW_DURATION = 150; Loading @@ -63,6 +70,9 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private Context mContext; private boolean mStretching; private boolean mPullingWithOneFinger; private boolean mWatchingForPull; private boolean mHasPopped; private View mEventSource; private View mCurrView; private View mCurrViewTopGlow; Loading @@ -70,7 +80,12 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private float mOldHeight; private float mNaturalHeight; private float mInitialTouchFocusY; private float mInitialTouchY; private float mInitialTouchSpan; private int mTouchSlop; private int mLastMotionY; private float mPopLimit; private int mPopDuration; private Callback mCallback; private ScaleGestureDetector mDetector; private ViewScaler mScaler; Loading @@ -78,6 +93,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private AnimatorSet mGlowAnimationSet; private ObjectAnimator mGlowTopAnimation; private ObjectAnimator mGlowBottomAnimation; private Vibrator mVibrator; private int mSmallSize; private int mLargeSize; Loading @@ -85,6 +101,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private int mGravity; private View mScrollView; private class ViewScaler { View mView; Loading @@ -93,7 +111,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mView = v; } public void setHeight(float h) { if (DEBUG) Slog.v(TAG, "SetHeight: setting to " + h); if (DEBUG_SCALE) Slog.v(TAG, "SetHeight: setting to " + h); ViewGroup.LayoutParams lp = mView.getLayoutParams(); lp.height = (int)h; mView.setLayoutParams(lp); Loading @@ -108,7 +126,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { } public int getNaturalHeight(int maximum) { ViewGroup.LayoutParams lp = mView.getLayoutParams(); if (DEBUG) Slog.v(TAG, "Inspecting a child of type: " + mView.getClass().getName()); if (DEBUG_SCALE) Slog.v(TAG, "Inspecting a child of type: " + mView.getClass().getName()); int oldHeight = lp.height; lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; mView.setLayoutParams(lp); Loading Loading @@ -142,6 +161,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mGravity = Gravity.TOP; mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f); mScaleAnimation.setDuration(EXPAND_DURATION); mPopLimit = mContext.getResources().getDimension(R.dimen.one_finger_pop_limit); mPopDuration = mContext.getResources().getInteger(R.integer.one_finger_pop_duration_ms); AnimatorListenerAdapter glowVisibilityController = new AnimatorListenerAdapter() { @Override Loading Loading @@ -169,38 +190,30 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation); mGlowAnimationSet.setDuration(GLOW_DURATION); final ViewConfiguration configuration = ViewConfiguration.get(mContext); mTouchSlop = configuration.getScaledTouchSlop(); mDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { if (DEBUG) Slog.v(TAG, "onscalebegin()"); if (DEBUG_SCALE) Slog.v(TAG, "onscalebegin()"); float x = detector.getFocusX(); float y = detector.getFocusY(); View v = null; if (mEventSource != null) { int[] location = new int[2]; mEventSource.getLocationOnScreen(location); x += (float) location[0]; y += (float) location[1]; v = mCallback.getChildAtRawPosition(x, y); } else { v = mCallback.getChildAtPosition(x, y); } // your fingers have to be somewhat close to the bounds of the view in question mInitialTouchFocusY = detector.getFocusY(); mInitialTouchSpan = Math.abs(detector.getCurrentSpan()); if (DEBUG) Slog.d(TAG, "got mInitialTouchSpan: (" + mInitialTouchSpan + ")"); if (DEBUG_SCALE) Slog.d(TAG, "got mInitialTouchSpan: (" + mInitialTouchSpan + ")"); mStretching = initScale(v); mStretching = initScale(findView(x, y)); return mStretching; } @Override public boolean onScale(ScaleGestureDetector detector) { if (DEBUG) Slog.v(TAG, "onscale() on " + mCurrView); if (DEBUG_SCALE) Slog.v(TAG, "onscale() on " + mCurrView); // are we scaling or dragging? float span = Math.abs(detector.getCurrentSpan()) - mInitialTouchSpan; Loading @@ -210,33 +223,71 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { drag *= mGravity == Gravity.BOTTOM ? -1f : 1f; float pull = Math.abs(drag) + Math.abs(span) + 1f; float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull; if (DEBUG) Slog.d(TAG, "current span handle is: " + hand); hand = hand + mOldHeight; float target = hand; if (DEBUG) Slog.d(TAG, "target is: " + target); hand = hand < mSmallSize ? mSmallSize : (hand > mLargeSize ? mLargeSize : hand); hand = hand > mNaturalHeight ? mNaturalHeight : hand; if (DEBUG) Slog.d(TAG, "scale continues: hand =" + hand); mScaler.setHeight(hand); float target = hand + mOldHeight; float newHeight = clamp(target); mScaler.setHeight(newHeight); // glow if overscale float stretch = (float) Math.abs((target - hand) / mMaximumStretch); float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f))); if (DEBUG) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength); setGlow(GLOW_BASE + strength * (1f - GLOW_BASE)); setGlow(calculateGlow(target, newHeight)); return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { if (DEBUG) Slog.v(TAG, "onscaleend()"); if (DEBUG_SCALE) Slog.v(TAG, "onscaleend()"); // I guess we're alone now if (DEBUG) Slog.d(TAG, "scale end"); if (DEBUG_SCALE) Slog.d(TAG, "scale end"); finishScale(false); clearView(); mStretching = false; } }); } private float clamp(float target) { float out = target; out = out < mSmallSize ? mSmallSize : (out > mLargeSize ? mLargeSize : out); out = out > mNaturalHeight ? mNaturalHeight : out; return out; } private View findView(float x, float y) { View v = null; if (mEventSource != null) { int[] location = new int[2]; mEventSource.getLocationOnScreen(location); x += (float) location[0]; y += (float) location[1]; v = mCallback.getChildAtRawPosition(x, y); } else { v = mCallback.getChildAtPosition(x, y); } return v; } private boolean isInside(View v, float x, float y) { if (DEBUG) Slog.d(TAG, "isinside (" + x + ", " + y + ")"); if (v == null) { if (DEBUG) Slog.d(TAG, "isinside null subject"); return false; } if (mEventSource != null) { int[] location = new int[2]; mEventSource.getLocationOnScreen(location); x += (float) location[0]; y += (float) location[1]; if (DEBUG) Slog.d(TAG, " to global (" + x + ", " + y + ")"); } int[] location = new int[2]; v.getLocationOnScreen(location); x -= (float) location[0]; y -= (float) location[1]; if (DEBUG) Slog.d(TAG, " to local (" + x + ", " + y + ")"); if (DEBUG) Slog.d(TAG, " inside (" + v.getWidth() + ", " + v.getHeight() + ")"); boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight()); return inside; } public void setEventSource(View eventSource) { mEventSource = eventSource; } Loading @@ -245,10 +296,23 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mGravity = gravity; } public void setScrollView(View scrollView) { mScrollView = scrollView; } private float calculateGlow(float target, float actual) { // glow if overscale if (DEBUG_GLOW) Slog.d(TAG, "target: " + target + " actual: " + actual); float stretch = (float) Math.abs((target - actual) / mMaximumStretch); float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f))); if (DEBUG_GLOW) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength); return (GLOW_BASE + strength * (1f - GLOW_BASE)); } public void setGlow(float glow) { if (!mGlowAnimationSet.isRunning() || glow == 0f) { if (mGlowAnimationSet.isRunning()) { mGlowAnimationSet.cancel(); mGlowAnimationSet.end(); } if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) { if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) { Loading Loading @@ -278,23 +342,115 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { public boolean onInterceptTouchEvent(MotionEvent ev) { if (DEBUG) Slog.d(TAG, "interceptTouch: act=" + (ev.getAction()) + " stretching=" + mStretching); " stretching=" + mStretching + " onefinger=" + mPullingWithOneFinger); // check for a two-finger gesture mDetector.onTouchEvent(ev); return mStretching; if (mStretching) { return true; } else { final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && mPullingWithOneFinger) { return true; } if (mScrollView != null && mScrollView.getScrollY() > 0) { return false; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { if (mWatchingForPull) { final int x = (int) ev.getX(); final int y = (int) ev.getY(); final int yDiff = y - mLastMotionY; if (yDiff > mTouchSlop) { mLastMotionY = y; mPullingWithOneFinger = initScale(findView(x, y)); if (mPullingWithOneFinger) { mInitialTouchY = mLastMotionY; mHasPopped = false; } } } break; } case MotionEvent.ACTION_DOWN: mWatchingForPull = isInside(mScrollView, ev.getX(), ev.getY()); mLastMotionY = (int) ev.getY(); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (mPullingWithOneFinger) { finishScale(false); clearView(); } mPullingWithOneFinger = false; mWatchingForPull = false; break; } return mPullingWithOneFinger; } } public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); if (DEBUG) Slog.d(TAG, "touch: act=" + (action) + " stretching=" + mStretching); if (DEBUG_SCALE) Slog.d(TAG, "touch: act=" + (action) + " stretching=" + mStretching + " onefinger=" + mPullingWithOneFinger); if (mStretching) { if (DEBUG) Slog.d(TAG, "detector ontouch"); mDetector.onTouchEvent(ev); } switch (action) { case MotionEvent.ACTION_MOVE: { if (mPullingWithOneFinger) { final float rawHeight = ev.getY() - mInitialTouchY + mOldHeight; final float newHeight = clamp(rawHeight); final boolean wasClosed = (mOldHeight == mSmallSize); boolean isFinished = false; if (rawHeight > mNaturalHeight) { isFinished = true; } if (rawHeight < mSmallSize) { isFinished = true; } final float pull = Math.abs(ev.getY() - mInitialTouchY); if (mHasPopped || pull > mPopLimit) { if (!mHasPopped) { vibrate(mPopDuration); mHasPopped = true; } } if (mHasPopped) { mScaler.setHeight(newHeight); setGlow(GLOW_BASE); } else { setGlow(calculateGlow(4f * pull, 0f)); } final int x = (int) ev.getX(); final int y = (int) ev.getY(); View underPointer = findView(x, y); if (isFinished && underPointer != null && underPointer != mCurrView) { finishScale(false); initScale(underPointer); mInitialTouchY = ev.getY(); mHasPopped = false; } return true; } break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (DEBUG) Slog.d(TAG, "cancel"); mStretching = false; if (mPullingWithOneFinger) { finishScale(false); mPullingWithOneFinger = false; } clearView(); break; } Loading @@ -303,7 +459,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private boolean initScale(View v) { if (v != null) { if (DEBUG) Slog.d(TAG, "scale begins on view: " + v); mStretching = true; mCallback.setUserLockedChild(v, true); setView(v); setGlow(GLOW_BASE); mScaler.setView(v); Loading @@ -318,30 +474,34 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { if (DEBUG) Slog.d(TAG, "got mOldHeight: " + mOldHeight + " mNaturalHeight: " + mNaturalHeight); v.getParent().requestDisallowInterceptTouchEvent(true); return true; } else { return false; } return mStretching; } private void finishScale(boolean force) { float currentHeight = mScaler.getHeight(); float targetHeight = mSmallSize; float h = mScaler.getHeight(); final boolean wasClosed = (mOldHeight == mSmallSize); if (wasClosed) { h = (force || h > mSmallSize) ? mNaturalHeight : mSmallSize; targetHeight = (force || currentHeight > mSmallSize) ? mNaturalHeight : mSmallSize; } else { h = (force || h < mNaturalHeight) ? mSmallSize : mNaturalHeight; targetHeight = (force || currentHeight < mNaturalHeight) ? mSmallSize : mNaturalHeight; } if (DEBUG && mCurrView != null) mCurrView.setBackgroundColor(0); if (mScaleAnimation.isRunning()) { mScaleAnimation.cancel(); } mScaleAnimation.setFloatValues(h); mScaleAnimation.setupStartValues(); mScaleAnimation.start(); mStretching = false; setGlow(0f); mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight); if (targetHeight != currentHeight) { mScaleAnimation.setFloatValues(targetHeight); mScaleAnimation.setupStartValues(); mScaleAnimation.start(); } mCallback.setUserLockedChild(mCurrView, false); if (DEBUG) Slog.d(TAG, "scale was finished on view: " + mCurrView); clearView(); } private void clearView() { Loading Loading @@ -369,6 +529,18 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { public void onClick(View v) { initScale(v); finishScale(true); clearView(); } /** * Triggers haptic feedback. */ private synchronized void vibrate(long duration) { if (mVibrator == null) { mVibrator = (android.os.Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); } mVibrator.vibrate(duration); } } Loading
packages/SystemUI/res/layout/status_bar_expanded.xml +1 −1 Original line number Diff line number Diff line Loading @@ -67,7 +67,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fadingEdge="none" android:overScrollMode="always" android:overScrollMode="ifContentScrolls" > <com.android.systemui.statusbar.policy.NotificationRowLayout android:id="@+id/latestItems" Loading
packages/SystemUI/res/values/config.xml +3 −0 Original line number Diff line number Diff line Loading @@ -65,5 +65,8 @@ <!-- Vibration duration for MultiWaveView used in SearchPanelView --> <integer translatable="false" name="config_search_panel_view_vibration_duration">20</integer> <!-- The length of the vibration when the notificaiotn pops open. --> <integer name="one_finger_pop_duration_ms">10</integer> </resources>
packages/SystemUI/res/values/dimens.xml +3 −0 Original line number Diff line number Diff line Loading @@ -150,4 +150,7 @@ <!-- Height of the carrier/wifi name label --> <dimen name="carrier_label_height">24dp</dimen> <!-- The distance you can pull a notificaiton before it pops open --> <dimen name="one_finger_pop_limit">32dp</dimen> </resources>
packages/SystemUI/res/values/ids.xml +1 −0 Original line number Diff line number Diff line Loading @@ -18,4 +18,5 @@ <resources> <item type="id" name="expandable_tag" /> <item type="id" name="user_expanded_tag" /> <item type="id" name="user_lock_tag" /> </resources>
packages/SystemUI/src/com/android/systemui/ExpandHelper.java +223 −51 Original line number Diff line number Diff line Loading @@ -22,24 +22,31 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.os.Vibrator; import android.util.Slog; import android.view.Gravity; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.View.OnClickListener; import java.util.Stack; public class ExpandHelper implements Gefingerpoken, OnClickListener { public interface Callback { View getChildAtRawPosition(float x, float y); View getChildAtPosition(float x, float y); boolean canChildBeExpanded(View v); boolean setUserExpandedChild(View v, boolean userxpanded); boolean setUserExpandedChild(View v, boolean userExpanded); boolean setUserLockedChild(View v, boolean userLocked); } private static final String TAG = "ExpandHelper"; protected static final boolean DEBUG = false; protected static final boolean DEBUG_SCALE = false; protected static final boolean DEBUG_GLOW = false; private static final long EXPAND_DURATION = 250; private static final long GLOW_DURATION = 150; Loading @@ -63,6 +70,9 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private Context mContext; private boolean mStretching; private boolean mPullingWithOneFinger; private boolean mWatchingForPull; private boolean mHasPopped; private View mEventSource; private View mCurrView; private View mCurrViewTopGlow; Loading @@ -70,7 +80,12 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private float mOldHeight; private float mNaturalHeight; private float mInitialTouchFocusY; private float mInitialTouchY; private float mInitialTouchSpan; private int mTouchSlop; private int mLastMotionY; private float mPopLimit; private int mPopDuration; private Callback mCallback; private ScaleGestureDetector mDetector; private ViewScaler mScaler; Loading @@ -78,6 +93,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private AnimatorSet mGlowAnimationSet; private ObjectAnimator mGlowTopAnimation; private ObjectAnimator mGlowBottomAnimation; private Vibrator mVibrator; private int mSmallSize; private int mLargeSize; Loading @@ -85,6 +101,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private int mGravity; private View mScrollView; private class ViewScaler { View mView; Loading @@ -93,7 +111,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mView = v; } public void setHeight(float h) { if (DEBUG) Slog.v(TAG, "SetHeight: setting to " + h); if (DEBUG_SCALE) Slog.v(TAG, "SetHeight: setting to " + h); ViewGroup.LayoutParams lp = mView.getLayoutParams(); lp.height = (int)h; mView.setLayoutParams(lp); Loading @@ -108,7 +126,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { } public int getNaturalHeight(int maximum) { ViewGroup.LayoutParams lp = mView.getLayoutParams(); if (DEBUG) Slog.v(TAG, "Inspecting a child of type: " + mView.getClass().getName()); if (DEBUG_SCALE) Slog.v(TAG, "Inspecting a child of type: " + mView.getClass().getName()); int oldHeight = lp.height; lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; mView.setLayoutParams(lp); Loading Loading @@ -142,6 +161,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mGravity = Gravity.TOP; mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f); mScaleAnimation.setDuration(EXPAND_DURATION); mPopLimit = mContext.getResources().getDimension(R.dimen.one_finger_pop_limit); mPopDuration = mContext.getResources().getInteger(R.integer.one_finger_pop_duration_ms); AnimatorListenerAdapter glowVisibilityController = new AnimatorListenerAdapter() { @Override Loading Loading @@ -169,38 +190,30 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation); mGlowAnimationSet.setDuration(GLOW_DURATION); final ViewConfiguration configuration = ViewConfiguration.get(mContext); mTouchSlop = configuration.getScaledTouchSlop(); mDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { if (DEBUG) Slog.v(TAG, "onscalebegin()"); if (DEBUG_SCALE) Slog.v(TAG, "onscalebegin()"); float x = detector.getFocusX(); float y = detector.getFocusY(); View v = null; if (mEventSource != null) { int[] location = new int[2]; mEventSource.getLocationOnScreen(location); x += (float) location[0]; y += (float) location[1]; v = mCallback.getChildAtRawPosition(x, y); } else { v = mCallback.getChildAtPosition(x, y); } // your fingers have to be somewhat close to the bounds of the view in question mInitialTouchFocusY = detector.getFocusY(); mInitialTouchSpan = Math.abs(detector.getCurrentSpan()); if (DEBUG) Slog.d(TAG, "got mInitialTouchSpan: (" + mInitialTouchSpan + ")"); if (DEBUG_SCALE) Slog.d(TAG, "got mInitialTouchSpan: (" + mInitialTouchSpan + ")"); mStretching = initScale(v); mStretching = initScale(findView(x, y)); return mStretching; } @Override public boolean onScale(ScaleGestureDetector detector) { if (DEBUG) Slog.v(TAG, "onscale() on " + mCurrView); if (DEBUG_SCALE) Slog.v(TAG, "onscale() on " + mCurrView); // are we scaling or dragging? float span = Math.abs(detector.getCurrentSpan()) - mInitialTouchSpan; Loading @@ -210,33 +223,71 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { drag *= mGravity == Gravity.BOTTOM ? -1f : 1f; float pull = Math.abs(drag) + Math.abs(span) + 1f; float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull; if (DEBUG) Slog.d(TAG, "current span handle is: " + hand); hand = hand + mOldHeight; float target = hand; if (DEBUG) Slog.d(TAG, "target is: " + target); hand = hand < mSmallSize ? mSmallSize : (hand > mLargeSize ? mLargeSize : hand); hand = hand > mNaturalHeight ? mNaturalHeight : hand; if (DEBUG) Slog.d(TAG, "scale continues: hand =" + hand); mScaler.setHeight(hand); float target = hand + mOldHeight; float newHeight = clamp(target); mScaler.setHeight(newHeight); // glow if overscale float stretch = (float) Math.abs((target - hand) / mMaximumStretch); float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f))); if (DEBUG) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength); setGlow(GLOW_BASE + strength * (1f - GLOW_BASE)); setGlow(calculateGlow(target, newHeight)); return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { if (DEBUG) Slog.v(TAG, "onscaleend()"); if (DEBUG_SCALE) Slog.v(TAG, "onscaleend()"); // I guess we're alone now if (DEBUG) Slog.d(TAG, "scale end"); if (DEBUG_SCALE) Slog.d(TAG, "scale end"); finishScale(false); clearView(); mStretching = false; } }); } private float clamp(float target) { float out = target; out = out < mSmallSize ? mSmallSize : (out > mLargeSize ? mLargeSize : out); out = out > mNaturalHeight ? mNaturalHeight : out; return out; } private View findView(float x, float y) { View v = null; if (mEventSource != null) { int[] location = new int[2]; mEventSource.getLocationOnScreen(location); x += (float) location[0]; y += (float) location[1]; v = mCallback.getChildAtRawPosition(x, y); } else { v = mCallback.getChildAtPosition(x, y); } return v; } private boolean isInside(View v, float x, float y) { if (DEBUG) Slog.d(TAG, "isinside (" + x + ", " + y + ")"); if (v == null) { if (DEBUG) Slog.d(TAG, "isinside null subject"); return false; } if (mEventSource != null) { int[] location = new int[2]; mEventSource.getLocationOnScreen(location); x += (float) location[0]; y += (float) location[1]; if (DEBUG) Slog.d(TAG, " to global (" + x + ", " + y + ")"); } int[] location = new int[2]; v.getLocationOnScreen(location); x -= (float) location[0]; y -= (float) location[1]; if (DEBUG) Slog.d(TAG, " to local (" + x + ", " + y + ")"); if (DEBUG) Slog.d(TAG, " inside (" + v.getWidth() + ", " + v.getHeight() + ")"); boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight()); return inside; } public void setEventSource(View eventSource) { mEventSource = eventSource; } Loading @@ -245,10 +296,23 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mGravity = gravity; } public void setScrollView(View scrollView) { mScrollView = scrollView; } private float calculateGlow(float target, float actual) { // glow if overscale if (DEBUG_GLOW) Slog.d(TAG, "target: " + target + " actual: " + actual); float stretch = (float) Math.abs((target - actual) / mMaximumStretch); float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f))); if (DEBUG_GLOW) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength); return (GLOW_BASE + strength * (1f - GLOW_BASE)); } public void setGlow(float glow) { if (!mGlowAnimationSet.isRunning() || glow == 0f) { if (mGlowAnimationSet.isRunning()) { mGlowAnimationSet.cancel(); mGlowAnimationSet.end(); } if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) { if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) { Loading Loading @@ -278,23 +342,115 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { public boolean onInterceptTouchEvent(MotionEvent ev) { if (DEBUG) Slog.d(TAG, "interceptTouch: act=" + (ev.getAction()) + " stretching=" + mStretching); " stretching=" + mStretching + " onefinger=" + mPullingWithOneFinger); // check for a two-finger gesture mDetector.onTouchEvent(ev); return mStretching; if (mStretching) { return true; } else { final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && mPullingWithOneFinger) { return true; } if (mScrollView != null && mScrollView.getScrollY() > 0) { return false; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { if (mWatchingForPull) { final int x = (int) ev.getX(); final int y = (int) ev.getY(); final int yDiff = y - mLastMotionY; if (yDiff > mTouchSlop) { mLastMotionY = y; mPullingWithOneFinger = initScale(findView(x, y)); if (mPullingWithOneFinger) { mInitialTouchY = mLastMotionY; mHasPopped = false; } } } break; } case MotionEvent.ACTION_DOWN: mWatchingForPull = isInside(mScrollView, ev.getX(), ev.getY()); mLastMotionY = (int) ev.getY(); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (mPullingWithOneFinger) { finishScale(false); clearView(); } mPullingWithOneFinger = false; mWatchingForPull = false; break; } return mPullingWithOneFinger; } } public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); if (DEBUG) Slog.d(TAG, "touch: act=" + (action) + " stretching=" + mStretching); if (DEBUG_SCALE) Slog.d(TAG, "touch: act=" + (action) + " stretching=" + mStretching + " onefinger=" + mPullingWithOneFinger); if (mStretching) { if (DEBUG) Slog.d(TAG, "detector ontouch"); mDetector.onTouchEvent(ev); } switch (action) { case MotionEvent.ACTION_MOVE: { if (mPullingWithOneFinger) { final float rawHeight = ev.getY() - mInitialTouchY + mOldHeight; final float newHeight = clamp(rawHeight); final boolean wasClosed = (mOldHeight == mSmallSize); boolean isFinished = false; if (rawHeight > mNaturalHeight) { isFinished = true; } if (rawHeight < mSmallSize) { isFinished = true; } final float pull = Math.abs(ev.getY() - mInitialTouchY); if (mHasPopped || pull > mPopLimit) { if (!mHasPopped) { vibrate(mPopDuration); mHasPopped = true; } } if (mHasPopped) { mScaler.setHeight(newHeight); setGlow(GLOW_BASE); } else { setGlow(calculateGlow(4f * pull, 0f)); } final int x = (int) ev.getX(); final int y = (int) ev.getY(); View underPointer = findView(x, y); if (isFinished && underPointer != null && underPointer != mCurrView) { finishScale(false); initScale(underPointer); mInitialTouchY = ev.getY(); mHasPopped = false; } return true; } break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (DEBUG) Slog.d(TAG, "cancel"); mStretching = false; if (mPullingWithOneFinger) { finishScale(false); mPullingWithOneFinger = false; } clearView(); break; } Loading @@ -303,7 +459,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private boolean initScale(View v) { if (v != null) { if (DEBUG) Slog.d(TAG, "scale begins on view: " + v); mStretching = true; mCallback.setUserLockedChild(v, true); setView(v); setGlow(GLOW_BASE); mScaler.setView(v); Loading @@ -318,30 +474,34 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { if (DEBUG) Slog.d(TAG, "got mOldHeight: " + mOldHeight + " mNaturalHeight: " + mNaturalHeight); v.getParent().requestDisallowInterceptTouchEvent(true); return true; } else { return false; } return mStretching; } private void finishScale(boolean force) { float currentHeight = mScaler.getHeight(); float targetHeight = mSmallSize; float h = mScaler.getHeight(); final boolean wasClosed = (mOldHeight == mSmallSize); if (wasClosed) { h = (force || h > mSmallSize) ? mNaturalHeight : mSmallSize; targetHeight = (force || currentHeight > mSmallSize) ? mNaturalHeight : mSmallSize; } else { h = (force || h < mNaturalHeight) ? mSmallSize : mNaturalHeight; targetHeight = (force || currentHeight < mNaturalHeight) ? mSmallSize : mNaturalHeight; } if (DEBUG && mCurrView != null) mCurrView.setBackgroundColor(0); if (mScaleAnimation.isRunning()) { mScaleAnimation.cancel(); } mScaleAnimation.setFloatValues(h); mScaleAnimation.setupStartValues(); mScaleAnimation.start(); mStretching = false; setGlow(0f); mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight); if (targetHeight != currentHeight) { mScaleAnimation.setFloatValues(targetHeight); mScaleAnimation.setupStartValues(); mScaleAnimation.start(); } mCallback.setUserLockedChild(mCurrView, false); if (DEBUG) Slog.d(TAG, "scale was finished on view: " + mCurrView); clearView(); } private void clearView() { Loading Loading @@ -369,6 +529,18 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { public void onClick(View v) { initScale(v); finishScale(true); clearView(); } /** * Triggers haptic feedback. */ private synchronized void vibrate(long duration) { if (mVibrator == null) { mVibrator = (android.os.Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); } mVibrator.vibrate(duration); } }