Merge "Preliminary implementation of StackView, which extends AdapterViewAnimator."
This commit is contained in:
@@ -213325,17 +213325,6 @@
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getVisibleTitleHeight"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getZoomControls"
|
||||
return="android.view.View"
|
||||
abstract="false"
|
||||
@@ -216659,7 +216648,7 @@
|
||||
</interface>
|
||||
<class name="AdapterViewAnimator"
|
||||
extends="android.widget.AdapterView"
|
||||
abstract="false"
|
||||
abstract="true"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
@@ -216709,28 +216698,6 @@
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getDefaultInAnimation"
|
||||
return="android.view.animation.Animation"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getDefaultOutAnimation"
|
||||
return="android.view.animation.Animation"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getDisplayedChild"
|
||||
return="int"
|
||||
abstract="false"
|
||||
@@ -216929,23 +216896,6 @@
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="showOnly"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="protected"
|
||||
>
|
||||
<parameter name="childIndex" type="int">
|
||||
</parameter>
|
||||
<parameter name="animate" type="boolean">
|
||||
</parameter>
|
||||
<parameter name="onLayout" type="boolean">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="showPrevious"
|
||||
return="void"
|
||||
abstract="false"
|
||||
@@ -230267,6 +230217,37 @@
|
||||
</parameter>
|
||||
</method>
|
||||
</interface>
|
||||
<class name="StackView"
|
||||
extends="android.widget.AdapterViewAnimator"
|
||||
abstract="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<constructor name="StackView"
|
||||
type="android.widget.StackView"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="context" type="android.content.Context">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<constructor name="StackView"
|
||||
type="android.widget.StackView"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="context" type="android.content.Context">
|
||||
</parameter>
|
||||
<parameter name="attrs" type="android.util.AttributeSet">
|
||||
</parameter>
|
||||
</constructor>
|
||||
</class>
|
||||
<class name="TabHost"
|
||||
extends="android.widget.FrameLayout"
|
||||
abstract="false"
|
||||
|
||||
@@ -16,14 +16,22 @@
|
||||
|
||||
package android.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import android.animation.PropertyAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
@@ -35,23 +43,100 @@ import android.view.animation.AnimationUtils;
|
||||
* @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
|
||||
* @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
|
||||
*/
|
||||
public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{
|
||||
public abstract class AdapterViewAnimator extends AdapterView<Adapter>
|
||||
implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{
|
||||
private static final String TAG = "RemoteViewAnimator";
|
||||
|
||||
/**
|
||||
* The index of the current child, which appears anywhere from the beginning
|
||||
* to the end of the current set of children, as specified by {@link #mActiveOffset}
|
||||
*/
|
||||
int mWhichChild = 0;
|
||||
boolean mFirstTime = true;
|
||||
|
||||
/**
|
||||
* Whether or not the first view(s) should be animated in
|
||||
*/
|
||||
boolean mAnimateFirstTime = true;
|
||||
|
||||
/**
|
||||
* Represents where the in the current window of
|
||||
* views the current <code>mDisplayedChild</code> sits
|
||||
*/
|
||||
int mActiveOffset = 0;
|
||||
|
||||
/**
|
||||
* The number of views that the {@link AdapterViewAnimator} keeps as children at any
|
||||
* given time (not counting views that are pending removal, see {@link #mPreviousViews}).
|
||||
*/
|
||||
int mNumActiveViews = 1;
|
||||
|
||||
/**
|
||||
* Array of the children of the {@link AdapterViewAnimator}. This array
|
||||
* is accessed in a circular fashion
|
||||
*/
|
||||
View[] mActiveViews;
|
||||
|
||||
/**
|
||||
* List of views pending removal from the {@link AdapterViewAnimator}
|
||||
*/
|
||||
ArrayList<View> mPreviousViews;
|
||||
|
||||
/**
|
||||
* The index, relative to the adapter, of the beginning of the window of views
|
||||
*/
|
||||
int mCurrentWindowStart = 0;
|
||||
|
||||
/**
|
||||
* The index, relative to the adapter, of the end of the window of views
|
||||
*/
|
||||
int mCurrentWindowEnd = -1;
|
||||
|
||||
/**
|
||||
* The same as {@link #mCurrentWindowStart}, except when the we have bounded
|
||||
* {@link #mCurrentWindowStart} to be non-negative
|
||||
*/
|
||||
int mCurrentWindowStartUnbounded = 0;
|
||||
|
||||
/**
|
||||
* Indicates whether to treat the adapter to be a circular structure, ie.
|
||||
* the view before 0 is considered to be <code>mAdapter.getCount() - 1</code>
|
||||
*
|
||||
* TODO: this doesn't do anything yet
|
||||
*
|
||||
*/
|
||||
boolean mCycleViews = false;
|
||||
|
||||
/**
|
||||
* Handler to post events to the main thread
|
||||
*/
|
||||
Handler mMainQueue;
|
||||
|
||||
/**
|
||||
* Listens for data changes from the adapter
|
||||
*/
|
||||
AdapterDataSetObserver mDataSetObserver;
|
||||
|
||||
View mPreviousView;
|
||||
View mCurrentView;
|
||||
/**
|
||||
* The {@link Adapter} for this {@link AdapterViewAnimator}
|
||||
*/
|
||||
Adapter mAdapter;
|
||||
|
||||
/**
|
||||
* The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator}
|
||||
*/
|
||||
RemoteViewsAdapter mRemoteViewsAdapter;
|
||||
|
||||
/**
|
||||
* Specifies whether this is the first time the animator is showing views
|
||||
*/
|
||||
boolean mFirstTime = true;
|
||||
|
||||
/**
|
||||
* TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
|
||||
*/
|
||||
Animation mInAnimation;
|
||||
Animation mOutAnimation;
|
||||
Adapter mAdapter;
|
||||
RemoteViewsAdapter mRemoteViewsAdapter;
|
||||
private Handler mMainQueue;
|
||||
private ArrayList<View> mViewsToBringToFront;
|
||||
|
||||
public AdapterViewAnimator(Context context) {
|
||||
super(context);
|
||||
@@ -61,8 +146,10 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
public AdapterViewAnimator(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewAnimator);
|
||||
int resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
|
||||
TypedArray a = context.obtainStyledAttributes(attrs,
|
||||
com.android.internal.R.styleable.ViewAnimator);
|
||||
int resource = a.getResourceId(
|
||||
com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
|
||||
if (resource > 0) {
|
||||
setInAnimation(context, resource);
|
||||
}
|
||||
@@ -72,7 +159,8 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
setOutAnimation(context, resource);
|
||||
}
|
||||
|
||||
boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
|
||||
boolean flag = a.getBoolean(
|
||||
com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
|
||||
setAnimateFirstView(flag);
|
||||
|
||||
a.recycle();
|
||||
@@ -85,6 +173,54 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
*/
|
||||
private void initViewAnimator(Context context, AttributeSet attrs) {
|
||||
mMainQueue = new Handler(Looper.myLooper());
|
||||
mActiveViews = new View[mNumActiveViews];
|
||||
mPreviousViews = new ArrayList<View>();
|
||||
mViewsToBringToFront = new ArrayList<View>();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used by subclasses to configure the animator to display the
|
||||
* desired number of views, and specify the offset
|
||||
*
|
||||
* @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
|
||||
* @param activeOffset This parameter specifies where the current index ({@link mWhichChild})
|
||||
* sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
|
||||
* and {@link setDisplayedChild} is called with 10, then the effective window will be
|
||||
* the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
|
||||
* window would instead contain indexes 10, 11 and 12.
|
||||
*/
|
||||
void configureViewAnimator(int numVisibleViews, int activeOffset) {
|
||||
if (activeOffset > numVisibleViews - 1) {
|
||||
// Throw an exception here.
|
||||
}
|
||||
mNumActiveViews = numVisibleViews;
|
||||
mActiveOffset = activeOffset;
|
||||
mActiveViews = new View[mNumActiveViews];
|
||||
mPreviousViews.clear();
|
||||
removeAllViewsInLayout();
|
||||
mCurrentWindowStart = 0;
|
||||
mCurrentWindowEnd = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class should be overridden by subclasses to customize view transitions within
|
||||
* the set of visible views
|
||||
*
|
||||
* @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
|
||||
* in the window
|
||||
* @param toIndex The relative index within the window that the view is going to, -1 if it is
|
||||
* being removed
|
||||
* @param view The view that is being animated
|
||||
*/
|
||||
void animateViewForTransition(int fromIndex, int toIndex, View view) {
|
||||
PropertyAnimator pa;
|
||||
if (fromIndex == -1) {
|
||||
pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f);
|
||||
pa.start();
|
||||
} else if (toIndex == -1) {
|
||||
pa = new PropertyAnimator(400, view, "alpha", 1.0f, 0.0f);
|
||||
pa.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,17 +250,27 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
/**
|
||||
* Return default inAnimation. To be overriden by subclasses.
|
||||
*/
|
||||
public Animation getDefaultInAnimation() {
|
||||
Animation getDefaultInAnimation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default outAnimation. To be overriden by subclasses.
|
||||
* Return default outAnimation. To be overridden by subclasses.
|
||||
*/
|
||||
public Animation getDefaultOutAnimation() {
|
||||
Animation getDefaultOutAnimation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be overridden by subclasses. This method applies a view / index specific
|
||||
* transform to the child view.
|
||||
*
|
||||
* @param child
|
||||
* @param relativeIndex
|
||||
*/
|
||||
void applyTransformForChildAtIndex(View child, int relativeIndex) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the currently displayed child view.
|
||||
*/
|
||||
@@ -160,70 +306,137 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
showOnly(childIndex, animate, false);
|
||||
}
|
||||
|
||||
private LayoutParams makeLayoutParams() {
|
||||
int width = mMeasuredWidth - mPaddingLeft - mPaddingRight;
|
||||
int height = mMeasuredHeight - mPaddingTop - mPaddingBottom;
|
||||
return new LayoutParams(width, height);
|
||||
private int modulo(int pos, int size) {
|
||||
return (size + (pos % size)) % size;
|
||||
}
|
||||
|
||||
protected void showOnly(int childIndex, boolean animate, boolean onLayout) {
|
||||
if (mAdapter != null) {
|
||||
// The previous view should be removed from the ViewGroup
|
||||
if (mPreviousView != null) {
|
||||
mPreviousView.clearAnimation();
|
||||
/**
|
||||
* Get the view at this index relative to the current window's start
|
||||
*
|
||||
* @param relativeIndex Position relative to the current window's start
|
||||
* @return View at this index, null if the index is outside the bounds
|
||||
*/
|
||||
View getViewAtRelativeIndex(int relativeIndex) {
|
||||
if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1) {
|
||||
int index = mCurrentWindowStartUnbounded + relativeIndex;
|
||||
return mActiveViews[modulo(index, mNumActiveViews)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: this is where we would store the the view for
|
||||
// recycling
|
||||
removeViewInLayout(mPreviousView);
|
||||
}
|
||||
private LayoutParams createOrReuseLayoutParams(View v) {
|
||||
final LayoutParams currentLp = (LayoutParams) v.getLayoutParams();
|
||||
if (currentLp instanceof LayoutParams) {
|
||||
return currentLp;
|
||||
}
|
||||
return new LayoutParams(v);
|
||||
}
|
||||
|
||||
// If the current view is still being animated, we should
|
||||
// force the animation to end
|
||||
if (mCurrentView != null) {
|
||||
mCurrentView.clearAnimation();
|
||||
}
|
||||
void showOnly(int childIndex, boolean animate, boolean onLayout) {
|
||||
if (mAdapter == null) return;
|
||||
|
||||
// load the new mCurrentView from our adapter
|
||||
mPreviousView = mCurrentView;
|
||||
mCurrentView = mAdapter.getView(childIndex, null, this);
|
||||
if (mPreviousView != mCurrentView) {
|
||||
addViewInLayout(mCurrentView, 0, makeLayoutParams(), true);
|
||||
mCurrentView.bringToFront();
|
||||
}
|
||||
for (int i = 0; i < mPreviousViews.size(); i++) {
|
||||
View viewToRemove = mPreviousViews.get(i);
|
||||
viewToRemove.clearAnimation();
|
||||
// applyTransformForChildAtIndex here just allows for any cleanup
|
||||
// associated with this view that may need to be done by a subclass
|
||||
applyTransformForChildAtIndex(viewToRemove, -1);
|
||||
removeViewInLayout(viewToRemove);
|
||||
}
|
||||
mPreviousViews.clear();
|
||||
int newWindowStartUnbounded = childIndex - mActiveOffset;
|
||||
int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
|
||||
int newWindowStart = Math.max(0, newWindowStartUnbounded);
|
||||
int newWindowEnd = Math.min(mAdapter.getCount(), newWindowEndUnbounded);
|
||||
|
||||
|
||||
|
||||
// Animate as necessary
|
||||
if (mPreviousView != null && mPreviousView != mCurrentView) {
|
||||
if (animate && mOutAnimation != null) {
|
||||
mPreviousView.startAnimation(mOutAnimation);
|
||||
// This section clears out any items that are in our mActiveViews list
|
||||
// but are outside the effective bounds of our window (this is becomes an issue
|
||||
// at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
|
||||
// newWindowEndUnbounded > mAdapter.getCount() - 1
|
||||
for (int i = newWindowStartUnbounded; i < newWindowEndUnbounded; i++) {
|
||||
if (i < newWindowStart || i > newWindowEnd) {
|
||||
int index = modulo(i, mNumActiveViews);
|
||||
if (mActiveViews[index] != null) {
|
||||
View previousView = mActiveViews[index];
|
||||
mPreviousViews.add(previousView);
|
||||
int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
|
||||
mNumActiveViews);
|
||||
animateViewForTransition(previousViewRelativeIndex, -1, previousView);
|
||||
}
|
||||
// This line results in the view becoming invisible *after*
|
||||
// the above animation is complete, or, if there is no animation
|
||||
// then it becomes invisble immediately
|
||||
mPreviousView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
if (mCurrentView != null && animate && mInAnimation != null) {
|
||||
mCurrentView.startAnimation(mInAnimation);
|
||||
}
|
||||
// If the window has changed
|
||||
if (! (newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
|
||||
// Run through the indices in the new range
|
||||
for (int i = newWindowStart; i <= newWindowEnd; i++) {
|
||||
|
||||
mFirstTime = false;
|
||||
if (!onLayout) {
|
||||
requestLayout();
|
||||
invalidate();
|
||||
} else {
|
||||
// If the Adapter tries to layout the current view when we get it using getView above
|
||||
// the layout will end up being ignored since we are currently laying out, so
|
||||
// we post a delayed requestLayout and invalidate
|
||||
mMainQueue.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCurrentView.requestLayout();
|
||||
mCurrentView.invalidate();
|
||||
int oldRelativeIndex = i - mCurrentWindowStartUnbounded;
|
||||
int newRelativeIndex = i - newWindowStartUnbounded;
|
||||
int index = modulo(i, mNumActiveViews);
|
||||
|
||||
// If this item is in the current window, great, we just need to apply
|
||||
// the transform for it's new relative position in the window, and animate
|
||||
// between it's current and new relative positions
|
||||
if (i >= mCurrentWindowStart && i <= mCurrentWindowEnd) {
|
||||
View view = mActiveViews[index];
|
||||
applyTransformForChildAtIndex(view, newRelativeIndex);
|
||||
animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
|
||||
|
||||
// Otherwise this view is new, so first we have to displace the view that's
|
||||
// taking the new view's place within our cache (a circular array)
|
||||
} else {
|
||||
if (mActiveViews[index] != null) {
|
||||
View previousView = mActiveViews[index];
|
||||
mPreviousViews.add(previousView);
|
||||
int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
|
||||
mNumActiveViews);
|
||||
animateViewForTransition(previousViewRelativeIndex, -1, previousView);
|
||||
|
||||
if (mCurrentWindowStart > newWindowStart) {
|
||||
mViewsToBringToFront.add(previousView);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// We've cleared a spot for the new view. Get it from the adapter, add it
|
||||
// and apply any transform / animation
|
||||
View newView = mAdapter.getView(i, null, this);
|
||||
if (newView != null) {
|
||||
mActiveViews[index] = newView;
|
||||
addViewInLayout(newView, -1, createOrReuseLayoutParams(newView));
|
||||
applyTransformForChildAtIndex(newView, newRelativeIndex);
|
||||
animateViewForTransition(-1, newRelativeIndex, newView);
|
||||
}
|
||||
}
|
||||
mActiveViews[index].bringToFront();
|
||||
}
|
||||
|
||||
for (int i = 0; i < mViewsToBringToFront.size(); i++) {
|
||||
View v = mViewsToBringToFront.get(i);
|
||||
v.bringToFront();
|
||||
}
|
||||
mViewsToBringToFront.clear();
|
||||
|
||||
mCurrentWindowStart = newWindowStart;
|
||||
mCurrentWindowEnd = newWindowEnd;
|
||||
mCurrentWindowStartUnbounded = newWindowStartUnbounded;
|
||||
}
|
||||
|
||||
mFirstTime = false;
|
||||
if (!onLayout) {
|
||||
requestLayout();
|
||||
invalidate();
|
||||
} else {
|
||||
// If the Adapter tries to layout the current view when we get it using getView
|
||||
// above the layout will end up being ignored since we are currently laying out, so
|
||||
// we post a delayed requestLayout and invalidate
|
||||
mMainQueue.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,10 +460,11 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
|
||||
int childRight = mPaddingLeft + child.getMeasuredWidth();
|
||||
int childBottom = mPaddingTop + child.getMeasuredHeight();
|
||||
LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
||||
|
||||
child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
|
||||
child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
|
||||
childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
|
||||
}
|
||||
|
||||
mDataChanged = false;
|
||||
}
|
||||
|
||||
@@ -261,8 +475,6 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
Log.v(TAG, "onMeasure");
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
|
||||
@@ -278,7 +490,6 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
|
||||
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
|
||||
}
|
||||
|
||||
setMeasuredDimension(widthSpecSize, heightSpecSize);
|
||||
}
|
||||
|
||||
@@ -302,7 +513,7 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
* @see #getDisplayedChild()
|
||||
*/
|
||||
public View getCurrentView() {
|
||||
return mCurrentView;
|
||||
return getViewAtRelativeIndex(mActiveOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,12 +623,15 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
mDataSetObserver = new AdapterDataSetObserver();
|
||||
mAdapter.registerDataSetObserver(mDataSetObserver);
|
||||
}
|
||||
setFocusable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up this AdapterViewAnimator to use a remote views adapter which connects to a RemoteViewsService
|
||||
* through the specified intent.
|
||||
* @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
|
||||
* Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
|
||||
* RemoteViewsService through the specified intent.
|
||||
*
|
||||
* @param intent the intent used to identify the RemoteViewsService for the adapter to
|
||||
* connect to.
|
||||
*/
|
||||
@android.view.RemotableViewMethod
|
||||
public void setRemoteViewsAdapter(Intent intent) {
|
||||
@@ -431,7 +645,7 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
|
||||
@Override
|
||||
public View getSelectedView() {
|
||||
return mCurrentView;
|
||||
return getViewAtRelativeIndex(mActiveOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,4 +666,61 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV
|
||||
setAdapter(mRemoteViewsAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
static class LayoutParams extends ViewGroup.LayoutParams {
|
||||
int horizontalOffset;
|
||||
int verticalOffset;
|
||||
View mView;
|
||||
|
||||
LayoutParams(View view) {
|
||||
super(0, 0);
|
||||
horizontalOffset = 0;
|
||||
verticalOffset = 0;
|
||||
mView = view;
|
||||
}
|
||||
|
||||
LayoutParams(Context c, AttributeSet attrs) {
|
||||
super(c, attrs);
|
||||
horizontalOffset = 0;
|
||||
verticalOffset = 0;
|
||||
}
|
||||
|
||||
void setHorizontalOffset(int newHorizontalOffset) {
|
||||
horizontalOffset = newHorizontalOffset;
|
||||
if (mView != null) {
|
||||
mView.requestLayout();
|
||||
mView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private Rect parentRect = new Rect();
|
||||
void invalidateGlobalRegion(View v, Rect r) {
|
||||
View p = v;
|
||||
boolean firstPass = true;
|
||||
parentRect.set(0, 0, 0, 0);
|
||||
while (p.getParent() != null && p.getParent() instanceof View
|
||||
&& !parentRect.contains(r)) {
|
||||
if (!firstPass) r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
|
||||
firstPass = false;
|
||||
p = (View) p.getParent();
|
||||
parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(),
|
||||
p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY());
|
||||
}
|
||||
p.invalidate(r.left, r.top, r.right, r.bottom);
|
||||
}
|
||||
|
||||
private Rect invalidateRect = new Rect();
|
||||
// This is public so that PropertyAnimator can access it
|
||||
public void setVerticalOffset(int newVerticalOffset) {
|
||||
int offsetDelta = newVerticalOffset - verticalOffset;
|
||||
verticalOffset = newVerticalOffset;
|
||||
if (mView != null) {
|
||||
mView.requestLayout();
|
||||
int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
|
||||
int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
|
||||
invalidateRect.set(mView.getLeft(), top, mView.getRight(), bottom);
|
||||
invalidateGlobalRegion(mView, invalidateRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +433,7 @@ public class RemoteViewsAdapter extends BaseAdapter {
|
||||
int cacheIndex = getCacheIndex(position);
|
||||
FrameLayout flipper = mViewCache[cacheIndex].flipper;
|
||||
flipper.setVisibility(View.VISIBLE);
|
||||
flipper.setAlpha(1.0f);
|
||||
|
||||
if (indexInfo == null) {
|
||||
// hide the item view and show the loading view
|
||||
|
||||
487
core/java/android/widget/StackView.java
Normal file
487
core/java/android/widget/StackView.java
Normal file
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.widget;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import android.animation.PropertyAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RemoteViews.RemoteView;
|
||||
|
||||
@RemoteView
|
||||
/**
|
||||
* A view that displays its children in a stack and allows users to discretely swipe
|
||||
* through the children.
|
||||
*/
|
||||
public class StackView extends AdapterViewAnimator {
|
||||
private final String TAG = "StackView";
|
||||
|
||||
/**
|
||||
* Default animation parameters
|
||||
*/
|
||||
private final int DEFAULT_ANIMATION_DURATION = 400;
|
||||
private final int MINIMUM_ANIMATION_DURATION = 50;
|
||||
|
||||
/**
|
||||
* These specify the different gesture states
|
||||
*/
|
||||
private final int GESTURE_NONE = 0;
|
||||
private final int GESTURE_SLIDE_UP = 1;
|
||||
private final int GESTURE_SLIDE_DOWN = 2;
|
||||
|
||||
/**
|
||||
* Specifies how far you need to swipe (up or down) before it
|
||||
* will be consider a completed gesture when you lift your finger
|
||||
*/
|
||||
private final float SWIPE_THRESHOLD_RATIO = 0.35f;
|
||||
private final float SLIDE_UP_RATIO = 0.7f;
|
||||
|
||||
private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>();
|
||||
private final WeakHashMap<View, Integer>
|
||||
mChildrenToApplyTransformsTo = new WeakHashMap<View, Integer>();
|
||||
|
||||
/**
|
||||
* Sentinel value for no current active pointer.
|
||||
* Used by {@link #mActivePointerId}.
|
||||
*/
|
||||
private static final int INVALID_POINTER = -1;
|
||||
|
||||
/**
|
||||
* These variables are all related to the current state of touch interaction
|
||||
* with the stack
|
||||
*/
|
||||
private boolean mGestureComplete = false;
|
||||
private float mInitialY;
|
||||
private float mInitialX;
|
||||
private int mActivePointerId;
|
||||
private int mYOffset = 0;
|
||||
private int mYVelocity = 0;
|
||||
private int mSwipeGestureType = GESTURE_NONE;
|
||||
private int mViewHeight;
|
||||
private int mSwipeThreshold;
|
||||
private int mTouchSlop;
|
||||
private int mMaximumVelocity;
|
||||
private VelocityTracker mVelocityTracker;
|
||||
|
||||
private boolean mFirstLayoutHappened = false;
|
||||
|
||||
// TODO: temp hack to get this thing started
|
||||
int mIndex = 5;
|
||||
|
||||
public StackView(Context context) {
|
||||
super(context);
|
||||
initStackView();
|
||||
}
|
||||
|
||||
public StackView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initStackView();
|
||||
}
|
||||
|
||||
private void initStackView() {
|
||||
configureViewAnimator(4, 2);
|
||||
setStaticTransformationsEnabled(true);
|
||||
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
|
||||
mTouchSlop = configuration.getScaledTouchSlop();// + 5;
|
||||
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the views between different relative indexes within the {@link AdapterViewAnimator}
|
||||
*/
|
||||
void animateViewForTransition(int fromIndex, int toIndex, View view) {
|
||||
if (fromIndex == -1 && toIndex == 0) {
|
||||
// Fade item in
|
||||
if (view.getAlpha() == 1) {
|
||||
view.setAlpha(0);
|
||||
}
|
||||
PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
|
||||
view, "alpha", view.getAlpha(), 1.0f);
|
||||
fadeIn.start();
|
||||
} else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
|
||||
// Slide item in
|
||||
view.setVisibility(VISIBLE);
|
||||
LayoutParams lp = (LayoutParams) view.getLayoutParams();
|
||||
|
||||
int largestDuration = (int) Math.round(
|
||||
(lp.verticalOffset*1.0f/-mViewHeight)*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);
|
||||
|
||||
PropertyAnimator slideDown = new PropertyAnimator(duration, lp,
|
||||
"verticalOffset", lp.verticalOffset, 0);
|
||||
slideDown.start();
|
||||
|
||||
PropertyAnimator fadeIn = new PropertyAnimator(duration, view,
|
||||
"alpha", view.getAlpha(), 1.0f);
|
||||
fadeIn.start();
|
||||
} else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
|
||||
// Slide item out
|
||||
LayoutParams lp = (LayoutParams) view.getLayoutParams();
|
||||
|
||||
int largestDuration = (int) Math.round(
|
||||
(1 - (lp.verticalOffset*1.0f/-mViewHeight))*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);
|
||||
|
||||
PropertyAnimator slideUp = new PropertyAnimator(duration, lp,
|
||||
"verticalOffset", lp.verticalOffset, -mViewHeight);
|
||||
slideUp.start();
|
||||
|
||||
PropertyAnimator fadeOut = new PropertyAnimator(duration, view,
|
||||
"alpha", view.getAlpha(), 0.0f);
|
||||
fadeOut.start();
|
||||
} else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
|
||||
// Make sure this view that is "waiting in the wings" is invisible
|
||||
view.setAlpha(0.0f);
|
||||
} else if (toIndex == -1) {
|
||||
// Fade item out
|
||||
PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
|
||||
view, "alpha", view.getAlpha(), 0);
|
||||
fadeOut.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any necessary tranforms for the child that is being added.
|
||||
*/
|
||||
void applyTransformForChildAtIndex(View child, int relativeIndex) {
|
||||
float rotation;
|
||||
|
||||
if (!mRotations.containsKey(child)) {
|
||||
rotation = (float) (Math.random()*26 - 13);
|
||||
mRotations.put(child, rotation);
|
||||
} else {
|
||||
rotation = mRotations.get(child);
|
||||
}
|
||||
|
||||
// Child has been removed
|
||||
if (relativeIndex == -1) {
|
||||
if (mRotations.containsKey(child)) {
|
||||
mRotations.remove(child);
|
||||
}
|
||||
if (mChildrenToApplyTransformsTo.containsKey(child)) {
|
||||
mChildrenToApplyTransformsTo.remove(child);
|
||||
}
|
||||
}
|
||||
|
||||
// if this view is already in the layout, we need to
|
||||
// wait until layout has finished in order to set the
|
||||
// pivot point of the rotation (requiring getMeasuredWidth/Height())
|
||||
mChildrenToApplyTransformsTo.put(child, relativeIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
if (!mChildrenToApplyTransformsTo.isEmpty()) {
|
||||
for (View child: mChildrenToApplyTransformsTo.keySet()) {
|
||||
if (mRotations.containsKey(child)) {
|
||||
child.setPivotX(child.getMeasuredWidth()/2);
|
||||
child.setPivotY(child.getMeasuredHeight()/2);
|
||||
child.setRotation(mRotations.get(child));
|
||||
}
|
||||
}
|
||||
mChildrenToApplyTransformsTo.clear();
|
||||
}
|
||||
|
||||
if (!mFirstLayoutHappened) {
|
||||
mViewHeight = (int) Math.round(SLIDE_UP_RATIO*getMeasuredHeight());
|
||||
mSwipeThreshold = (int) Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight);
|
||||
|
||||
// TODO: Right now this walks all the way up the view hierarchy and disables
|
||||
// ClipChildren and ClipToPadding. We're probably going to want to reset
|
||||
// these flags as well.
|
||||
setClipChildren(false);
|
||||
ViewGroup view = this;
|
||||
while (view.getParent() != null && view.getParent() instanceof ViewGroup) {
|
||||
view = (ViewGroup) view.getParent();
|
||||
view.setClipChildren(false);
|
||||
view.setClipToPadding(false);
|
||||
}
|
||||
|
||||
mFirstLayoutHappened = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getAction();
|
||||
switch(action & MotionEvent.ACTION_MASK) {
|
||||
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
if (mActivePointerId == INVALID_POINTER) {
|
||||
mInitialX = ev.getX();
|
||||
mInitialY = ev.getY();
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex == INVALID_POINTER) {
|
||||
// no data for our primary pointer, this shouldn't happen, log it
|
||||
Log.d(TAG, "Error: No data for our primary pointer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
float newY = ev.getY(pointerIndex);
|
||||
float deltaY = newY - mInitialY;
|
||||
|
||||
if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
|
||||
mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
|
||||
mGestureComplete = false;
|
||||
cancelLongPress();
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
onSecondaryPointerUp(ev);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mSwipeGestureType = GESTURE_NONE;
|
||||
mGestureComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
return mSwipeGestureType != GESTURE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getAction();
|
||||
int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex == INVALID_POINTER) {
|
||||
// no data for our primary pointer, this shouldn't happen, log it
|
||||
Log.d(TAG, "Error: No data for our primary pointer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
float newY = ev.getY(pointerIndex);
|
||||
float deltaY = newY - mInitialY;
|
||||
|
||||
if (mVelocityTracker == null) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
switch (action & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
|
||||
mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
|
||||
mGestureComplete = false;
|
||||
cancelLongPress();
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
|
||||
if (!mGestureComplete) {
|
||||
if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
|
||||
View v = getViewAtRelativeIndex(mNumActiveViews - 1);
|
||||
if (v != null) {
|
||||
// This view is present but hidden, make sure it's visible
|
||||
// if they pull down
|
||||
v.setVisibility(VISIBLE);
|
||||
|
||||
float r = (deltaY-mTouchSlop)*1.0f / (mSwipeThreshold);
|
||||
mYOffset = Math.min(-mViewHeight + (int) Math.round(
|
||||
r*mSwipeThreshold) - mTouchSlop, 0);
|
||||
LayoutParams lp = (LayoutParams) v.getLayoutParams();
|
||||
lp.setVerticalOffset(mYOffset);
|
||||
|
||||
float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight));
|
||||
alpha = Math.min(1.0f, alpha);
|
||||
v.setAlpha(alpha);
|
||||
}
|
||||
return true;
|
||||
} else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
|
||||
View v = getViewAtRelativeIndex(mNumActiveViews - 2);
|
||||
|
||||
if (v != null) {
|
||||
float r = -(deltaY*1.0f + mTouchSlop) / (mSwipeThreshold);
|
||||
mYOffset = Math.min((int) Math.round(r*-mSwipeThreshold), 0);
|
||||
LayoutParams lp = (LayoutParams) v.getLayoutParams();
|
||||
lp.setVerticalOffset(mYOffset);
|
||||
|
||||
float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight));
|
||||
alpha = Math.min(1.0f, alpha);
|
||||
v.setAlpha(alpha);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
handlePointerUp(ev);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
onSecondaryPointerUp(ev);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mGestureComplete = true;
|
||||
mSwipeGestureType = GESTURE_NONE;
|
||||
mYOffset = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Rect touchRect = new Rect();
|
||||
private void onSecondaryPointerUp(MotionEvent ev) {
|
||||
final int activePointerIndex = ev.getActionIndex();
|
||||
final int pointerId = ev.getPointerId(activePointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
|
||||
int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1
|
||||
: mNumActiveViews - 2;
|
||||
|
||||
View v = getViewAtRelativeIndex(activeViewIndex);
|
||||
if (v == null) return;
|
||||
|
||||
// Our primary pointer has gone up -- let's see if we can find
|
||||
// another pointer on the view. If so, then we should replace
|
||||
// our primary pointer with this new pointer and adjust things
|
||||
// so that the view doesn't jump
|
||||
for (int index = 0; index < ev.getPointerCount(); index++) {
|
||||
if (index != activePointerIndex) {
|
||||
|
||||
float x = ev.getX(index);
|
||||
float y = ev.getY(index);
|
||||
|
||||
touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
|
||||
if (touchRect.contains((int) Math.round(x), (int) Math.round(y))) {
|
||||
float oldX = ev.getX(activePointerIndex);
|
||||
float oldY = ev.getY(activePointerIndex);
|
||||
|
||||
// adjust our frame of reference to avoid a jump
|
||||
mInitialY += (y - oldY);
|
||||
mInitialX += (x - oldX);
|
||||
|
||||
mActivePointerId = ev.getPointerId(index);
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
// ok, we're good, we found a new pointer which is touching the active view
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we made it this far, it means we didn't find a satisfactory new pointer :(,
|
||||
// so end the
|
||||
handlePointerUp(ev);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePointerUp(MotionEvent ev) {
|
||||
int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
float newY = ev.getY(pointerIndex);
|
||||
int deltaY = (int) (newY - mInitialY);
|
||||
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
|
||||
if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN &&
|
||||
!mGestureComplete) {
|
||||
// Swipe threshold exceeded, swipe down
|
||||
showNext();
|
||||
} else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP &&
|
||||
!mGestureComplete) {
|
||||
// Swipe threshold exceeded, swipe up
|
||||
showPrevious();
|
||||
} else if (mSwipeGestureType == GESTURE_SLIDE_UP && !mGestureComplete) {
|
||||
// Didn't swipe up far enough, snap back down
|
||||
View v = getViewAtRelativeIndex(mNumActiveViews - 2);
|
||||
if (v != null) {
|
||||
// Compute the animation duration based on how far they pulled it up
|
||||
LayoutParams lp = (LayoutParams) v.getLayoutParams();
|
||||
int duration = (int) Math.round(
|
||||
lp.verticalOffset*1.0f/-mViewHeight*DEFAULT_ANIMATION_DURATION);
|
||||
duration = Math.max(MINIMUM_ANIMATION_DURATION, duration);
|
||||
|
||||
// Animate back down
|
||||
PropertyAnimator slideDown = new PropertyAnimator(duration, lp,
|
||||
"verticalOffset", lp.verticalOffset, 0);
|
||||
slideDown.start();
|
||||
PropertyAnimator fadeIn = new PropertyAnimator(duration, v,
|
||||
"alpha",v.getAlpha(), 1.0f);
|
||||
fadeIn.start();
|
||||
}
|
||||
} else if (mSwipeGestureType == GESTURE_SLIDE_DOWN && !mGestureComplete) {
|
||||
// Didn't swipe down far enough, snap back up
|
||||
View v = getViewAtRelativeIndex(mNumActiveViews - 1);
|
||||
if (v != null) {
|
||||
// Compute the animation duration based on how far they pulled it down
|
||||
LayoutParams lp = (LayoutParams) v.getLayoutParams();
|
||||
int duration = (int) Math.round(
|
||||
(1 - lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION);
|
||||
duration = Math.max(MINIMUM_ANIMATION_DURATION, duration);
|
||||
|
||||
// Animate back up
|
||||
PropertyAnimator slideUp = new PropertyAnimator(duration, lp,
|
||||
"verticalOffset", lp.verticalOffset, -mViewHeight);
|
||||
slideUp.start();
|
||||
PropertyAnimator fadeOut = new PropertyAnimator(duration, v,
|
||||
"alpha",v.getAlpha(), 0.0f);
|
||||
fadeOut.start();
|
||||
}
|
||||
}
|
||||
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mGestureComplete = true;
|
||||
mSwipeGestureType = GESTURE_NONE;
|
||||
mYOffset = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteAdapterConnected() {
|
||||
super.onRemoteAdapterConnected();
|
||||
setDisplayedChild(mIndex);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user