• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
14 package android.support.v17.leanback.widget;
15 
16 import android.os.Handler;
17 import android.support.v4.app.ActivityCompat;
18 import android.support.v4.app.SharedElementCallback;
19 import android.support.v4.view.ViewCompat;
20 import android.support.v17.leanback.R;
21 import android.support.v17.leanback.transition.TransitionListener;
22 import android.support.v17.leanback.transition.TransitionHelper;
23 import android.support.v17.leanback.widget.DetailsOverviewRowPresenter.ViewHolder;
24 import android.app.Activity;
25 import android.content.Intent;
26 import android.graphics.Matrix;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.View.MeasureSpec;
32 import android.widget.ImageView;
33 import android.widget.ImageView.ScaleType;
34 
35 import java.util.List;
36 
37 final 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 
hasImageViewScaleChange(View snapshotView)52     private boolean hasImageViewScaleChange(View snapshotView) {
53         return snapshotView instanceof ImageView;
54     }
55 
saveImageViewScale()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 
updateImageViewAfterScaleTypeChange(ImageView imageView)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 
changeImageViewScale(View snapshotView)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 
restoreImageViewScale()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
onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)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
onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)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 
setSharedElementEnterTransition(Activity activity, String sharedElementName, long timeoutMs)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 
onBindToDrawable(ViewHolder vh)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                 Object transition = TransitionHelper.getSharedElementEnterTransition(
236                         mActivityToRunTransition.getWindow());
237                 if (transition != null) {
238                     TransitionHelper.addTransitionListener(transition, new TransitionListener() {
239                         @Override
240                         public void onTransitionEnd(Object transition) {
241                             if (DEBUG) {
242                                 Log.d(TAG, "onTransitionEnd " + mActivityToRunTransition);
243                             }
244                             // after transition if the action row still focused, transfer
245                             // focus to its children
246                             if (mViewHolder.mActionsRow.isFocused()) {
247                                 mViewHolder.mActionsRow.requestFocus();
248                             }
249                             TransitionHelper.removeTransitionListener(transition, this);
250                         }
251                     });
252                 }
253                 startPostponedEnterTransition();
254             }
255         });
256     }
257 
startPostponedEnterTransition()258     private void startPostponedEnterTransition() {
259         if (!mStartedPostpone) {
260             if (DEBUG) {
261                 Log.d(TAG, "startPostponedEnterTransition " + mActivityToRunTransition);
262             }
263             ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
264             mStartedPostpone = true;
265         }
266     }
267 }
268