• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.tv.dvr.ui.browse;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.app.Activity;
24 import android.content.Context;
25 import android.graphics.Paint;
26 import android.graphics.Paint.FontMetricsInt;
27 import androidx.leanback.widget.Presenter;
28 import android.text.TextUtils;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.ViewTreeObserver;
33 import android.view.accessibility.AccessibilityManager;
34 import android.widget.LinearLayout;
35 import android.widget.TextView;
36 import com.android.tv.R;
37 import com.android.tv.ui.ViewUtils;
38 import com.android.tv.util.Utils;
39 
40 /**
41  * An {@link Presenter} for rendering a detailed description of an DVR item. Typically this
42  * Presenter will be used in a {@link
43  * androidx.leanback.widget.DetailsOverviewRowPresenter}. Most codes of this class is
44  * originated from {@link androidx.leanback.widget.AbstractDetailsDescriptionPresenter}.
45  * The latter class are re-used to provide a customized version of {@link
46  * androidx.leanback.widget.DetailsOverviewRow}.
47  */
48 public class DetailsContentPresenter extends Presenter {
49     /** The ViewHolder for the {@link DetailsContentPresenter}. */
50     public static class ViewHolder extends Presenter.ViewHolder {
51         final TextView mTitle;
52         final TextView mSubtitle;
53         final LinearLayout mDescriptionContainer;
54         final LinearLayout mErrorMessage;
55         final TextView mBody;
56         final TextView mReadMoreView;
57         final int mTitleMargin;
58         final int mUnderTitleBaselineMargin;
59         final int mUnderSubtitleBaselineMargin;
60         final int mTitleLineSpacing;
61         final int mBodyLineSpacing;
62         final int mBodyMaxLines;
63         final int mBodyMinLines;
64         final FontMetricsInt mTitleFontMetricsInt;
65         final FontMetricsInt mSubtitleFontMetricsInt;
66         final FontMetricsInt mBodyFontMetricsInt;
67         final int mTitleMaxLines;
68 
69         private Activity mActivity;
70         private boolean mFullTextMode;
71         private int mFullTextAnimationDuration;
72         private boolean mIsListeningToPreDraw;
73 
74         private ViewTreeObserver.OnPreDrawListener mPreDrawListener =
75                 new ViewTreeObserver.OnPreDrawListener() {
76                     @Override
77                     public boolean onPreDraw() {
78                         if (mSubtitle.getVisibility() == View.VISIBLE
79                                 && mSubtitle.getTop() > view.getHeight()
80                                 && mTitle.getLineCount() > 1) {
81                             mTitle.setMaxLines(mTitle.getLineCount() - 1);
82                             return false;
83                         }
84                         final int bodyLines = mBody.getLineCount();
85                         int maxLines =
86                                 mFullTextMode
87                                         ? bodyLines
88                                         : (mTitle.getLineCount() > 1
89                                                 ? mBodyMinLines
90                                                 : mBodyMaxLines);
91                         if (bodyLines > maxLines) {
92                             mReadMoreView.setVisibility(View.VISIBLE);
93                             mDescriptionContainer.setFocusable(true);
94                             mDescriptionContainer.setClickable(true);
95                             mDescriptionContainer.setOnClickListener(
96                                     new View.OnClickListener() {
97                                         @Override
98                                         public void onClick(View view) {
99                                             mFullTextMode = true;
100                                             mReadMoreView.setVisibility(View.GONE);
101                                             mDescriptionContainer.setFocusable(
102                                                     ((AccessibilityManager)
103                                                                     view.getContext()
104                                                                             .getSystemService(
105                                                                                     Context
106                                                                                             .ACCESSIBILITY_SERVICE))
107                                                             .isEnabled());
108                                             mDescriptionContainer.setClickable(false);
109                                             mDescriptionContainer.setOnClickListener(null);
110                                             int oldMaxLines = mBody.getMaxLines();
111                                             mBody.setMaxLines(bodyLines);
112                                             // Minus 1 from line difference to eliminate the space
113                                             // originally occupied by "READ MORE"
114                                             showFullText(
115                                                     (bodyLines - oldMaxLines - 1)
116                                                             * mBodyLineSpacing);
117                                         }
118                                     });
119                         }
120                         if (mReadMoreView.getVisibility() == View.VISIBLE
121                                 && mSubtitle.getVisibility() == View.VISIBLE) {
122                             // If both "READ MORE" and subtitle is shown, the capable maximum lines
123                             // will be one line less.
124                             maxLines -= 1;
125                         }
126                         if (mBody.getMaxLines() != maxLines) {
127                             mBody.setMaxLines(maxLines);
128                             return false;
129                         } else {
130                             removePreDrawListener();
131                             return true;
132                         }
133                     }
134                 };
135 
ViewHolder(final View view)136         public ViewHolder(final View view) {
137             super(view);
138             view.addOnAttachStateChangeListener(
139                     new View.OnAttachStateChangeListener() {
140                         @Override
141                         public void onViewAttachedToWindow(View v) {
142                             // In case predraw listener was removed in detach, make sure
143                             // we have the proper layout.
144                             addPreDrawListener();
145                         }
146 
147                         @Override
148                         public void onViewDetachedFromWindow(View v) {
149                             removePreDrawListener();
150                         }
151                     });
152             mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title);
153             mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle);
154             mErrorMessage =
155                     (LinearLayout) view.findViewById(R.id.dvr_details_description_error_message);
156             mBody = (TextView) view.findViewById(R.id.dvr_details_description_body);
157             mDescriptionContainer =
158                     (LinearLayout) view.findViewById(R.id.dvr_details_description_container);
159             // We have to explicitly set focusable to true here for accessibility, since we might
160             // set the view's focusable state when we need to show "READ MORE", which would remove
161             // the default focusable state for accessibility.
162             mDescriptionContainer.setFocusable(
163                     ((AccessibilityManager)
164                                     view.getContext()
165                                             .getSystemService(Context.ACCESSIBILITY_SERVICE))
166                             .isEnabled());
167             mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more);
168 
169             FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
170             final int titleAscent =
171                     view.getResources()
172                             .getDimensionPixelSize(R.dimen.lb_details_description_title_baseline);
173             // Ascent is negative
174             mTitleMargin = titleAscent + titleFontMetricsInt.ascent;
175 
176             mUnderTitleBaselineMargin =
177                     view.getResources()
178                             .getDimensionPixelSize(
179                                     R.dimen.lb_details_description_under_title_baseline_margin);
180             mUnderSubtitleBaselineMargin =
181                     view.getResources()
182                             .getDimensionPixelSize(
183                                     R.dimen.dvr_details_description_under_subtitle_baseline_margin);
184 
185             mTitleLineSpacing =
186                     view.getResources()
187                             .getDimensionPixelSize(
188                                     R.dimen.lb_details_description_title_line_spacing);
189             mBodyLineSpacing =
190                     view.getResources()
191                             .getDimensionPixelSize(
192                                     R.dimen.lb_details_description_body_line_spacing);
193 
194             mBodyMaxLines =
195                     view.getResources().getInteger(R.integer.lb_details_description_body_max_lines);
196             mBodyMinLines =
197                     view.getResources().getInteger(R.integer.lb_details_description_body_min_lines);
198             mTitleMaxLines = mTitle.getMaxLines();
199 
200             mTitleFontMetricsInt = getFontMetricsInt(mTitle);
201             mSubtitleFontMetricsInt = getFontMetricsInt(mSubtitle);
202             mBodyFontMetricsInt = getFontMetricsInt(mBody);
203         }
204 
addPreDrawListener()205         void addPreDrawListener() {
206             if (!mIsListeningToPreDraw) {
207                 mIsListeningToPreDraw = true;
208                 view.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
209             }
210         }
211 
removePreDrawListener()212         void removePreDrawListener() {
213             if (mIsListeningToPreDraw) {
214                 view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
215                 mIsListeningToPreDraw = false;
216             }
217         }
218 
getTitle()219         public TextView getTitle() {
220             return mTitle;
221         }
222 
getSubtitle()223         public TextView getSubtitle() {
224             return mSubtitle;
225         }
226 
getBody()227         public TextView getBody() {
228             return mBody;
229         }
230 
getFontMetricsInt(TextView textView)231         private FontMetricsInt getFontMetricsInt(TextView textView) {
232             Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
233             paint.setTextSize(textView.getTextSize());
234             paint.setTypeface(textView.getTypeface());
235             return paint.getFontMetricsInt();
236         }
237 
showFullText(int heightDiff)238         private void showFullText(int heightDiff) {
239             final ViewGroup detailsFrame = (ViewGroup) mActivity.findViewById(R.id.details_frame);
240             int nowHeight = ViewUtils.getLayoutHeight(detailsFrame);
241             Animator expandAnimator =
242                     ViewUtils.createHeightAnimator(detailsFrame, nowHeight, nowHeight + heightDiff);
243             expandAnimator.setDuration(mFullTextAnimationDuration);
244             Animator shiftAnimator =
245                     ObjectAnimator.ofPropertyValuesHolder(
246                             detailsFrame,
247                             PropertyValuesHolder.ofFloat(
248                                     View.TRANSLATION_Y, 0f, -(heightDiff / 2)));
249             shiftAnimator.setDuration(mFullTextAnimationDuration);
250             AnimatorSet fullTextAnimator = new AnimatorSet();
251             fullTextAnimator.playTogether(expandAnimator, shiftAnimator);
252             fullTextAnimator.start();
253         }
254     }
255 
256     private final Activity mActivity;
257     private final int mFullTextAnimationDuration;
258 
DetailsContentPresenter(Activity activity)259     public DetailsContentPresenter(Activity activity) {
260         super();
261         mActivity = activity;
262         mFullTextAnimationDuration =
263                 mActivity
264                         .getResources()
265                         .getInteger(R.integer.dvr_details_full_text_animation_duration);
266     }
267 
268     @Override
onCreateViewHolder(ViewGroup parent)269     public final ViewHolder onCreateViewHolder(ViewGroup parent) {
270         View v =
271                 LayoutInflater.from(parent.getContext())
272                         .inflate(R.layout.dvr_details_description, parent, false);
273         return new ViewHolder(v);
274     }
275 
276     @Override
onBindViewHolder(Presenter.ViewHolder viewHolder, Object item)277     public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
278         final ViewHolder vh = (ViewHolder) viewHolder;
279         final DetailsContent detailsContent = (DetailsContent) item;
280 
281         vh.mActivity = mActivity;
282         vh.mFullTextAnimationDuration = mFullTextAnimationDuration;
283 
284         boolean hasTitle = true;
285         if (TextUtils.isEmpty(detailsContent.getTitle())) {
286             vh.mTitle.setVisibility(View.GONE);
287             hasTitle = false;
288         } else {
289             vh.mTitle.setText(detailsContent.getTitle());
290             vh.mTitle.setVisibility(View.VISIBLE);
291             vh.mTitle.setLineSpacing(
292                     vh.mTitleLineSpacing
293                             - vh.mTitle.getLineHeight()
294                             + vh.mTitle.getLineSpacingExtra(),
295                     vh.mTitle.getLineSpacingMultiplier());
296             vh.mTitle.setMaxLines(vh.mTitleMaxLines);
297         }
298         setTopMargin(vh.mTitle, vh.mTitleMargin);
299 
300         boolean hasSubtitle = true;
301         if (detailsContent.getStartTimeUtcMillis() != DetailsContent.INVALID_TIME
302                 && detailsContent.getEndTimeUtcMillis() != DetailsContent.INVALID_TIME) {
303             vh.mSubtitle.setText(
304                     Utils.getDurationString(
305                             viewHolder.view.getContext(),
306                             detailsContent.getStartTimeUtcMillis(),
307                             detailsContent.getEndTimeUtcMillis(),
308                             false));
309             vh.mSubtitle.setVisibility(View.VISIBLE);
310             if (hasTitle) {
311                 setTopMargin(
312                         vh.mSubtitle,
313                         vh.mUnderTitleBaselineMargin
314                                 + vh.mSubtitleFontMetricsInt.ascent
315                                 - vh.mTitleFontMetricsInt.descent);
316             } else {
317                 setTopMargin(vh.mSubtitle, 0);
318             }
319         } else {
320             vh.mSubtitle.setVisibility(View.GONE);
321             hasSubtitle = false;
322         }
323 
324         if (TextUtils.isEmpty(detailsContent.getDescription())) {
325             vh.mBody.setVisibility(View.GONE);
326         } else {
327             if (detailsContent.shouldShowErrorMessage()) {
328                 vh.mErrorMessage.setVisibility(View.VISIBLE);
329             }
330             vh.mBody.setText(detailsContent.getDescription());
331             vh.mBody.setVisibility(View.VISIBLE);
332             vh.mBody.setLineSpacing(
333                     vh.mBodyLineSpacing - vh.mBody.getLineHeight() + vh.mBody.getLineSpacingExtra(),
334                     vh.mBody.getLineSpacingMultiplier());
335             if (hasSubtitle) {
336                 setTopMargin(
337                         vh.mDescriptionContainer,
338                         vh.mUnderSubtitleBaselineMargin
339                                 + vh.mBodyFontMetricsInt.ascent
340                                 - vh.mSubtitleFontMetricsInt.descent
341                                 - vh.mBody.getPaddingTop());
342             } else if (hasTitle) {
343                 setTopMargin(
344                         vh.mDescriptionContainer,
345                         vh.mUnderTitleBaselineMargin
346                                 + vh.mBodyFontMetricsInt.ascent
347                                 - vh.mTitleFontMetricsInt.descent
348                                 - vh.mBody.getPaddingTop());
349             } else {
350                 setTopMargin(vh.mDescriptionContainer, 0);
351             }
352         }
353     }
354 
355     @Override
onUnbindViewHolder(Presenter.ViewHolder viewHolder)356     public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {}
357 
setTopMargin(View view, int topMargin)358     private void setTopMargin(View view, int topMargin) {
359         ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
360         lp.topMargin = topMargin;
361         view.setLayoutParams(lp);
362     }
363 }
364