Home | History | Annotate | Download | only in ui
      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 }