使用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″

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM