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