1 /*
2  * Copyright (C) 2014 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 package android.app;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.graphics.Bitmap;
21 import android.graphics.Matrix;
22 import android.graphics.RectF;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.graphics.drawable.Drawable;
25 import android.os.Bundle;
26 import android.os.Parcelable;
27 import android.transition.TransitionUtils;
28 import android.view.View;
29 import android.widget.ImageView;
30 import android.widget.ImageView.ScaleType;
31 
32 import java.util.List;
33 import java.util.Map;
34 
35 /**
36  * Listener provided in
37  * {@link Activity#setEnterSharedElementCallback(SharedElementCallback)} and
38  * {@link Activity#setExitSharedElementCallback(SharedElementCallback)} as well as
39  * {@link Fragment#setEnterSharedElementCallback(SharedElementCallback)} and
40  * {@link Fragment#setExitSharedElementCallback(SharedElementCallback)}
41  * to monitor the Shared element transitions. The events can be used to customize Activity
42  * and Fragment Transition behavior.
43  */
44 public abstract class SharedElementCallback {
45     private Matrix mTempMatrix;
46     private static final String BUNDLE_SNAPSHOT_BITMAP = "sharedElement:snapshot:bitmap";
47     private static final String BUNDLE_SNAPSHOT_IMAGE_SCALETYPE = "sharedElement:snapshot:imageScaleType";
48     private static final String BUNDLE_SNAPSHOT_IMAGE_MATRIX = "sharedElement:snapshot:imageMatrix";
49 
50     static final SharedElementCallback NULL_CALLBACK = new SharedElementCallback() {
51     };
52 
53     /**
54      * In Activity Transitions, onSharedElementStart is called immediately before
55      * capturing the start of the shared element state on enter and reenter transitions and
56      * immediately before capturing the end of the shared element state for exit and return
57      * transitions.
58      * <p>
59      * In Fragment Transitions, onSharedElementStart is called immediately before capturing the
60      * start state of all shared element transitions.
61      * <p>
62      * This call can be used to adjust the transition start state by modifying the shared
63      * element Views. Note that no layout step will be executed between onSharedElementStart
64      * and the transition state capture.
65      * <p>
66      * For Activity Transitions, any changes made in {@link #onSharedElementEnd(List, List, List)}
67      * that are not updated during by layout should be corrected in onSharedElementStart for exit and
68      * return transitions. For example, rotation or scale will not be affected by layout and
69      * if changed in {@link #onSharedElementEnd(List, List, List)}, it will also have to be reset
70      * in onSharedElementStart again to correct the end state.
71      *
72      * @param sharedElementNames The names of the shared elements that were accepted into
73      *                           the View hierarchy.
74      * @param sharedElements The shared elements that are part of the View hierarchy.
75      * @param sharedElementSnapshots The Views containing snap shots of the shared element
76      *                               from the launching Window. These elements will not
77      *                               be part of the scene, but will be positioned relative
78      *                               to the Window decor View. This list is null for Fragment
79      *                               Transitions.
80      */
onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)81     public void onSharedElementStart(List<String> sharedElementNames,
82             List<View> sharedElements, List<View> sharedElementSnapshots) {}
83 
84     /**
85      * In Activity Transitions, onSharedElementEnd is called immediately before
86      * capturing the end of the shared element state on enter and reenter transitions and
87      * immediately before capturing the start of the shared element state for exit and return
88      * transitions.
89      * <p>
90      * In Fragment Transitions, onSharedElementEnd is called immediately before capturing the
91      * end state of all shared element transitions.
92      * <p>
93      * This call can be used to adjust the transition end state by modifying the shared
94      * element Views. Note that no layout step will be executed between onSharedElementEnd
95      * and the transition state capture.
96      * <p>
97      * Any changes made in {@link #onSharedElementStart(List, List, List)} that are not updated
98      * during layout should be corrected in onSharedElementEnd. For example, rotation or scale
99      * will not be affected by layout and if changed in
100      * {@link #onSharedElementStart(List, List, List)}, it will also have to be reset in
101      * onSharedElementEnd again to correct the end state.
102      *
103      * @param sharedElementNames The names of the shared elements that were accepted into
104      *                           the View hierarchy.
105      * @param sharedElements The shared elements that are part of the View hierarchy.
106      * @param sharedElementSnapshots The Views containing snap shots of the shared element
107      *                               from the launching Window. These elements will not
108      *                               be part of the scene, but will be positioned relative
109      *                               to the Window decor View. This list will be null for
110      *                               Fragment Transitions.
111      */
onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)112     public void onSharedElementEnd(List<String> sharedElementNames,
113             List<View> sharedElements, List<View> sharedElementSnapshots) {}
114 
115     /**
116      * Called after {@link #onMapSharedElements(java.util.List, java.util.Map)} when
117      * transferring shared elements in. Any shared elements that have no mapping will be in
118      * <var>rejectedSharedElements</var>. The elements remaining in
119      * <var>rejectedSharedElements</var> will be transitioned out of the Scene. If a
120      * View is removed from <var>rejectedSharedElements</var>, it must be handled by the
121      * <code>SharedElementCallback</code>.
122      * <p>
123      * Views in rejectedSharedElements will have their position and size set to the
124      * position of the calling shared element, relative to the Window decor View and contain
125      * snapshots of the View from the calling Activity or Fragment. This
126      * view may be safely added to the decor View's overlay to remain in position.
127      * </p>
128      * <p>This method is not called for Fragment Transitions. All rejected shared elements
129      * will be handled by the exit transition.</p>
130      *
131      * @param rejectedSharedElements Views containing visual information of shared elements
132      *                               that are not part of the entering scene. These Views
133      *                               are positioned relative to the Window decor View. A
134      *                               View removed from this list will not be transitioned
135      *                               automatically.
136      */
onRejectSharedElements(List<View> rejectedSharedElements)137     public void onRejectSharedElements(List<View> rejectedSharedElements) {}
138 
139     /**
140      * Lets the SharedElementCallback adjust the mapping of shared element names to
141      * Views.
142      *
143      * @param names The names of all shared elements transferred from the calling Activity
144      *              or Fragment in the order they were provided.
145      * @param sharedElements The mapping of shared element names to Views. The best guess
146      *                       will be filled into sharedElements based on the transitionNames.
147      */
onMapSharedElements(List<String> names, Map<String, View> sharedElements)148     public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {}
149 
150     /**
151      * Creates a snapshot of a shared element to be used by the remote Activity and reconstituted
152      * with {@link #onCreateSnapshotView(android.content.Context, android.os.Parcelable)}. A
153      * null return value will mean that the remote Activity will have a null snapshot View in
154      * {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} and
155      * {@link #onSharedElementEnd(java.util.List, java.util.List, java.util.List)}.
156      *
157      * <p>This is not called for Fragment Transitions.</p>
158      *
159      * @param sharedElement The shared element View to create a snapshot for.
160      * @param viewToGlobalMatrix A matrix containing a transform from the view to the screen
161      *                           coordinates.
162      * @param screenBounds The bounds of shared element in screen coordinate space. This is
163      *                     the bounds of the view with the viewToGlobalMatrix applied.
164      * @return A snapshot to send to the remote Activity to be reconstituted with
165      * {@link #onCreateSnapshotView(android.content.Context, android.os.Parcelable)} and passed
166      * into {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} and
167      * {@link #onSharedElementEnd(java.util.List, java.util.List, java.util.List)}.
168      */
onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds)169     public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix,
170             RectF screenBounds) {
171         if (sharedElement instanceof ImageView) {
172             ImageView imageView = ((ImageView) sharedElement);
173             Drawable d = imageView.getDrawable();
174             Drawable bg = imageView.getBackground();
175             if (d != null && (bg == null || bg.getAlpha() == 0)) {
176                 Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
177                 if (bitmap != null) {
178                     Bundle bundle = new Bundle();
179                     bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
180                     bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE,
181                             imageView.getScaleType().toString());
182                     if (imageView.getScaleType() == ScaleType.MATRIX) {
183                         Matrix matrix = imageView.getImageMatrix();
184                         float[] values = new float[9];
185                         matrix.getValues(values);
186                         bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values);
187                     }
188                     return bundle;
189                 }
190             }
191         }
192         if (mTempMatrix == null) {
193             mTempMatrix = new Matrix(viewToGlobalMatrix);
194         } else {
195             mTempMatrix.set(viewToGlobalMatrix);
196         }
197         return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds);
198     }
199 
200     /**
201      * Reconstitutes a snapshot View from a Parcelable returned in
202      * {@link #onCaptureSharedElementSnapshot(android.view.View, android.graphics.Matrix,
203      * android.graphics.RectF)} to be used in {@link #onSharedElementStart(java.util.List,
204      * java.util.List, java.util.List)} and {@link #onSharedElementEnd(java.util.List,
205      * java.util.List, java.util.List)}. The returned View will be sized and positioned after
206      * this call so that it is ready to be added to the decor View's overlay.
207      *
208      * <p>This is not called for Fragment Transitions.</p>
209      *
210      * @param context The Context used to create the snapshot View.
211      * @param snapshot The Parcelable returned by {@link #onCaptureSharedElementSnapshot(
212      * android.view.View, android.graphics.Matrix, android.graphics.RectF)}.
213      * @return A View to be sent in {@link #onSharedElementStart(java.util.List, java.util.List,
214      * java.util.List)} and {@link #onSharedElementEnd(java.util.List, java.util.List,
215      * java.util.List)}. A null value will produce a null snapshot value for those two methods.
216      */
onCreateSnapshotView(Context context, Parcelable snapshot)217     public View onCreateSnapshotView(Context context, Parcelable snapshot) {
218         View view = null;
219         if (snapshot instanceof Bundle) {
220             Bundle bundle = (Bundle) snapshot;
221             Bitmap bitmap = (Bitmap) bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP);
222             if (bitmap == null) {
223                 return null;
224             }
225             ImageView imageView = new ImageView(context);
226             view = imageView;
227             imageView.setImageBitmap(bitmap);
228             imageView.setScaleType(
229                     ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE)));
230             if (imageView.getScaleType() == ScaleType.MATRIX) {
231                 float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX);
232                 Matrix matrix = new Matrix();
233                 matrix.setValues(values);
234                 imageView.setImageMatrix(matrix);
235             }
236         } else if (snapshot instanceof Bitmap) {
237             Bitmap bitmap = (Bitmap) snapshot;
238             view = new View(context);
239             Resources resources = context.getResources();
240             view.setBackground(new BitmapDrawable(resources, bitmap));
241         }
242         return view;
243     }
244 
245     /**
246      * Called during an Activity Transition when the shared elements have arrived at the
247      * final location and are ready to be transferred. This method is called for both the
248      * source and destination Activities.
249      * <p>
250      * When the shared elements are ready to be transferred,
251      * {@link OnSharedElementsReadyListener#onSharedElementsReady()}
252      * must be called to trigger the transfer.
253      * <p>
254      * The default behavior is to trigger the transfer immediately.
255      *
256      * @param sharedElementNames The names of the shared elements that are being transferred..
257      * @param sharedElements The shared elements that are part of the View hierarchy.
258      * @param listener The listener to call when the shared elements are ready to be hidden
259      *                 in the source Activity or shown in the destination Activity.
260      */
onSharedElementsArrived(List<String> sharedElementNames, List<View> sharedElements, OnSharedElementsReadyListener listener)261     public void onSharedElementsArrived(List<String> sharedElementNames,
262             List<View> sharedElements, OnSharedElementsReadyListener listener) {
263         listener.onSharedElementsReady();
264     }
265 
266     /**
267      * Listener to be called after {@link
268      * SharedElementCallback#onSharedElementsArrived(List, List, OnSharedElementsReadyListener)}
269      * when the shared elements are ready to be hidden in the source Activity and shown in the
270      * destination Activity.
271      */
272     public interface OnSharedElementsReadyListener {
273 
274         /**
275          * Call this method during or after the OnSharedElementsReadyListener has been received
276          * in {@link SharedElementCallback#onSharedElementsArrived(List, List,
277          * OnSharedElementsReadyListener)} to indicate that the shared elements are ready to be
278          * hidden in the source and shown in the destination Activity.
279          */
onSharedElementsReady()280         void onSharedElementsReady();
281     }
282 }
283