Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.systemui.statusbar;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ValueAnimator;
     22 import android.util.ArrayMap;
     23 import android.util.ArraySet;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.view.animation.Interpolator;
     27 
     28 import com.android.systemui.Interpolators;
     29 import com.android.systemui.R;
     30 import com.android.systemui.statusbar.notification.TransformState;
     31 import com.android.systemui.statusbar.stack.StackStateAnimator;
     32 
     33 import java.util.Stack;
     34 
     35 /**
     36  * A view that can be transformed to and from.
     37  */
     38 public class ViewTransformationHelper implements TransformableView,
     39         TransformState.TransformInfo {
     40 
     41     private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
     42 
     43     private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
     44     private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
     45     private ValueAnimator mViewTransformationAnimation;
     46 
     47     public void addTransformedView(int key, View transformedView) {
     48         mTransformedViews.put(key, transformedView);
     49     }
     50 
     51     public void reset() {
     52         mTransformedViews.clear();
     53     }
     54 
     55     public void setCustomTransformation(CustomTransformation transformation, int viewType) {
     56         mCustomTransformations.put(viewType, transformation);
     57     }
     58 
     59     @Override
     60     public TransformState getCurrentState(int fadingView) {
     61         View view = mTransformedViews.get(fadingView);
     62         if (view != null && view.getVisibility() != View.GONE) {
     63             return TransformState.createFrom(view, this);
     64         }
     65         return null;
     66     }
     67 
     68     @Override
     69     public void transformTo(final TransformableView notification, final Runnable endRunnable) {
     70         if (mViewTransformationAnimation != null) {
     71             mViewTransformationAnimation.cancel();
     72         }
     73         mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
     74         mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     75             @Override
     76             public void onAnimationUpdate(ValueAnimator animation) {
     77                 transformTo(notification, animation.getAnimatedFraction());
     78             }
     79         });
     80         mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
     81         mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
     82         mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
     83             public boolean mCancelled;
     84 
     85             @Override
     86             public void onAnimationEnd(Animator animation) {
     87                 if (!mCancelled) {
     88                     if (endRunnable != null) {
     89                         endRunnable.run();
     90                     }
     91                     setVisible(false);
     92                     mViewTransformationAnimation = null;
     93                 } else {
     94                     abortTransformations();
     95                 }
     96             }
     97 
     98             @Override
     99             public void onAnimationCancel(Animator animation) {
    100                 mCancelled = true;
    101             }
    102         });
    103         mViewTransformationAnimation.start();
    104     }
    105 
    106     @Override
    107     public void transformTo(TransformableView notification, float transformationAmount) {
    108         for (Integer viewType : mTransformedViews.keySet()) {
    109             TransformState ownState = getCurrentState(viewType);
    110             if (ownState != null) {
    111                 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
    112                 if (customTransformation != null && customTransformation.transformTo(
    113                         ownState, notification, transformationAmount)) {
    114                     ownState.recycle();
    115                     continue;
    116                 }
    117                 TransformState otherState = notification.getCurrentState(viewType);
    118                 if (otherState != null) {
    119                     ownState.transformViewTo(otherState, transformationAmount);
    120                     otherState.recycle();
    121                 } else {
    122                     ownState.disappear(transformationAmount, notification);
    123                 }
    124                 ownState.recycle();
    125             }
    126         }
    127     }
    128 
    129     @Override
    130     public void transformFrom(final TransformableView notification) {
    131         if (mViewTransformationAnimation != null) {
    132             mViewTransformationAnimation.cancel();
    133         }
    134         mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
    135         mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    136             @Override
    137             public void onAnimationUpdate(ValueAnimator animation) {
    138                 transformFrom(notification, animation.getAnimatedFraction());
    139             }
    140         });
    141         mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
    142             public boolean mCancelled;
    143 
    144             @Override
    145             public void onAnimationEnd(Animator animation) {
    146                 if (!mCancelled) {
    147                     setVisible(true);
    148                 } else {
    149                     abortTransformations();
    150                 }
    151             }
    152 
    153             @Override
    154             public void onAnimationCancel(Animator animation) {
    155                 mCancelled = true;
    156             }
    157         });
    158         mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
    159         mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
    160         mViewTransformationAnimation.start();
    161     }
    162 
    163     @Override
    164     public void transformFrom(TransformableView notification, float transformationAmount) {
    165         for (Integer viewType : mTransformedViews.keySet()) {
    166             TransformState ownState = getCurrentState(viewType);
    167             if (ownState != null) {
    168                 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
    169                 if (customTransformation != null && customTransformation.transformFrom(
    170                         ownState, notification, transformationAmount)) {
    171                     ownState.recycle();
    172                     continue;
    173                 }
    174                 TransformState otherState = notification.getCurrentState(viewType);
    175                 if (otherState != null) {
    176                     ownState.transformViewFrom(otherState, transformationAmount);
    177                     otherState.recycle();
    178                 } else {
    179                     ownState.appear(transformationAmount, notification);
    180                 }
    181                 ownState.recycle();
    182             }
    183         }
    184     }
    185 
    186     @Override
    187     public void setVisible(boolean visible) {
    188         if (mViewTransformationAnimation != null) {
    189             mViewTransformationAnimation.cancel();
    190         }
    191         for (Integer viewType : mTransformedViews.keySet()) {
    192             TransformState ownState = getCurrentState(viewType);
    193             if (ownState != null) {
    194                 ownState.setVisible(visible, false /* force */);
    195                 ownState.recycle();
    196             }
    197         }
    198     }
    199 
    200     private void abortTransformations() {
    201         for (Integer viewType : mTransformedViews.keySet()) {
    202             TransformState ownState = getCurrentState(viewType);
    203             if (ownState != null) {
    204                 ownState.abortTransformation();
    205                 ownState.recycle();
    206             }
    207         }
    208     }
    209 
    210     /**
    211      * Add the remaining transformation views such that all views are being transformed correctly
    212      * @param viewRoot the root below which all elements need to be transformed
    213      */
    214     public void addRemainingTransformTypes(View viewRoot) {
    215         // lets now tag the right views
    216         int numValues = mTransformedViews.size();
    217         for (int i = 0; i < numValues; i++) {
    218             View view = mTransformedViews.valueAt(i);
    219             while (view != viewRoot.getParent()) {
    220                 view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
    221                 view = (View) view.getParent();
    222             }
    223         }
    224         Stack<View> stack = new Stack<>();
    225         // Add the right views now
    226         stack.push(viewRoot);
    227         while (!stack.isEmpty()) {
    228             View child = stack.pop();
    229             Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
    230             if (containsView == null) {
    231                 // This one is unhandled, let's add it to our list.
    232                 int id = child.getId();
    233                 if (id != View.NO_ID) {
    234                     // We only fade views with an id
    235                     addTransformedView(id, child);
    236                     continue;
    237                 }
    238             }
    239             child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
    240             if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
    241                 ViewGroup group = (ViewGroup) child;
    242                 for (int i = 0; i < group.getChildCount(); i++) {
    243                     stack.push(group.getChildAt(i));
    244                 }
    245             }
    246         }
    247     }
    248 
    249     public void resetTransformedView(View view) {
    250         TransformState state = TransformState.createFrom(view, this);
    251         state.setVisible(true /* visible */, true /* force */);
    252         state.recycle();
    253     }
    254 
    255     /**
    256      * @return a set of all views are being transformed.
    257      */
    258     public ArraySet<View> getAllTransformingViews() {
    259         return new ArraySet<>(mTransformedViews.values());
    260     }
    261 
    262     @Override
    263     public boolean isAnimating() {
    264         return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
    265     }
    266 
    267     public static abstract class CustomTransformation {
    268         /**
    269          * Transform a state to the given view
    270          * @param ownState the state to transform
    271          * @param notification the view to transform to
    272          * @param transformationAmount how much transformation should be done
    273          * @return whether a custom transformation is performed
    274          */
    275         public abstract boolean transformTo(TransformState ownState,
    276                 TransformableView notification,
    277                 float transformationAmount);
    278 
    279         /**
    280          * Transform to this state from the given view
    281          * @param ownState the state to transform to
    282          * @param notification the view to transform from
    283          * @param transformationAmount how much transformation should be done
    284          * @return whether a custom transformation is performed
    285          */
    286         public abstract boolean transformFrom(TransformState ownState,
    287                 TransformableView notification,
    288                 float transformationAmount);
    289 
    290         /**
    291          * Perform a custom initialisation before transforming.
    292          *
    293          * @param ownState our own state
    294          * @param otherState the other state
    295          * @return whether a custom initialization is done
    296          */
    297         public boolean initTransformation(TransformState ownState,
    298                 TransformState otherState) {
    299             return false;
    300         }
    301 
    302         public boolean customTransformTarget(TransformState ownState,
    303                 TransformState otherState) {
    304             return false;
    305         }
    306 
    307         /**
    308          * Get a custom interpolator for this animation
    309          * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY
    310          * @param isFrom true if this transformation from the other view
    311          */
    312         public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) {
    313             return null;
    314         }
    315     }
    316 }
    317