DetailsOverviewSharedElementHelper.java revision 3103f63e99d47573823957f7aa34308555873221
165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn/* 265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Copyright (C) 2014 The Android Open Source Project 365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * 465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * in compliance with the License. You may obtain a copy of the License at 665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * 765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * http://www.apache.org/licenses/LICENSE-2.0 865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * 965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Unless required by applicable law or agreed to in writing, software distributed under the License 1065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 1165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * or implied. See the License for the specific language governing permissions and limitations under 1265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * the License. 1365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 1465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennpackage android.support.v17.leanback.widget; 1565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 1665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.app.Activity; 1765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.graphics.Matrix; 1865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.os.Handler; 1965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.support.v17.leanback.transition.TransitionHelper; 2065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.support.v17.leanback.transition.TransitionListener; 2165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.support.v17.leanback.widget.DetailsOverviewRowPresenter.ViewHolder; 2265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.support.v4.app.ActivityCompat; 2365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.support.v4.app.SharedElementCallback; 2465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.support.v4.view.ViewCompat; 2565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.text.TextUtils; 2665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.util.Log; 2765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.view.View; 2865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.view.View.MeasureSpec; 2965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.view.ViewGroup; 3065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.widget.ImageView; 3165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.widget.ImageView.ScaleType; 3265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 3365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport java.util.List; 3465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 3565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennfinal class DetailsOverviewSharedElementHelper extends SharedElementCallback { 3665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 3765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn static final String TAG = "DetailsOverviewSharedElementHelper"; 3865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn static final boolean DEBUG = false; 3965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 4065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn ViewHolder mViewHolder; 4165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Activity mActivityToRunTransition; 4265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn boolean mStartedPostpone; 4365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn String mSharedElementName; 4465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn int mRightPanelWidth; 4565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn int mRightPanelHeight; 4665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 4765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private ScaleType mSavedScaleType; 4865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private Matrix mSavedMatrix; 4965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 5065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private boolean hasImageViewScaleChange(View snapshotView) { 5165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn return snapshotView instanceof ImageView; 5265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 5365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 5465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private void saveImageViewScale() { 5565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mSavedScaleType == null) { 5665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // only save first time after initialize/restoreImageViewScale() 5765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn ImageView imageView = mViewHolder.mImageView; 5865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mSavedScaleType = imageView.getScaleType(); 5965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mSavedMatrix = mSavedScaleType == ScaleType.MATRIX ? imageView.getMatrix() : null; 6065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (DEBUG) { 6165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Log.d(TAG, "saveImageViewScale: "+mSavedScaleType); 62 } 63 } 64 } 65 66 private static void updateImageViewAfterScaleTypeChange(ImageView imageView) { 67 // enforcing imageView to update its internal bounds/matrix immediately 68 imageView.measure( 69 MeasureSpec.makeMeasureSpec(imageView.getMeasuredWidth(), MeasureSpec.EXACTLY), 70 MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY)); 71 imageView.layout(imageView.getLeft(), imageView.getTop(), 72 imageView.getRight(), imageView.getBottom()); 73 } 74 75 private void changeImageViewScale(View snapshotView) { 76 ImageView snapshotImageView = (ImageView) snapshotView; 77 ImageView imageView = mViewHolder.mImageView; 78 if (DEBUG) { 79 Log.d(TAG, "changeImageViewScale to "+snapshotImageView.getScaleType()); 80 } 81 imageView.setScaleType(snapshotImageView.getScaleType()); 82 if (snapshotImageView.getScaleType() == ScaleType.MATRIX) { 83 imageView.setImageMatrix(snapshotImageView.getImageMatrix()); 84 } 85 updateImageViewAfterScaleTypeChange(imageView); 86 } 87 88 private void restoreImageViewScale() { 89 if (mSavedScaleType != null) { 90 if (DEBUG) { 91 Log.d(TAG, "restoreImageViewScale to "+mSavedScaleType); 92 } 93 ImageView imageView = mViewHolder.mImageView; 94 imageView.setScaleType(mSavedScaleType); 95 if (mSavedScaleType == ScaleType.MATRIX) { 96 imageView.setImageMatrix(mSavedMatrix); 97 } 98 // only restore once unless another save happens 99 mSavedScaleType = null; 100 updateImageViewAfterScaleTypeChange(imageView); 101 } 102 } 103 104 @Override 105 public void onSharedElementStart(List<String> sharedElementNames, 106 List<View> sharedElements, List<View> sharedElementSnapshots) { 107 if (DEBUG) { 108 Log.d(TAG, "onSharedElementStart " + mActivityToRunTransition); 109 } 110 if (sharedElements.size() < 1) { 111 return; 112 } 113 View overviewView = sharedElements.get(0); 114 if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) { 115 return; 116 } 117 View snapshot = sharedElementSnapshots.get(0); 118 if (hasImageViewScaleChange(snapshot)) { 119 saveImageViewScale(); 120 changeImageViewScale(snapshot); 121 } 122 View imageView = mViewHolder.mImageView; 123 final int width = overviewView.getWidth(); 124 final int height = overviewView.getHeight(); 125 imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 126 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 127 imageView.layout(0, 0, width, height); 128 final View rightPanel = mViewHolder.mRightPanel; 129 if (mRightPanelWidth != 0 && mRightPanelHeight != 0) { 130 rightPanel.measure(MeasureSpec.makeMeasureSpec(mRightPanelWidth, MeasureSpec.EXACTLY), 131 MeasureSpec.makeMeasureSpec(mRightPanelHeight, MeasureSpec.EXACTLY)); 132 rightPanel.layout(width, rightPanel.getTop(), width + mRightPanelWidth, 133 rightPanel.getTop() + mRightPanelHeight); 134 } else { 135 rightPanel.offsetLeftAndRight(width - rightPanel.getLeft()); 136 } 137 mViewHolder.mActionsRow.setVisibility(View.INVISIBLE); 138 mViewHolder.mDetailsDescriptionFrame.setVisibility(View.INVISIBLE); 139 } 140 141 @Override 142 public void onSharedElementEnd(List<String> sharedElementNames, 143 List<View> sharedElements, List<View> sharedElementSnapshots) { 144 if (DEBUG) { 145 Log.d(TAG, "onSharedElementEnd " + mActivityToRunTransition); 146 } 147 if (sharedElements.size() < 1) { 148 return; 149 } 150 View overviewView = sharedElements.get(0); 151 if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) { 152 return; 153 } 154 restoreImageViewScale(); 155 // temporary let action row take focus so we defer button background animation 156 mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 157 mViewHolder.mActionsRow.setVisibility(View.VISIBLE); 158 mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 159 mViewHolder.mDetailsDescriptionFrame.setVisibility(View.VISIBLE); 160 } 161 162 void setSharedElementEnterTransition(Activity activity, String sharedElementName, 163 long timeoutMs) { 164 if (activity == null && !TextUtils.isEmpty(sharedElementName) 165 || activity != null && TextUtils.isEmpty(sharedElementName)) { 166 throw new IllegalArgumentException(); 167 } 168 if (activity == mActivityToRunTransition 169 && TextUtils.equals(sharedElementName, mSharedElementName)) { 170 return; 171 } 172 if (mActivityToRunTransition != null) { 173 ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, null); 174 } 175 mActivityToRunTransition = activity; 176 mSharedElementName = sharedElementName; 177 if (DEBUG) { 178 Log.d(TAG, "postponeEnterTransition " + mActivityToRunTransition); 179 } 180 ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this); 181 ActivityCompat.postponeEnterTransition(mActivityToRunTransition); 182 if (timeoutMs > 0) { 183 new Handler().postDelayed(new Runnable() { 184 @Override 185 public void run() { 186 if (mStartedPostpone) { 187 return; 188 } 189 if (DEBUG) { 190 Log.d(TAG, "timeout " + mActivityToRunTransition); 191 } 192 startPostponedEnterTransition(); 193 } 194 }, timeoutMs); 195 } 196 } 197 198 void onBindToDrawable(ViewHolder vh) { 199 if (DEBUG) { 200 Log.d(TAG, "onBindToDrawable, could start transition of " + mActivityToRunTransition); 201 } 202 if (mViewHolder != null) { 203 if (DEBUG) { 204 Log.d(TAG, "rebind? clear transitionName on current viewHolder " 205 + mViewHolder.mOverviewFrame); 206 } 207 ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, null); 208 } 209 // After we got a image drawable, we can determine size of right panel. 210 // We want right panel to have fixed size so that the right panel don't change size 211 // when the overview is layout as a small bounds in transition. 212 mViewHolder = vh; 213 mViewHolder.mRightPanel.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 214 @Override 215 public void onLayoutChange(View v, int left, int top, int right, int bottom, 216 int oldLeft, int oldTop, int oldRight, int oldBottom) { 217 mViewHolder.mRightPanel.removeOnLayoutChangeListener(this); 218 mRightPanelWidth = mViewHolder.mRightPanel.getWidth(); 219 mRightPanelHeight = mViewHolder.mRightPanel.getHeight(); 220 if (DEBUG) { 221 Log.d(TAG, "onLayoutChange records size of right panel as " 222 + mRightPanelWidth + ", "+ mRightPanelHeight); 223 } 224 } 225 }); 226 mViewHolder.mRightPanel.postOnAnimation(new Runnable() { 227 @Override 228 public void run() { 229 if (DEBUG) { 230 Log.d(TAG, "setTransitionName "+mViewHolder.mOverviewFrame); 231 } 232 ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, mSharedElementName); 233 Object transition = TransitionHelper.getSharedElementEnterTransition( 234 mActivityToRunTransition.getWindow()); 235 if (transition != null) { 236 TransitionHelper.addTransitionListener(transition, new TransitionListener() { 237 @Override 238 public void onTransitionEnd(Object transition) { 239 if (DEBUG) { 240 Log.d(TAG, "onTransitionEnd " + mActivityToRunTransition); 241 } 242 // after transition if the action row still focused, transfer 243 // focus to its children 244 if (mViewHolder.mActionsRow.isFocused()) { 245 mViewHolder.mActionsRow.requestFocus(); 246 } 247 TransitionHelper.removeTransitionListener(transition, this); 248 } 249 }); 250 } 251 startPostponedEnterTransition(); 252 } 253 }); 254 } 255 256 void startPostponedEnterTransition() { 257 if (!mStartedPostpone) { 258 if (DEBUG) { 259 Log.d(TAG, "startPostponedEnterTransition " + mActivityToRunTransition); 260 } 261 ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition); 262 mStartedPostpone = true; 263 } 264 } 265} 266