Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b04f7ad9 authored by Adam Cohen's avatar Adam Cohen
Browse files

-> Added 3D rotation to the StackView transition

-> Fixed a bug with multiple animations occuring at the same time
-> Added looping paramater to AdapterViewAnimator
-> Added restoration of state to AdapterViewAnimator
-> Fixed a flicker in the default AdapterViewAnimator transition
   (could be seen in AdapterViewFlipper)
-> Fixed a bug where touch events of StackView weren't be
   propagated to the proper child

Change-Id: I270280cabc42ad77d28e3e7d7d80aa4c17548cab
parent acf61782
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -217478,6 +217478,30 @@
 visibility="public"
>
</method>
<method name="onRestoreInstanceState"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="state" type="android.os.Parcelable">
</parameter>
</method>
<method name="onSaveInstanceState"
 return="android.os.Parcelable"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="setAdapter"
 return="void"
 abstract="false"
+117 −13
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -93,15 +95,6 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
     */
    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
     */
@@ -127,6 +120,12 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
     */
    boolean mFirstTime = true;

    /**
     * Specifies if the animator should wrap from 0 to the end and vice versa
     * or have hard boundaries at the beginning and end
     */
    boolean mShouldLoop = true;

    /**
     * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
     */
@@ -184,8 +183,10 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
     *        and {@link #setDisplayedChild(int)} 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.
     * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
     *        we loop back to the end, or do we do nothing
     */
     void configureViewAnimator(int numVisibleViews, int activeOffset) {
     void configureViewAnimator(int numVisibleViews, int activeOffset, boolean shouldLoop) {
        if (activeOffset > numVisibleViews - 1) {
            // Throw an exception here.
        }
@@ -196,6 +197,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
        removeAllViewsInLayout();
        mCurrentWindowStart = 0;
        mCurrentWindowEnd = -1;
        mShouldLoop = shouldLoop;
    }

    /**
@@ -211,6 +213,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
    void animateViewForTransition(int fromIndex, int toIndex, View view) {
        PropertyAnimator pa;
        if (fromIndex == -1) {
            view.setAlpha(0.0f);
            pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f);
            pa.start();
        } else if (toIndex == -1) {
@@ -228,9 +231,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
        if (mAdapter != null) {
            mWhichChild = whichChild;
            if (whichChild >= mAdapter.getCount()) {
                mWhichChild = 0;
                mWhichChild = mShouldLoop ? 0 : mAdapter.getCount() - 1;
            } else if (whichChild < 0) {
                mWhichChild = mAdapter.getCount() - 1;
                mWhichChild = mShouldLoop ? mAdapter.getCount() - 1 : 0;
            }

            boolean hasFocus = getFocusedChild() != null;
@@ -323,7 +326,10 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
    private LayoutParams createOrReuseLayoutParams(View v) {
        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
        if (currentLp instanceof LayoutParams) {
            return (LayoutParams) currentLp;
            LayoutParams lp = (LayoutParams) currentLp;
            lp.setHorizontalOffset(0);
            lp.setVerticalOffset(0);
            return lp;
        }
        return new LayoutParams(v);
    }
@@ -358,6 +364,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
                    int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
                            mNumActiveViews);
                    animateViewForTransition(previousViewRelativeIndex, -1, previousView);
                    mActiveViews[index] = null;
                }
            }
        }
@@ -464,6 +471,66 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
        mDataChanged = false;
    }

    static class SavedState extends BaseSavedState {
        int whichChild;

        /**
         * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
         */
        SavedState(Parcelable superState, int whichChild) {
            super(superState);
            this.whichChild = whichChild;
        }

        /**
         * Constructor called from {@link #CREATOR}
         */
        private SavedState(Parcel in) {
            super(in);
            whichChild = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(whichChild);
        }

        @Override
        public String toString() {
            return "AdapterViewAnimator.SavedState{ whichChild = " + whichChild + " }";
        }

        public static final Parcelable.Creator<SavedState> CREATOR
                = new Parcelable.Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        return new SavedState(superState, mWhichChild);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        // Here we set mWhichChild in addition to setDisplayedChild
        // We do the former in case mAdapter is null, and hence setDisplayedChild won't
        // set mWhichChild
        mWhichChild = ss.whichChild;
        setDisplayedChild(mWhichChild);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int count = getChildCount();
@@ -663,6 +730,24 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
        }
    }

    private final Rect dirtyRect = new Rect();
    @Override
    public void removeViewInLayout(View view) {
        // TODO: need to investigate this block a bit more
        // and perhaps fix some other invalidations issues.
        View parent = null;
        view.setVisibility(INVISIBLE);
        if (view.getLayoutParams() instanceof LayoutParams) {
            LayoutParams lp = (LayoutParams) view.getLayoutParams();
            parent = lp.getParentAndDirtyRegion(dirtyRect);
        }

        super.removeViewInLayout(view);

        if (parent != null)
            parent.invalidate(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
    }

    static class LayoutParams extends ViewGroup.LayoutParams {
        int horizontalOffset;
        int verticalOffset;
@@ -697,6 +782,25 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
            p.invalidate(r.left, r.top, r.right, r.bottom);
        }

        public View getParentAndDirtyRegion(Rect globalRect) {
            globalRect.set(mView.getLeft(), mView.getTop(), mView.getRight(), mView.getBottom());
            View p = mView;
            boolean firstPass = true;
            parentRect.set(0, 0, 0, 0);
            while (p.getParent() != null && p.getParent() instanceof View
                    && !parentRect.contains(globalRect)) {
                if (!firstPass) {
                    globalRect.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());
            }
            return p;
        }

        private Rect invalidateRect = new Rect();
        // This is public so that PropertyAnimator can access it
        public void setVerticalOffset(int newVerticalOffset) {
+34 −33
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.RemotableViewMethod;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.RemoteViews.RemoteView;

/**
@@ -137,6 +136,32 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
        updateRunning();
    }

    /**
     * How long to wait before flipping to the next view
     *
     * @param milliseconds
     *            time in milliseconds
     */
    public void setFlipInterval(int milliseconds) {
        mFlipInterval = milliseconds;
    }

    /**
     * Start a timer to cycle through child views
     */
    public void startFlipping() {
        mStarted = true;
        updateRunning();
    }

    /**
     * No more flips
     */
    public void stopFlipping() {
        mStarted = false;
        updateRunning();
    }

    /**
    * {@inheritDoc}
    */
@@ -170,30 +195,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
   }

   /**
     * How long to wait before flipping to the next view
     *
     * @param milliseconds
     *            time in milliseconds
     */
    public void setFlipInterval(int milliseconds) {
        mFlipInterval = milliseconds;
    }

    /**
     * Start a timer to cycle through child views
     */
    public void startFlipping() {
        mStarted = true;
        updateRunning();
    }

    /**
     * No more flips
     */
    public void stopFlipping() {
        mStarted = false;
        updateRunning();
    }

    /**
     * Internal method to start or stop dispatching flip {@link Message} based
+69 −18
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.RemoteViews.RemoteView;

@RemoteView
@@ -47,8 +48,8 @@ public class StackView extends AdapterViewAnimator {
    /**
     * Default animation parameters
     */
    private static final int DEFAULT_ANIMATION_DURATION = 400;
    private static final int MINIMUM_ANIMATION_DURATION = 50;
    private final int DEFAULT_ANIMATION_DURATION = 500;
    private final int MINIMUM_ANIMATION_DURATION = 50;

    /**
     * These specify the different gesture states
@@ -93,9 +94,6 @@ public class StackView extends AdapterViewAnimator {
    private StackSlider mStackSlider;
    private boolean mFirstLayoutHappened = false;

    // TODO: temp hack to get this thing started
    int mIndex = 5;

    public StackView(Context context) {
        super(context);
        initStackView();
@@ -107,10 +105,10 @@ public class StackView extends AdapterViewAnimator {
    }

    private void initStackView() {
        configureViewAnimator(4, 2);
        configureViewAnimator(4, 2, false);
        setStaticTransformationsEnabled(true);
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();// + 5;
        mTouchSlop = configuration.getScaledTouchSlop();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        mActivePointerId = INVALID_POINTER;

@@ -133,6 +131,8 @@ public class StackView extends AdapterViewAnimator {
            if (view.getAlpha() == 1) {
                view.setAlpha(0);
            }
            view.setVisibility(VISIBLE);

            PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
                    view, "alpha", view.getAlpha(), 1.0f);
            fadeIn.start();
@@ -152,13 +152,15 @@ public class StackView extends AdapterViewAnimator {
            duration = Math.min(duration, largestDuration);
            duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);

            PropertyAnimator slideInY = new PropertyAnimator(duration, mStackSlider,
            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, mStackSlider,
            PropertyAnimator slideInX = new PropertyAnimator(duration, animationSlider,
                    "XProgress", mStackSlider.getXProgress(), 0);
            slideInX.setInterpolator(new LinearInterpolator());
            slideInX.start();

        } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
            // Slide item out
            LayoutParams lp = (LayoutParams) view.getLayoutParams();
@@ -172,16 +174,21 @@ public class StackView extends AdapterViewAnimator {
            duration = Math.min(duration, largestDuration);
            duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);

            PropertyAnimator slideOutY = new PropertyAnimator(duration, mStackSlider,
            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, mStackSlider,
            PropertyAnimator slideOutX = new PropertyAnimator(duration, animationSlider,
                    "XProgress", mStackSlider.getXProgress(), 0);
            slideOutX.setInterpolator(new LinearInterpolator());
            slideOutX.start();

        } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
            // Make sure this view that is "waiting in the wings" is invisible
            view.setAlpha(0.0f);
            view.setVisibility(INVISIBLE);
            LayoutParams lp = (LayoutParams) view.getLayoutParams();
            lp.setVerticalOffset(-mViewHeight);
        } else if (toIndex == -1) {
            // Fade item out
            PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
@@ -435,21 +442,27 @@ public class StackView extends AdapterViewAnimator {
            // Didn't swipe up far enough, snap back down
            int duration = Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION);

            PropertyAnimator snapBackY = new PropertyAnimator(duration, mStackSlider,
            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, mStackSlider,
            PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider,
                    "XProgress", mStackSlider.getXProgress(), 0);
            snapBackX.setInterpolator(new LinearInterpolator());
            snapBackX.start();
        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
            // Didn't swipe down far enough, snap back up
            int duration = Math.round((1 -
                    mStackSlider.getYProgress())*DEFAULT_ANIMATION_DURATION);
            PropertyAnimator snapBackY = new PropertyAnimator(duration, mStackSlider,
            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, mStackSlider,
            PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider,
                    "XProgress", mStackSlider.getXProgress(), 0);
            snapBackX.setInterpolator(new LinearInterpolator());
            snapBackX.start();
        }

@@ -462,6 +475,15 @@ public class StackView extends AdapterViewAnimator {
        float mYProgress;
        float mXProgress;

        public StackSlider() {
        }

        public StackSlider(StackSlider copy) {
            mView = copy.mView;
            mYProgress = copy.mYProgress;
            mXProgress = copy.mXProgress;
        }

        private float cubic(float r) {
            return (float) (Math.pow(2*r-1, 3) + 1)/2.0f;
        }
@@ -484,6 +506,15 @@ public class StackView extends AdapterViewAnimator {
            }
        }

        private float rotationInterpolator(float r) {
            float pivot = 0.2f;
            if (r < pivot) {
                return 0;
            } else {
                return (r-pivot)/(1-pivot);
            }
        }

        void setView(View v) {
            mView = v;
        }
@@ -501,7 +532,20 @@ public class StackView extends AdapterViewAnimator {
            viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
            highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
            mHighlight.setAlpha(highlightAlphaInterpolator(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.
            if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
                mView.setVisibility(VISIBLE);
            } else if (alpha == 0 && mView.getAlpha() != 0 && mView.getVisibility() == VISIBLE) {
                mView.setVisibility(INVISIBLE);
            }

            mView.setAlpha(viewAlphaInterpolator(1-r));
            mView.setRotationX(90.0f*rotationInterpolator(r));
            mHighlight.setRotationX(90.0f*rotationInterpolator(r));
        }

        public void setXProgress(float r) {
@@ -530,7 +574,7 @@ public class StackView extends AdapterViewAnimator {
    @Override
    public void onRemoteAdapterConnected() {
        super.onRemoteAdapterConnected();
        setDisplayedChild(mIndex);
        setDisplayedChild(mWhichChild);
    }

    private static final Paint sHolographicPaint = new Paint();
@@ -547,12 +591,19 @@ public class StackView extends AdapterViewAnimator {
    }

    static Bitmap createOutline(View v) {
        if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
            return null;
        }

        Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        float rotationX = v.getRotationX();
        v.setRotationX(0);
        canvas.concat(v.getMatrix());
        v.draw(canvas);
        v.setRotationX(rotationX);

        Bitmap outlineBitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
                Bitmap.Config.ARGB_8888);