1 /* 2 * Copyright (C) 2015 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.internal.widget; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.text.BoringLayout; 22 import android.text.Layout; 23 import android.text.StaticLayout; 24 import android.text.TextUtils; 25 import android.util.AttributeSet; 26 import android.view.RemotableViewMethod; 27 import android.widget.RemoteViews; 28 import android.widget.TextView; 29 30 import com.android.internal.R; 31 32 /** 33 * A TextView that can float around an image on the end. 34 * 35 * @hide 36 */ 37 @RemoteViews.RemoteView 38 public class ImageFloatingTextView extends TextView { 39 40 /** Number of lines from the top to indent */ 41 private int mIndentLines; 42 43 /** Resolved layout direction */ 44 private int mResolvedDirection = LAYOUT_DIRECTION_UNDEFINED; 45 private int mMaxLinesForHeight = -1; 46 private boolean mFirstMeasure = true; 47 private int mLayoutMaxLines = -1; 48 private boolean mBlockLayouts; 49 ImageFloatingTextView(Context context)50 public ImageFloatingTextView(Context context) { 51 this(context, null); 52 } 53 ImageFloatingTextView(Context context, @Nullable AttributeSet attrs)54 public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs) { 55 this(context, attrs, 0); 56 } 57 ImageFloatingTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)58 public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 59 this(context, attrs, defStyleAttr, 0); 60 } 61 ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)62 public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr, 63 int defStyleRes) { 64 super(context, attrs, defStyleAttr, defStyleRes); 65 } 66 67 @Override makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TextUtils.TruncateAt effectiveEllipsize, boolean useSaved)68 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 69 Layout.Alignment alignment, boolean shouldEllipsize, 70 TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) { 71 CharSequence text = getText() == null ? "" : getText(); 72 StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(), 73 getPaint(), wantWidth) 74 .setAlignment(alignment) 75 .setTextDirection(getTextDirectionHeuristic()) 76 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 77 .setIncludePad(getIncludeFontPadding()) 78 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) 79 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); 80 int maxLines; 81 if (mMaxLinesForHeight > 0) { 82 maxLines = mMaxLinesForHeight; 83 } else { 84 maxLines = getMaxLines() >= 0 ? getMaxLines() : Integer.MAX_VALUE; 85 } 86 builder.setMaxLines(maxLines); 87 mLayoutMaxLines = maxLines; 88 if (shouldEllipsize) { 89 builder.setEllipsize(effectiveEllipsize) 90 .setEllipsizedWidth(ellipsisWidth); 91 } 92 93 // we set the endmargin on the requested number of lines. 94 int endMargin = getContext().getResources().getDimensionPixelSize( 95 R.dimen.notification_content_picture_margin); 96 int[] margins = null; 97 if (mIndentLines > 0) { 98 margins = new int[mIndentLines + 1]; 99 for (int i = 0; i < mIndentLines; i++) { 100 margins[i] = endMargin; 101 } 102 } 103 if (mResolvedDirection == LAYOUT_DIRECTION_RTL) { 104 builder.setIndents(margins, null); 105 } else { 106 builder.setIndents(null, margins); 107 } 108 109 return builder.build(); 110 } 111 112 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)113 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 114 int height = MeasureSpec.getSize(heightMeasureSpec); 115 // Lets calculate how many lines the given measurement allows us. 116 int availableHeight = height - mPaddingTop - mPaddingBottom; 117 int maxLines = availableHeight / getLineHeight(); 118 maxLines = Math.max(1, maxLines); 119 if (getMaxLines() > 0) { 120 maxLines = Math.min(getMaxLines(), maxLines); 121 } 122 if (maxLines != mMaxLinesForHeight) { 123 mMaxLinesForHeight = maxLines; 124 if (getLayout() != null && mMaxLinesForHeight != mLayoutMaxLines) { 125 // Invalidate layout. 126 mBlockLayouts = true; 127 setHint(getHint()); 128 mBlockLayouts = false; 129 } 130 } 131 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 132 } 133 134 @Override requestLayout()135 public void requestLayout() { 136 if (!mBlockLayouts) { 137 super.requestLayout(); 138 } 139 } 140 141 @Override onRtlPropertiesChanged(int layoutDirection)142 public void onRtlPropertiesChanged(int layoutDirection) { 143 super.onRtlPropertiesChanged(layoutDirection); 144 145 if (layoutDirection != mResolvedDirection && isLayoutDirectionResolved()) { 146 mResolvedDirection = layoutDirection; 147 if (mIndentLines > 0) { 148 // Invalidate layout. 149 setHint(getHint()); 150 } 151 } 152 } 153 154 @RemotableViewMethod setHasImage(boolean hasImage)155 public void setHasImage(boolean hasImage) { 156 setNumIndentLines(hasImage ? 2 : 0); 157 } 158 159 /** 160 * @param lines the number of lines at the top that should be indented by indentEnd 161 * @return whether a change was made 162 */ setNumIndentLines(int lines)163 public boolean setNumIndentLines(int lines) { 164 if (mIndentLines != lines) { 165 mIndentLines = lines; 166 // Invalidate layout. 167 setHint(getHint()); 168 return true; 169 } 170 return false; 171 } 172 getLayoutHeight()173 public int getLayoutHeight() { 174 return getLayout().getHeight(); 175 } 176 } 177