使用drawableStart减少ImageView数量
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″