Android 動態實現圓角背景和圖示換色小技巧
前言
不知道你們有沒有遇到這樣一種場景:
設計師:“首頁這個按鈕圓角度數為5個畫素”
你:“OK”,言語間你已經在drawable目錄下建立了一個xml檔案,定義了圓角的shape,然後給Imageview設定上:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#f1de11"/> <corners android:radius="5px"/> </shape>
過了5分鐘……
設計師:頂部的Tab選中時的背景也給它紅色圓角8畫素吧
你:“可以”。反正舉手之勞,再建個xml就好了”
過了一會兒ui複審…
設計師:“新訊息提醒改成小圓形吧”
你內心:Orz…再這麼建下去…

開個玩笑,不過確實很多時候我們的專案中會存在很多圓角背景的ui,而且一般都還每個地方的圓角度數都略有差別,這種時候是不是內心有一種特別想動態更改xml的屬性的衝動(不然每個都對應一個檔案到時候豈不是一堆),既然xml不可以,何不試一下程式碼上動態生成呢?
動態實現圓角背景
我們都知道ImageView可以設定Drawable,如果我們動態生成一個圓角的Drawable豈不美哉,恰好Android中有這麼一個類GradientDrawable,它繼承於Drawable,提供了各種shape標籤的屬性設定介面,轉換成對應形態的Drawable物件。因此我們可以定義這麼一個方法,只需傳入圓角度數、顏色和邊緣寬度,以及是否填充,即可得到一個等同於xml效果的Drawable資源物件:
public static GradientDrawable getRoundRectDrawable(int radius, int color, boolean isFill, int strokeWidth){ //左上、右上、右下、左下的圓角半徑 float[] radius = {radius, radius, radius, radius, radius, radius, radius, radius}; GradientDrawable drawable = new GradientDrawable(); drawable.setCornerRadii(radius); drawable.setColor(isFill ? color : Color.TRANSPARENT); drawable.setStroke(isFill ? 0 : strokeWidth, color); return drawable; }
使用:
ImageView imageView = findViewById(R.id.image_view); imageView.setBackground(ShapeUtils.getRoundRectDrawable(40, Color.YELLOW, true, 10));
效果如圖:

動態生成圓角
就再也不用因為某個小屬性的差別而新建那麼多的xml檔案,減少了apk的體積,但是另一方面,畢竟是動態生成的Drawable,所以效能上會略有影響,因此要根據實際需要可採用動態方式和靜態方式相結合。(此處只是舉例最簡單的shape例子,其它屬性設定可參見API或文末Github地址)
動態圖示換色
另外一種場景,就是很多app都會有底部tab用於切換主功能,當前選中的那個tab的圖示肯定和其他未被選中的tab的圖示不一樣,有些是做了一些動畫效果並且對圖示細節進行了一定調整,另外一些是圖示無論是否被選中,都是那樣的形狀,只是單純換了個顏色,這種情況我們一般會讓ui再另外切一套著色了的圖示,然後程式碼中動態切換圖示,達到切換tab的效果。但這種方式同樣存在一個問題,兩套一摸一樣的圖示,只是顏色不一樣,這樣會不會有點佔用apk體積,是否可以通過動態給Icon塗上顏色呢?
答案是可以的,同樣是通過Drawable來操作,圖示本身可以通過getDrawable轉換為Drawable物件,然後再通過DrawCompat來進行重新著色:
/** * 將drawable顏色著色為color * @param drawable * @param color * @return 重繪後的Drawable */ public static Drawable drawColor(Drawable drawable, int color) { final Drawable wrappedDrawable = DrawableCompat.wrap(drawable); wrappedDrawable.mutate(); DrawableCompat.setTint(wrappedDrawable, color); return wrappedDrawable; }
首先 DrawableCompat.wrap
是將drawable轉換為可著色的Drawable物件,然後呼叫 mutate
是表示只對當前這個物件進行著色,假如不呼叫這句,到時候一著完色,以後再getDrawable獲取這個物件的時候,就都變成著色後的了(即拿不到之前原來的那個Drawable了),然後 setTint
就是將我們想要重繪的顏色繪製上去了,最後將新的Drawable返回,同樣設定給ImageView,就可以變成另外一個顏色的Icon了。
弄了個簡單的動畫,不斷對icon進行染色:
final ImageView imageView = findViewById(R.id.image_view); ValueAnimator animator = ValueAnimator.ofArgb(Color.parseColor("#3F51B5"),Color.parseColor("#FF4081")); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int color = (int)animation.getAnimatedValue(); imageView.setBackground(ShapeUtils.drawColor(getResources().getDrawable(R.drawable.ic_android), color)); } });
效果如圖:

圖示染色
總結
這兩種方式有時候在特別多重複但卻有略微差別的ui場景中可以派上用場,另外還可以用來統一控制某些圖示的顏色或者多個圓角的控制,都是以時間換空間的方式,結合實際情況進行運用。
這裡只是列舉了幾個shape的常用屬性,它還有其他很多屬性可以動態設定,把它們封裝成了一個工具類,程式碼已傳到 GitHub-ZJYWidget 。裡面有很多實用的自定義View原始碼及demo,會長期維護,歡迎Star~ 如有不足之處或建議還望指正,相互學習,相互進步,如果覺得不錯動動小手給個喜歡, 謝謝~