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