• 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 android.support.v17.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  * android.support.v17.leanback.widget.DetailsOverviewRowPresenter}. Most codes of this class is
44  * originated from {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}.
45  * The latter class are re-used to provide a customized version of {@link
46  * android.support.v17.leanback.widget.DetailsOverviewRow}.
47  */
48 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 TextView mBody;
55         final TextView mReadMoreView;
56         final int mTitleMargin;
57         final int mUnderTitleBaselineMargin;
58         final int mUnderSubtitleBaselineMargin;
59         final int mTitleLineSpacing;
60         final int mBodyLineSpacing;
61         final int mBodyMaxLines;
62         final int mBodyMinLines;
63         final FontMetricsInt mTitleFontMetricsInt;
64         final FontMetricsInt mSubtitleFontMetricsInt;
65         final FontMetricsInt mBodyFontMetricsInt;
66         final int mTitleMaxLines;
67 
68         private Activity mActivity;
69         private boolean mFullTextMode;
70         private int mFullTextAnimationDuration;
71         private boolean mIsListeningToPreDraw;
72 
73         private ViewTreeObserver.OnPreDrawListener mPreDrawListener =
74                 new ViewTreeObserver.OnPreDrawListener() {
75                     @Override
76                     public boolean onPreDraw() {
77                         if (mSubtitle.getVisibility() == View.VISIBLE
78                                 && mSubtitle.getTop() > view.getHeight()
79                                 && mTitle.getLineCount() > 1) {
80                             mTitle.setMaxLines(mTitle.getLineCount() - 1);
81                             return false;
82                         }
83                         final int bodyLines = mBody.getLineCount();
84                         int maxLines =
85                                 mFullTextMode
86                                         ? bodyLines
87                                         : (mTitle.getLineCount() > 1
88                                                 ? mBodyMinLines
89                                                 : mBodyMaxLines);
90                         if (bodyLines > maxLines) {
91                             mReadMoreView.setVisibility(View.VISIBLE);
92                             mDescriptionContainer.setFocusable(true);
93                             mDescriptionContainer.setClickable(true);
94                             mDescriptionContainer.setOnClickListener(
95                                     new View.OnClickListener() {
96                                         @Override
97                                         public void onClick(View view) {
98                                             mFullTextMode = true;
99                                             mReadMoreView.setVisibility(View.GONE);
100                                             mDescriptionContainer.setFocusable(
101                                                     ((AccessibilityManager)
102                                                                     view.getContext()
103                                                                             .getSystemService(
104                                                                                     Context
105                                                                                             .ACCESSIBILITY_SERVICE))
106                                                             .isEnabled());
107                                             mDescriptionContainer.setClickable(false);
108                                             mDescriptionContainer.setOnClickListener(null);
109                                             int oldMaxLines = mBody.getMaxLines();
110                                             mBody.setMaxLines(bodyLines);
111                                             // Minus 1 from line difference to eliminate the space
112                                             // originally occupied by "READ MORE"
113                                             showFullText(
114                                                     (bodyLines - oldMaxLines - 1)
115                                                             * mBodyLineSpacing);
116                                         }
117                                     });
118                         }
119                         if (mReadMoreView.getVisibility() == View.VISIBLE
120                                 && mSubtitle.getVisibility() == View.VISIBLE) {
121                             // If both "READ MORE" and subtitle is shown, the capable maximum lines
122                             // will be one line less.
123                             maxLines -= 1;
124                         }
125                         if (mBody.getMaxLines() != maxLines) {
126                             mBody.setMaxLines(maxLines);
127                             return false;
128                         } else {
129                             removePreDrawListener();
130                             return true;
131                         }
132                     }
133                 };
134 
ViewHolder(final View view)135         public ViewHolder(final View view) {
136             super(view);
137             view.addOnAttachStateChangeListener(
138                     new View.OnAttachStateChangeListener() {
139                         @Override
140                         public void onViewAttachedToWindow(View v) {
141                             // In case predraw listener was removed in detach, make sure
142                             // we have the proper layout.
143                             addPreDrawListener();
144                         }
145 
146                         @Override
147                         public void onViewDetachedFromWindow(View v) {
148                             removePreDrawListener();
149                         }
150                     });
151             mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title);
152             mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle);
153             mBody = (TextView) view.findViewById(R.id.dvr_details_description_body);
154             mDescriptionContainer =
155                     (LinearLayout) view.findViewById(R.id.dvr_details_description_container);
156             // We have to explicitly set focusable to true here for accessibility, since we might
157             // set the view's focusable state when we need to show "READ MORE", which would remove
158             // the default focusable state for accessibility.
159             mDescriptionContainer.setFocusable(
160                     ((AccessibilityManager)
161                                     view.getContext()
162                                             .getSystemService(Context.ACCESSIBILITY_SERVICE))
163                             .isEnabled());
164             mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more);
165 
166             FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
167             final int titleAscent =
168                     view.getResources()
169                             .getDimensionPixelSize(R.dimen.lb_details_description_title_baseline);
170             // Ascent is negative
171             mTitleMargin = titleAscent + titleFontMetricsInt.ascent;
172 
173             mUnderTitleBaselineMargin =
174                     view.getResources()
175                             .getDimensionPixelSize(
176                                     R.dimen.lb_details_description_under_title_baseline_margin);
177             mUnderSubtitleBaselineMargin =
178                     view.getResources()
179                             .getDimensionPixelSize(
180                                     R.dimen.dvr_details_description_under_subtitle_baseline_margin);
181 
182             mTitleLineSpacing =
183                     view.getResources()
184                             .getDimensionPixelSize(
185                                     R.dimen.lb_details_description_title_line_spacing);
186             mBodyLineSpacing =
187                     view.getResources()
188                             .getDimensionPixelSize(
189                                     R.dimen.lb_details_description_body_line_spacing);
190 
191             mBodyMaxLines =
192                     view.getResources().getInteger(R.integer.lb_details_description_body_max_lines);
193             mBodyMinLines =
194                     view.getResources().getInteger(R.integer.lb_details_description_body_min_lines);
195             mTitleMaxLines = mTitle.getMaxLines();
196 
197             mTitleFontMetricsInt = getFontMetricsInt(mTitle);
198             mSubtitleFontMetricsInt = getFontMetricsInt(mSubtitle);
199             mBodyFontMetricsInt = getFontMetricsInt(mBody);
200         }
201 
addPreDrawListener()202         void addPreDrawListener() {
203             if (!mIsListeningToPreDraw) {
204                 mIsListeningToPreDraw = true;
205                 view.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
206             }
207         }
208 
removePreDrawListener()209         void removePreDrawListener() {
210             if (mIsListeningToPreDraw) {
211                 view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
212                 mIsListeningToPreDraw = false;
213             }
214         }
215 
getTitle()216         public TextView getTitle() {
217             return mTitle;
218         }
219 
getSubtitle()220         public TextView getSubtitle() {
221             return mSubtitle;
222         }
223 
getBody()224         public TextView getBody() {
225             return mBody;
226         }
227 
getFontMetricsInt(TextView textView)228         private FontMetricsInt getFontMetricsInt(TextView textView) {
229             Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
230             paint.setTextSize(textView.getTextSize());
231             paint.setTypeface(textView.getTypeface());
232             return paint.getFontMetricsInt();
233         }
234 
showFullText(int heightDiff)235         private void showFullText(int heightDiff) {
236             final ViewGroup detailsFrame = (ViewGroup) mActivity.findViewById(R.id.details_frame);
237             int nowHeight = ViewUtils.getLayoutHeight(detailsFrame);
238             Animator expandAnimator =
239                     ViewUtils.createHeightAnimator(detailsFrame, nowHeight, nowHeight + heightDiff);
240             expandAnimator.setDuration(mFullTextAnimationDuration);
241             Animator shiftAnimator =
242                     ObjectAnimator.ofPropertyValuesHolder(
243                             detailsFrame,
244                             PropertyValuesHolder.ofFloat(
245                                     View.TRANSLATION_Y, 0f, -(heightDiff / 2)));
246             shiftAnimator.setDuration(mFullTextAnimationDuration);
247             AnimatorSet fullTextAnimator = new AnimatorSet();
248             fullTextAnimator.playTogether(expandAnimator, shiftAnimator);
249             fullTextAnimator.start();
250         }
251     }
252 
253     private final Activity mActivity;
254     private final int mFullTextAnimationDuration;
255 
DetailsContentPresenter(Activity activity)256     public DetailsContentPresenter(Activity activity) {
257         super();
258         mActivity = activity;
259         mFullTextAnimationDuration =
260                 mActivity
261                         .getResources()
262                         .getInteger(R.integer.dvr_details_full_text_animation_duration);
263     }
264 
265     @Override
onCreateViewHolder(ViewGroup parent)266     public final ViewHolder onCreateViewHolder(ViewGroup parent) {
267         View v =
268                 LayoutInflater.from(parent.getContext())
269                         .inflate(R.layout.dvr_details_description, parent, false);
270         return new ViewHolder(v);
271     }
272 
273     @Override
onBindViewHolder(Presenter.ViewHolder viewHolder, Object item)274     public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
275         final ViewHolder vh = (ViewHolder) viewHolder;
276         final DetailsContent detailsContent = (DetailsContent) item;
277 
278         vh.mActivity = mActivity;
279         vh.mFullTextAnimationDuration = mFullTextAnimationDuration;
280 
281         boolean hasTitle = true;
282         if (TextUtils.isEmpty(detailsContent.getTitle())) {
283             vh.mTitle.setVisibility(View.GONE);
284             hasTitle = false;
285         } else {
286             vh.mTitle.setText(detailsContent.getTitle());
287             vh.mTitle.setVisibility(View.VISIBLE);
288             vh.mTitle.setLineSpacing(
289                     vh.mTitleLineSpacing
290                             - vh.mTitle.getLineHeight()
291                             + vh.mTitle.getLineSpacingExtra(),
292                     vh.mTitle.getLineSpacingMultiplier());
293             vh.mTitle.setMaxLines(vh.mTitleMaxLines);
294         }
295         setTopMargin(vh.mTitle, vh.mTitleMargin);
296 
297         boolean hasSubtitle = true;
298         if (detailsContent.getStartTimeUtcMillis() != DetailsContent.INVALID_TIME
299                 && detailsContent.getEndTimeUtcMillis() != DetailsContent.INVALID_TIME) {
300             vh.mSubtitle.setText(
301                     Utils.getDurationString(
302                             viewHolder.view.getContext(),
303                             detailsContent.getStartTimeUtcMillis(),
304                             detailsContent.getEndTimeUtcMillis(),
305                             false));
306             vh.mSubtitle.setVisibility(View.VISIBLE);
307             if (hasTitle) {
308                 setTopMargin(
309                         vh.mSubtitle,
310                         vh.mUnderTitleBaselineMargin
311                                 + vh.mSubtitleFontMetricsInt.ascent
312                                 - vh.mTitleFontMetricsInt.descent);
313             } else {
314                 setTopMargin(vh.mSubtitle, 0);
315             }
316         } else {
317             vh.mSubtitle.setVisibility(View.GONE);
318             hasSubtitle = false;
319         }
320 
321         if (TextUtils.isEmpty(detailsContent.getDescription())) {
322             vh.mBody.setVisibility(View.GONE);
323         } else {
324             vh.mBody.setText(detailsContent.getDescription());
325             vh.mBody.setVisibility(View.VISIBLE);
326             vh.mBody.setLineSpacing(
327                     vh.mBodyLineSpacing - vh.mBody.getLineHeight() + vh.mBody.getLineSpacingExtra(),
328                     vh.mBody.getLineSpacingMultiplier());
329             if (hasSubtitle) {
330                 setTopMargin(
331                         vh.mDescriptionContainer,
332                         vh.mUnderSubtitleBaselineMargin
333                                 + vh.mBodyFontMetricsInt.ascent
334                                 - vh.mSubtitleFontMetricsInt.descent
335                                 - vh.mBody.getPaddingTop());
336             } else if (hasTitle) {
337                 setTopMargin(
338                         vh.mDescriptionContainer,
339                         vh.mUnderTitleBaselineMargin
340                                 + vh.mBodyFontMetricsInt.ascent
341                                 - vh.mTitleFontMetricsInt.descent
342                                 - vh.mBody.getPaddingTop());
343             } else {
344                 setTopMargin(vh.mDescriptionContainer, 0);
345             }
346         }
347     }
348 
349     @Override
onUnbindViewHolder(Presenter.ViewHolder viewHolder)350     public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {}
351 
setTopMargin(View view, int topMargin)352     private void setTopMargin(View view, int topMargin) {
353         ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
354         lp.topMargin = topMargin;
355         view.setLayoutParams(lp);
356     }
357 }
358