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