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; 18 19 import android.app.Activity; 20 import android.animation.Animator; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.graphics.Paint; 25 import android.graphics.Paint.FontMetricsInt; 26 import android.support.v17.leanback.widget.Presenter; 27 import android.text.TextUtils; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.ViewTreeObserver; 32 import android.widget.LinearLayout; 33 import android.widget.TextView; 34 35 import com.android.tv.R; 36 import com.android.tv.ui.ViewUtils; 37 import com.android.tv.util.Utils; 38 39 /** 40 * An {@link Presenter} for rendering a detailed description of an DVR item. 41 * Typically this Presenter will be used in a {@link DetailsOverviewRowPresenter}. 42 * Most codes of this class is originated from 43 * {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}. 44 * The latter class are re-used to provide a customized version of 45 * {@link android.support.v17.leanback.widget.DetailsOverviewRow}. 46 */ 47 public class DetailsContentPresenter extends Presenter { 48 /** 49 * The ViewHolder for the {@link DetailsContentPresenter}. 50 */ 51 public static class ViewHolder extends Presenter.ViewHolder { 52 final TextView mTitle; 53 final TextView mSubtitle; 54 final LinearLayout mDescriptionContainer; 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 final int maxLines = mFullTextMode ? bodyLines : 86 (mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines); 87 if (bodyLines > maxLines) { 88 mReadMoreView.setVisibility(View.VISIBLE); 89 mDescriptionContainer.setFocusable(true); 90 mDescriptionContainer.setOnClickListener(new View.OnClickListener() { 91 @Override 92 public void onClick(View view) { 93 mFullTextMode = true; 94 mReadMoreView.setVisibility(View.GONE); 95 mDescriptionContainer.setFocusable(false); 96 mDescriptionContainer.setOnClickListener(null); 97 mBody.setMaxLines(bodyLines); 98 // Minus 1 from line difference to eliminate the space 99 // originally occupied by "READ MORE" 100 showFullText((bodyLines - maxLines - 1) * mBodyLineSpacing); 101 } 102 }); 103 } 104 if (mBody.getMaxLines() != maxLines) { 105 mBody.setMaxLines(maxLines); 106 return false; 107 } else { 108 removePreDrawListener(); 109 return true; 110 } 111 } 112 }; 113 114 public ViewHolder(final View view) { 115 super(view); 116 mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title); 117 mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle); 118 mBody = (TextView) view.findViewById(R.id.dvr_details_description_body); 119 mDescriptionContainer = 120 (LinearLayout) view.findViewById(R.id.dvr_details_description_container); 121 mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more); 122 123 FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle); 124 final int titleAscent = view.getResources().getDimensionPixelSize( 125 R.dimen.lb_details_description_title_baseline); 126 // Ascent is negative 127 mTitleMargin = titleAscent + titleFontMetricsInt.ascent; 128 129 mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize( 130 R.dimen.lb_details_description_under_title_baseline_margin); 131 mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize( 132 R.dimen.lb_details_description_under_subtitle_baseline_margin); 133 134 mTitleLineSpacing = view.getResources().getDimensionPixelSize( 135 R.dimen.lb_details_description_title_line_spacing); 136 mBodyLineSpacing = view.getResources().getDimensionPixelSize( 137 R.dimen.lb_details_description_body_line_spacing); 138 139 mBodyMaxLines = view.getResources().getInteger( 140 R.integer.lb_details_description_body_max_lines); 141 mBodyMinLines = view.getResources().getInteger( 142 R.integer.lb_details_description_body_min_lines); 143 mTitleMaxLines = mTitle.getMaxLines(); 144 145 mTitleFontMetricsInt = getFontMetricsInt(mTitle); 146 mSubtitleFontMetricsInt = getFontMetricsInt(mSubtitle); 147 mBodyFontMetricsInt = getFontMetricsInt(mBody); 148 } 149 150 void addPreDrawListener() { 151 if (!mIsListeningToPreDraw) { 152 mIsListeningToPreDraw = true; 153 view.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); 154 } 155 } 156 157 void removePreDrawListener() { 158 if (mIsListeningToPreDraw) { 159 view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener); 160 mIsListeningToPreDraw = false; 161 } 162 } 163 164 public TextView getTitle() { 165 return mTitle; 166 } 167 168 public TextView getSubtitle() { 169 return mSubtitle; 170 } 171 172 public TextView getBody() { 173 return mBody; 174 } 175 176 private FontMetricsInt getFontMetricsInt(TextView textView) { 177 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 178 paint.setTextSize(textView.getTextSize()); 179 paint.setTypeface(textView.getTypeface()); 180 return paint.getFontMetricsInt(); 181 } 182 183 private void showFullText(int heightDiff) { 184 final ViewGroup detailsFrame = (ViewGroup) mActivity.findViewById(R.id.details_frame); 185 int nowHeight = ViewUtils.getLayoutHeight(detailsFrame); 186 Animator expandAnimator = ViewUtils.createHeightAnimator( 187 detailsFrame, nowHeight, nowHeight + heightDiff); 188 expandAnimator.setDuration(mFullTextAnimationDuration); 189 Animator shiftAnimator = ObjectAnimator.ofPropertyValuesHolder(detailsFrame, 190 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 191 0f, -(heightDiff / 2))); 192 shiftAnimator.setDuration(mFullTextAnimationDuration); 193 AnimatorSet fullTextAnimator = new AnimatorSet(); 194 fullTextAnimator.playTogether(expandAnimator, shiftAnimator); 195 fullTextAnimator.start(); 196 } 197 } 198 199 private final Activity mActivity; 200 private final int mFullTextAnimationDuration; 201 202 public DetailsContentPresenter(Activity activity) { 203 super(); 204 mActivity = activity; 205 mFullTextAnimationDuration = mActivity.getResources() 206 .getInteger(R.integer.dvr_details_full_text_animation_duration); 207 } 208 209 @Override 210 public final ViewHolder onCreateViewHolder(ViewGroup parent) { 211 View v = LayoutInflater.from(parent.getContext()) 212 .inflate(R.layout.dvr_details_description, parent, false); 213 return new ViewHolder(v); 214 } 215 216 @Override 217 public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { 218 final ViewHolder vh = (ViewHolder) viewHolder; 219 final DetailsContent detailsContent = (DetailsContent) item; 220 221 vh.mActivity = mActivity; 222 vh.mFullTextAnimationDuration = mFullTextAnimationDuration; 223 224 boolean hasTitle = true; 225 if (TextUtils.isEmpty(detailsContent.getTitle())) { 226 vh.mTitle.setVisibility(View.GONE); 227 hasTitle = false; 228 } else { 229 vh.mTitle.setText(detailsContent.getTitle()); 230 vh.mTitle.setVisibility(View.VISIBLE); 231 vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight() 232 + vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier()); 233 vh.mTitle.setMaxLines(vh.mTitleMaxLines); 234 } 235 setTopMargin(vh.mTitle, vh.mTitleMargin); 236 237 boolean hasSubtitle = true; 238 if (detailsContent.getStartTimeUtcMillis() != DetailsContent.INVALID_TIME 239 && detailsContent.getEndTimeUtcMillis() != DetailsContent.INVALID_TIME) { 240 vh.mSubtitle.setText(Utils.getDurationString(viewHolder.view.getContext(), 241 detailsContent.getStartTimeUtcMillis(), 242 detailsContent.getEndTimeUtcMillis(), false)); 243 vh.mSubtitle.setVisibility(View.VISIBLE); 244 if (hasTitle) { 245 setTopMargin(vh.mSubtitle, vh.mUnderTitleBaselineMargin 246 + vh.mSubtitleFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent); 247 } else { 248 setTopMargin(vh.mSubtitle, 0); 249 } 250 } else { 251 vh.mSubtitle.setVisibility(View.GONE); 252 hasSubtitle = false; 253 } 254 255 if (TextUtils.isEmpty(detailsContent.getDescription())) { 256 vh.mBody.setVisibility(View.GONE); 257 } else { 258 vh.mBody.setText(detailsContent.getDescription()); 259 vh.mBody.setVisibility(View.VISIBLE); 260 vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight() 261 + vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier()); 262 if (hasSubtitle) { 263 setTopMargin(vh.mDescriptionContainer, vh.mUnderSubtitleBaselineMargin 264 + vh.mBodyFontMetricsInt.ascent - vh.mSubtitleFontMetricsInt.descent 265 - vh.mBody.getPaddingTop()); 266 } else if (hasTitle) { 267 setTopMargin(vh.mDescriptionContainer, vh.mUnderTitleBaselineMargin 268 + vh.mBodyFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent 269 - vh.mBody.getPaddingTop()); 270 } else { 271 setTopMargin(vh.mDescriptionContainer, 0); 272 } 273 } 274 } 275 276 @Override 277 public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { } 278 279 @Override 280 public void onViewAttachedToWindow(Presenter.ViewHolder holder) { 281 // In case predraw listener was removed in detach, make sure 282 // we have the proper layout. 283 ViewHolder vh = (ViewHolder) holder; 284 vh.addPreDrawListener(); 285 super.onViewAttachedToWindow(holder); 286 } 287 288 @Override 289 public void onViewDetachedFromWindow(Presenter.ViewHolder holder) { 290 ViewHolder vh = (ViewHolder) holder; 291 vh.removePreDrawListener(); 292 super.onViewDetachedFromWindow(holder); 293 } 294 295 private void setTopMargin(View view, int topMargin) { 296 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 297 lp.topMargin = topMargin; 298 view.setLayoutParams(lp); 299 } 300 }