summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java96
-rw-r--r--core/java/android/widget/StackView.java384
2 files changed, 324 insertions, 156 deletions
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 0636d721c148..830e89960209 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -31,6 +31,7 @@ import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -128,6 +129,13 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
boolean mShouldLoop = true;
/**
+ * The width and height of some child, used as a size reference in-case our
+ * dimensions are unspecified by the parent.
+ */
+ int mReferenceChildWidth = -1;
+ int mReferenceChildHeight = -1;
+
+ /**
* TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
*/
Animation mInAnimation;
@@ -414,7 +422,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
FrameLayout fl = new FrameLayout(mContext);
fl.addView(newView);
mActiveViews[index] = fl;
- addViewInLayout(fl, -1, createOrReuseLayoutParams(fl));
+ addChild(fl);
applyTransformForChildAtIndex(fl, newRelativeIndex);
animateViewForTransition(-1, newRelativeIndex, fl);
}
@@ -451,6 +459,64 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
}
+ private void addChild(View child) {
+ addViewInLayout(child, -1, createOrReuseLayoutParams(child));
+
+ // This code is used to obtain a reference width and height of a child in case we need
+ // to decide our own size. TODO: Do we want to update the size of the child that we're
+ // using for reference size? If so, when?
+ if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
+ int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ child.measure(measureSpec, measureSpec);
+ mReferenceChildWidth = child.getMeasuredWidth();
+ mReferenceChildHeight = child.getMeasuredHeight();
+ }
+ }
+
+ private void measureChildren() {
+ final int count = getChildCount();
+ final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+ final int childHeight = mMeasuredHeight - mPaddingTop - mPaddingBottom;
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+ final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
+
+ // We need to deal with the case where our parent hasn't told us how
+ // big we should be. In this case we try to use the desired size of the first
+ // child added.
+ if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
+ mPaddingBottom : 0;
+ } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+ heightSpecSize = haveChildRefSize ? Math.min(mReferenceChildHeight + mPaddingTop +
+ mPaddingBottom, heightSpecSize) : 0;
+ }
+
+ if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
+ widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
+ mPaddingRight : 0;
+ } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+ widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
+ mPaddingRight, widthSpecSize) : 0;
+ }
+
+ setMeasuredDimension(widthSpecSize, heightSpecSize);
+ measureChildren();
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
boolean dataChanged = mDataChanged;
@@ -472,8 +538,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
int childRight = mPaddingLeft + child.getMeasuredWidth();
int childBottom = mPaddingTop + child.getMeasuredHeight();
- child.layout(mPaddingLeft, mPaddingTop,
- childRight, childBottom);
+ child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
}
mDataChanged = false;
}
@@ -538,31 +603,6 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
setDisplayedChild(mWhichChild);
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int count = getChildCount();
-
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
-
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- lp.width = widthSpecSize - mPaddingLeft - mPaddingRight;
- lp.height = heightSpecSize - mPaddingTop - mPaddingBottom;
-
- int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
- MeasureSpec.EXACTLY);
- int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
- MeasureSpec.EXACTLY);
-
- child.measure(childWidthMeasureSpec, childheightMeasureSpec);
- }
- setMeasuredDimension(widthSpecSize, heightSpecSize);
- }
-
/**
* Shows only the specified child. The other displays Views exit the screen
* with the {@link #getOutAnimation() out animation} and the specified child
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 9816b395a2a1..9025b83d5022 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -16,11 +16,11 @@
package android.widget;
-import java.util.WeakHashMap;
-
import android.animation.PropertyAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -28,6 +28,7 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.TableMaskFilter;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -35,6 +36,8 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.LayoutParams;
import android.view.animation.LinearInterpolator;
import android.widget.RemoteViews.RemoteView;
@@ -53,6 +56,19 @@ public class StackView extends AdapterViewAnimator {
private final int MINIMUM_ANIMATION_DURATION = 50;
/**
+ * Parameters effecting the perspective visuals
+ */
+ private static float PERSPECTIVE_SHIFT_FACTOR = 0.12f;
+ private static float PERSPECTIVE_SCALE_FACTOR = 0.35f;
+
+ /**
+ * Represent the two possible stack modes, one where items slide up, and the other
+ * where items slide down. The perspective is also inverted between these two modes.
+ */
+ private static final int ITEMS_SLIDE_UP = 0;
+ private static final int ITEMS_SLIDE_DOWN = 1;
+
+ /**
* These specify the different gesture states
*/
private static final int GESTURE_NONE = 0;
@@ -66,8 +82,6 @@ public class StackView extends AdapterViewAnimator {
private static final float SWIPE_THRESHOLD_RATIO = 0.35f;
private static final float SLIDE_UP_RATIO = 0.7f;
- private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>();
-
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
@@ -75,6 +89,12 @@ public class StackView extends AdapterViewAnimator {
private static final int INVALID_POINTER = -1;
/**
+ * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
+ */
+ private static final int NUM_ACTIVE_VIEWS = 5;
+
+
+ /**
* These variables are all related to the current state of touch interaction
* with the stack
*/
@@ -95,6 +115,7 @@ public class StackView extends AdapterViewAnimator {
private boolean mFirstLayoutHappened = false;
private ViewGroup mAncestorContainingAllChildren = null;
private int mAncestorHeight = 0;
+ private int mStackMode;
public StackView(Context context) {
super(context);
@@ -107,7 +128,7 @@ public class StackView extends AdapterViewAnimator {
}
private void initStackView() {
- configureViewAnimator(4, 2, false);
+ configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2, false);
setStaticTransformationsEnabled(true);
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
@@ -125,6 +146,11 @@ public class StackView extends AdapterViewAnimator {
setClipChildren(false);
setClipToPadding(false);
+ // This sets the form of the StackView, which is currently to have the perspective-shifted
+ // views above the active view, and have items slide down when sliding out. The opposite is
+ // available by using ITEMS_SLIDE_UP.
+ mStackMode = ITEMS_SLIDE_DOWN;
+
// This is a flag to indicate the the stack is loading for the first time
mWhichChild = -1;
}
@@ -140,7 +166,7 @@ public class StackView extends AdapterViewAnimator {
}
view.setVisibility(VISIBLE);
- PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
+ PropertyAnimator<Float> fadeIn = new PropertyAnimator<Float>(DEFAULT_ANIMATION_DURATION,
view, "alpha", view.getAlpha(), 1.0f);
fadeIn.start();
} else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
@@ -148,49 +174,32 @@ public class StackView extends AdapterViewAnimator {
view.setVisibility(VISIBLE);
LayoutParams lp = (LayoutParams) view.getLayoutParams();
- int largestDuration =
- Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION);
-
- int duration = largestDuration;
- if (mYVelocity != 0) {
- duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity);
- }
-
- duration = Math.min(duration, largestDuration);
- duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
+ int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
StackSlider animationSlider = new StackSlider(mStackSlider);
- PropertyAnimator slideInY = new PropertyAnimator(duration, animationSlider,
- "YProgress", mStackSlider.getYProgress(), 0);
- slideInY.setInterpolator(new LinearInterpolator());
- slideInY.start();
- PropertyAnimator slideInX = new PropertyAnimator(duration, animationSlider,
- "XProgress", mStackSlider.getXProgress(), 0);
- slideInX.setInterpolator(new LinearInterpolator());
- slideInX.start();
+ PropertyValuesHolder<Float> slideInY =
+ new PropertyValuesHolder<Float>("YProgress", 0.0f);
+ PropertyValuesHolder<Float> slideInX =
+ new PropertyValuesHolder<Float>("XProgress", 0.0f);
+ PropertyAnimator pa = new PropertyAnimator(duration, animationSlider,
+ slideInX, slideInY);
+ pa.setInterpolator(new LinearInterpolator());
+ pa.start();
} else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
// Slide item out
LayoutParams lp = (LayoutParams) view.getLayoutParams();
- int largestDuration = Math.round(mStackSlider.getDurationForOffscreenPosition()*
- DEFAULT_ANIMATION_DURATION);
- int duration = largestDuration;
- if (mYVelocity != 0) {
- duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity);
- }
-
- duration = Math.min(duration, largestDuration);
- duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
+ int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
StackSlider animationSlider = new StackSlider(mStackSlider);
- PropertyAnimator slideOutY = new PropertyAnimator(duration, animationSlider,
- "YProgress", mStackSlider.getYProgress(), 1);
- slideOutY.setInterpolator(new LinearInterpolator());
- slideOutY.start();
- PropertyAnimator slideOutX = new PropertyAnimator(duration, animationSlider,
- "XProgress", mStackSlider.getXProgress(), 0);
- slideOutX.setInterpolator(new LinearInterpolator());
- slideOutX.start();
+ PropertyValuesHolder<Float> slideOutY =
+ new PropertyValuesHolder<Float>("YProgress", 1.0f);
+ PropertyValuesHolder<Float> slideOutX =
+ new PropertyValuesHolder<Float>("XProgress", 0.0f);
+ PropertyAnimator pa = new PropertyAnimator(duration, animationSlider,
+ slideOutX, slideOutY);
+ pa.setInterpolator(new LinearInterpolator());
+ pa.start();
} else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
// Make sure this view that is "waiting in the wings" is invisible
view.setAlpha(0.0f);
@@ -199,28 +208,41 @@ public class StackView extends AdapterViewAnimator {
lp.setVerticalOffset(-mViewHeight);
} else if (toIndex == -1) {
// Fade item out
- PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
- view, "alpha", view.getAlpha(), 0);
+ PropertyAnimator<Float> fadeOut = new PropertyAnimator<Float>
+ (DEFAULT_ANIMATION_DURATION, view, "alpha", view.getAlpha(), 0.0f);
fadeOut.start();
}
+
+ // Implement the faked perspective
+ if (toIndex != -1) {
+ float maxPerpectiveShift = mViewHeight * PERSPECTIVE_SHIFT_FACTOR;
+ int index = toIndex;
+
+ if (toIndex == mNumActiveViews -1) index--;
+
+ float r = (index * 1.0f) / (mNumActiveViews - 2);
+
+ float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
+ PropertyValuesHolder<Float> scaleX = new PropertyValuesHolder<Float>("scaleX", scale);
+ PropertyValuesHolder<Float> scaleY = new PropertyValuesHolder<Float>("scaleY", scale);
+
+ r = (float) Math.pow(r, 2);
+
+ int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
+ float transY = -stackDirection * r * maxPerpectiveShift +
+ stackDirection * (1 - scale) * (mViewHeight / 2.0f);
+
+ PropertyValuesHolder<Float> translationY =
+ new PropertyValuesHolder<Float>("translationY", transY);
+ PropertyAnimator pa = new PropertyAnimator(100, view, scaleX, scaleY, translationY);
+ pa.start();
+ }
}
/**
* Apply any necessary tranforms for the child that is being added.
*/
void applyTransformForChildAtIndex(View child, int relativeIndex) {
- if (!mRotations.containsKey(child)) {
- float rotation = (float) (Math.random()*26 - 13);
- mRotations.put(child, rotation);
- child.setRotation(rotation);
- }
-
- // Child has been removed
- if (relativeIndex == -1) {
- if (mRotations.containsKey(child)) {
- mRotations.remove(child);
- }
- }
}
@Override
@@ -248,8 +270,8 @@ public class StackView extends AdapterViewAnimator {
private void onLayout() {
if (!mFirstLayoutHappened) {
- mViewHeight = Math.round(SLIDE_UP_RATIO*getMeasuredHeight());
- mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight);
+ mViewHeight = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
+ mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mViewHeight);
mFirstLayoutHappened = true;
}
}
@@ -299,8 +321,14 @@ public class StackView extends AdapterViewAnimator {
cancelLongPress();
requestDisallowInterceptTouchEvent(true);
- int activeIndex = swipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1
- : mNumActiveViews - 2;
+ int activeIndex;
+ if (mStackMode == ITEMS_SLIDE_UP) {
+ activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
+ mNumActiveViews - 1 : mNumActiveViews - 2;
+ } else {
+ activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
+ mNumActiveViews - 2 : mNumActiveViews - 1;
+ }
if (mAdapter == null) return;
@@ -317,6 +345,8 @@ public class StackView extends AdapterViewAnimator {
if (v == null) return;
mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
+ mHighlight.setRotation(v.getRotation());
+ mHighlight.setTranslationY(v.getTranslationY());
mHighlight.bringToFront();
v.bringToFront();
mStackSlider.setView(v);
@@ -352,14 +382,16 @@ public class StackView extends AdapterViewAnimator {
case MotionEvent.ACTION_MOVE: {
beginGestureIfNeeded(deltaY);
- float rx = deltaX/(mViewHeight*1.0f);
+ float rx = deltaX / (mViewHeight * 1.0f);
if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
- float r = (deltaY-mTouchSlop*1.0f)/mViewHeight*1.0f;
+ float r = (deltaY - mTouchSlop * 1.0f) / mViewHeight * 1.0f;
+ if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
mStackSlider.setYProgress(1 - r);
mStackSlider.setXProgress(rx);
return true;
} else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
- float r = -(deltaY + mTouchSlop*1.0f)/mViewHeight*1.0f;
+ float r = -(deltaY + mTouchSlop * 1.0f) / mViewHeight * 1.0f;
+ if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
mStackSlider.setYProgress(r);
mStackSlider.setXProgress(rx);
return true;
@@ -447,41 +479,59 @@ public class StackView extends AdapterViewAnimator {
if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
&& mStackSlider.mMode == StackSlider.NORMAL_MODE) {
// Swipe threshold exceeded, swipe down
- showNext();
+ if (mStackMode == ITEMS_SLIDE_UP) {
+ showNext();
+ } else {
+ showPrevious();
+ }
mHighlight.bringToFront();
} else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
&& mStackSlider.mMode == StackSlider.NORMAL_MODE) {
// Swipe threshold exceeded, swipe up
- showPrevious();
+ if (mStackMode == ITEMS_SLIDE_UP) {
+ showPrevious();
+ } else {
+ showNext();
+ }
+
mHighlight.bringToFront();
- } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
+ } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
// Didn't swipe up far enough, snap back down
- int duration =
- Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION);
+ int duration;
+ float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
+ if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
+ duration = Math.round(mStackSlider.getDurationForNeutralPosition());
+ } else {
+ duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
+ }
StackSlider animationSlider = new StackSlider(mStackSlider);
- PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider,
- "YProgress", mStackSlider.getYProgress(), 0);
- snapBackY.setInterpolator(new LinearInterpolator());
- snapBackY.start();
- PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider,
- "XProgress", mStackSlider.getXProgress(), 0);
- snapBackX.setInterpolator(new LinearInterpolator());
- snapBackX.start();
+ PropertyValuesHolder<Float> snapBackY =
+ new PropertyValuesHolder<Float>("YProgress", finalYProgress);
+ PropertyValuesHolder<Float> snapBackX =
+ new PropertyValuesHolder<Float>("XProgress", 0.0f);
+ PropertyAnimator pa = new PropertyAnimator(duration, animationSlider,
+ snapBackX, snapBackY);
+ pa.setInterpolator(new LinearInterpolator());
+ pa.start();
} else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
// Didn't swipe down far enough, snap back up
- int duration = Math.round(mStackSlider.getDurationForOffscreenPosition()*
- DEFAULT_ANIMATION_DURATION);
+ float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
+ int duration;
+ if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
+ duration = Math.round(mStackSlider.getDurationForNeutralPosition());
+ } else {
+ duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
+ }
StackSlider animationSlider = new StackSlider(mStackSlider);
- PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider,
- "YProgress", mStackSlider.getYProgress(), 1);
- snapBackY.setInterpolator(new LinearInterpolator());
- snapBackY.start();
- PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider,
- "XProgress", mStackSlider.getXProgress(), 0);
- snapBackX.setInterpolator(new LinearInterpolator());
- snapBackX.start();
+ PropertyValuesHolder<Float> snapBackY =
+ new PropertyValuesHolder<Float>("YProgress", finalYProgress);
+ PropertyValuesHolder<Float> snapBackX =
+ new PropertyValuesHolder<Float>("XProgress", 0.0f);
+ PropertyAnimator pa = new PropertyAnimator(duration, animationSlider,
+ snapBackX, snapBackY);
+ pa.start();
}
mActivePointerId = INVALID_POINTER;
@@ -510,22 +560,22 @@ public class StackView extends AdapterViewAnimator {
}
private float cubic(float r) {
- return (float) (Math.pow(2*r-1, 3) + 1)/2.0f;
+ return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
}
private float highlightAlphaInterpolator(float r) {
float pivot = 0.4f;
if (r < pivot) {
- return 0.85f*cubic(r/pivot);
+ return 0.85f * cubic(r / pivot);
} else {
- return 0.85f*cubic(1 - (r-pivot)/(1-pivot));
+ return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
}
}
private float viewAlphaInterpolator(float r) {
float pivot = 0.3f;
if (r > pivot) {
- return (r - pivot)/(1 - pivot);
+ return (r - pivot) / (1 - pivot);
} else {
return 0;
}
@@ -536,7 +586,7 @@ public class StackView extends AdapterViewAnimator {
if (r < pivot) {
return 0;
} else {
- return (r-pivot)/(1-pivot);
+ return (r - pivot) / (1 - pivot);
}
}
@@ -553,13 +603,15 @@ public class StackView extends AdapterViewAnimator {
final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
+ int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
+
switch (mMode) {
case NORMAL_MODE:
- viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
- highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
+ viewLp.setVerticalOffset(Math.round(-r * stackDirection * mViewHeight));
+ highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mViewHeight));
mHighlight.setAlpha(highlightAlphaInterpolator(r));
- float alpha = viewAlphaInterpolator(1-r);
+ float alpha = viewAlphaInterpolator(1 - r);
// We make sure that views which can't be seen (have 0 alpha) are also invisible
// so that they don't interfere with click events.
@@ -571,19 +623,19 @@ public class StackView extends AdapterViewAnimator {
}
mView.setAlpha(alpha);
- mView.setRotationX(90.0f*rotationInterpolator(r));
- mHighlight.setRotationX(90.0f*rotationInterpolator(r));
+ mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
+ mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
break;
case BEGINNING_OF_STACK_MODE:
- r = r*0.2f;
- viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
- highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
+ r = r * 0.2f;
+ viewLp.setVerticalOffset(Math.round(-stackDirection * r * mViewHeight));
+ highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mViewHeight));
mHighlight.setAlpha(highlightAlphaInterpolator(r));
break;
case END_OF_STACK_MODE:
- r = (1-r)*0.2f;
- viewLp.setVerticalOffset(Math.round(r*mViewHeight));
- highlightLp.setVerticalOffset(Math.round(r*mViewHeight));
+ r = (1-r) * 0.2f;
+ viewLp.setVerticalOffset(Math.round(stackDirection * r * mViewHeight));
+ highlightLp.setVerticalOffset(Math.round(stackDirection * r * mViewHeight));
mHighlight.setAlpha(highlightAlphaInterpolator(r));
break;
}
@@ -600,8 +652,8 @@ public class StackView extends AdapterViewAnimator {
final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
r *= 0.2f;
- viewLp.setHorizontalOffset(Math.round(r*mViewHeight));
- highlightLp.setHorizontalOffset(Math.round(r*mViewHeight));
+ viewLp.setHorizontalOffset(Math.round(r * mViewHeight));
+ highlightLp.setHorizontalOffset(Math.round(r * mViewHeight));
}
void setMode(int mode) {
@@ -609,31 +661,51 @@ public class StackView extends AdapterViewAnimator {
}
float getDurationForNeutralPosition() {
- return getDuration(false);
+ return getDuration(false, 0);
}
float getDurationForOffscreenPosition() {
- return getDuration(mMode == END_OF_STACK_MODE ? false : true);
+ return getDuration(true, 0);
+ }
+
+ float getDurationForNeutralPosition(float velocity) {
+ return getDuration(false, velocity);
+ }
+
+ float getDurationForOffscreenPosition(float velocity) {
+ return getDuration(true, velocity);
}
- private float getDuration(boolean invert) {
+ private float getDuration(boolean invert, float velocity) {
if (mView != null) {
final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
- float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset,2) +
- Math.pow(viewLp.verticalOffset,2));
+ float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
+ Math.pow(viewLp.verticalOffset, 2));
float maxd = (float) Math.sqrt(Math.pow(mViewHeight, 2) +
- Math.pow(0.4f*mViewHeight, 2));
- return invert ? (1-d/maxd) : d/maxd;
+ Math.pow(0.4f * mViewHeight, 2));
+
+ if (velocity == 0) {
+ return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
+ } else {
+ float duration = invert ? d / Math.abs(velocity) :
+ (maxd - d) / Math.abs(velocity);
+ if (duration < MINIMUM_ANIMATION_DURATION ||
+ duration > DEFAULT_ANIMATION_DURATION) {
+ return getDuration(invert, 0);
+ } else {
+ return duration;
+ }
+ }
}
return 0;
}
- float getYProgress() {
+ public float getYProgress() {
return mYProgress;
}
- float getXProgress() {
+ public float getXProgress() {
return mXProgress;
}
}
@@ -654,6 +726,8 @@ public class StackView extends AdapterViewAnimator {
LayoutParams lp = (LayoutParams) currentLp;
lp.setHorizontalOffset(0);
lp.setVerticalOffset(0);
+ lp.width = 0;
+ lp.width = 0;
return lp;
}
return new LayoutParams(v);
@@ -684,15 +758,59 @@ public class StackView extends AdapterViewAnimator {
child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
- //TODO: temp until fix in View
- child.setPivotX(child.getMeasuredWidth()/2);
- child.setPivotY(child.getMeasuredHeight()/2);
}
mDataChanged = false;
onLayout();
}
+ private void measureChildren() {
+ final int count = getChildCount();
+ final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+ final int childHeight = Math.round(mMeasuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR))
+ - mPaddingTop - mPaddingBottom;
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+ final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
+
+ // We need to deal with the case where our parent hasn't told us how
+ // big we should be. In this case we should
+ float factor = 1/(1 - PERSPECTIVE_SHIFT_FACTOR);
+ if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ heightSpecSize = haveChildRefSize ?
+ Math.round(mReferenceChildHeight * (1 + factor)) +
+ mPaddingTop + mPaddingBottom : 0;
+ } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+ heightSpecSize = haveChildRefSize ? Math.min(
+ Math.round(mReferenceChildHeight * (1 + factor)) + mPaddingTop +
+ mPaddingBottom, heightSpecSize) : 0;
+ }
+
+ if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
+ widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
+ mPaddingRight : 0;
+ } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+ widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
+ mPaddingRight, widthSpecSize) : 0;
+ }
+
+ setMeasuredDimension(widthSpecSize, heightSpecSize);
+ measureChildren();
+ }
+
class LayoutParams extends ViewGroup.LayoutParams {
int horizontalOffset;
int verticalOffset;
@@ -700,6 +818,8 @@ public class StackView extends AdapterViewAnimator {
LayoutParams(View view) {
super(0, 0);
+ width = 0;
+ height = 0;
horizontalOffset = 0;
verticalOffset = 0;
mView = view;
@@ -709,6 +829,8 @@ public class StackView extends AdapterViewAnimator {
super(c, attrs);
horizontalOffset = 0;
verticalOffset = 0;
+ width = 0;
+ height = 0;
}
private Rect parentRect = new Rect();
@@ -731,6 +853,10 @@ public class StackView extends AdapterViewAnimator {
gp = (View) p.getParent();
parentRect.set(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY(),
p.getRight() - gp.getScrollX(), p.getBottom() - gp.getScrollY());
+
+ // TODO: we need to stop early here if we've hit the edge of the screen
+ // so as to prevent us from walking too high in the hierarchy. A lot of this
+ // code might become a lot more straightforward.
}
if (depth > mAncestorHeight) {
@@ -799,7 +925,7 @@ public class StackView extends AdapterViewAnimator {
private static class HolographicHelper {
private final Paint mHolographicPaint = new Paint();
private final Paint mErasePaint = new Paint();
- private final float STROKE_WIDTH = 3.0f;
+ private final Paint mBlurPaint = new Paint();
HolographicHelper() {
initializePaints();
@@ -808,8 +934,10 @@ public class StackView extends AdapterViewAnimator {
void initializePaints() {
mHolographicPaint.setColor(0xff6699ff);
mHolographicPaint.setFilterBitmap(true);
+ mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mErasePaint.setFilterBitmap(true);
+ mBlurPaint.setMaskFilter(new BlurMaskFilter(2, BlurMaskFilter.Blur.NORMAL));
}
Bitmap createOutline(View v) {
@@ -822,31 +950,31 @@ public class StackView extends AdapterViewAnimator {
Canvas canvas = new Canvas(bitmap);
float rotationX = v.getRotationX();
+ float rotation = v.getRotation();
+ float translationY = v.getTranslationY();
v.setRotationX(0);
+ v.setRotation(0);
+ v.setTranslationY(0);
canvas.concat(v.getMatrix());
v.draw(canvas);
-
v.setRotationX(rotationX);
+ v.setRotation(rotation);
+ v.setTranslationY(translationY);
+ canvas.setMatrix(id);
drawOutline(canvas, bitmap);
return bitmap;
}
final Matrix id = new Matrix();
- final Matrix scaleMatrix = new Matrix();
void drawOutline(Canvas dest, Bitmap src) {
- Bitmap mask = src.extractAlpha();
-
+ int[] xy = new int[2];
+ Bitmap mask = src.extractAlpha(mBlurPaint, xy);
+ Canvas maskCanvas = new Canvas(mask);
+ maskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
dest.drawColor(0, PorterDuff.Mode.CLEAR);
-
- float xScale = STROKE_WIDTH*2/(dest.getWidth());
- float yScale = STROKE_WIDTH*2/(dest.getHeight());
-
- scaleMatrix.reset();
- scaleMatrix.preScale(1+xScale, 1+yScale, dest.getWidth()/2, dest.getHeight()/2);
dest.setMatrix(id);
- dest.drawBitmap(mask, scaleMatrix, mHolographicPaint);
- dest.drawBitmap(mask, id, mErasePaint);
+ dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
mask.recycle();
}
}