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