1 /* 2 * Copyright (C) 2006 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 android.text.style; 18 19 import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.annotation.IntDef; 22 import android.annotation.IntRange; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.graphics.Canvas; 27 import android.graphics.Paint; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 31 import java.lang.annotation.Retention; 32 import java.lang.ref.WeakReference; 33 34 /** 35 * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with 36 * the bottom or with the baseline of the surrounding text. 37 * <p> 38 * For an implementation that constructs the drawable from various sources (<code>Bitmap</code>, 39 * <code>Drawable</code>, resource id or <code>Uri</code>) use {@link ImageSpan}. 40 * <p> 41 * A simple implementation of <code>DynamicDrawableSpan</code> that uses drawables from resources 42 * looks like this: 43 * <pre> 44 * class MyDynamicDrawableSpan extends DynamicDrawableSpan { 45 * 46 * private final Context mContext; 47 * private final int mResourceId; 48 * 49 * public MyDynamicDrawableSpan(Context context, @DrawableRes int resourceId) { 50 * mContext = context; 51 * mResourceId = resourceId; 52 * } 53 * 54 * {@literal @}Override 55 * public Drawable getDrawable() { 56 * Drawable drawable = mContext.getDrawable(mResourceId); 57 * drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 58 * return drawable; 59 * } 60 * }</pre> 61 * The class can be used like this: 62 * <pre> 63 * SpannableString string = new SpannableString("Text with a drawable span"); 64 * string.setSpan(new MyDynamicDrawableSpan(context, R.mipmap.ic_launcher), 12, 20, Spanned 65 * .SPAN_EXCLUSIVE_EXCLUSIVE);</pre> 66 * <img src="{@docRoot}reference/android/images/text/style/dynamicdrawablespan.png" /> 67 * <figcaption>Replacing text with a drawable.</figcaption> 68 */ 69 public abstract class DynamicDrawableSpan extends ReplacementSpan { 70 71 /** 72 * A constant indicating that the bottom of this span should be aligned 73 * with the bottom of the surrounding text, i.e., at the same level as the 74 * lowest descender in the text. 75 */ 76 public static final int ALIGN_BOTTOM = 0; 77 78 /** 79 * A constant indicating that the bottom of this span should be aligned 80 * with the baseline of the surrounding text. 81 */ 82 public static final int ALIGN_BASELINE = 1; 83 84 /** 85 * A constant indicating that this span should be vertically centered between 86 * the top and the lowest descender. 87 */ 88 public static final int ALIGN_CENTER = 2; 89 90 /** 91 * Defines acceptable alignment types. 92 * @hide 93 */ 94 @Retention(SOURCE) 95 @IntDef(prefix = { "ALIGN_" }, value = { 96 ALIGN_BOTTOM, 97 ALIGN_BASELINE, 98 ALIGN_CENTER 99 }) 100 public @interface AlignmentType {} 101 102 protected final int mVerticalAlignment; 103 104 @UnsupportedAppUsage 105 private WeakReference<Drawable> mDrawableRef; 106 107 /** 108 * Creates a {@link DynamicDrawableSpan}. The default vertical alignment is 109 * {@link #ALIGN_BOTTOM} 110 */ DynamicDrawableSpan()111 public DynamicDrawableSpan() { 112 mVerticalAlignment = ALIGN_BOTTOM; 113 } 114 115 /** 116 * Creates a {@link DynamicDrawableSpan} based on a vertical alignment.\ 117 * 118 * @param verticalAlignment one of {@link #ALIGN_BOTTOM}, {@link #ALIGN_BASELINE} or 119 * {@link #ALIGN_CENTER} 120 */ DynamicDrawableSpan(@lignmentType int verticalAlignment)121 protected DynamicDrawableSpan(@AlignmentType int verticalAlignment) { 122 mVerticalAlignment = verticalAlignment; 123 } 124 125 /** 126 * Returns the vertical alignment of this span, one of {@link #ALIGN_BOTTOM}, 127 * {@link #ALIGN_BASELINE} or {@link #ALIGN_CENTER}. 128 */ getVerticalAlignment()129 public @AlignmentType int getVerticalAlignment() { 130 return mVerticalAlignment; 131 } 132 133 /** 134 * Your subclass must implement this method to provide the bitmap 135 * to be drawn. The dimensions of the bitmap must be the same 136 * from each call to the next. 137 */ getDrawable()138 public abstract Drawable getDrawable(); 139 140 @Override getSize(@onNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm)141 public int getSize(@NonNull Paint paint, CharSequence text, 142 @IntRange(from = 0) int start, @IntRange(from = 0) int end, 143 @Nullable Paint.FontMetricsInt fm) { 144 Drawable d = getCachedDrawable(); 145 Rect rect = d.getBounds(); 146 147 if (fm != null) { 148 fm.ascent = -rect.bottom; 149 fm.descent = 0; 150 151 fm.top = fm.ascent; 152 fm.bottom = 0; 153 } 154 155 return rect.right; 156 } 157 158 @Override draw(@onNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint)159 public void draw(@NonNull Canvas canvas, CharSequence text, 160 @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, 161 int top, int y, int bottom, @NonNull Paint paint) { 162 Drawable b = getCachedDrawable(); 163 canvas.save(); 164 165 int transY = bottom - b.getBounds().bottom; 166 if (mVerticalAlignment == ALIGN_BASELINE) { 167 transY -= paint.getFontMetricsInt().descent; 168 } else if (mVerticalAlignment == ALIGN_CENTER) { 169 transY = (bottom - top) / 2 - b.getBounds().height() / 2; 170 } 171 172 canvas.translate(x, transY); 173 b.draw(canvas); 174 canvas.restore(); 175 } 176 getCachedDrawable()177 private Drawable getCachedDrawable() { 178 WeakReference<Drawable> wr = mDrawableRef; 179 Drawable d = null; 180 181 if (wr != null) { 182 d = wr.get(); 183 } 184 185 if (d == null) { 186 d = getDrawable(); 187 mDrawableRef = new WeakReference<Drawable>(d); 188 } 189 190 return d; 191 } 192 } 193 194