DetailsOverviewRowPresenter.java revision b34a2372153298ebdc3e148e1c1f3b3924efab08
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 ViewGroup mOverviewView;
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            mOverviewView = (ViewGroup) rootView.findViewById(R.id.details_overview);
152            mImageView = (ImageView) rootView.findViewById(R.id.details_overview_image);
153            mDetailsDescriptionFrame =
154                    (FrameLayout) rootView.findViewById(R.id.details_overview_description);
155            mActionsRow =
156                    (HorizontalGridView) rootView.findViewById(R.id.details_overview_actions);
157            mActionsRow.setOnScrollListener(mScrollListener);
158
159            final int fadeLength = rootView.getResources().getDimensionPixelSize(
160                    R.dimen.lb_details_overview_actions_fade_size);
161            mActionsRow.setFadingRightEdgeLength(fadeLength);
162            mActionsRow.setFadingLeftEdgeLength(fadeLength);
163        }
164    }
165
166    private final Presenter mDetailsPresenter;
167    private final ActionPresenterSelector mActionPresenterSelector;
168    private final ItemBridgeAdapter mActionBridgeAdapter;
169    private int mBackgroundColor = Color.TRANSPARENT;
170    private boolean mBackgroundColorSet;
171    private boolean mIsStyleLarge = true;
172
173    /**
174     * Constructor for a DetailsOverviewRowPresenter.
175     *
176     * @param detailsPresenter The {@link Presenter} used to render the detailed
177     *        description of the row.
178     */
179    public DetailsOverviewRowPresenter(Presenter detailsPresenter) {
180        setHeaderPresenter(null);
181        setSelectEffectEnabled(false);
182        mDetailsPresenter = detailsPresenter;
183        mActionPresenterSelector = new ActionPresenterSelector();
184        mActionBridgeAdapter = new ItemBridgeAdapter();
185    }
186
187    /**
188     * Sets the listener for Action click events.
189     */
190    public void setOnActionClickedListener(OnActionClickedListener listener) {
191        mActionPresenterSelector.setOnActionClickedListener(listener);
192    }
193
194    /**
195     * Gets the listener for Action click events.
196     */
197    public OnActionClickedListener getOnActionClickedListener() {
198        return mActionPresenterSelector.getOnActionClickedListener();
199    }
200
201    /**
202     * Sets the background color.  If not set, a default from the theme will be used.
203     */
204    public void setBackgroundColor(int color) {
205        mBackgroundColor = color;
206        mBackgroundColorSet = true;
207    }
208
209    /**
210     * Returns the background color.  If no background color was set, transparent
211     * is returned.
212     */
213    public int getBackgroundColor() {
214        return mBackgroundColor;
215    }
216
217    /**
218     * Sets the layout style to be large or small. This affects the height of
219     * the overview, including the text description. The default is large.
220     */
221    public void setStyleLarge(boolean large) {
222        mIsStyleLarge = large;
223    }
224
225    /**
226     * Returns true if the layout style is large.
227     */
228    public boolean isStyleLarge() {
229        return mIsStyleLarge;
230    }
231
232    /**
233     * Get overview view with background color.
234     */
235    public ViewGroup getOverviewView(ViewHolder holder) {
236        return holder.mOverviewView;
237    }
238
239    private int getDefaultBackgroundColor(Context context) {
240        TypedValue outValue = new TypedValue();
241        context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
242        return context.getResources().getColor(outValue.resourceId);
243    }
244
245    @Override
246    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
247        View v = LayoutInflater.from(parent.getContext())
248            .inflate(R.layout.lb_details_overview, parent, false);
249        ViewHolder vh = new ViewHolder(v);
250
251        vh.mDetailsDescriptionViewHolder =
252            mDetailsPresenter.onCreateViewHolder(vh.mDetailsDescriptionFrame);
253        vh.mDetailsDescriptionFrame.addView(vh.mDetailsDescriptionViewHolder.view);
254
255        initDetailsOverview(vh);
256
257        return vh;
258    }
259
260    private int getCardHeight(Context context) {
261        int resId = mIsStyleLarge ? R.dimen.lb_details_overview_height_large :
262            R.dimen.lb_details_overview_height_small;
263        return context.getResources().getDimensionPixelSize(resId);
264    }
265
266    private void initDetailsOverview(ViewHolder vh) {
267        View overview = vh.view.findViewById(R.id.details_overview);
268        ViewGroup.LayoutParams lp = overview.getLayoutParams();
269        lp.height = getCardHeight(overview.getContext());
270        overview.setLayoutParams(lp);
271
272        overview.setBackgroundColor(mBackgroundColorSet ?
273                mBackgroundColor : getDefaultBackgroundColor(overview.getContext()));
274        ShadowHelper.getInstance().setZ(overview, 0f);
275    }
276
277    @Override
278    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
279        super.onBindRowViewHolder(holder, item);
280
281        DetailsOverviewRow row = (DetailsOverviewRow) item;
282        ViewHolder vh = (ViewHolder) holder;
283
284        ViewGroup.MarginLayoutParams layoutParams =
285                (ViewGroup.MarginLayoutParams) vh.mImageView.getLayoutParams();
286        final int cardHeight = getCardHeight(vh.mImageView.getContext());
287        final int verticalMargin = vh.mImageView.getResources().getDimensionPixelSize(
288                R.dimen.lb_details_overview_image_margin_vertical);
289        final int horizontalMargin = vh.mImageView.getResources().getDimensionPixelSize(
290                R.dimen.lb_details_overview_image_margin_horizontal);
291        boolean scaleImage = row.isImageScaleUpAllowed();
292        boolean useMargin = false;
293        boolean landscape = false;
294
295        // If large style and landscape image we always use margin.
296        if (row.getImageDrawable().getIntrinsicWidth() >
297                row.getImageDrawable().getIntrinsicHeight()) {
298            landscape = true;
299            if (mIsStyleLarge) {
300                useMargin = true;
301            }
302        }
303        // If long dimension bigger than the card height we scale down.
304        if ((landscape && row.getImageDrawable().getIntrinsicWidth() > cardHeight) ||
305                    (!landscape && row.getImageDrawable().getIntrinsicHeight() > cardHeight)) {
306            scaleImage = true;
307        }
308        // If we're not scaling to fit the card height then we always use margin.
309        if (!scaleImage) {
310            useMargin = true;
311        }
312        // If using margin than may need to scale down.
313        if (useMargin && !scaleImage) {
314            if (landscape && row.getImageDrawable().getIntrinsicWidth() >
315                    cardHeight - horizontalMargin) {
316                scaleImage = true;
317            } else if (!landscape && row.getImageDrawable().getIntrinsicHeight() >
318                    cardHeight - 2 * verticalMargin) {
319                scaleImage = true;
320            }
321        }
322
323        if (useMargin) {
324            layoutParams.leftMargin = horizontalMargin;
325            layoutParams.topMargin = layoutParams.bottomMargin = verticalMargin;
326        } else {
327            layoutParams.leftMargin = layoutParams.topMargin = layoutParams.bottomMargin = 0;
328        }
329        if (scaleImage) {
330            vh.mImageView.setScaleType(ImageView.ScaleType.FIT_START);
331            vh.mImageView.setAdjustViewBounds(true);
332            vh.mImageView.setMaxWidth(cardHeight);
333            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
334            layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
335        } else {
336            vh.mImageView.setScaleType(ImageView.ScaleType.CENTER);
337            vh.mImageView.setAdjustViewBounds(false);
338            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
339            // Limit width to the card height
340            layoutParams.width = Math.min(cardHeight, row.getImageDrawable().getIntrinsicWidth());
341        }
342        vh.mImageView.setLayoutParams(layoutParams);
343        vh.mImageView.setImageDrawable(row.getImageDrawable());
344
345        mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row);
346
347        mActionBridgeAdapter.clear();
348        ArrayObjectAdapter aoa = new ArrayObjectAdapter(mActionPresenterSelector);
349        aoa.addAll(0, (Collection)row.getActions());
350
351        mActionBridgeAdapter.setAdapter(aoa);
352        vh.mActionsRow.setAdapter(mActionBridgeAdapter);
353
354        vh.bind(mActionBridgeAdapter);
355    }
356
357    @Override
358    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
359        super.onUnbindRowViewHolder(holder);
360
361        ViewHolder vh = (ViewHolder) holder;
362        if (vh.mDetailsDescriptionViewHolder != null) {
363            mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder);
364        }
365
366        vh.mActionsRow.setAdapter(null);
367    }
368}
369