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