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                     ownState.disappear(transformationAmount, notification);
    120                 }
    121                 ownState.recycle();
    122             }
    123         }
    124     }
    125 
    126     @Override
    127     public void transformFrom(final TransformableView notification) {
    128         if (mViewTransformationAnimation != null) {
    129             mViewTransformationAnimation.cancel();
    130         }
    131         mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
    132         mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    133             @Override
    134             public void onAnimationUpdate(ValueAnimator animation) {
    135                 transformFrom(notification, animation.getAnimatedFraction());
    136             }
    137         });
    138         mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
    139             public boolean mCancelled;
    140 
    141             @Override
    142             public void onAnimationEnd(Animator animation) {
    143                 if (!mCancelled) {
    144                     setVisible(true);
    145                 } else {
    146                     abortTransformations();
    147                 }
    148             }
    149 
    150             @Override
    151             public void onAnimationCancel(Animator animation) {
    152                 mCancelled = true;
    153             }
    154         });
    155         mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
    156         mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
    157         mViewTransformationAnimation.start();
    158     }
    159 
    160     @Override
    161     public void transformFrom(TransformableView notification, float transformationAmount) {
    162         for (Integer viewType : mTransformedViews.keySet()) {
    163             TransformState ownState = getCurrentState(viewType);
    164             if (ownState != null) {
    165                 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
    166                 if (customTransformation != null && customTransformation.transformFrom(
    167                         ownState, notification, transformationAmount)) {
    168                     ownState.recycle();
    169                     continue;
    170                 }
    171                 TransformState otherState = notification.getCurrentState(viewType);
    172                 if (otherState != null) {
    173                     ownState.transformViewFrom(otherState, transformationAmount);
    174                     otherState.recycle();
    175                 } else {
    176                     ownState.appear(transformationAmount, notification);
    177                 }
    178                 ownState.recycle();
    179             }
    180         }
    181     }
    182 
    183     @Override
    184     public void setVisible(boolean visible) {
    185         if (mViewTransformationAnimation != null) {
    186             mViewTransformationAnimation.cancel();
    187         }
    188         for (Integer viewType : mTransformedViews.keySet()) {
    189             TransformState ownState = getCurrentState(viewType);
    190             if (ownState != null) {
    191                 ownState.setVisible(visible, false /* force */);
    192                 ownState.recycle();
    193             }
    194         }
    195     }
    196 
    197     private void abortTransformations() {
    198         for (Integer viewType : mTransformedViews.keySet()) {
    199             TransformState ownState = getCurrentState(viewType);
    200             if (ownState != null) {
    201                 ownState.abortTransformation();
    202                 ownState.recycle();
    203             }
    204         }
    205     }
    206 
    207     /**
    208      * Add the remaining transformation views such that all views are being transformed correctly
    209      * @param viewRoot the root below which all elements need to be transformed
    210      */
    211     public void addRemainingTransformTypes(View viewRoot) {
    212         // lets now tag the right views
    213         int numValues = mTransformedViews.size();
    214         for (int i = 0; i < numValues; i++) {
    215             View view = mTransformedViews.valueAt(i);
    216             while (view != viewRoot.getParent()) {
    217                 view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
    218                 view = (View) view.getParent();
    219             }
    220         }
    221         Stack<View> stack = new Stack<>();
    222         // Add the right views now
    223         stack.push(viewRoot);
    224         while (!stack.isEmpty()) {
    225             View child = stack.pop();
    226             if (child.getVisibility() == View.GONE) {
    227                 continue;
    228             }
    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);
    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     public static abstract class CustomTransformation {
    263         /**
    264          * Transform a state to the given view
    265          * @param ownState the state to transform
    266          * @param notification the view to transform to
    267          * @param transformationAmount how much transformation should be done
    268          * @return whether a custom transformation is performed
    269          */
    270         public abstract boolean transformTo(TransformState ownState,
    271                 TransformableView notification,
    272                 float transformationAmount);
    273 
    274         /**
    275          * Transform to this state from the given view
    276          * @param ownState the state to transform to
    277          * @param notification the view to transform from
    278          * @param transformationAmount how much transformation should be done
    279          * @return whether a custom transformation is performed
    280          */
    281         public abstract boolean transformFrom(TransformState ownState,
    282                 TransformableView notification,
    283                 float transformationAmount);
    284 
    285         /**
    286          * Perform a custom initialisation before transforming.
    287          *
    288          * @param ownState our own state
    289          * @param otherState the other state
    290          * @return whether a custom initialization is done
    291          */
    292         public boolean initTransformation(TransformState ownState,
    293                 TransformState otherState) {
    294             return false;
    295         }
    296 
    297         public boolean customTransformTarget(TransformState ownState,
    298                 TransformState otherState) {
    299             return false;
    300         }
    301     }
    302 }
    303