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 
     27 import com.android.systemui.Interpolators;
     28 import com.android.systemui.R;
     29 import com.android.systemui.statusbar.notification.TransformState;
     30 import com.android.systemui.statusbar.stack.StackStateAnimator;
     31 
     32 import java.util.Stack;
     33 
     34 /**
     35  * A view that can be transformed to and from.
     36  */
     37 public class ViewTransformationHelper implements TransformableView {
     38 
     39     private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
     40 
     41     private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
     42     private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
     43     private ValueAnimator mViewTransformationAnimation;
     44 
     45     public void addTransformedView(int key, View transformedView) {
     46         mTransformedViews.put(key, transformedView);
     47     }
     48 
     49     public void reset() {
     50         mTransformedViews.clear();
     51     }
     52 
     53     public void setCustomTransformation(CustomTransformation transformation, int viewType) {
     54         mCustomTransformations.put(viewType, transformation);
     55     }
     56 
     57     @Override
     58     public TransformState getCurrentState(int fadingView) {
     59         View view = mTransformedViews.get(fadingView);
     60         if (view != null && view.getVisibility() != View.GONE) {
     61             return TransformState.createFrom(view);
     62         }
     63         return null;
     64     }
     65 
     66     @Override
     67     public void transformTo(final TransformableView notification, final Runnable endRunnable) {
     68         if (mViewTransformationAnimation != null) {
     69             mViewTransformationAnimation.cancel();
     70         }
     71         mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
     72         mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     73             @Override
     74             public void onAnimationUpdate(ValueAnimator animation) {
     75                 transformTo(notification, animation.getAnimatedFraction());
     76             }
     77         });
     78         mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
     79         mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
     80         mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
     81             public boolean mCancelled;
     82 
     83             @Override
     84             public void onAnimationEnd(Animator animation) {
     85                 if (!mCancelled) {
     86                     if (endRunnable != null) {
     87                         endRunnable.run();
     88                     }
     89                     setVisible(false);
     90                 } else {
     91                     abortTransformations();
     92                 }
     93             }
     94 
     95             @Override
     96             public void onAnimationCancel(Animator animation) {
     97                 mCancelled = true;
     98             }
     99         });
    100         mViewTransformationAnimation.start();
    101     }
    102 
    103     @Override
    104     public void transformTo(TransformableView notification, float transformationAmount) {
    105         for (Integer viewType : mTransformedViews.keySet()) {
    106             TransformState ownState = getCurrentState(viewType);
    107             if (ownState != null) {
    108                 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
    109                 if (customTransformation != null && customTransformation.transformTo(
    110                         ownState, notification, transformationAmount)) {
    111                     ownState.recycle();
    112                     continue;
    113                 }
    114                 TransformState otherState = notification.getCurrentState(viewType);
    115                 if (otherState != null) {
    116                     ownState.transformViewTo(otherState, transformationAmount);
    117                     otherState.recycle();
    118                 } else {
    119                     // there's no other view available
    120                     CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), transformationAmount);
    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                     // There's no other view, lets fade us in
    178                     // Certain views need to prepare the fade in and make sure its children are
    179                     // completely visible. An example is the notification header.
    180                     if (transformationAmount == 0.0f) {
    181                         ownState.prepareFadeIn();
    182                     }
    183                     CrossFadeHelper.fadeIn(mTransformedViews.get(viewType), transformationAmount);
    184                 }
    185                 ownState.recycle();
    186             }
    187         }
    188     }
    189 
    190     @Override
    191     public void setVisible(boolean visible) {
    192         if (mViewTransformationAnimation != null) {
    193             mViewTransformationAnimation.cancel();
    194         }
    195         for (Integer viewType : mTransformedViews.keySet()) {
    196             TransformState ownState = getCurrentState(viewType);
    197             if (ownState != null) {
    198                 ownState.setVisible(visible, false /* force */);
    199                 ownState.recycle();
    200             }
    201         }
    202     }
    203 
    204     private void abortTransformations() {
    205         for (Integer viewType : mTransformedViews.keySet()) {
    206             TransformState ownState = getCurrentState(viewType);
    207             if (ownState != null) {
    208                 ownState.abortTransformation();
    209                 ownState.recycle();
    210             }
    211         }
    212     }
    213 
    214     /**
    215      * Add the remaining transformation views such that all views are being transformed correctly
    216      * @param viewRoot the root below which all elements need to be transformed
    217      */
    218     public void addRemainingTransformTypes(View viewRoot) {
    219         // lets now tag the right views
    220         int numValues = mTransformedViews.size();
    221         for (int i = 0; i < numValues; i++) {
    222             View view = mTransformedViews.valueAt(i);
    223             while (view != viewRoot.getParent()) {
    224                 view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
    225                 view = (View) view.getParent();
    226             }
    227         }
    228         Stack<View> stack = new Stack<>();
    229         // Add the right views now
    230         stack.push(viewRoot);
    231         while (!stack.isEmpty()) {
    232             View child = stack.pop();
    233             if (child.getVisibility() == View.GONE) {
    234                 continue;
    235             }
    236             Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
    237             if (containsView == null) {
    238                 // This one is unhandled, let's add it to our list.
    239                 int id = child.getId();
    240                 if (id != View.NO_ID) {
    241                     // We only fade views with an id
    242                     addTransformedView(id, child);
    243                     continue;
    244                 }
    245             }
    246             child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
    247             if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
    248                 ViewGroup group = (ViewGroup) child;
    249                 for (int i = 0; i < group.getChildCount(); i++) {
    250                     stack.push(group.getChildAt(i));
    251                 }
    252             }
    253         }
    254     }
    255 
    256     public void resetTransformedView(View view) {
    257         TransformState state = TransformState.createFrom(view);
    258         state.setVisible(true /* visible */, true /* force */);
    259         state.recycle();
    260     }
    261 
    262     /**
    263      * @return a set of all views are being transformed.
    264      */
    265     public ArraySet<View> getAllTransformingViews() {
    266         return new ArraySet<>(mTransformedViews.values());
    267     }
    268 
    269     public static abstract class CustomTransformation {
    270         /**
    271          * Transform a state to the given view
    272          * @param ownState the state to transform
    273          * @param notification the view to transform to
    274          * @param transformationAmount how much transformation should be done
    275          * @return whether a custom transformation is performed
    276          */
    277         public abstract boolean transformTo(TransformState ownState,
    278                 TransformableView notification,
    279                 float transformationAmount);
    280 
    281         /**
    282          * Transform to this state from the given view
    283          * @param ownState the state to transform to
    284          * @param notification the view to transform from
    285          * @param transformationAmount how much transformation should be done
    286          * @return whether a custom transformation is performed
    287          */
    288         public abstract boolean transformFrom(TransformState ownState,
    289                 TransformableView notification,
    290                 float transformationAmount);
    291 
    292         /**
    293          * Perform a custom initialisation before transforming.
    294          *
    295          * @param ownState our own state
    296          * @param otherState the other state
    297          * @return whether a custom initialization is done
    298          */
    299         public boolean initTransformation(TransformState ownState,
    300                 TransformState otherState) {
    301             return false;
    302         }
    303 
    304         public boolean customTransformTarget(TransformState ownState,
    305                 TransformState otherState) {
    306             return false;
    307         }
    308     }
    309 }
    310