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