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