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