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