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