Home | History | Annotate | Download | only in notification
      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.notification;
     18 
     19 import android.util.ArraySet;
     20 import android.util.Pools;
     21 import android.view.NotificationHeaderView;
     22 import android.view.View;
     23 import android.view.ViewGroup;
     24 import android.view.ViewParent;
     25 import android.widget.ImageView;
     26 import android.widget.ProgressBar;
     27 import android.widget.TextView;
     28 
     29 import com.android.systemui.Interpolators;
     30 import com.android.systemui.R;
     31 import com.android.systemui.statusbar.CrossFadeHelper;
     32 import com.android.systemui.statusbar.ExpandableNotificationRow;
     33 import com.android.systemui.statusbar.ViewTransformationHelper;
     34 
     35 /**
     36  * A transform state of a view.
     37 */
     38 public class TransformState {
     39 
     40     private static final float UNDEFINED = -1f;
     41     private static final int TRANSOFORM_X = 0x1;
     42     private static final int TRANSOFORM_Y = 0x10;
     43     private static final int TRANSOFORM_ALL = TRANSOFORM_X | TRANSOFORM_Y;
     44     private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
     45     private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
     46     private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
     47     private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
     48     private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
     49     private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
     50     private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
     51     private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
     52 
     53     protected View mTransformedView;
     54     private int[] mOwnPosition = new int[2];
     55     private float mTransformationEndY = UNDEFINED;
     56     private float mTransformationEndX = UNDEFINED;
     57 
     58     public void initFrom(View view) {
     59         mTransformedView = view;
     60     }
     61 
     62     /**
     63      * Transforms the {@link #mTransformedView} from the given transformviewstate
     64      * @param otherState the state to transform from
     65      * @param transformationAmount how much to transform
     66      */
     67     public void transformViewFrom(TransformState otherState, float transformationAmount) {
     68         mTransformedView.animate().cancel();
     69         if (sameAs(otherState)) {
     70             if (mTransformedView.getVisibility() == View.INVISIBLE) {
     71                 // We have the same content, lets show ourselves
     72                 mTransformedView.setAlpha(1.0f);
     73                 mTransformedView.setVisibility(View.VISIBLE);
     74             }
     75         } else {
     76             CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
     77         }
     78         transformViewFullyFrom(otherState, transformationAmount);
     79     }
     80 
     81     public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
     82         transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount);
     83     }
     84 
     85     public void transformViewVerticalFrom(TransformState otherState,
     86             ViewTransformationHelper.CustomTransformation customTransformation,
     87             float transformationAmount) {
     88         transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
     89     }
     90 
     91     public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
     92         transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount);
     93     }
     94 
     95     private void transformViewFrom(TransformState otherState, int transformationFlags,
     96             ViewTransformationHelper.CustomTransformation customTransformation,
     97             float transformationAmount) {
     98         final View transformedView = mTransformedView;
     99         boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
    100         boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
    101         boolean transformScale = transformScale();
    102         // lets animate the positions correctly
    103         if (transformationAmount == 0.0f
    104                 || transformX && getTransformationStartX() == UNDEFINED
    105                 || transformY && getTransformationStartY() == UNDEFINED
    106                 || transformScale && getTransformationStartScaleX() == UNDEFINED
    107                 || transformScale && getTransformationStartScaleY() == UNDEFINED) {
    108             int[] otherPosition;
    109             if (transformationAmount != 0.0f) {
    110                 otherPosition = otherState.getLaidOutLocationOnScreen();
    111             } else {
    112                 otherPosition = otherState.getLocationOnScreen();
    113             }
    114             int[] ownStablePosition = getLaidOutLocationOnScreen();
    115             if (customTransformation == null
    116                     || !customTransformation.initTransformation(this, otherState)) {
    117                 if (transformX) {
    118                     setTransformationStartX(otherPosition[0] - ownStablePosition[0]);
    119                 }
    120                 if (transformY) {
    121                     setTransformationStartY(otherPosition[1] - ownStablePosition[1]);
    122                 }
    123                 // we also want to animate the scale if we're the same
    124                 View otherView = otherState.getTransformedView();
    125                 if (transformScale && otherView.getWidth() != transformedView.getWidth()) {
    126                     setTransformationStartScaleX(otherView.getWidth() * otherView.getScaleX()
    127                             / (float) transformedView.getWidth());
    128                     transformedView.setPivotX(0);
    129                 } else {
    130                     setTransformationStartScaleX(UNDEFINED);
    131                 }
    132                 if (transformScale && otherView.getHeight() != transformedView.getHeight()) {
    133                     setTransformationStartScaleY(otherView.getHeight() * otherView.getScaleY()
    134                             / (float) transformedView.getHeight());
    135                     transformedView.setPivotY(0);
    136                 } else {
    137                     setTransformationStartScaleY(UNDEFINED);
    138                 }
    139             }
    140             if (!transformX) {
    141                 setTransformationStartX(UNDEFINED);
    142             }
    143             if (!transformY) {
    144                 setTransformationStartY(UNDEFINED);
    145             }
    146             if (!transformScale) {
    147                 setTransformationStartScaleX(UNDEFINED);
    148                 setTransformationStartScaleY(UNDEFINED);
    149             }
    150             setClippingDeactivated(transformedView, true);
    151         }
    152         float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
    153                 transformationAmount);
    154         if (transformX) {
    155             transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
    156                     0.0f,
    157                     interpolatedValue));
    158         }
    159         if (transformY) {
    160             transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
    161                     0.0f,
    162                     interpolatedValue));
    163         }
    164         if (transformScale) {
    165             float transformationStartScaleX = getTransformationStartScaleX();
    166             if (transformationStartScaleX != UNDEFINED) {
    167                 transformedView.setScaleX(
    168                         NotificationUtils.interpolate(transformationStartScaleX,
    169                                 1.0f,
    170                                 interpolatedValue));
    171             }
    172             float transformationStartScaleY = getTransformationStartScaleY();
    173             if (transformationStartScaleY != UNDEFINED) {
    174                 transformedView.setScaleY(
    175                         NotificationUtils.interpolate(transformationStartScaleY,
    176                                 1.0f,
    177                                 interpolatedValue));
    178             }
    179         }
    180     }
    181 
    182     protected boolean transformScale() {
    183         return false;
    184     }
    185 
    186     /**
    187      * Transforms the {@link #mTransformedView} to the given transformviewstate
    188      * @param otherState the state to transform from
    189      * @param transformationAmount how much to transform
    190      * @return whether an animation was started
    191      */
    192     public boolean transformViewTo(TransformState otherState, float transformationAmount) {
    193         mTransformedView.animate().cancel();
    194         if (sameAs(otherState)) {
    195             // We have the same text, lets show ourselfs
    196             if (mTransformedView.getVisibility() == View.VISIBLE) {
    197                 mTransformedView.setAlpha(0.0f);
    198                 mTransformedView.setVisibility(View.INVISIBLE);
    199             }
    200             return false;
    201         } else {
    202             CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
    203         }
    204         transformViewFullyTo(otherState, transformationAmount);
    205         return true;
    206     }
    207 
    208     public void transformViewFullyTo(TransformState otherState, float transformationAmount) {
    209         transformViewTo(otherState, TRANSOFORM_ALL, null, transformationAmount);
    210     }
    211 
    212     public void transformViewVerticalTo(TransformState otherState,
    213             ViewTransformationHelper.CustomTransformation customTransformation,
    214             float transformationAmount) {
    215         transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
    216     }
    217 
    218     public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
    219         transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount);
    220     }
    221 
    222     private void transformViewTo(TransformState otherState, int transformationFlags,
    223             ViewTransformationHelper.CustomTransformation customTransformation,
    224             float transformationAmount) {
    225         // lets animate the positions correctly
    226 
    227         final View transformedView = mTransformedView;
    228         boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
    229         boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
    230         boolean transformScale = transformScale();
    231         // lets animate the positions correctly
    232         if (transformationAmount == 0.0f) {
    233             if (transformX) {
    234                 float transformationStartX = getTransformationStartX();
    235                 float start = transformationStartX != UNDEFINED ? transformationStartX
    236                         : transformedView.getTranslationX();
    237                 setTransformationStartX(start);
    238             }
    239             if (transformY) {
    240                 float transformationStartY = getTransformationStartY();
    241                 float start = transformationStartY != UNDEFINED ? transformationStartY
    242                         : transformedView.getTranslationY();
    243                 setTransformationStartY(start);
    244             }
    245             View otherView = otherState.getTransformedView();
    246             if (transformScale && otherView.getWidth() != transformedView.getWidth()) {
    247                 setTransformationStartScaleX(transformedView.getScaleX());
    248                 transformedView.setPivotX(0);
    249             } else {
    250                 setTransformationStartScaleX(UNDEFINED);
    251             }
    252             if (transformScale && otherView.getHeight() != transformedView.getHeight()) {
    253                 setTransformationStartScaleY(transformedView.getScaleY());
    254                 transformedView.setPivotY(0);
    255             } else {
    256                 setTransformationStartScaleY(UNDEFINED);
    257             }
    258             setClippingDeactivated(transformedView, true);
    259         }
    260         float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
    261                 transformationAmount);
    262         int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
    263         int[] ownPosition = getLaidOutLocationOnScreen();
    264         if (transformX) {
    265             float endX = otherStablePosition[0] - ownPosition[0];
    266             if (customTransformation != null
    267                     && customTransformation.customTransformTarget(this, otherState)) {
    268                 endX = mTransformationEndX;
    269             }
    270             transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
    271                     endX,
    272                     interpolatedValue));
    273         }
    274         if (transformY) {
    275             float endY = otherStablePosition[1] - ownPosition[1];
    276             if (customTransformation != null
    277                     && customTransformation.customTransformTarget(this, otherState)) {
    278                 endY = mTransformationEndY;
    279             }
    280             transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
    281                     endY,
    282                     interpolatedValue));
    283         }
    284         if (transformScale) {
    285             View otherView = otherState.getTransformedView();
    286             float transformationStartScaleX = getTransformationStartScaleX();
    287             if (transformationStartScaleX != UNDEFINED) {
    288                 transformedView.setScaleX(
    289                         NotificationUtils.interpolate(transformationStartScaleX,
    290                                 (otherView.getWidth() / (float) transformedView.getWidth()),
    291                                 interpolatedValue));
    292             }
    293             float transformationStartScaleY = getTransformationStartScaleY();
    294             if (transformationStartScaleY != UNDEFINED) {
    295                 transformedView.setScaleY(
    296                         NotificationUtils.interpolate(transformationStartScaleY,
    297                                 (otherView.getHeight() / (float) transformedView.getHeight()),
    298                                 interpolatedValue));
    299             }
    300         }
    301     }
    302 
    303     public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
    304         if (!(transformedView.getParent() instanceof ViewGroup)) {
    305             return;
    306         }
    307         ViewGroup view = (ViewGroup) transformedView.getParent();
    308         while (true) {
    309             ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
    310             if (clipSet == null) {
    311                 clipSet = new ArraySet<>();
    312                 view.setTag(CLIP_CLIPPING_SET, clipSet);
    313             }
    314             Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
    315             if (clipChildren == null) {
    316                 clipChildren = view.getClipChildren();
    317                 view.setTag(CLIP_CHILDREN_TAG, clipChildren);
    318             }
    319             Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
    320             if (clipToPadding == null) {
    321                 clipToPadding = view.getClipToPadding();
    322                 view.setTag(CLIP_TO_PADDING, clipToPadding);
    323             }
    324             ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
    325                     ? (ExpandableNotificationRow) view
    326                     : null;
    327             if (!deactivated) {
    328                 clipSet.remove(transformedView);
    329                 if (clipSet.isEmpty()) {
    330                     view.setClipChildren(clipChildren);
    331                     view.setClipToPadding(clipToPadding);
    332                     view.setTag(CLIP_CLIPPING_SET, null);
    333                     if (row != null) {
    334                         row.setClipToActualHeight(true);
    335                     }
    336                 }
    337             } else {
    338                 clipSet.add(transformedView);
    339                 view.setClipChildren(false);
    340                 view.setClipToPadding(false);
    341                 if (row != null && row.isChildInGroup()) {
    342                     // We still want to clip to the parent's height
    343                     row.setClipToActualHeight(false);
    344                 }
    345             }
    346             if (row != null && !row.isChildInGroup()) {
    347                 return;
    348             }
    349             final ViewParent parent = view.getParent();
    350             if (parent instanceof ViewGroup) {
    351                 view = (ViewGroup) parent;
    352             } else {
    353                 return;
    354             }
    355         }
    356     }
    357 
    358     public int[] getLaidOutLocationOnScreen() {
    359         int[] location = getLocationOnScreen();
    360         location[0] -= mTransformedView.getTranslationX();
    361         location[1] -= mTransformedView.getTranslationY();
    362         return location;
    363     }
    364 
    365     public int[] getLocationOnScreen() {
    366         mTransformedView.getLocationOnScreen(mOwnPosition);
    367         return mOwnPosition;
    368     }
    369 
    370     protected boolean sameAs(TransformState otherState) {
    371         return false;
    372     }
    373 
    374     public static TransformState createFrom(View view) {
    375         if (view instanceof TextView) {
    376             TextViewTransformState result = TextViewTransformState.obtain();
    377             result.initFrom(view);
    378             return result;
    379         }
    380         if (view.getId() == com.android.internal.R.id.actions_container) {
    381             ActionListTransformState result = ActionListTransformState.obtain();
    382             result.initFrom(view);
    383             return result;
    384         }
    385         if (view instanceof NotificationHeaderView) {
    386             HeaderTransformState result = HeaderTransformState.obtain();
    387             result.initFrom(view);
    388             return result;
    389         }
    390         if (view instanceof ImageView) {
    391             ImageTransformState result = ImageTransformState.obtain();
    392             result.initFrom(view);
    393             return result;
    394         }
    395         if (view instanceof ProgressBar) {
    396             ProgressTransformState result = ProgressTransformState.obtain();
    397             result.initFrom(view);
    398             return result;
    399         }
    400         TransformState result = obtain();
    401         result.initFrom(view);
    402         return result;
    403     }
    404 
    405     public void recycle() {
    406         reset();
    407         if (getClass() == TransformState.class) {
    408             sInstancePool.release(this);
    409         }
    410     }
    411 
    412     public void setTransformationEndY(float transformationEndY) {
    413         mTransformationEndY = transformationEndY;
    414     }
    415 
    416     public void setTransformationEndX(float transformationEndX) {
    417         mTransformationEndX = transformationEndX;
    418     }
    419 
    420     public float getTransformationStartX() {
    421         Object tag = mTransformedView.getTag(TRANSFORMATION_START_X);
    422         return tag == null ? UNDEFINED : (float) tag;
    423     }
    424 
    425     public float getTransformationStartY() {
    426         Object tag = mTransformedView.getTag(TRANSFORMATION_START_Y);
    427         return tag == null ? UNDEFINED : (float) tag;
    428     }
    429 
    430     public float getTransformationStartScaleX() {
    431         Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_X);
    432         return tag == null ? UNDEFINED : (float) tag;
    433     }
    434 
    435     public float getTransformationStartScaleY() {
    436         Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_Y);
    437         return tag == null ? UNDEFINED : (float) tag;
    438     }
    439 
    440     public void setTransformationStartX(float transformationStartX) {
    441         mTransformedView.setTag(TRANSFORMATION_START_X, transformationStartX);
    442     }
    443 
    444     public void setTransformationStartY(float transformationStartY) {
    445         mTransformedView.setTag(TRANSFORMATION_START_Y, transformationStartY);
    446     }
    447 
    448     private void setTransformationStartScaleX(float startScaleX) {
    449         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, startScaleX);
    450     }
    451 
    452     private void setTransformationStartScaleY(float startScaleY) {
    453         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, startScaleY);
    454     }
    455 
    456     protected void reset() {
    457         mTransformedView = null;
    458         mTransformationEndX = UNDEFINED;
    459         mTransformationEndY = UNDEFINED;
    460     }
    461 
    462     public void setVisible(boolean visible, boolean force) {
    463         if (!force && mTransformedView.getVisibility() == View.GONE) {
    464             return;
    465         }
    466         if (mTransformedView.getVisibility() != View.GONE) {
    467             mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    468         }
    469         mTransformedView.animate().cancel();
    470         mTransformedView.setAlpha(visible ? 1.0f : 0.0f);
    471         resetTransformedView();
    472     }
    473 
    474     public void prepareFadeIn() {
    475         resetTransformedView();
    476     }
    477 
    478     protected void resetTransformedView() {
    479         mTransformedView.setTranslationX(0);
    480         mTransformedView.setTranslationY(0);
    481         mTransformedView.setScaleX(1.0f);
    482         mTransformedView.setScaleY(1.0f);
    483         setClippingDeactivated(mTransformedView, false);
    484         abortTransformation();
    485     }
    486 
    487     public void abortTransformation() {
    488         mTransformedView.setTag(TRANSFORMATION_START_X, UNDEFINED);
    489         mTransformedView.setTag(TRANSFORMATION_START_Y, UNDEFINED);
    490         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, UNDEFINED);
    491         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, UNDEFINED);
    492     }
    493 
    494     public static TransformState obtain() {
    495         TransformState instance = sInstancePool.acquire();
    496         if (instance != null) {
    497             return instance;
    498         }
    499         return new TransformState();
    500     }
    501 
    502     public View getTransformedView() {
    503         return mTransformedView;
    504     }
    505 }
    506