1.應用場景
1.1 簡介
應用中經常有一張圖片和文字同時出現的情況,如下:
- 可以使用一個ImageView + 1個TextView 實現,
- 也可以用一個TextView+它的 drawableLeft、drawableRight、drawableTop、drawableBottom、drawableStart、drawableEnd等屬性實現。
使用一個TextView 的方案有一些好處:
- 減少控件數量、減少layout.xml 中元素數量、減少繪制次數,減少內存中的一個控件對象等等。
- 減少ImageView 因未指定 android:contentDescription 屬性產生的警告。(設置 --> 無障礙 --> TalkBack ,打開語音朗讀功能)
1.2 TextView的子類都可以使用
TextView的子類都可以使用這系列屬性。Button、Switch、CheckBox、EditText等等。
2.使用方法
用app:drawableStartCompat系列(gradle-6.5+),不用android:drawableStart,android:drawableLeft系列 。
前者的問題是當前版本暫不支持數據綁定
1 <TextView 2 android:background="@color/f8f8f8" 3 android:text="文本" 4 app:drawableBottomCompat="@drawable/setting2" 5 app:drawableEndCompat="@drawable/ic_setting" 6 app:drawableStartCompat="@mipmap/floweret64" 7 app:drawableTopCompat="@drawable/ic_setting" 8 ... 9 />
效果如下:
3.設置圖片尺寸
3.1 使用圖片尺寸
圖片的大小由圖片資源指定且固定,不會跟textview大小變化而變化,不適應按比例顯示的方案.
1 <TextView 2 //... 3 android:background="@color/f8f8f8" 4 android:text="文本" 5 app:drawableLeftCompat="@mipmap/setting256" 6 app:layout_constraintStart_toStartOf="parent" 7 />
效果如下:
圖片大小256*256像素
3.2 在代碼中修改圖片尺寸
下面的這個是按照比例顯示的
1 <TextView 2 ... 3 android:background="@color/f8f8f8" 4 android:drawablePadding="16dp" 5 android:text="按百分比設置的寬高、在代碼中修改它的大小。" 6 app:drawableTopCompat="@drawable/ic_setting" 7 app:layout_constraintHeight_percent="0.12" 8 app:layout_constraintWidth_percent="0.70" />
未在代碼中修改大小前的效果如下:
在代碼中修改它
1 fun textViewReSize(){ 2 val drawables = percentTextView.compoundDrawables 3 val viewWidth = percentTextView.measuredWidth 4 val viewHeight = percentTextView.measuredHeight 5 val drawable = drawables[1] 6 val height = (viewHeight * 0.8f).toInt() 7 val width = (viewHeight * 0.8f).toInt() 8 val top = 0 9 val left = 0 10 drawable.bounds.set(Rect(left,top,left + width,top + height)) 11 drawable.invalidateSelf() 12 }
修改后效果如下:
大小對了,但是圖片和文本重疊了。在代碼中重新指定下 drawablePadding就可以了。
1 fun textViewReSize(){ 2 val drawables = percentTextView.compoundDrawables 3 val viewWidth = percentTextView.measuredWidth 4 val viewHeight = percentTextView.measuredHeight 5 val drawable = drawables[1] 6 val height = (viewHeight * 0.8f).toInt() 7 val width = (viewHeight * 0.8f).toInt() 8 val top = 0 9 val left = 0 10 drawable.bounds.set(Rect(left,top,width,height)) 11 percentTextView.compoundDrawablePadding = width 12 drawable.invalidateSelf() 13 }
效果如下:
文本和圖片不重疊了,但是圖片位置不對,Rect(0,0,width,height),為什么會在中間靠后這個位置?
查看下TextView的onDraw函數,關鍵代碼如下
1 @Override 2 protected void onDraw(Canvas canvas) { 3 restartMarqueeIfNeeded(); 4 5 // Draw the background for this view 6 super.onDraw(canvas); 7 8 final int compoundPaddingLeft = getCompoundPaddingLeft(); 9 final int compoundPaddingTop = getCompoundPaddingTop(); 10 final int compoundPaddingRight = getCompoundPaddingRight(); 11 final int compoundPaddingBottom = getCompoundPaddingBottom(); 12 final int scrollX = mScrollX; 13 final int scrollY = mScrollY; 14 final int right = mRight; 15 final int left = mLeft; 16 final int bottom = mBottom; 17 final int top = mTop; 18 final boolean isLayoutRtl = isLayoutRtl(); 19 final int offset = getHorizontalOffsetForDrawables(); 20 final int leftOffset = isLayoutRtl ? 0 : offset; 21 final int rightOffset = isLayoutRtl ? offset : 0; 22 23 final Drawables dr = mDrawables; 24 if (dr != null) { 25 /* 26 * Compound, not extended, because the icon is not clipped 27 * if the text height is smaller. 28 */ 29 30 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 31 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 32 33 //... 34 35 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 36 // Make sure to update invalidateDrawable() when changing this code. 37 if (dr.mShowing[Drawables.TOP] != null) { 38 canvas.save(); 39 canvas.translate(scrollX + compoundPaddingLeft 40 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 41 dr.mShowing[Drawables.TOP].draw(canvas); 42 canvas.restore(); 43 } 44 45 //... 46 } 47 ...
堅向的位置沒有出錯,所以排除 scrollY + mPaddingTop 這句,
通過調試 發現 scrollX 和 compoundPaddingLeft ,它們都是0,所以決定位置的是 (hspace - dr.mDrawableWidthTop) / 2 這句代碼。
hspace = right - left - compoundPaddingRight - compoundPaddingLeft
這句代碼中compoundPaddingRight 和 compoundPaddingLeft 也是0,
dr.mDrawableWidthTop這句是用了寬度,它的寬度在代碼中的設置如下
它就是compundRect.with,最終代碼如下
1 fun textViewReSize(){ 2 val drawables = percentTextView.compoundDrawables 3 val viewWidth = percentTextView.measuredWidth 4 val viewHeight = percentTextView.measuredHeight 5 val drawable = drawables[1] 6 val height = (viewHeight * 0.8f).toInt() 7 val width = (viewHeight * 0.8f).toInt() 8 val top = /*drawable.intrinsicHeight / 2 - height / 2*/ 0 9 val left = drawable.intrinsicWidth / 2 - width / 2 10 drawable.bounds.set(Rect(left,top,left + width,top + height)) 11 drawable.invalidateSelf() 12 percentTextView.compoundDrawablePadding = width 13 }
最終效果:
4.設置圖片與文本間距
使用 drawablePadding 屬性指定文本與圖片之間的間距,上下左右只用這一個屬性,哪個方向有圖片,間距就是該值。
1 <TextView 2 3 android:background="@color/f8f8f8" 4 android:drawablePadding="8dp" 5 android:text="圖片文本間距8dp" 6 app:drawableEndCompat="@drawable/ic_setting" 7 app:drawableStartCompat="@mipmap/floweret64" 8 app:layout_constraintStart_toStartOf="parent" 9 ... />
效果如下:
5.着色
在api >= 23 以后,用drawableTint與drawableTintMode 可以給圖片着色,
准備的資源如下:
原圖a : png #7cba59 原圖b:layer-list drawable 填充色:#7F6200EE
5.1 使用方法
1 <TextView 2 app:drawableStartCompat="@mipmap/png" 3 app:drawableTint="@color/tint" 4 app:drawableTintMode="src_over" 5 />
app:drawableTint指定顏色,app:drawableTintMode 指定着色模式
使用 app:drawableTint系列而不用 android:drawableTint系列。
5.2 drawableTintMode可選的值
src_over 、src_in、src_atop、multiply、screen、add , 含義及源碼如下:
1 <attr name="drawableTintMode"> 2 <!-- The tint is drawn on top of the drawable. 3 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> 4 <enum name="src_over" value="3"/> 5 <!-- The tint is masked by the alpha channel of the drawable. The drawable’s 6 color channels are thrown out. [Sa * Da, Sc * Da] --> 7 <enum name="src_in" value="5"/> 8 <!-- The tint is drawn above the drawable, but with the drawable’s alpha 9 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> 10 <enum name="src_atop" value="9"/> 11 <!-- Multiplies the color and alpha channels of the drawable with those of 12 the tint. [Sa * Da, Sc * Dc] --> 13 <enum name="multiply" value="14"/> 14 <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> 15 <enum name="screen" value="15"/> 16 <!-- Combines the tint and drawable color and alpha channels, clamping the 17 result to valid color values. Saturate(S + D) --> 18 <enum name="add" value="16"/> 19 </attr>
5.3 src_over
用目標顏色蓋在最上層
5.4 src_in
給原圖輪廓內部非透明部分用顏色填充
5.5 src_atop
給圖片的非透明部分上層填充顏色
5.6 multiply
圖片的顏色和透明通道分別乖以 給定的顏色值。
5.7 screen
[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] ,這個注釋很簡單,沒看明白。sa,da,sc,dc分別是啥。
5.8 add
合並圖片的顏色、透明通道和給定的顏色值。
6.下載代碼
https://github.com/f9q/textDrawable
7.問題
如果項目中多個TextView使用一個png圖片,在代碼中修改它們的大小有點麻煩。
7.1 解決辦法1
可以為每個TextView定義單獨的BitmapDrawable,它的大小是固定的,圖片引用png,如:
1 <?xml version="1.0" encoding="utf-8"?> 2 <layer-list xmlns:tools="http://schemas.android.com/tools" 3 xmlns:android="http://schemas.android.com/apk/res/android"> 4 <item android:width="36dp" android:height="36dp" tools:targetApi="m" tools:ignore="UnusedAttribute"> 5 <bitmap android:src="@drawable/logo" /> 6 </item> 7 </layer-list>
這種方法的 缺點是要求 api > 22
7.1 解決辦法2
給TextView擴展一個方法,在方法里設置drawable的大小。
1 class MainActivity : AppCompatActivity() { 2 3 lateinit var percentTextView : TextView 4 5 override fun onCreate(savedInstanceState: Bundle?) { 6 super.onCreate(savedInstanceState) 7 setContentView(R.layout.activity_main) 8 percentTextView = findViewById(R.id.percentTv) 9 percentTextView.resetDrawableSize() 10 } 11 fun TextView.resetDrawableSize(){ 12 this.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener{ 13 override fun onPreDraw(): Boolean { 14 val drawables = compoundDrawables 15 val viewWidth = measuredWidth 16 val viewHeight = measuredHeight 17 val drawable = drawables[1] 18 val height = (viewHeight * 0.7f).toInt() 19 val width = (viewHeight * 0.7f).toInt() 20 val top = /*drawable.intrinsicHeight / 2 - height / 2*/ 0 21 val left = drawable.intrinsicWidth / 2 - width / 2 22 drawable.bounds.set(Rect(left,top,left + width,top + height)) 23 compoundDrawablePadding = width 24 drawable.invalidateSelf() 25 viewTreeObserver.removeOnPreDrawListener(this ) 26 return true 27 } 28 }) 29 } 30 }
7.3 設置文本行間距
文本有多行時,如果默認的行間距不滿足需求,通過下面兩個屬性都可自定義行間距:
- android:lineSpacingExtra 設置行間:,如”2dp”
- android:lineSpacingMultiplier 設置行間距倍數: 如”1.2″