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 
     40     private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
     41 
     42     private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
     43     private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
     44     private ValueAnimator mViewTransformationAnimation;
     45 
     46     public void addTransformedView(int key, View transformedView) {
     47         mTransformedViews.put(key, transformedView);
     48     }
     49 
     50     public void reset() {
     51         mTransformedViews.clear();
     52     }
     53 
     54     public void setCustomTransformation(CustomTransformation transformation, int viewType) {
     55         mCustomTransformations.put(viewType, transformation);
     56     }
     57 
     58     @Override
     59     public TransformState getCurrentState(int fadingView) {
     60         View view = mTransformedViews.get(fadingView);
     61         if (view != null && view.getVisibility() != View.GONE) {
     62             return TransformState.createFrom(view);
     63         }
     64         return null;
     65     }
     66 
     67     @Override
     68     public void transformTo(final TransformableView notification, final Runnable endRunnable) {
     69         if (mViewTransformationAnimation != null) {
     70             mViewTransformationAnimation.cancel();
     71         }
     72         mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
     73         mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     74             @Override
     75             public void onAnimationUpdate(ValueAnimator animation) {
     76                 transformTo(notification, animation.getAnimatedFraction());
     77             }
     78         });
     79         mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
     80         mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
     81         mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
     82             public boolean mCancelled;
     83 
     84             @Override
     85             public void onAnimationEnd(Animator animation) {
     86                 if (!mCancelled) {
     87                     if (endRunnable != null) {
     88                         endRunnable.run();
     89                     }
     90                     setVisible(false);
     91                 } else {
     92                     abortTransformations();
     93                 }
     94             }
     95 
     96             @Override
     97             public void onAnimationCancel(Animator animation) {
     98                 mCancelled = true;
     99             }
    100         });
    101         mViewTransformationAnimation.start();
    102     }
    103 
    104     @Override
    105     public void transformTo(TransformableView notification, float transformationAmount) {
    106         for (Integer viewType : mTransformedViews.keySet()) {
    107             TransformState ownState = getCurrentState(viewType);
    108             if (ownState != null) {
    109                 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
    110                 if (customTransformation != null && customTransformation.transformTo(
    111                         ownState, notification, transformationAmount)) {
    112                     ownState.recycle();
    113                     continue;
    114                 }
    115                 TransformState otherState = notification.getCurrentState(viewType);
    116                 if (otherState != null) {
    117                     ownState.transformViewTo(otherState, transformationAmount);
    118                     otherState.recycle();
    119                 } else {
    120                     ownState.disappear(transformationAmount, notification);
    121                 }
    122                 ownState.recycle();
    123             }
    124         }
    125     }
    126 
    127     @Override
    128     public void transformFrom(final TransformableView notification) {
    129         if (mViewTransformationAnimation != null) {
    130             mViewTransformationAnimation.cancel();
    131         }
    132         mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
    133         mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    134             @Override
    135             public void onAnimationUpdate(ValueAnimator animation) {
    136                 transformFrom(notification, animation.getAnimatedFraction());
    137             }
    138         });
    139         mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
    140             public boolean mCancelled;
    141 
    142             @Override
    143             public void onAnimationEnd(Animator animation) {
    144                 if (!mCancelled) {
    145                     setVisible(true);
    146                 } else {
    147                     abortTransformations();
    148                 }
    149             }
    150 
    151             @Override
    152             public void onAnimationCancel(Animator animation) {
    153                 mCancelled = true;
    154             }
    155         });
    156         mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
    157         mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
    158         mViewTransformationAnimation.start();
    159     }
    160 
    161     @Override
    162     public void transformFrom(TransformableView notification, float transformationAmount) {
    163         for (Integer viewType : mTransformedViews.keySet()) {
    164             TransformState ownState = getCurrentState(viewType);
    165             if (ownState != null) {
    166                 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
    167                 if (customTransformation != null && customTransformation.transformFrom(
    168                         ownState, notification, transformationAmount)) {
    169                     ownState.recycle();
    170                     continue;
    171                 }
    172                 TransformState otherState = notification.getCurrentState(viewType);
    173                 if (otherState != null) {
    174                     ownState.transformViewFrom(otherState, transformationAmount);
    175                     otherState.recycle();
    176                 } else {
    177                     ownState.appear(transformationAmount, notification);
    178                 }
    179                 ownState.recycle();
    180             }
    181         }
    182     }
    183 
    184     @Override
    185     public void setVisible(boolean visible) {
    186         if (mViewTransformationAnimation != null) {
    187             mViewTransformationAnimation.cancel();
    188         }
    189         for (Integer viewType : mTransformedViews.keySet()) {
    190             TransformState ownState = getCurrentState(viewType);
    191             if (ownState != null) {
    192                 ownState.setVisible(visible, false /* force */);
    193                 ownState.recycle();
    194             }
    195         }
    196     }
    197 
    198     private void abortTransformations() {
    199         for (Integer viewType : mTransformedViews.keySet()) {
    200             TransformState ownState = getCurrentState(viewType);
    201             if (ownState != null) {
    202                 ownState.abortTransformation();
    203                 ownState.recycle();
    204             }
    205         }
    206     }
    207 
    208     /**
    209      * Add the remaining transformation views such that all views are being transformed correctly
    210      * @param viewRoot the root below which all elements need to be transformed
    211      */
    212     public void addRemainingTransformTypes(View viewRoot) {
    213         // lets now tag the right views
    214         int numValues = mTransformedViews.size();
    215         for (int i = 0; i < numValues; i++) {
    216             View view = mTransformedViews.valueAt(i);
    217             while (view != viewRoot.getParent()) {
    218                 view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
    219                 view = (View) view.getParent();
    220             }
    221         }
    222         Stack<View> stack = new Stack<>();
    223         // Add the right views now
    224         stack.push(viewRoot);
    225         while (!stack.isEmpty()) {
    226             View child = stack.pop();
    227             Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
    228             if (containsView == null) {
    229                 // This one is unhandled, let's add it to our list.
    230                 int id = child.getId();
    231                 if (id != View.NO_ID) {
    232                     // We only fade views with an id
    233                     addTransformedView(id, child);
    234                     continue;
    235                 }
    236             }
    237             child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
    238             if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
    239                 ViewGroup group = (ViewGroup) child;
    240                 for (int i = 0; i < group.getChildCount(); i++) {
    241                     stack.push(group.getChildAt(i));
    242                 }
    243             }
    244         }
    245     }
    246 
    247     public void resetTransformedView(View view) {
    248         TransformState state = TransformState.createFrom(view);
    249         state.setVisible(true /* visible */, true /* force */);
    250         state.recycle();
    251     }
    252 
    253     /**
    254      * @return a set of all views are being transformed.
    255      */
    256     public ArraySet<View> getAllTransformingViews() {
    257         return new ArraySet<>(mTransformedViews.values());
    258     }
    259 
    260     public static abstract class CustomTransformation {
    261         /**
    262          * Transform a state to the given view
    263          * @param ownState the state to transform
    264          * @param notification the view to transform to
    265          * @param transformationAmount how much transformation should be done
    266          * @return whether a custom transformation is performed
    267          */
    268         public abstract boolean transformTo(TransformState ownState,
    269                 TransformableView notification,
    270                 float transformationAmount);
    271 
    272         /**
    273          * Transform to this state from the given view
    274          * @param ownState the state to transform to
    275          * @param notification the view to transform from
    276          * @param transformationAmount how much transformation should be done
    277          * @return whether a custom transformation is performed
    278          */
    279         public abstract boolean transformFrom(TransformState ownState,
    280                 TransformableView notification,
    281                 float transformationAmount);
    282 
    283         /**
    284          * Perform a custom initialisation before transforming.
    285          *
    286          * @param ownState our own state
    287          * @param otherState the other state
    288          * @return whether a custom initialization is done
    289          */
    290         public boolean initTransformation(TransformState ownState,
    291                 TransformState otherState) {
    292             return false;
    293         }
    294 
    295         public boolean customTransformTarget(TransformState ownState,
    296                 TransformState otherState) {
    297             return false;
    298         }
    299 
    300         /**
    301          * Get a custom interpolator for this animation
    302          * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY
    303          * @param isFrom true if this transformation from the other view
    304          */
    305         public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) {
    306             return null;
    307         }
    308     }
    309 }
    310