DetailsOverviewRowPresenter.java revision 4121f22713bbed467a977ec0d867ef53989ff374
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.app.Activity;
17import android.content.Context;
18import android.graphics.Bitmap;
19import android.graphics.Color;
20import android.graphics.drawable.Drawable;
21import android.graphics.drawable.BitmapDrawable;
22import android.support.v17.leanback.R;
23import android.support.v7.widget.RecyclerView;
24import android.util.Log;
25import android.util.TypedValue;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.FrameLayout;
30import android.widget.ImageView;
31
32import java.util.Collection;
33
34/**
35 * A DetailsOverviewRowPresenter renders a {@link DetailsOverviewRow} to display an
36 * overview of an item. Typically this row will be the first row in a fragment
37 * such as the {@link android.support.v17.leanback.app.DetailsFragment
38 * DetailsFragment}.
39 *
40 * <p>The detailed description is rendered using a {@link Presenter}.
41 */
42public class DetailsOverviewRowPresenter extends RowPresenter {
43
44    private static final String TAG = "DetailsOverviewRowPresenter";
45    private static final boolean DEBUG = false;
46
47    private static final int MORE_ACTIONS_FADE_MS = 100;
48
49    /**
50     * A ViewHolder for the DetailsOverviewRow.
51     */
52    public static class ViewHolder extends RowPresenter.ViewHolder {
53        final ViewGroup mOverviewView;
54        final ImageView mImageView;
55        final ViewGroup mRightPanel;
56        final FrameLayout mDetailsDescriptionFrame;
57        final HorizontalGridView mActionsRow;
58        Presenter.ViewHolder mDetailsDescriptionViewHolder;
59        int mNumItems;
60        boolean mShowMoreRight;
61        boolean mShowMoreLeft;
62
63        void bind(ItemBridgeAdapter bridgeAdapter) {
64            mNumItems = bridgeAdapter.getItemCount();
65            bridgeAdapter.setAdapterListener(mAdapterListener);
66
67            mShowMoreRight = false;
68            mShowMoreLeft = true;
69            showMoreLeft(false);
70        }
71
72        final View.OnLayoutChangeListener mLayoutChangeListener =
73                new View.OnLayoutChangeListener() {
74
75            @Override
76            public void onLayoutChange(View v, int left, int top, int right,
77                    int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
78                if (DEBUG) Log.v(TAG, "onLayoutChange " + v);
79                checkFirstAndLastPosition(false);
80            }
81        };
82
83        final ItemBridgeAdapter.AdapterListener mAdapterListener =
84                new ItemBridgeAdapter.AdapterListener() {
85
86            @Override
87            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
88                // Remove first to ensure we don't add ourselves more than once.
89                viewHolder.itemView.removeOnLayoutChangeListener(mLayoutChangeListener);
90                viewHolder.itemView.addOnLayoutChangeListener(mLayoutChangeListener);
91            }
92            @Override
93            public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
94                viewHolder.itemView.removeOnLayoutChangeListener(mLayoutChangeListener);
95                checkFirstAndLastPosition(false);
96            }
97        };
98
99        final RecyclerView.OnScrollListener mScrollListener =
100                new RecyclerView.OnScrollListener() {
101
102            @Override
103            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
104            }
105            @Override
106            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
107                checkFirstAndLastPosition(true);
108            }
109        };
110
111        private int getViewCenter(View view) {
112            return (view.getRight() - view.getLeft()) / 2;
113        }
114
115        private void checkFirstAndLastPosition(boolean fromScroll) {
116            RecyclerView.ViewHolder viewHolder;
117
118            viewHolder = mActionsRow.findViewHolderForPosition(mNumItems - 1);
119            boolean showRight = (viewHolder == null ||
120                    viewHolder.itemView.getRight() > mActionsRow.getWidth());
121
122            viewHolder = mActionsRow.findViewHolderForPosition(0);
123            boolean showLeft = (viewHolder == null || viewHolder.itemView.getLeft() < 0);
124
125            if (DEBUG) Log.v(TAG, "checkFirstAndLast fromScroll " + fromScroll +
126                    " showRight " + showRight + " showLeft " + showLeft);
127
128            showMoreRight(showRight);
129            showMoreLeft(showLeft);
130        }
131
132        private void showMoreLeft(boolean show) {
133            if (show != mShowMoreLeft) {
134                mActionsRow.setFadingLeftEdge(show);
135                mShowMoreLeft = show;
136            }
137        }
138
139        private void showMoreRight(boolean show) {
140            if (show != mShowMoreRight) {
141                mActionsRow.setFadingRightEdge(show);
142                mShowMoreRight = show;
143            }
144        }
145
146        /**
147         * Constructor for the ViewHolder.
148         *
149         * @param rootView The root View that this view holder will be attached
150         *        to.
151         */
152        public ViewHolder(View rootView) {
153            super(rootView);
154            mOverviewView = (ViewGroup) rootView.findViewById(R.id.details_overview);
155            mImageView = (ImageView) rootView.findViewById(R.id.details_overview_image);
156            mRightPanel = (ViewGroup) rootView.findViewById(R.id.details_overview_right_panel);
157            mDetailsDescriptionFrame =
158                    (FrameLayout) mRightPanel.findViewById(R.id.details_overview_description);
159            mActionsRow =
160                    (HorizontalGridView) mRightPanel.findViewById(R.id.details_overview_actions);
161            mActionsRow.setOnScrollListener(mScrollListener);
162
163            final int fadeLength = rootView.getResources().getDimensionPixelSize(
164                    R.dimen.lb_details_overview_actions_fade_size);
165            mActionsRow.setFadingRightEdgeLength(fadeLength);
166            mActionsRow.setFadingLeftEdgeLength(fadeLength);
167        }
168    }
169
170    private final Presenter mDetailsPresenter;
171    private final ActionPresenterSelector mActionPresenterSelector;
172    private final ItemBridgeAdapter mActionBridgeAdapter;
173    private int mBackgroundColor = Color.TRANSPARENT;
174    private boolean mBackgroundColorSet;
175    private boolean mIsStyleLarge = true;
176
177    private DetailsOverviewSharedElementHelper mSharedElementHelper;
178
179    /**
180     * Constructor for a DetailsOverviewRowPresenter.
181     *
182     * @param detailsPresenter The {@link Presenter} used to render the detailed
183     *        description of the row.
184     */
185    public DetailsOverviewRowPresenter(Presenter detailsPresenter) {
186        setHeaderPresenter(null);
187        setSelectEffectEnabled(false);
188        mDetailsPresenter = detailsPresenter;
189        mActionPresenterSelector = new ActionPresenterSelector();
190        mActionBridgeAdapter = new ItemBridgeAdapter();
191    }
192
193    /**
194     * Sets the listener for Action click events.
195     */
196    public void setOnActionClickedListener(OnActionClickedListener listener) {
197        mActionPresenterSelector.setOnActionClickedListener(listener);
198    }
199
200    /**
201     * Gets the listener for Action click events.
202     */
203    public OnActionClickedListener getOnActionClickedListener() {
204        return mActionPresenterSelector.getOnActionClickedListener();
205    }
206
207    /**
208     * Sets the background color.  If not set, a default from the theme will be used.
209     */
210    public void setBackgroundColor(int color) {
211        mBackgroundColor = color;
212        mBackgroundColorSet = true;
213    }
214
215    /**
216     * Returns the background color.  If no background color was set, transparent
217     * is returned.
218     */
219    public int getBackgroundColor() {
220        return mBackgroundColor;
221    }
222
223    /**
224     * Sets the layout style to be large or small. This affects the height of
225     * the overview, including the text description. The default is large.
226     */
227    public void setStyleLarge(boolean large) {
228        mIsStyleLarge = large;
229    }
230
231    /**
232     * Returns true if the layout style is large.
233     */
234    public boolean isStyleLarge() {
235        return mIsStyleLarge;
236    }
237
238    /**
239     * Set enter transition of target activity (typically a DetailActivity) to be
240     * transiting into overview row created by this presenter.
241     * <p>
242     * It assumes shared element passed from calling activity is an ImageView;
243     * the shared element transits to overview image on the left of detail
244     * overview row, while bounds of overview row grows and reveals text
245     * and buttons on the right.
246     * <p>
247     * The method must be invoked in target Activity's onCreate().
248     */
249    public final void setSharedElementEnterTransition(Activity activity,
250            String sharedElementName) {
251        if (mSharedElementHelper == null) {
252            mSharedElementHelper = new DetailsOverviewSharedElementHelper();
253        }
254        mSharedElementHelper.setSharedElementEnterTransition(activity, sharedElementName);
255    }
256
257    private int getDefaultBackgroundColor(Context context) {
258        TypedValue outValue = new TypedValue();
259        context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
260        return context.getResources().getColor(outValue.resourceId);
261    }
262
263    @Override
264    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
265        View v = LayoutInflater.from(parent.getContext())
266            .inflate(R.layout.lb_details_overview, parent, false);
267        ViewHolder vh = new ViewHolder(v);
268
269        vh.mDetailsDescriptionViewHolder =
270            mDetailsPresenter.onCreateViewHolder(vh.mDetailsDescriptionFrame);
271        vh.mDetailsDescriptionFrame.addView(vh.mDetailsDescriptionViewHolder.view);
272
273        initDetailsOverview(vh);
274
275        return vh;
276    }
277
278    private int getCardHeight(Context context) {
279        int resId = mIsStyleLarge ? R.dimen.lb_details_overview_height_large :
280            R.dimen.lb_details_overview_height_small;
281        return context.getResources().getDimensionPixelSize(resId);
282    }
283
284    private void initDetailsOverview(ViewHolder vh) {
285        View overview = vh.view.findViewById(R.id.details_overview);
286        ViewGroup.LayoutParams lp = overview.getLayoutParams();
287        lp.height = getCardHeight(overview.getContext());
288        overview.setLayoutParams(lp);
289
290        overview.setBackgroundColor(mBackgroundColorSet ?
291                mBackgroundColor : getDefaultBackgroundColor(overview.getContext()));
292        ShadowHelper.getInstance().setZ(overview, 0f);
293    }
294
295    private static int getNonNegativeWidth(Drawable drawable) {
296        final int width = (drawable == null) ? 0 : drawable.getIntrinsicWidth();
297        return (width > 0 ? width : 0);
298    }
299
300    private static int getNonNegativeHeight(Drawable drawable) {
301        final int height = (drawable == null) ? 0 : drawable.getIntrinsicHeight();
302        return (height > 0 ? height : 0);
303    }
304
305    @Override
306    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
307        super.onBindRowViewHolder(holder, item);
308
309        DetailsOverviewRow row = (DetailsOverviewRow) item;
310        ViewHolder vh = (ViewHolder) holder;
311
312        ViewGroup.MarginLayoutParams layoutParams =
313                (ViewGroup.MarginLayoutParams) vh.mImageView.getLayoutParams();
314        final int cardHeight = getCardHeight(vh.mImageView.getContext());
315        final int verticalMargin = vh.mImageView.getResources().getDimensionPixelSize(
316                R.dimen.lb_details_overview_image_margin_vertical);
317        final int horizontalMargin = vh.mImageView.getResources().getDimensionPixelSize(
318                R.dimen.lb_details_overview_image_margin_horizontal);
319        final int drawableWidth = getNonNegativeWidth(row.getImageDrawable());
320        final int drawableHeight = getNonNegativeHeight(row.getImageDrawable());
321
322        boolean scaleImage = row.isImageScaleUpAllowed();
323        boolean useMargin = false;
324
325        if (row.getImageDrawable() != null) {
326            boolean landscape = false;
327
328            // If large style and landscape image we always use margin.
329            if (drawableWidth > drawableHeight) {
330                landscape = true;
331                if (mIsStyleLarge) {
332                    useMargin = true;
333                }
334            }
335            // If long dimension bigger than the card height we scale down.
336            if ((landscape && drawableWidth > cardHeight) ||
337                    (!landscape && drawableHeight > cardHeight)) {
338                scaleImage = true;
339            }
340            // If we're not scaling to fit the card height then we always use margin.
341            if (!scaleImage) {
342                useMargin = true;
343            }
344            // If using margin than may need to scale down.
345            if (useMargin && !scaleImage) {
346                if (landscape && drawableWidth > cardHeight - horizontalMargin) {
347                    scaleImage = true;
348                } else if (!landscape && drawableHeight > cardHeight - 2 * verticalMargin) {
349                    scaleImage = true;
350                }
351            }
352        }
353
354        if (useMargin) {
355            layoutParams.leftMargin = horizontalMargin;
356            layoutParams.topMargin = layoutParams.bottomMargin = verticalMargin;
357        } else {
358            layoutParams.leftMargin = layoutParams.topMargin = layoutParams.bottomMargin = 0;
359        }
360        if (scaleImage) {
361            vh.mImageView.setScaleType(ImageView.ScaleType.FIT_START);
362            vh.mImageView.setAdjustViewBounds(true);
363            vh.mImageView.setMaxWidth(cardHeight);
364            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
365            layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
366        } else {
367            vh.mImageView.setScaleType(ImageView.ScaleType.CENTER);
368            vh.mImageView.setAdjustViewBounds(false);
369            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
370            // Limit width to the card height
371            layoutParams.width = Math.min(cardHeight, drawableWidth);
372        }
373        vh.mImageView.setLayoutParams(layoutParams);
374        vh.mImageView.setImageDrawable(row.getImageDrawable());
375
376        mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem());
377
378        mActionBridgeAdapter.clear();
379        ArrayObjectAdapter aoa = new ArrayObjectAdapter(mActionPresenterSelector);
380        aoa.addAll(0, (Collection)row.getActions());
381
382        mActionBridgeAdapter.setAdapter(aoa);
383        vh.mActionsRow.setAdapter(mActionBridgeAdapter);
384
385        vh.bind(mActionBridgeAdapter);
386
387        if (row.getImageDrawable() != null && mSharedElementHelper != null) {
388            mSharedElementHelper.onBindToDrawable(vh);
389        }
390    }
391
392    @Override
393    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
394        super.onUnbindRowViewHolder(holder);
395
396        ViewHolder vh = (ViewHolder) holder;
397        if (vh.mDetailsDescriptionViewHolder != null) {
398            mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder);
399        }
400
401        vh.mActionsRow.setAdapter(null);
402    }
403}
404