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