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