首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Android ImageView 共用 Drawable 導致透明度異常:mutate() 機制深度解析與正確姿勢
     編輯:程式設計疑難解答

場景:同一 `Drawable` 實例 `setImageDrawable()` 給兩個 `ImageView`,調用 `drawable.mutate().setAlpha(50)` 期望兩者皆變透明,**實際只有第二個生效**。本文深度剖析 `Drawable.mutate()` 的「寫時複製」機制,揭示共用 Drawable 狀態共享的本質,給出正確解決方案。

現象復現與根因

Drawable drawable = getResources().getDrawable(R.drawable.ic_icon);
drawable.mutate().setAlpha(50); // 嘗試設置透明度
imageView1.setImageDrawable(drawable);
imageView2.setImageDrawable(drawable); // 只有 imageView2 變透明!

核心原因:

  1. 資源共享:`Resources.getDrawable()` 返回**同一個** `Drawable.ConstantState` 實例化的 Drawable (為了記憶體效率)。多個 View 共用同一 Drawable 實例。
  2. `mutate()` 行為:將 Drawable 轉為**可變狀態**,使其**不再共享 ConstantState**。但關鍵在於:**每次調用 `mutate()` 都會創建一個新的可變副本**。
  3. 代碼執行流:
    1. `drawable.mutate()` → 創建副本 A,設置 Alpha=50,**但未持有引用** (副本 A 即刻丟失)。
    2. `imageView1.setImageDrawable(drawable)` → `imageView1` 持有**原始共享 Drawable** (未 mutate)。
    3. `drawable.mutate()` → 再次創建副本 B,設置 Alpha=50。
    4. `imageView2.setImageDrawable(drawable)` → `imageView2` 持有**副本 B** (已 mutate)。
  4. 結果:`imageView1` 顯示原始不透明 Drawable,`imageView2` 顯示透明副本 B。

正確解決方案:持有 mutate() 返回的副本

Drawable original = getResources().getDrawable(R.drawable.ic_icon);

// 方案一:mutate 一次,持有引用,多 View 共用該「可變副本」 (同步變化)
Drawable mutableShared = original.mutate();
mutableShared.setAlpha(50);
imageView1.setImageDrawable(mutableShared);
imageView2.setImageDrawable(mutableShared); // 兩者同步透明,後續修改 mutableShared 兩者聯動

// 方案二:各 View 獨立 mutate (獨立變化)
Drawable mutable1 = original.mutate();
mutable1.setAlpha(50);
imageView1.setImageDrawable(mutable1);

Drawable mutable2 = original.mutate();
mutable2.setAlpha(100); // 不同透明度
imageView2.setImageDrawable(mutable2);

進階:觸發事件時修改 Drawable 的陷阱

若在點擊事件中:

imageView.setOnClickListener(v -> {
    // 錯誤:每次點擊 mutate() 創建新副本,舊副本被 GC,View 仍持有舊引用 → 不生效
    drawable.mutate().setAlpha(128); 
    
    // 正確:持有 View 當前 Drawable 引用並修改
    Drawable current = imageView.getDrawable();
    if (current != null) current.setAlpha(128); // 直接修改,無需 mutate (已是可變)
    imageView.invalidate(); // 觸發重繪
});

**關鍵原則**:`mutate()` 是**創建副本**的工廠方法,**必須持有返回值**;View 已持有的 Drawable 直接修改屬性即可,無需再次 `mutate()`。

記憶體視角:ConstantState 與 享元模式

  • ConstantState:Drawable 的不可變狀態定義 (位圖像素、形狀路徑、固有尺寸等)。**多個 Drawable 實例可共享同一 ConstantState**。
  • mutate():強制為當前 Drawable 實例創建**獨立的 ConstantState 副本**,打破共享。**代價**:記憶體增加 (每個 mutate 實例擁有獨狀態)。
  • 最佳實踐:靜態資源 (Icon、背景) **盡量共享 ConstantState** (不調用 `mutate`),動態屬性 (Alpha、ColorFilter、Bounds) 需差異化時**按需 `mutate`**。

總結

**`Drawable.mutate()` 返回新副本,不修改原對象。** 共用 Drawable 導致狀態同步,需 `mutate` 打破共享;但必須**持有返回的副本引用**並傳給 View。理解「ConstantState 共享 → mutate 打破共享 → 副本持有」的生命週期鏈條,即可精準控制每個 ImageView 的顯示狀態,避免「只有最後一個生效」的低級錯誤。

  1. 上一篇文章:
  2. 下一篇文章:

Copyright © 程式碼之家 All Rights Reserved