Implementing new slider / widget interaction where frame goes around the widget

-> Frame resizes dynamically as the security slides
-> Widget shrinks immediately as the handle begins being brought up
-> Widget grows only upon security settling in the down position
-> Cleaned up a lot of state interaction between the security slider
   and the widget pager / pages
-> Loosened long press slop (was using Euclidean distance by accident
   instead of x crossed or y crossed logic)

Change-Id: Ic52b62e577c539327b0a65c9277300fc626cf190
This commit is contained in:
Adam Cohen
2012-10-28 18:29:17 -07:00
parent b317db4c5c
commit e3643138c8
6 changed files with 165 additions and 53 deletions

View File

@@ -60,8 +60,10 @@ public class CheckLongPressHelper {
public void onMove(MotionEvent ev) {
float x = ev.getX();
float y = ev.getY();
boolean xMoved = Math.abs(mDownX - x) > mScaledTouchSlop;
boolean yMoved = Math.abs(mDownY - y) > mScaledTouchSlop;
if (Math.sqrt(Math.pow(mDownX - x, 2) + Math.pow(mDownY - y, 2)) > mScaledTouchSlop) {
if (xMoved || yMoved) {
cancelLongPress();
}
}
@@ -77,4 +79,4 @@ public class CheckLongPressHelper {
public boolean hasPerformedLongPress() {
return mHasPerformedLongPress;
}
}
}

View File

@@ -22,7 +22,6 @@ import android.view.View;
public class KeyguardViewStateManager implements SlidingChallengeLayout.OnChallengeScrolledListener {
private KeyguardWidgetPager mPagedView;
private int mCurrentPageIndex;
private ChallengeLayout mChallengeLayout;
private Runnable mHideHintsRunnable;
private KeyguardSecurityView mKeyguardSecurityContainer;
@@ -31,6 +30,12 @@ public class KeyguardViewStateManager implements SlidingChallengeLayout.OnChalle
private static final int SCREEN_ON_RING_HINT_DELAY = 300;
Handler mMainQueue = new Handler(Looper.myLooper());
int mLastScrollState = SlidingChallengeLayout.SCROLL_STATE_IDLE;
// Paged view state
private int mPageListeningToSlider = -1;
private int mCurrentPage = -1;
int mChallengeTop = 0;
public KeyguardViewStateManager() {
@@ -66,28 +71,39 @@ public class KeyguardViewStateManager implements SlidingChallengeLayout.OnChalle
}
public void onPageSwitch(View newPage, int newPageIndex) {
// Reset the previous page size and ensure the current page is sized appropriately
if (mPagedView != null) {
KeyguardWidgetFrame oldPage = mPagedView.getWidgetPageAt(mCurrentPageIndex);
// Reset the old widget page to full size
if (oldPage != null) {
oldPage.resetSize();
// Reset the previous page size and ensure the current page is sized appropriately.
// We only modify the page state if it is not currently under control by the slider.
// This prevents conflicts.
if (mPagedView != null && mChallengeLayout != null) {
KeyguardWidgetFrame prevPage = mPagedView.getWidgetPageAt(mCurrentPage);
if (prevPage != null && mCurrentPage != mPageListeningToSlider) {
prevPage.resetSize();
}
KeyguardWidgetFrame newCurPage = mPagedView.getWidgetPageAt(newPageIndex);
if (mChallengeLayout.isChallengeOverlapping()) {
sizeWidgetFrameToChallengeTop(newCurPage);
boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping();
if (challengeOverlapping && !newCurPage.isSmall()
&& mPageListeningToSlider != newPageIndex) {
shrinkWidget(newCurPage);
}
}
mCurrentPageIndex = newPageIndex;
mCurrentPage = newPageIndex;
}
private void sizeWidgetFrameToChallengeTop(KeyguardWidgetFrame frame) {
if (frame == null) return;
private int getChallengeTopRelativeToFrame(KeyguardWidgetFrame frame, int top) {
mTmpPoint[0] = 0;
mTmpPoint[1] = mChallengeTop;
mTmpPoint[1] = top;
mapPoint((View) mChallengeLayout, frame, mTmpPoint);
frame.setChallengeTop(mTmpPoint[1]);
return mTmpPoint[1];
}
private void shrinkWidget(KeyguardWidgetFrame frame) {
if (frame != null && mChallengeLayout != null &&
mChallengeLayout instanceof SlidingChallengeLayout) {
SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
int top = scl.getMaxChallengeTop();
frame.shrinkWidget(getChallengeTopRelativeToFrame(frame, top));
}
}
/**
@@ -114,20 +130,17 @@ public class KeyguardViewStateManager implements SlidingChallengeLayout.OnChalle
@Override
public void onScrollStateChanged(int scrollState) {
if (mPagedView == null || mChallengeLayout == null) return;
boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping();
if (scrollState == SlidingChallengeLayout.SCROLL_STATE_IDLE) {
if (mPagedView == null) return;
KeyguardWidgetFrame frame = mPagedView.getWidgetPageAt(mPageListeningToSlider);
if (frame == null) return;
boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping();
int curPage = mPagedView.getCurrentPage();
KeyguardWidgetFrame frame = mPagedView.getWidgetPageAt(curPage);
if (frame != null) {
if (!challengeOverlapping) {
frame.resetSize();
} else {
sizeWidgetFrameToChallengeTop(frame);
}
if (!challengeOverlapping) {
frame.resetSize();
}
frame.hideFrame(this);
if (challengeOverlapping) {
mPagedView.setOnlyAllowEdgeSwipes(true);
@@ -140,10 +153,38 @@ public class KeyguardViewStateManager implements SlidingChallengeLayout.OnChalle
} else {
mKeyguardSecurityContainer.onPause();
}
} else {
mPageListeningToSlider = -1;
} else if (mLastScrollState == SlidingChallengeLayout.SCROLL_STATE_IDLE) {
// Whether dragging or settling, if the last state was idle, we use this signal
// to update the current page who will receive events from the sliding challenge.
// We resize the frame as appropriate.
mPageListeningToSlider = mPagedView.getNextPage();
KeyguardWidgetFrame frame = mPagedView.getWidgetPageAt(mPageListeningToSlider);
if (frame == null) return;
frame.showFrame(this);
// As soon as the security begins sliding, the widget becomes small (if it wasn't
// small to begin with).
if (!frame.isSmall()) {
// We need to fetch the final page, in case the pages are in motion.
mPageListeningToSlider = mPagedView.getNextPage();
System.out.println("Shrink widget from scroll state changed!");
shrinkWidget(frame);
}
// View is on the move. Pause the security view until it completes.
mKeyguardSecurityContainer.onPause();
}
mLastScrollState = scrollState;
}
@Override
public void onScrollPositionChanged(float scrollPosition, int challengeTop) {
mChallengeTop = challengeTop;
KeyguardWidgetFrame frame = mPagedView.getWidgetPageAt(mPageListeningToSlider);
if (frame != null) {
frame.adjustFrame(getChallengeTopRelativeToFrame(frame, mChallengeTop));
}
}
public void showUsabilityHints() {
@@ -164,10 +205,4 @@ public class KeyguardViewStateManager implements SlidingChallengeLayout.OnChalle
mMainQueue.postDelayed(mHideHintsRunnable, SCREEN_ON_HINT_DURATION);
}
@Override
public void onScrollPositionChanged(float scrollPosition, int challengeTop) {
mChallengeTop = challengeTop;
}
}

View File

@@ -16,6 +16,9 @@
package com.android.internal.policy.impl.keyguard;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.res.Resources;
@@ -49,6 +52,8 @@ public class KeyguardWidgetFrame extends FrameLayout {
private final Rect mForegroundRect = new Rect();
private int mForegroundAlpha = 0;
private CheckLongPressHelper mLongPressHelper;
private Animator mFrameFade;
private boolean mIsSmall = false;
private float mBackgroundAlpha;
private float mContentAlpha;
@@ -56,6 +61,11 @@ public class KeyguardWidgetFrame extends FrameLayout {
private Drawable mBackgroundDrawable;
private Rect mBackgroundRect = new Rect();
// Multiple callers may try and adjust the alpha of the frame. When a caller shows
// the outlines, we give that caller control, and nobody else can fade them out.
// This prevents animation conflicts.
private Object mBgAlphaController;
public KeyguardWidgetFrame(Context context) {
this(context, null, 0);
}
@@ -80,6 +90,11 @@ public class KeyguardWidgetFrame extends FrameLayout {
mGradientPaint.setXfermode(sAddBlendMode);
}
@Override
protected void onDetachedFromWindow() {
cancelLongPress();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Watch for longpress events at this level to make sure
@@ -237,12 +252,29 @@ public class KeyguardWidgetFrame extends FrameLayout {
}
}
/**
* Set the top location of the challenge.
*
* @param top The top of the challenge, in _local_ coordinates, or -1 to indicate the challenge
* is down.
*/
private void setChallengeTop(int top, boolean updateWidgetSize) {
// The widget starts below the padding, and extends to the top of the challengs.
int widgetHeight = top - getPaddingTop();
int frameHeight = top + getPaddingBottom();
setFrameHeight(frameHeight);
if (updateWidgetSize) {
setWidgetHeight(widgetHeight);
}
}
/**
* Depending on whether the security is up, the widget size needs to change
*
* @param height The height of the widget, -1 for full height
*/
public void setWidgetHeight(int height) {
private void setWidgetHeight(int height) {
System.out.println("Set widget height: " + this + " : " + height);
boolean needLayout = false;
View widget = getContent();
if (widget != null) {
@@ -257,22 +289,56 @@ public class KeyguardWidgetFrame extends FrameLayout {
}
}
/**
* Set the top location of the challenge.
*
* @param top The top of the challenge, in _local_ coordinates, or -1 to indicate the challenge
* is down.
*/
public void setChallengeTop(int top) {
// The widget starts below the padding, and extends to the top of the challengs.
int widgetHeight = top - getPaddingTop();
setWidgetHeight(widgetHeight);
public boolean isSmall() {
return mIsSmall;
}
public void adjustFrame(int challengeTop) {
setChallengeTop(challengeTop, false);
}
public void shrinkWidget(int challengeTop) {
mIsSmall = true;
setChallengeTop(challengeTop, true);
}
public void resetSize() {
mIsSmall = false;
setFrameHeight(getMeasuredHeight());
setWidgetHeight(LayoutParams.MATCH_PARENT);
}
public void setFrameHeight(int height) {
height = Math.min(height, getMeasuredHeight());
mBackgroundRect.set(0, 0, getMeasuredWidth(), height);
invalidate();
}
public void hideFrame(Object caller) {
fadeFrame(caller, false, 0f, 150);
}
public void showFrame(Object caller) {
fadeFrame(caller, true, 1f, 150);
}
public void fadeFrame(Object caller, boolean takeControl, float alpha, int duration) {
if (takeControl) {
mBgAlphaController = caller;
}
if (mBgAlphaController != caller) return;
if (mFrameFade != null) {
mFrameFade.cancel();
mFrameFade = null;
}
PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", alpha);
mFrameFade = ObjectAnimator.ofPropertyValuesHolder(this, bgAlpha);
mFrameFade.setDuration(duration);
mFrameFade.start();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -285,6 +351,7 @@ public class KeyguardWidgetFrame extends FrameLayout {
mRightToLeftGradient = new LinearGradient(x1, 0f, x0, 0f,
mGradientColor, 0, Shader.TileMode.CLAMP);
mBackgroundRect.set(0, 0, w, h);
invalidate();
}
void setOverScrollAmount(float r, boolean left) {

View File

@@ -433,9 +433,11 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit
int count = getChildCount();
PropertyValuesHolder alpha;
PropertyValuesHolder outlineAlpha;
ArrayList<Animator> anims = new ArrayList<Animator>();
int duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION :
CHILDREN_OUTLINE_FADE_OUT_DURATION;
int curPage = getNextPage();
for (int i = 0; i < count; i++) {
float finalContentAlpha;
@@ -446,16 +448,15 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit
} else {
finalContentAlpha = 0f;
}
float finalOutlineAlpha = show ? getAlphaForPage(mScreenCenter, i) : 0f;
KeyguardWidgetFrame child = getWidgetPageAt(i);
alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha);
outlineAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha",finalOutlineAlpha);
ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha, outlineAlpha);
ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha);
anims.add(a);
float finalOutlineAlpha = show ? getAlphaForPage(mScreenCenter, i) : 0f;
child.fadeFrame(this, show, finalOutlineAlpha, duration);
}
int duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION :
CHILDREN_OUTLINE_FADE_OUT_DURATION;
mChildrenOutlineFadeAnimation = new AnimatorSet();
mChildrenOutlineFadeAnimation.playTogether(anims);

View File

@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;

View File

@@ -374,7 +374,7 @@ public class SlidingChallengeLayout extends ViewGroup implements ChallengeLayout
mScrollState = state;
animateHandle(state == SCROLL_STATE_IDLE && !mChallengeShowing);
animateFrame(state != SCROLL_STATE_IDLE, false);
animateFrame(false , false);
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(state);
}
@@ -845,6 +845,14 @@ public class SlidingChallengeLayout extends ViewGroup implements ChallengeLayout
}
}
public int getMaxChallengeTop() {
if (mChallengeView == null) return 0;
final int layoutBottom = getLayoutBottom();
final int challengeHeight = mChallengeView.getHeight();
return layoutBottom - challengeHeight;
}
/**
* Move the bottom edge of mChallengeView to a new position and notify the listener
* if it represents a change in position. Changes made through this method will