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