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  */
14 package android.support.v17.leanback.widget;
15 
16 import android.graphics.Paint;
17 import android.graphics.Paint.FontMetricsInt;
18 import android.support.v17.leanback.R;
19 import android.text.TextUtils;
20 import android.view.LayoutInflater;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.ViewTreeObserver;
24 import 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  */
34 public abstract class AbstractDetailsDescriptionPresenter extends Presenter {
35 
36     /**
37      * The ViewHolder for the {@link AbstractDetailsDescriptionPresenter}.
38      */
39     public static class ViewHolder extends Presenter.ViewHolder {
40         private final TextView mTitle;
41         private final TextView mSubtitle;
42         private final TextView mBody;
43         private final int mTitleMargin;
44         private final int mUnderTitleBaselineMargin;
45         private final int mUnderSubtitleBaselineMargin;
46         private final int mTitleLineSpacing;
47         private final int mBodyLineSpacing;
48         private final int mBodyMaxLines;
49         private final int mBodyMinLines;
50         private final FontMetricsInt mTitleFontMetricsInt;
51         private final FontMetricsInt mSubtitleFontMetricsInt;
52         private final FontMetricsInt mBodyFontMetricsInt;
53         private final int mTitleMaxLines;
54         private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
55 
ViewHolder(final View view)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 
addPreDrawListener()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 
removePreDrawListener()124         void removePreDrawListener() {
125             if (mPreDrawListener != null) {
126                 view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
127                 mPreDrawListener = null;
128             }
129         }
130 
getTitle()131         public TextView getTitle() {
132             return mTitle;
133         }
134 
getSubtitle()135         public TextView getSubtitle() {
136             return mSubtitle;
137         }
138 
getBody()139         public TextView getBody() {
140             return mBody;
141         }
142 
getFontMetricsInt(TextView textView)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
onCreateViewHolder(ViewGroup parent)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
onBindViewHolder(Presenter.ViewHolder viewHolder, Object item)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      */
onBindDescription(ViewHolder vh, Object item)215     protected abstract void onBindDescription(ViewHolder vh, Object item);
216 
217     @Override
onUnbindViewHolder(Presenter.ViewHolder viewHolder)218     public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {}
219 
220     @Override
onViewAttachedToWindow(Presenter.ViewHolder holder)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
onViewDetachedFromWindow(Presenter.ViewHolder holder)230     public void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
231         ViewHolder vh = (ViewHolder) holder;
232         vh.removePreDrawListener();
233         super.onViewDetachedFromWindow(holder);
234     }
235 
setTopMargin(TextView textView, int topMargin)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