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