DetailsOverviewSharedElementHelper.java revision b81a2943b9e150c6caca969e62c5375928c4cd1c
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 */ 14package android.support.v17.leanback.widget; 15 16import android.os.Handler; 17import android.support.v4.app.ActivityCompat; 18import android.support.v4.app.SharedElementCallback; 19import android.support.v4.view.ViewCompat; 20import android.support.v17.leanback.R; 21import android.support.v17.leanback.transition.TransitionListener; 22import android.support.v17.leanback.transition.TransitionHelper; 23import android.support.v17.leanback.widget.DetailsOverviewRowPresenter.ViewHolder; 24import android.app.Activity; 25import android.content.Intent; 26import android.graphics.Matrix; 27import android.text.TextUtils; 28import android.util.Log; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.View.MeasureSpec; 32import android.widget.ImageView; 33import android.widget.ImageView.ScaleType; 34 35import java.util.List; 36 37final class DetailsOverviewSharedElementHelper extends SharedElementCallback { 38 39 private static final String TAG = "DetailsOverviewSharedElementHelper"; 40 private static final boolean DEBUG = false; 41 42 private ViewHolder mViewHolder; 43 private Activity mActivityToRunTransition; 44 private String mSharedElementName; 45 private int mRightPanelWidth; 46 private int mRightPanelHeight; 47 48 private ScaleType mSavedScaleType; 49 private Matrix mSavedMatrix; 50 51 private boolean hasImageViewScaleChange(View snapshotView) { 52 return snapshotView instanceof ImageView; 53 } 54 55 private void saveImageViewScale() { 56 if (mSavedScaleType == null) { 57 // only save first time after initialize/restoreImageViewScale() 58 ImageView imageView = mViewHolder.mImageView; 59 mSavedScaleType = imageView.getScaleType(); 60 mSavedMatrix = mSavedScaleType == ScaleType.MATRIX ? imageView.getMatrix() : null; 61 if (DEBUG) { 62 Log.d(TAG, "saveImageViewScale: "+mSavedScaleType); 63 } 64 } 65 } 66 67 private static void updateImageViewAfterScaleTypeChange(ImageView imageView) { 68 // enforcing imageView to update its internal bounds/matrix immediately 69 imageView.measure( 70 MeasureSpec.makeMeasureSpec(imageView.getMeasuredWidth(), MeasureSpec.EXACTLY), 71 MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY)); 72 imageView.layout(imageView.getLeft(), imageView.getTop(), 73 imageView.getRight(), imageView.getBottom()); 74 } 75 76 private void changeImageViewScale(View snapshotView) { 77 ImageView snapshotImageView = (ImageView) snapshotView; 78 ImageView imageView = mViewHolder.mImageView; 79 if (DEBUG) { 80 Log.d(TAG, "changeImageViewScale to "+snapshotImageView.getScaleType()); 81 } 82 imageView.setScaleType(snapshotImageView.getScaleType()); 83 if (snapshotImageView.getScaleType() == ScaleType.MATRIX) { 84 imageView.setImageMatrix(snapshotImageView.getImageMatrix()); 85 } 86 updateImageViewAfterScaleTypeChange(imageView); 87 } 88 89 private void restoreImageViewScale() { 90 if (mSavedScaleType != null) { 91 if (DEBUG) { 92 Log.d(TAG, "restoreImageViewScale to "+mSavedScaleType); 93 } 94 ImageView imageView = mViewHolder.mImageView; 95 imageView.setScaleType(mSavedScaleType); 96 if (mSavedScaleType == ScaleType.MATRIX) { 97 imageView.setImageMatrix(mSavedMatrix); 98 } 99 // only restore once unless another save happens 100 mSavedScaleType = null; 101 updateImageViewAfterScaleTypeChange(imageView); 102 } 103 } 104 105 @Override 106 public void onSharedElementStart(List<String> sharedElementNames, 107 List<View> sharedElements, List<View> sharedElementSnapshots) { 108 if (sharedElements.size() < 1) { 109 return; 110 } 111 View overviewView = sharedElements.get(0); 112 if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) { 113 return; 114 } 115 View snapshot = sharedElementSnapshots.get(0); 116 if (hasImageViewScaleChange(snapshot)) { 117 saveImageViewScale(); 118 changeImageViewScale(snapshot); 119 } 120 View imageView = mViewHolder.mImageView; 121 final int width = overviewView.getWidth(); 122 final int height = overviewView.getHeight(); 123 imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 124 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 125 imageView.layout(0, 0, width, height); 126 final View rightPanel = mViewHolder.mRightPanel; 127 if (mRightPanelWidth != 0 && mRightPanelHeight != 0) { 128 rightPanel.measure(MeasureSpec.makeMeasureSpec(mRightPanelWidth, MeasureSpec.EXACTLY), 129 MeasureSpec.makeMeasureSpec(mRightPanelHeight, MeasureSpec.EXACTLY)); 130 rightPanel.layout(width, rightPanel.getTop(), width + mRightPanelWidth, 131 rightPanel.getTop() + mRightPanelHeight); 132 } else { 133 rightPanel.offsetLeftAndRight(width - rightPanel.getLeft()); 134 } 135 mViewHolder.mActionsRow.setVisibility(View.INVISIBLE); 136 mViewHolder.mDetailsDescriptionFrame.setVisibility(View.INVISIBLE); 137 } 138 139 @Override 140 public void onSharedElementEnd(List<String> sharedElementNames, 141 List<View> sharedElements, List<View> sharedElementSnapshots) { 142 if (sharedElements.size() < 1) { 143 return; 144 } 145 View overviewView = sharedElements.get(0); 146 if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) { 147 return; 148 } 149 restoreImageViewScale(); 150 // temporary let action row take focus so we defer button background animation 151 mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 152 mViewHolder.mActionsRow.setVisibility(View.VISIBLE); 153 mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 154 mViewHolder.mDetailsDescriptionFrame.setVisibility(View.VISIBLE); 155 } 156 157 void setSharedElementEnterTransition(Activity activity, String sharedElementName, 158 long timeoutMs) { 159 if (activity == null && !TextUtils.isEmpty(sharedElementName) || 160 activity != null && TextUtils.isEmpty(sharedElementName)) { 161 throw new IllegalArgumentException(); 162 } 163 if (activity == mActivityToRunTransition && 164 TextUtils.equals(sharedElementName, mSharedElementName)) { 165 return; 166 } 167 if (mActivityToRunTransition != null) { 168 ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, null); 169 } 170 mActivityToRunTransition = activity; 171 mSharedElementName = sharedElementName; 172 if (mActivityToRunTransition != null) { 173 ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this); 174 ActivityCompat.postponeEnterTransition(mActivityToRunTransition); 175 if (timeoutMs > 0) { 176 new Handler().postDelayed(new Runnable() { 177 @Override 178 public void run() { 179 if (mActivityToRunTransition == null) { 180 return; 181 } 182 ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition); 183 mActivityToRunTransition = null; 184 } 185 }, timeoutMs); 186 } 187 } 188 } 189 190 void onBindToDrawable(ViewHolder vh) { 191 // After we got a image drawable, we can determine size of right panel. 192 // We want right panel to have fixed size so that the right panel don't change size 193 // when the overview is layout as a small bounds in transition. 194 mViewHolder = vh; 195 mViewHolder.mRightPanel.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 196 @Override 197 public void onLayoutChange(View v, int left, int top, int right, int bottom, 198 int oldLeft, int oldTop, int oldRight, int oldBottom) { 199 mViewHolder.mRightPanel.removeOnLayoutChangeListener(this); 200 mRightPanelWidth = mViewHolder.mRightPanel.getWidth(); 201 mRightPanelHeight = mViewHolder.mRightPanel.getHeight(); 202 } 203 }); 204 if (mActivityToRunTransition != null) { 205 mViewHolder.mRightPanel.postOnAnimation(new Runnable() { 206 @Override 207 public void run() { 208 if (mActivityToRunTransition == null) { 209 return; 210 } 211 final TransitionHelper transitionHelper = TransitionHelper.getInstance(); 212 Object transition = transitionHelper.getSharedElementEnterTransition( 213 mActivityToRunTransition.getWindow()); 214 if (transition != null) { 215 transitionHelper.setTransitionListener(transition, new TransitionListener() { 216 @Override 217 public void onTransitionEnd(Object transition) { 218 // after transition if the action row still focused, transfer 219 // focus to its children 220 if (mViewHolder.mActionsRow.isFocused()) { 221 mViewHolder.mActionsRow.requestFocus(); 222 } 223 transitionHelper.setTransitionListener(transition, null); 224 } 225 }); 226 } 227 ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, mSharedElementName); 228 ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition); 229 mActivityToRunTransition = null; 230 mSharedElementName = null; 231 } 232 }); 233 } 234 } 235} 236