Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 package androidx.leanback.widget;
     15 
     16 import android.app.Activity;
     17 import android.graphics.Matrix;
     18 import android.os.Handler;
     19 import android.text.TextUtils;
     20 import android.util.Log;
     21 import android.view.View;
     22 import android.view.View.MeasureSpec;
     23 import android.view.ViewGroup;
     24 import android.widget.ImageView;
     25 import android.widget.ImageView.ScaleType;
     26 
     27 import androidx.core.app.ActivityCompat;
     28 import androidx.core.app.SharedElementCallback;
     29 import androidx.core.view.ViewCompat;
     30 import androidx.leanback.transition.TransitionHelper;
     31 import androidx.leanback.transition.TransitionListener;
     32 import androidx.leanback.widget.DetailsOverviewRowPresenter.ViewHolder;
     33 
     34 import java.lang.ref.WeakReference;
     35 import java.util.List;
     36 
     37 final class DetailsOverviewSharedElementHelper extends SharedElementCallback {
     38 
     39     static final String TAG = "DetailsTransitionHelper";
     40     static final boolean DEBUG = false;
     41 
     42     static class TransitionTimeOutRunnable implements Runnable {
     43         WeakReference<DetailsOverviewSharedElementHelper> mHelperRef;
     44 
     45         TransitionTimeOutRunnable(DetailsOverviewSharedElementHelper helper) {
     46             mHelperRef = new WeakReference<DetailsOverviewSharedElementHelper>(helper);
     47         }
     48 
     49         @Override
     50         public void run() {
     51             DetailsOverviewSharedElementHelper helper = mHelperRef.get();
     52             if (helper == null) {
     53                 return;
     54             }
     55             if (DEBUG) {
     56                 Log.d(TAG, "timeout " + helper.mActivityToRunTransition);
     57             }
     58             helper.startPostponedEnterTransition();
     59         }
     60     }
     61 
     62     ViewHolder mViewHolder;
     63     Activity mActivityToRunTransition;
     64     boolean mStartedPostpone;
     65     String mSharedElementName;
     66     int mRightPanelWidth;
     67     int mRightPanelHeight;
     68 
     69     private ScaleType mSavedScaleType;
     70     private Matrix mSavedMatrix;
     71 
     72     private boolean hasImageViewScaleChange(View snapshotView) {
     73         return snapshotView instanceof ImageView;
     74     }
     75 
     76     private void saveImageViewScale() {
     77         if (mSavedScaleType == null) {
     78             // only save first time after initialize/restoreImageViewScale()
     79             ImageView imageView = mViewHolder.mImageView;
     80             mSavedScaleType = imageView.getScaleType();
     81             mSavedMatrix = mSavedScaleType == ScaleType.MATRIX ? imageView.getMatrix() : null;
     82             if (DEBUG) {
     83                 Log.d(TAG, "saveImageViewScale: "+mSavedScaleType);
     84             }
     85         }
     86     }
     87 
     88     private static void updateImageViewAfterScaleTypeChange(ImageView imageView) {
     89         // enforcing imageView to update its internal bounds/matrix immediately
     90         imageView.measure(
     91                 MeasureSpec.makeMeasureSpec(imageView.getMeasuredWidth(), MeasureSpec.EXACTLY),
     92                 MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY));
     93         imageView.layout(imageView.getLeft(), imageView.getTop(),
     94                 imageView.getRight(), imageView.getBottom());
     95     }
     96 
     97     private void changeImageViewScale(View snapshotView) {
     98         ImageView snapshotImageView = (ImageView) snapshotView;
     99         ImageView imageView = mViewHolder.mImageView;
    100         if (DEBUG) {
    101             Log.d(TAG, "changeImageViewScale to "+snapshotImageView.getScaleType());
    102         }
    103         imageView.setScaleType(snapshotImageView.getScaleType());
    104         if (snapshotImageView.getScaleType() == ScaleType.MATRIX) {
    105             imageView.setImageMatrix(snapshotImageView.getImageMatrix());
    106         }
    107         updateImageViewAfterScaleTypeChange(imageView);
    108     }
    109 
    110     private void restoreImageViewScale() {
    111         if (mSavedScaleType != null) {
    112             if (DEBUG) {
    113                 Log.d(TAG, "restoreImageViewScale to "+mSavedScaleType);
    114             }
    115             ImageView imageView = mViewHolder.mImageView;
    116             imageView.setScaleType(mSavedScaleType);
    117             if (mSavedScaleType == ScaleType.MATRIX) {
    118                 imageView.setImageMatrix(mSavedMatrix);
    119             }
    120             // only restore once unless another save happens
    121             mSavedScaleType = null;
    122             updateImageViewAfterScaleTypeChange(imageView);
    123         }
    124     }
    125 
    126     @Override
    127     public void onSharedElementStart(List<String> sharedElementNames,
    128             List<View> sharedElements, List<View> sharedElementSnapshots) {
    129         if (DEBUG) {
    130             Log.d(TAG, "onSharedElementStart " + mActivityToRunTransition);
    131         }
    132         if (sharedElements.size() < 1) {
    133             return;
    134         }
    135         View overviewView = sharedElements.get(0);
    136         if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) {
    137             return;
    138         }
    139         View snapshot = sharedElementSnapshots.get(0);
    140         if (hasImageViewScaleChange(snapshot)) {
    141             saveImageViewScale();
    142             changeImageViewScale(snapshot);
    143         }
    144         View imageView = mViewHolder.mImageView;
    145         final int width = overviewView.getWidth();
    146         final int height = overviewView.getHeight();
    147         imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
    148                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    149         imageView.layout(0, 0, width, height);
    150         final View rightPanel = mViewHolder.mRightPanel;
    151         if (mRightPanelWidth != 0 && mRightPanelHeight != 0) {
    152             rightPanel.measure(MeasureSpec.makeMeasureSpec(mRightPanelWidth, MeasureSpec.EXACTLY),
    153                     MeasureSpec.makeMeasureSpec(mRightPanelHeight, MeasureSpec.EXACTLY));
    154             rightPanel.layout(width, rightPanel.getTop(), width + mRightPanelWidth,
    155                     rightPanel.getTop() + mRightPanelHeight);
    156         } else {
    157             rightPanel.offsetLeftAndRight(width - rightPanel.getLeft());
    158         }
    159         mViewHolder.mActionsRow.setVisibility(View.INVISIBLE);
    160         mViewHolder.mDetailsDescriptionFrame.setVisibility(View.INVISIBLE);
    161     }
    162 
    163     @Override
    164     public void onSharedElementEnd(List<String> sharedElementNames,
    165             List<View> sharedElements, List<View> sharedElementSnapshots) {
    166         if (DEBUG) {
    167             Log.d(TAG, "onSharedElementEnd " + mActivityToRunTransition);
    168         }
    169         if (sharedElements.size() < 1) {
    170             return;
    171         }
    172         View overviewView = sharedElements.get(0);
    173         if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) {
    174             return;
    175         }
    176         restoreImageViewScale();
    177         // temporary let action row take focus so we defer button background animation
    178         mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
    179         mViewHolder.mActionsRow.setVisibility(View.VISIBLE);
    180         mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    181         // switch focusability to VISIBLE wont trigger focusableViewAvailable() on O because
    182         // shared element details_frame is still INVISIBLE. b/63544781
    183         mViewHolder.mActionsRow.requestFocus();
    184         mViewHolder.mDetailsDescriptionFrame.setVisibility(View.VISIBLE);
    185     }
    186 
    187     void setSharedElementEnterTransition(Activity activity, String sharedElementName,
    188             long timeoutMs) {
    189         if ((activity == null && !TextUtils.isEmpty(sharedElementName))
    190                 || (activity != null && TextUtils.isEmpty(sharedElementName))) {
    191             throw new IllegalArgumentException();
    192         }
    193         if (activity == mActivityToRunTransition
    194                 && TextUtils.equals(sharedElementName, mSharedElementName)) {
    195             return;
    196         }
    197         if (mActivityToRunTransition != null) {
    198             ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, null);
    199         }
    200         mActivityToRunTransition = activity;
    201         mSharedElementName = sharedElementName;
    202         if (DEBUG) {
    203             Log.d(TAG, "postponeEnterTransition " + mActivityToRunTransition);
    204         }
    205         ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this);
    206         ActivityCompat.postponeEnterTransition(mActivityToRunTransition);
    207         if (timeoutMs > 0) {
    208             new Handler().postDelayed(new TransitionTimeOutRunnable(this), timeoutMs);
    209         }
    210     }
    211 
    212     void onBindToDrawable(ViewHolder vh) {
    213         if (DEBUG) {
    214             Log.d(TAG, "onBindToDrawable, could start transition of " + mActivityToRunTransition);
    215         }
    216         if (mViewHolder != null) {
    217             if (DEBUG) {
    218                 Log.d(TAG, "rebind? clear transitionName on current viewHolder "
    219                         + mViewHolder.mOverviewFrame);
    220             }
    221             ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, null);
    222         }
    223         // After we got a image drawable,  we can determine size of right panel.
    224         // We want right panel to have fixed size so that the right panel don't change size
    225         // when the overview is layout as a small bounds in transition.
    226         mViewHolder = vh;
    227         mViewHolder.mRightPanel.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    228             @Override
    229             public void onLayoutChange(View v, int left, int top, int right, int bottom,
    230                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
    231                 mViewHolder.mRightPanel.removeOnLayoutChangeListener(this);
    232                 mRightPanelWidth = mViewHolder.mRightPanel.getWidth();
    233                 mRightPanelHeight = mViewHolder.mRightPanel.getHeight();
    234                 if (DEBUG) {
    235                     Log.d(TAG, "onLayoutChange records size of right panel as "
    236                             + mRightPanelWidth + ", "+ mRightPanelHeight);
    237                 }
    238             }
    239         });
    240         mViewHolder.mRightPanel.postOnAnimation(new Runnable() {
    241             @Override
    242             public void run() {
    243                 if (DEBUG) {
    244                     Log.d(TAG, "setTransitionName "+mViewHolder.mOverviewFrame);
    245                 }
    246                 ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, mSharedElementName);
    247                 Object transition = TransitionHelper.getSharedElementEnterTransition(
    248                         mActivityToRunTransition.getWindow());
    249                 if (transition != null) {
    250                     TransitionHelper.addTransitionListener(transition, new TransitionListener() {
    251                         @Override
    252                         public void onTransitionEnd(Object transition) {
    253                             if (DEBUG) {
    254                                 Log.d(TAG, "onTransitionEnd " + mActivityToRunTransition);
    255                             }
    256                             // after transition if the action row still focused, transfer
    257                             // focus to its children
    258                             if (mViewHolder.mActionsRow.isFocused()) {
    259                                 mViewHolder.mActionsRow.requestFocus();
    260                             }
    261                             TransitionHelper.removeTransitionListener(transition, this);
    262                         }
    263                     });
    264                 }
    265                 startPostponedEnterTransition();
    266             }
    267         });
    268     }
    269 
    270     void startPostponedEnterTransition() {
    271         if (!mStartedPostpone) {
    272             if (DEBUG) {
    273                 Log.d(TAG, "startPostponedEnterTransition " + mActivityToRunTransition);
    274             }
    275             ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
    276             mStartedPostpone = true;
    277         }
    278     }
    279 }
    280