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