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