[go: nahoru, domu]

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.graphics.drawable.ColorDrawable;
23import android.os.Handler;
24import android.support.annotation.ColorInt;
25import android.support.v17.leanback.R;
26import android.support.v7.widget.RecyclerView;
27import android.util.Log;
28import android.util.TypedValue;
29import android.view.KeyEvent;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.FrameLayout;
34import android.widget.ImageView;
35
36import java.util.Collection;
37
38/**
39 * Renders a {@link DetailsOverviewRow} to display an overview of an item.
40 * Typically this row will be the first row in a fragment
41 * such as the {@link android.support.v17.leanback.app.DetailsFragment
42 * DetailsFragment}.  The View created by the DetailsOverviewRowPresenter is made in three parts:
43 * ImageView on the left, action list view on the bottom and a customizable detailed
44 * description view on the right.
45 *
46 * <p>The detailed description is rendered using a {@link Presenter} passed in
47 * {@link #DetailsOverviewRowPresenter(Presenter)}.  Typically this will be an instance of
48 * {@link AbstractDetailsDescriptionPresenter}.  The application can access the
49 * detailed description ViewHolder from {@link ViewHolder#mDetailsDescriptionViewHolder}.
50 * </p>
51 *
52 * <p>
53 * To participate in activity transition, call {@link #setSharedElementEnterTransition(Activity,
54 * String)} during Activity's onCreate().
55 * </p>
56 *
57 * <p>
58 * Because transition support and layout are fully controlled by DetailsOverviewRowPresenter,
59 * developer can not override DetailsOverviewRowPresenter.ViewHolder for adding/replacing views
60 * of DetailsOverviewRowPresenter.  If further customization is required beyond replacing
61 * the detailed description, the application should create a new row presenter class.
62 * </p>
63 * @deprecated  Use {@link FullWidthDetailsOverviewRowPresenter}
64 */
65@Deprecated
66public class DetailsOverviewRowPresenter extends RowPresenter {
67
68    private static final String TAG = "DetailsOverviewRowPresenter";
69    private static final boolean DEBUG = false;
70
71    private static final int MORE_ACTIONS_FADE_MS = 100;
72    private static final long DEFAULT_TIMEOUT = 5000;
73
74    class ActionsItemBridgeAdapter extends ItemBridgeAdapter {
75        DetailsOverviewRowPresenter.ViewHolder mViewHolder;
76
77        ActionsItemBridgeAdapter(DetailsOverviewRowPresenter.ViewHolder viewHolder) {
78            mViewHolder = viewHolder;
79        }
80
81        @Override
82        public void onBind(final ItemBridgeAdapter.ViewHolder ibvh) {
83            if (mViewHolder.getOnItemViewClickedListener() != null ||
84                    mActionClickedListener != null) {
85                ibvh.getPresenter().setOnClickListener(
86                        ibvh.getViewHolder(), new View.OnClickListener() {
87                            @Override
88                            public void onClick(View v) {
89                                if (mViewHolder.getOnItemViewClickedListener() != null) {
90                                    mViewHolder.getOnItemViewClickedListener().onItemClicked(
91                                            ibvh.getViewHolder(), ibvh.getItem(),
92                                            mViewHolder, mViewHolder.getRow());
93                                }
94                                if (mActionClickedListener != null) {
95                                    mActionClickedListener.onActionClicked((Action) ibvh.getItem());
96                                }
97                            }
98                        });
99            }
100        }
101        @Override
102        public void onUnbind(final ItemBridgeAdapter.ViewHolder ibvh) {
103            if (mViewHolder.getOnItemViewClickedListener() != null ||
104                    mActionClickedListener != null) {
105                ibvh.getPresenter().setOnClickListener(ibvh.getViewHolder(), null);
106            }
107        }
108        @Override
109        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
110            // Remove first to ensure we don't add ourselves more than once.
111            viewHolder.itemView.removeOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
112            viewHolder.itemView.addOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
113        }
114        @Override
115        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
116            viewHolder.itemView.removeOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
117            mViewHolder.checkFirstAndLastPosition(false);
118        }
119    }
120
121    /**
122     * A ViewHolder for the DetailsOverviewRow.
123     */
124    public final class ViewHolder extends RowPresenter.ViewHolder {
125        final FrameLayout mOverviewFrame;
126        final ViewGroup mOverviewView;
127        final ImageView mImageView;
128        final ViewGroup mRightPanel;
129        final FrameLayout mDetailsDescriptionFrame;
130        final HorizontalGridView mActionsRow;
131        public final Presenter.ViewHolder mDetailsDescriptionViewHolder;
132        int mNumItems;
133        boolean mShowMoreRight;
134        boolean mShowMoreLeft;
135        ItemBridgeAdapter mActionBridgeAdapter;
136        final Handler mHandler = new Handler();
137
138        final Runnable mUpdateDrawableCallback = new Runnable() {
139            @Override
140            public void run() {
141                bindImageDrawable(ViewHolder.this);
142            }
143        };
144
145        final DetailsOverviewRow.Listener mListener = new DetailsOverviewRow.Listener() {
146            @Override
147            public void onImageDrawableChanged(DetailsOverviewRow row) {
148                mHandler.removeCallbacks(mUpdateDrawableCallback);
149                mHandler.post(mUpdateDrawableCallback);
150            }
151
152            @Override
153            public void onItemChanged(DetailsOverviewRow row) {
154                if (mDetailsDescriptionViewHolder != null) {
155                    mDetailsPresenter.onUnbindViewHolder(mDetailsDescriptionViewHolder);
156                }
157                mDetailsPresenter.onBindViewHolder(mDetailsDescriptionViewHolder, row.getItem());
158            }
159
160            @Override
161            public void onActionsAdapterChanged(DetailsOverviewRow row) {
162                bindActions(row.getActionsAdapter());
163            }
164        };
165
166        void bindActions(ObjectAdapter adapter) {
167            mActionBridgeAdapter.setAdapter(adapter);
168            mActionsRow.setAdapter(mActionBridgeAdapter);
169            mNumItems = mActionBridgeAdapter.getItemCount();
170
171            mShowMoreRight = false;
172            mShowMoreLeft = true;
173            showMoreLeft(false);
174        }
175
176        final View.OnLayoutChangeListener mLayoutChangeListener =
177                new View.OnLayoutChangeListener() {
178
179            @Override
180            public void onLayoutChange(View v, int left, int top, int right,
181                    int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
182                if (DEBUG) Log.v(TAG, "onLayoutChange " + v);
183                checkFirstAndLastPosition(false);
184            }
185        };
186
187        final OnChildSelectedListener mChildSelectedListener = new OnChildSelectedListener() {
188            @Override
189            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
190                dispatchItemSelection(view);
191            }
192        };
193
194        void dispatchItemSelection(View view) {
195            if (!isSelected()) {
196                return;
197            }
198            ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) (view != null ?
199                    mActionsRow.getChildViewHolder(view) :
200                    mActionsRow.findViewHolderForPosition(mActionsRow.getSelectedPosition()));
201            if (ibvh == null) {
202                if (getOnItemViewSelectedListener() != null) {
203                    getOnItemViewSelectedListener().onItemSelected(null, null,
204                            ViewHolder.this, getRow());
205                }
206            } else {
207                if (getOnItemViewSelectedListener() != null) {
208                    getOnItemViewSelectedListener().onItemSelected(ibvh.getViewHolder(), ibvh.getItem(),
209                            ViewHolder.this, getRow());
210                }
211            }
212        };
213
214        final RecyclerView.OnScrollListener mScrollListener =
215                new RecyclerView.OnScrollListener() {
216
217            @Override
218            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
219            }
220            @Override
221            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
222                checkFirstAndLastPosition(true);
223            }
224        };
225
226        private int getViewCenter(View view) {
227            return (view.getRight() - view.getLeft()) / 2;
228        }
229
230        private void checkFirstAndLastPosition(boolean fromScroll) {
231            RecyclerView.ViewHolder viewHolder;
232
233            viewHolder = mActionsRow.findViewHolderForPosition(mNumItems - 1);
234            boolean showRight = (viewHolder == null ||
235                    viewHolder.itemView.getRight() > mActionsRow.getWidth());
236
237            viewHolder = mActionsRow.findViewHolderForPosition(0);
238            boolean showLeft = (viewHolder == null || viewHolder.itemView.getLeft() < 0);
239
240            if (DEBUG) Log.v(TAG, "checkFirstAndLast fromScroll " + fromScroll +
241                    " showRight " + showRight + " showLeft " + showLeft);
242
243            showMoreRight(showRight);
244            showMoreLeft(showLeft);
245        }
246
247        private void showMoreLeft(boolean show) {
248            if (show != mShowMoreLeft) {
249                mActionsRow.setFadingLeftEdge(show);
250                mShowMoreLeft = show;
251            }
252        }
253
254        private void showMoreRight(boolean show) {
255            if (show != mShowMoreRight) {
256                mActionsRow.setFadingRightEdge(show);
257                mShowMoreRight = show;
258            }
259        }
260
261        /**
262         * Constructor for the ViewHolder.
263         *
264         * @param rootView The root View that this view holder will be attached
265         *        to.
266         */
267        public ViewHolder(View rootView, Presenter detailsPresenter) {
268            super(rootView);
269            mOverviewFrame = (FrameLayout) rootView.findViewById(R.id.details_frame);
270            mOverviewView = (ViewGroup) rootView.findViewById(R.id.details_overview);
271            mImageView = (ImageView) rootView.findViewById(R.id.details_overview_image);
272            mRightPanel = (ViewGroup) rootView.findViewById(R.id.details_overview_right_panel);
273            mDetailsDescriptionFrame =
274                    (FrameLayout) mRightPanel.findViewById(R.id.details_overview_description);
275            mActionsRow =
276                    (HorizontalGridView) mRightPanel.findViewById(R.id.details_overview_actions);
277            mActionsRow.setHasOverlappingRendering(false);
278            mActionsRow.setOnScrollListener(mScrollListener);
279            mActionsRow.setAdapter(mActionBridgeAdapter);
280            mActionsRow.setOnChildSelectedListener(mChildSelectedListener);
281
282            final int fadeLength = rootView.getResources().getDimensionPixelSize(
283                    R.dimen.lb_details_overview_actions_fade_size);
284            mActionsRow.setFadingRightEdgeLength(fadeLength);
285            mActionsRow.setFadingLeftEdgeLength(fadeLength);
286            mDetailsDescriptionViewHolder =
287                    detailsPresenter.onCreateViewHolder(mDetailsDescriptionFrame);
288            mDetailsDescriptionFrame.addView(mDetailsDescriptionViewHolder.view);
289        }
290    }
291
292    private final Presenter mDetailsPresenter;
293    private OnActionClickedListener mActionClickedListener;
294
295    private int mBackgroundColor = Color.TRANSPARENT;
296    private boolean mBackgroundColorSet;
297    private boolean mIsStyleLarge = true;
298
299    private DetailsOverviewSharedElementHelper mSharedElementHelper;
300
301    /**
302     * Constructor for a DetailsOverviewRowPresenter.
303     *
304     * @param detailsPresenter The {@link Presenter} used to render the detailed
305     *        description of the row.
306     */
307    public DetailsOverviewRowPresenter(Presenter detailsPresenter) {
308        setHeaderPresenter(null);
309        setSelectEffectEnabled(false);
310        mDetailsPresenter = detailsPresenter;
311    }
312
313    /**
314     * Sets the listener for Action click events.
315     */
316    public void setOnActionClickedListener(OnActionClickedListener listener) {
317        mActionClickedListener = listener;
318    }
319
320    /**
321     * Returns the listener for Action click events.
322     */
323    public OnActionClickedListener getOnActionClickedListener() {
324        return mActionClickedListener;
325    }
326
327    /**
328     * Sets the background color.  If not set, a default from the theme will be used.
329     */
330    public void setBackgroundColor(@ColorInt int color) {
331        mBackgroundColor = color;
332        mBackgroundColorSet = true;
333    }
334
335    /**
336     * Returns the background color.  If no background color was set, transparent
337     * is returned.
338     */
339    @ColorInt
340    public int getBackgroundColor() {
341        return mBackgroundColor;
342    }
343
344    /**
345     * Sets the layout style to be large or small. This affects the height of
346     * the overview, including the text description. The default is large.
347     */
348    public void setStyleLarge(boolean large) {
349        mIsStyleLarge = large;
350    }
351
352    /**
353     * Returns true if the layout style is large.
354     */
355    public boolean isStyleLarge() {
356        return mIsStyleLarge;
357    }
358
359    /**
360     * Sets the enter transition of target activity to be
361     * transiting into overview row created by this presenter.  The transition will
362     * be cancelled if the overview image is not loaded in the timeout period.
363     * <p>
364     * It assumes shared element passed from calling activity is an ImageView;
365     * the shared element transits to overview image on the starting edge of the detail
366     * overview row, while bounds of overview row grows and reveals text
367     * and action buttons.
368     * <p>
369     * The method must be invoked in target Activity's onCreate().
370     */
371    public final void setSharedElementEnterTransition(Activity activity,
372            String sharedElementName, long timeoutMs) {
373        if (mSharedElementHelper == null) {
374            mSharedElementHelper = new DetailsOverviewSharedElementHelper();
375        }
376        mSharedElementHelper.setSharedElementEnterTransition(activity, sharedElementName,
377                timeoutMs);
378    }
379
380    /**
381     * Sets the enter transition of target activity to be
382     * transiting into overview row created by this presenter.  The transition will
383     * be cancelled if overview image is not loaded in a default timeout period.
384     * <p>
385     * It assumes shared element passed from calling activity is an ImageView;
386     * the shared element transits to overview image on the starting edge of the detail
387     * overview row, while bounds of overview row grows and reveals text
388     * and action buttons.
389     * <p>
390     * The method must be invoked in target Activity's onCreate().
391     */
392    public final void setSharedElementEnterTransition(Activity activity,
393            String sharedElementName) {
394        setSharedElementEnterTransition(activity, sharedElementName, DEFAULT_TIMEOUT);
395    }
396
397    private int getDefaultBackgroundColor(Context context) {
398        TypedValue outValue = new TypedValue();
399        if (context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true)) {
400            return context.getResources().getColor(outValue.resourceId);
401        }
402        return context.getResources().getColor(R.color.lb_default_brand_color);
403    }
404
405    @Override
406    protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) {
407        super.onRowViewSelected(vh, selected);
408        if (selected) {
409            ((ViewHolder) vh).dispatchItemSelection(null);
410        }
411    }
412
413    @Override
414    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
415        View v = LayoutInflater.from(parent.getContext())
416            .inflate(R.layout.lb_details_overview, parent, false);
417        ViewHolder vh = new ViewHolder(v, mDetailsPresenter);
418
419        initDetailsOverview(vh);
420
421        return vh;
422    }
423
424    private int getCardHeight(Context context) {
425        int resId = mIsStyleLarge ? R.dimen.lb_details_overview_height_large :
426            R.dimen.lb_details_overview_height_small;
427        return context.getResources().getDimensionPixelSize(resId);
428    }
429
430    private void initDetailsOverview(final ViewHolder vh) {
431        vh.mActionBridgeAdapter = new ActionsItemBridgeAdapter(vh);
432        final View overview = vh.mOverviewFrame;
433        ViewGroup.LayoutParams lp = overview.getLayoutParams();
434        lp.height = getCardHeight(overview.getContext());
435        overview.setLayoutParams(lp);
436
437        if (!getSelectEffectEnabled()) {
438            vh.mOverviewFrame.setForeground(null);
439        }
440        vh.mActionsRow.setOnUnhandledKeyListener(new BaseGridView.OnUnhandledKeyListener() {
441            @Override
442            public boolean onUnhandledKey(KeyEvent event) {
443                if (vh.getOnKeyListener() != null) {
444                    if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) {
445                        return true;
446                    }
447                }
448                return false;
449            }
450        });
451    }
452
453    private static int getNonNegativeWidth(Drawable drawable) {
454        final int width = (drawable == null) ? 0 : drawable.getIntrinsicWidth();
455        return (width > 0 ? width : 0);
456    }
457
458    private static int getNonNegativeHeight(Drawable drawable) {
459        final int height = (drawable == null) ? 0 : drawable.getIntrinsicHeight();
460        return (height > 0 ? height : 0);
461    }
462
463    private void bindImageDrawable(ViewHolder vh) {
464        DetailsOverviewRow row = (DetailsOverviewRow) vh.getRow();
465
466        ViewGroup.MarginLayoutParams layoutParams =
467                (ViewGroup.MarginLayoutParams) vh.mImageView.getLayoutParams();
468        final int cardHeight = getCardHeight(vh.mImageView.getContext());
469        final int verticalMargin = vh.mImageView.getResources().getDimensionPixelSize(
470                R.dimen.lb_details_overview_image_margin_vertical);
471        final int horizontalMargin = vh.mImageView.getResources().getDimensionPixelSize(
472                R.dimen.lb_details_overview_image_margin_horizontal);
473        final int drawableWidth = getNonNegativeWidth(row.getImageDrawable());
474        final int drawableHeight = getNonNegativeHeight(row.getImageDrawable());
475
476        boolean scaleImage = row.isImageScaleUpAllowed();
477        boolean useMargin = false;
478
479        if (row.getImageDrawable() != null) {
480            boolean landscape = false;
481
482            // If large style and landscape image we always use margin.
483            if (drawableWidth > drawableHeight) {
484                landscape = true;
485                if (mIsStyleLarge) {
486                    useMargin = true;
487                }
488            }
489            // If long dimension bigger than the card height we scale down.
490            if ((landscape && drawableWidth > cardHeight) ||
491                    (!landscape && drawableHeight > cardHeight)) {
492                scaleImage = true;
493            }
494            // If we're not scaling to fit the card height then we always use margin.
495            if (!scaleImage) {
496                useMargin = true;
497            }
498            // If using margin than may need to scale down.
499            if (useMargin && !scaleImage) {
500                if (landscape && drawableWidth > cardHeight - horizontalMargin) {
501                    scaleImage = true;
502                } else if (!landscape && drawableHeight > cardHeight - 2 * verticalMargin) {
503                    scaleImage = true;
504                }
505            }
506        }
507
508        final int bgColor = mBackgroundColorSet ? mBackgroundColor :
509            getDefaultBackgroundColor(vh.mOverviewView.getContext());
510
511        if (useMargin) {
512            layoutParams.setMarginStart(horizontalMargin);
513            layoutParams.topMargin = layoutParams.bottomMargin = verticalMargin;
514            vh.mOverviewFrame.setBackgroundColor(bgColor);
515            vh.mRightPanel.setBackground(null);
516            vh.mImageView.setBackground(null);
517        } else {
518            layoutParams.leftMargin = layoutParams.topMargin = layoutParams.bottomMargin = 0;
519            vh.mRightPanel.setBackgroundColor(bgColor);
520            vh.mImageView.setBackgroundColor(bgColor);
521            vh.mOverviewFrame.setBackground(null);
522        }
523        RoundedRectHelper.getInstance().setClipToRoundedOutline(vh.mOverviewFrame, true);
524
525        if (scaleImage) {
526            vh.mImageView.setScaleType(ImageView.ScaleType.FIT_START);
527            vh.mImageView.setAdjustViewBounds(true);
528            vh.mImageView.setMaxWidth(cardHeight);
529            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
530            layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
531        } else {
532            vh.mImageView.setScaleType(ImageView.ScaleType.CENTER);
533            vh.mImageView.setAdjustViewBounds(false);
534            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
535            // Limit width to the card height
536            layoutParams.width = Math.min(cardHeight, drawableWidth);
537        }
538        vh.mImageView.setLayoutParams(layoutParams);
539        vh.mImageView.setImageDrawable(row.getImageDrawable());
540        if (row.getImageDrawable() != null && mSharedElementHelper != null) {
541            mSharedElementHelper.onBindToDrawable(vh);
542        }
543    }
544
545    @Override
546    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
547        super.onBindRowViewHolder(holder, item);
548
549        DetailsOverviewRow row = (DetailsOverviewRow) item;
550        ViewHolder vh = (ViewHolder) holder;
551
552        bindImageDrawable(vh);
553        mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem());
554        vh.bindActions(row.getActionsAdapter());
555        row.addListener(vh.mListener);
556    }
557
558    @Override
559    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
560        ViewHolder vh = (ViewHolder) holder;
561        DetailsOverviewRow dor = (DetailsOverviewRow) vh.getRow();
562        dor.removeListener(vh.mListener);
563        if (vh.mDetailsDescriptionViewHolder != null) {
564            mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder);
565        }
566        super.onUnbindRowViewHolder(holder);
567    }
568
569    @Override
570    public final boolean isUsingDefaultSelectEffect() {
571        return false;
572    }
573
574    @Override
575    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
576        super.onSelectLevelChanged(holder);
577        if (getSelectEffectEnabled()) {
578            ViewHolder vh = (ViewHolder) holder;
579            int dimmedColor = vh.mColorDimmer.getPaint().getColor();
580            ((ColorDrawable) vh.mOverviewFrame.getForeground().mutate()).setColor(dimmedColor);
581        }
582    }
583
584    @Override
585    protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
586        super.onRowViewAttachedToWindow(vh);
587        if (mDetailsPresenter != null) {
588            mDetailsPresenter.onViewAttachedToWindow(
589                    ((ViewHolder) vh).mDetailsDescriptionViewHolder);
590        }
591    }
592
593    @Override
594    protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
595        super.onRowViewDetachedFromWindow(vh);
596        if (mDetailsPresenter != null) {
597            mDetailsPresenter.onViewDetachedFromWindow(
598                    ((ViewHolder) vh).mDetailsDescriptionViewHolder);
599        }
600    }
601}
602