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      * Called immediately after the start state is set for the shared element.
55      * The shared element will start at the size and position of the shared element
56      * in the launching Activity or Fragment.
57      *
58      * @param sharedElementNames The names of the shared elements that were accepted into
59      *                           the View hierarchy.
60      * @param sharedElements The shared elements that are part of the View hierarchy.
61      * @param sharedElementSnapshots The Views containing snap shots of the shared element
62      *                               from the launching Window. These elements will not
63      *                               be part of the scene, but will be positioned relative
64      *                               to the Window decor View. This list is null for Fragment
65      *                               Transitions.
66      */
onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)67     public void onSharedElementStart(List<String> sharedElementNames,
68             List<View> sharedElements, List<View> sharedElementSnapshots) {}
69 
70     /**
71      * Called after the end state is set for the shared element, but before the end state
72      * is captured by the shared element transition.
73      * <p>
74      *     Any customization done in
75      *     {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)}
76      *     may need to be modified to the final state of the shared element if it is not
77      *     automatically corrected by layout. For example, rotation or scale will not
78      *     be affected by layout and if changed in {@link #onSharedElementStart(java.util.List,
79      *     java.util.List, java.util.List)}, it will also have to be set here again to correct
80      *     the end state.
81      * </p>
82      *
83      * @param sharedElementNames The names of the shared elements that were accepted into
84      *                           the View hierarchy.
85      * @param sharedElements The shared elements that are part of the View hierarchy.
86      * @param sharedElementSnapshots The Views containing snap shots of the shared element
87      *                               from the launching Window. These elements will not
88      *                               be part of the scene, but will be positioned relative
89      *                               to the Window decor View. This list will be null for
90      *                               Fragment Transitions.
91      */
onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)92     public void onSharedElementEnd(List<String> sharedElementNames,
93             List<View> sharedElements, List<View> sharedElementSnapshots) {}
94 
95     /**
96      * Called after {@link #onMapSharedElements(java.util.List, java.util.Map)} when
97      * transferring shared elements in. Any shared elements that have no mapping will be in
98      * <var>rejectedSharedElements</var>. The elements remaining in
99      * <var>rejectedSharedElements</var> will be transitioned out of the Scene. If a
100      * View is removed from <var>rejectedSharedElements</var>, it must be handled by the
101      * <code>SharedElementCallback</code>.
102      * <p>
103      * Views in rejectedSharedElements will have their position and size set to the
104      * position of the calling shared element, relative to the Window decor View and contain
105      * snapshots of the View from the calling Activity or Fragment. This
106      * view may be safely added to the decor View's overlay to remain in position.
107      * </p>
108      * <p>This method is not called for Fragment Transitions. All rejected shared elements
109      * will be handled by the exit transition.</p>
110      *
111      * @param rejectedSharedElements Views containing visual information of shared elements
112      *                               that are not part of the entering scene. These Views
113      *                               are positioned relative to the Window decor View. A
114      *                               View removed from this list will not be transitioned
115      *                               automatically.
116      */
onRejectSharedElements(List<View> rejectedSharedElements)117     public void onRejectSharedElements(List<View> rejectedSharedElements) {}
118 
119     /**
120      * Lets the SharedElementCallback adjust the mapping of shared element names to
121      * Views.
122      *
123      * @param names The names of all shared elements transferred from the calling Activity
124      *              or Fragment in the order they were provided.
125      * @param sharedElements The mapping of shared element names to Views. The best guess
126      *                       will be filled into sharedElements based on the transitionNames.
127      */
onMapSharedElements(List<String> names, Map<String, View> sharedElements)128     public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {}
129 
130     /**
131      * Creates a snapshot of a shared element to be used by the remote Activity and reconstituted
132      * with {@link #onCreateSnapshotView(android.content.Context, android.os.Parcelable)}. A
133      * null return value will mean that the remote Activity will have a null snapshot View in
134      * {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} and
135      * {@link #onSharedElementEnd(java.util.List, java.util.List, java.util.List)}.
136      *
137      * <p>This is not called for Fragment Transitions.</p>
138      *
139      * @param sharedElement The shared element View to create a snapshot for.
140      * @param viewToGlobalMatrix A matrix containing a transform from the view to the screen
141      *                           coordinates.
142      * @param screenBounds The bounds of shared element in screen coordinate space. This is
143      *                     the bounds of the view with the viewToGlobalMatrix applied.
144      * @return A snapshot to send to the remote Activity to be reconstituted with
145      * {@link #onCreateSnapshotView(android.content.Context, android.os.Parcelable)} and passed
146      * into {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} and
147      * {@link #onSharedElementEnd(java.util.List, java.util.List, java.util.List)}.
148      */
onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds)149     public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix,
150             RectF screenBounds) {
151         if (sharedElement instanceof ImageView) {
152             ImageView imageView = ((ImageView) sharedElement);
153             Drawable d = imageView.getDrawable();
154             Drawable bg = imageView.getBackground();
155             if (d != null && (bg == null || bg.getAlpha() == 0)) {
156                 Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
157                 if (bitmap != null) {
158                     Bundle bundle = new Bundle();
159                     bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
160                     bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE,
161                             imageView.getScaleType().toString());
162                     if (imageView.getScaleType() == ScaleType.MATRIX) {
163                         Matrix matrix = imageView.getImageMatrix();
164                         float[] values = new float[9];
165                         matrix.getValues(values);
166                         bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values);
167                     }
168                     return bundle;
169                 }
170             }
171         }
172         if (mTempMatrix == null) {
173             mTempMatrix = new Matrix(viewToGlobalMatrix);
174         } else {
175             mTempMatrix.set(viewToGlobalMatrix);
176         }
177         return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds);
178     }
179 
180     /**
181      * Reconstitutes a snapshot View from a Parcelable returned in
182      * {@link #onCaptureSharedElementSnapshot(android.view.View, android.graphics.Matrix,
183      * android.graphics.RectF)} to be used in {@link #onSharedElementStart(java.util.List,
184      * java.util.List, java.util.List)} and {@link #onSharedElementEnd(java.util.List,
185      * java.util.List, java.util.List)}. The returned View will be sized and positioned after
186      * this call so that it is ready to be added to the decor View's overlay.
187      *
188      * <p>This is not called for Fragment Transitions.</p>
189      *
190      * @param context The Context used to create the snapshot View.
191      * @param snapshot The Parcelable returned by {@link #onCaptureSharedElementSnapshot(
192      * android.view.View, android.graphics.Matrix, android.graphics.RectF)}.
193      * @return A View to be sent in {@link #onSharedElementStart(java.util.List, java.util.List,
194      * java.util.List)} and {@link #onSharedElementEnd(java.util.List, java.util.List,
195      * java.util.List)}. A null value will produce a null snapshot value for those two methods.
196      */
onCreateSnapshotView(Context context, Parcelable snapshot)197     public View onCreateSnapshotView(Context context, Parcelable snapshot) {
198         View view = null;
199         if (snapshot instanceof Bundle) {
200             Bundle bundle = (Bundle) snapshot;
201             Bitmap bitmap = (Bitmap) bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP);
202             if (bitmap == null) {
203                 return null;
204             }
205             ImageView imageView = new ImageView(context);
206             view = imageView;
207             imageView.setImageBitmap(bitmap);
208             imageView.setScaleType(
209                     ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE)));
210             if (imageView.getScaleType() == ScaleType.MATRIX) {
211                 float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX);
212                 Matrix matrix = new Matrix();
213                 matrix.setValues(values);
214                 imageView.setImageMatrix(matrix);
215             }
216         } else if (snapshot instanceof Bitmap) {
217             Bitmap bitmap = (Bitmap) snapshot;
218             view = new View(context);
219             Resources resources = context.getResources();
220             view.setBackground(new BitmapDrawable(resources, bitmap));
221         }
222         return view;
223     }
224 }
225