AbstractDetailsDescriptionPresenter.java revision 433391b15bd5ff711ada5894427e90f52186ea76
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.graphics.Paint;
17import android.graphics.Paint.FontMetricsInt;
18import android.support.v17.leanback.R;
19import android.text.TextUtils;
20import android.view.LayoutInflater;
21import android.view.View;
22import android.view.ViewGroup;
23import android.view.ViewTreeObserver;
24import android.widget.TextView;
25
26/**
27 * An abstract {@link Presenter} for rendering a detailed description of an
28 * item. Typically this Presenter will be used in a DetailsOveriewRowPresenter.
29 *
30 * <p>Subclasses will override {@link #onBindDescription} to implement the data
31 * binding for this Presenter.
32 */
33public abstract class AbstractDetailsDescriptionPresenter extends Presenter {
34
35    public static class ViewHolder extends Presenter.ViewHolder {
36        private final TextView mTitle;
37        private final TextView mSubtitle;
38        private final TextView mBody;
39        private final int mTitleMargin;
40        private final int mUnderTitleBaselineMargin;
41        private final int mUnderSubtitleBaselineMargin;
42        private final int mTitleLineSpacing;
43        private final int mBodyLineSpacing;
44        private final int mBodyMaxLines;
45        private final int mBodyMinLines;
46        private final FontMetricsInt mTitleFontMetricsInt;
47        private final FontMetricsInt mSubtitleFontMetricsInt;
48        private final FontMetricsInt mBodyFontMetricsInt;
49        private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
50
51        public ViewHolder(final View view) {
52            super(view);
53            mTitle = (TextView) view.findViewById(R.id.lb_details_description_title);
54            mSubtitle = (TextView) view.findViewById(R.id.lb_details_description_subtitle);
55            mBody = (TextView) view.findViewById(R.id.lb_details_description_body);
56
57            FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
58            final int titleAscent = view.getResources().getDimensionPixelSize(
59                    R.dimen.lb_details_description_title_baseline);
60            // Ascent is negative
61            mTitleMargin = titleAscent + titleFontMetricsInt.ascent;
62
63            mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize(
64                    R.dimen.lb_details_description_under_title_baseline_margin);
65            mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize(
66                    R.dimen.lb_details_description_under_subtitle_baseline_margin);
67
68            mTitleLineSpacing = view.getResources().getDimensionPixelSize(
69                    R.dimen.lb_details_description_title_line_spacing);
70            mBodyLineSpacing = view.getResources().getDimensionPixelSize(
71                    R.dimen.lb_details_description_body_line_spacing);
72
73            mBodyMaxLines = view.getResources().getInteger(
74                    R.integer.lb_details_description_body_max_lines);
75            mBodyMinLines = view.getResources().getInteger(
76                    R.integer.lb_details_description_body_min_lines);
77
78            mTitleFontMetricsInt = getFontMetricsInt(mTitle);
79            mSubtitleFontMetricsInt = getFontMetricsInt(mSubtitle);
80            mBodyFontMetricsInt = getFontMetricsInt(mBody);
81
82            mTitle.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
83                @Override
84                public void onLayoutChange(View v, int left, int top, int right, int bottom,
85                                           int oldLeft, int oldTop, int oldRight, int oldBottom) {
86                    addPreDrawListener();
87                }
88            });
89        }
90
91        void addPreDrawListener() {
92            if (mPreDrawListener != null) {
93                return;
94            }
95            mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
96                @Override
97                public boolean onPreDraw() {
98                    final int titleLines = mTitle.getLineCount();
99                    final int maxLines = titleLines > 1 ? mBodyMinLines : mBodyMaxLines;
100                    if (mBody.getMaxLines() != maxLines) {
101                        mBody.setMaxLines(maxLines);
102                        return false;
103                    } else {
104                        removePreDrawListener();
105                        return true;
106                    }
107                }
108            };
109            view.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
110        }
111
112        void removePreDrawListener() {
113            if (mPreDrawListener != null) {
114                view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
115                mPreDrawListener = null;
116            }
117        }
118
119        public TextView getTitle() {
120            return mTitle;
121        }
122
123        public TextView getSubtitle() {
124            return mSubtitle;
125        }
126
127        public TextView getBody() {
128            return mBody;
129        }
130
131        private FontMetricsInt getFontMetricsInt(TextView textView) {
132            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
133            paint.setTextSize(textView.getTextSize());
134            paint.setTypeface(textView.getTypeface());
135            return paint.getFontMetricsInt();
136        }
137    }
138
139    @Override
140    public final ViewHolder onCreateViewHolder(ViewGroup parent) {
141        View v = LayoutInflater.from(parent.getContext())
142            .inflate(R.layout.lb_details_description, parent, false);
143        return new ViewHolder(v);
144    }
145
146    @Override
147    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
148        ViewHolder vh = (ViewHolder) viewHolder;
149        onBindDescription(vh, item);
150
151        boolean hasTitle = true;
152        if (TextUtils.isEmpty(vh.mTitle.getText())) {
153            vh.mTitle.setVisibility(View.GONE);
154            hasTitle = false;
155        } else {
156            vh.mTitle.setVisibility(View.VISIBLE);
157            vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight() +
158                    vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier());
159        }
160        setTopMargin(vh.mTitle, vh.mTitleMargin);
161
162        boolean hasSubtitle = true;
163        if (TextUtils.isEmpty(vh.mSubtitle.getText())) {
164            vh.mSubtitle.setVisibility(View.GONE);
165            hasSubtitle = false;
166        } else {
167            vh.mSubtitle.setVisibility(View.VISIBLE);
168            if (hasTitle) {
169                setTopMargin(vh.mSubtitle, vh.mUnderTitleBaselineMargin +
170                        vh.mSubtitleFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent);
171            } else {
172                setTopMargin(vh.mSubtitle, 0);
173            }
174        }
175
176        if (TextUtils.isEmpty(vh.mBody.getText())) {
177            vh.mBody.setVisibility(View.GONE);
178        } else {
179            vh.mBody.setVisibility(View.VISIBLE);
180            vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight() +
181                    vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier());
182
183            if (hasSubtitle) {
184                setTopMargin(vh.mBody, vh.mUnderSubtitleBaselineMargin +
185                        vh.mBodyFontMetricsInt.ascent - vh.mSubtitleFontMetricsInt.descent);
186            } else if (hasTitle) {
187                setTopMargin(vh.mBody, vh.mUnderTitleBaselineMargin +
188                        vh.mBodyFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent);
189            } else {
190                setTopMargin(vh.mBody, 0);
191            }
192        }
193    }
194
195    /**
196     * Binds the data from the item referenced in the DetailsOverviewRow to the
197     * ViewHolder.
198     *
199     * @param vh The ViewHolder for this details description view.
200     * @param item The item from the DetailsOverviewRow being presented.
201     */
202    protected abstract void onBindDescription(ViewHolder vh, Object item);
203
204    @Override
205    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {}
206
207    @Override
208    public void onViewAttachedToWindow(Presenter.ViewHolder holder) {
209        // In case predraw listener was removed in detach, make sure
210        // we have the proper layout.
211        ViewHolder vh = (ViewHolder) holder;
212        vh.addPreDrawListener();
213        super.onViewAttachedToWindow(holder);
214    }
215
216    @Override
217    public void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
218        ViewHolder vh = (ViewHolder) holder;
219        vh.removePreDrawListener();
220        super.onViewDetachedFromWindow(holder);
221    }
222
223    private void setTopMargin(TextView textView, int topMargin) {
224        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
225        lp.topMargin = topMargin;
226        textView.setLayoutParams(lp);
227    }
228}
229