FullWidthDetailsOverviewSharedElementHelper.java revision 2056c3e52465864d60f6f64c16a0a27629ba5fd8
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.FullWidthDetailsOverviewRowPresenter.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
37import java.lang.ref.WeakReference;
38
39/**
40 * Helper class to assist delayed shared element activity transition for view created by
41 * {@link FullWidthDetailsOverviewRowPresenter}. User must call
42 * {@link #setSharedElementEnterTransition(Activity, String, long)} during activity onCreate() and
43 * call {@link FullWidthDetailsOverviewRowPresenter#setListener(FullWidthDetailsOverviewRowPresenter.Listener)}.
44 * The helper implements {@link FullWidthDetailsOverviewRowPresenter.Listener} and starts delayed
45 * activity transition once {@link FullWidthDetailsOverviewRowPresenter.Listener#onBindLogo(ViewHolder)}
46 * is called.
47 */
48public class FullWidthDetailsOverviewSharedElementHelper extends
49        FullWidthDetailsOverviewRowPresenter.Listener {
50
51    static final String TAG = "DetailsTransitionHelper";
52    static final boolean DEBUG = false;
53
54    private static final long DEFAULT_TIMEOUT = 5000;
55
56    static class TransitionTimeOutRunnable implements Runnable {
57        WeakReference<FullWidthDetailsOverviewSharedElementHelper> mHelperRef;
58
59        TransitionTimeOutRunnable(FullWidthDetailsOverviewSharedElementHelper helper) {
60            mHelperRef = new WeakReference<FullWidthDetailsOverviewSharedElementHelper>(helper);
61        }
62
63        @Override
64        public void run() {
65            FullWidthDetailsOverviewSharedElementHelper helper = mHelperRef.get();
66            if (helper == null) {
67                return;
68            }
69            if (DEBUG) {
70                Log.d(TAG, "timeout " + helper.mActivityToRunTransition);
71            }
72            helper.startPostponedEnterTransition();
73        }
74    }
75
76    ViewHolder mViewHolder;
77    Activity mActivityToRunTransition;
78    private boolean mStartedPostpone;
79    String mSharedElementName;
80    private boolean mAutoStartSharedElementTransition = true;
81
82    public void setSharedElementEnterTransition(Activity activity, String sharedElementName) {
83        setSharedElementEnterTransition(activity, sharedElementName, DEFAULT_TIMEOUT);
84    }
85
86    public void setSharedElementEnterTransition(Activity activity, String sharedElementName,
87            long timeoutMs) {
88        if (activity == null && !TextUtils.isEmpty(sharedElementName) ||
89                activity != null && TextUtils.isEmpty(sharedElementName)) {
90            throw new IllegalArgumentException();
91        }
92        if (activity == mActivityToRunTransition &&
93                TextUtils.equals(sharedElementName, mSharedElementName)) {
94            return;
95        }
96        mActivityToRunTransition = activity;
97        mSharedElementName = sharedElementName;
98        if (DEBUG) {
99            Log.d(TAG, "postponeEnterTransition " + mActivityToRunTransition);
100        }
101        Object transition = TransitionHelper.getSharedElementEnterTransition(activity.getWindow());
102        setAutoStartSharedElementTransition(transition != null);
103        ActivityCompat.postponeEnterTransition(mActivityToRunTransition);
104        if (timeoutMs > 0) {
105            new Handler().postDelayed(new TransitionTimeOutRunnable(this), timeoutMs);
106        }
107    }
108
109    /**
110     * Enable or disable auto startPostponedEnterTransition() when bound to logo. When it's
111     * disabled, app must call {@link #startPostponedEnterTransition()} to kick off
112     * windowEnterTransition. By default, it is disabled when there is no
113     * windowEnterSharedElementTransition set on the activity.
114     */
115    public void setAutoStartSharedElementTransition(boolean enabled) {
116        mAutoStartSharedElementTransition = enabled;
117    }
118
119    /**
120     * Returns true if auto startPostponedEnterTransition() when bound to logo. When it's
121     * disabled, app must call {@link #startPostponedEnterTransition()} to kick off
122     * windowEnterTransition. By default, it is disabled when there is no
123     * windowEnterSharedElementTransition set on the activity.
124     */
125    public boolean getAutoStartSharedElementTransition() {
126        return mAutoStartSharedElementTransition;
127    }
128
129    @Override
130    public void onBindLogo(ViewHolder vh) {
131        if (DEBUG) {
132            Log.d(TAG, "onBindLogo, could start transition of " + mActivityToRunTransition);
133        }
134        mViewHolder = vh;
135        if (!mAutoStartSharedElementTransition) {
136            return;
137        }
138        if (mViewHolder != null) {
139            if (DEBUG) {
140                Log.d(TAG, "rebind? clear transitionName on current viewHolder "
141                        + mViewHolder.getOverviewView());
142            }
143            ViewCompat.setTransitionName(mViewHolder.getLogoViewHolder().view, null);
144        }
145        // After we got a image drawable,  we can determine size of right panel.
146        // We want right panel to have fixed size so that the right panel don't change size
147        // when the overview is layout as a small bounds in transition.
148        mViewHolder.getDetailsDescriptionFrame().postOnAnimation(new Runnable() {
149            @Override
150            public void run() {
151                if (DEBUG) {
152                    Log.d(TAG, "setTransitionName "+mViewHolder.getOverviewView());
153                }
154                ViewCompat.setTransitionName(mViewHolder.getLogoViewHolder().view,
155                        mSharedElementName);
156                Object transition = TransitionHelper.getSharedElementEnterTransition(
157                        mActivityToRunTransition.getWindow());
158                if (transition != null) {
159                    TransitionHelper.addTransitionListener(transition, new TransitionListener() {
160                        @Override
161                        public void onTransitionEnd(Object transition) {
162                            if (DEBUG) {
163                                Log.d(TAG, "onTransitionEnd " + mActivityToRunTransition);
164                            }
165                            // after transition if the action row still focused, transfer
166                            // focus to its children
167                            if (mViewHolder.getActionsRow().isFocused()) {
168                                mViewHolder.getActionsRow().requestFocus();
169                            }
170                            TransitionHelper.removeTransitionListener(transition, this);
171                        }
172                    });
173                }
174                startPostponedEnterTransitionInternal();
175            }
176        });
177    }
178
179    /**
180     * Manually start postponed enter transition.
181     */
182    public void startPostponedEnterTransition() {
183        new Handler().post(new Runnable(){
184            @Override
185            public void run() {
186                startPostponedEnterTransitionInternal();
187            }
188        });
189    }
190
191    void startPostponedEnterTransitionInternal() {
192        if (!mStartedPostpone && mViewHolder != null) {
193            if (DEBUG) {
194                Log.d(TAG, "startPostponedEnterTransition " + mActivityToRunTransition);
195            }
196            ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
197            mStartedPostpone = true;
198        }
199    }
200}
201