2011-05-16 51 views
10

我想創建自定義按鈕以在TabHost中使用。我一直試圖只使用相同的圖像資源(png),但根據狀態改變colorfilter。所以我做了這個位作爲佈局自定義按鈕:StateListDrawable切換顏色過濾器

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" android:layout_height="fill_parent"> 
    <ImageView android:id="@+id/tab_icon" 
     android:layout_centerInParent="true" android:layout_alignParentTop="true" 
     android:layout_centerHorizontal="true" android:layout_width="wrap_content" 
     android:layout_height="wrap_content"/> 
    <TextView android:id="@+id/tab_text" android:layout_below="@id/tab_icon" 
     android:layout_centerHorizontal="true" android:layout_width="wrap_content" 
     android:layout_height="wrap_content" /> 
</RelativeLayout> 

在我的活動,我添加的標籤是這樣的:

tabHost.addTab(tabHost.newTabSpec(TAB_NAME_NEWS).setIndicator(buildTab(R.drawable.tab_icon_news, R.string.news)) 
      .setContent(newsIntent)); 

這是「buildTab」的方法:

private final static int[] SELECTED = new int[] { android.R.attr.state_selected }; 
private final static int[] IDLE = new int[] { -android.R.attr.state_selected }; 

private View buildTab(int icon, int label) { 
    LayoutInflater inflater = LayoutInflater.from(this); 
    View view = inflater.inflate(R.layout.tab_button, null); 
    StateListDrawable drawable = new StateListDrawable(); 

    Drawable selected = getResources().getDrawable(icon); 
    selected.mutate(); 
    selected.setBounds(0, 0, selected.getIntrinsicWidth(), selected.getIntrinsicHeight()); 
    selected.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x0000FF00)); 
    drawable.addState(SELECTED, selected); 

    Drawable idle = getResources().getDrawable(icon); 
    idle.mutate(); 
    idle.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x000000FF)); 
    drawable.addState(IDLE, idle); 

    ((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(drawable); 
    ((TextView) view.findViewById(R.id.tab_text)).setText(getString(label)); 
    return view; 
} 

在所選擇的狀態下,圖像應該是完全綠色(0x0000FF00),以及在非選擇狀態時,它應該是藍色(0x000000FF)。

問題是colorfilters似乎被完全忽略。在任何情況下,我都看不到顏色發生變化。

我還試圖通過設置在<ImageView/>android:tint屬性來獲得相同的結果,但顯然你不能用一個<selector>有一個參考,因爲它拋出一個NumberFormatException

我看不出我做錯了什麼,所以任何幫助將不勝感激。

回答

15

好的,我從來沒有得到上面的代碼工作,所以這就是我最終做的。

首先,我子類LayerDrawable:

public class StateDrawable extends LayerDrawable { 

    public StateDrawable(Drawable[] layers) { 
     super(layers); 
    } 

    @Override 
    protected boolean onStateChange(int[] states) { 
     for (int state : states) { 
      if (state == android.R.attr.state_selected) { 
       super.setColorFilter(Color.argb(255, 255, 195, 0), PorterDuff.Mode.SRC_ATOP); 
      } else { 
       super.setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_ATOP); 
      } 
     } 
     return super.onStateChange(states); 
    } 

    @Override 
    public boolean isStateful() { 
     return true; 
    } 

} 

我改變了buildTab()方法如下:

private View buildTab(int icon, int label) { 
    LayoutInflater inflater = LayoutInflater.from(this); 
    View view = inflater.inflate(R.layout.tab_button, null); 
    ((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(new StateDrawable(new Drawable[] { getResources() 
      .getDrawable(icon) })); 
    ((TextView) view.findViewById(R.id.tab_text)).setText(getString(label)); 
    return view; 
} 

我還是添加的標籤是這樣的:

Intent fooIntent = new Intent().setClass(this, FooActivity.class); 
tabHost.addTab(tabHost.newTabSpec(TAB_NAME_INFO).setIndicator(buildTab(R.drawable.tab_icon_info, R.string.info)).setContent(infoIntent)); 

該作品對我來說,與android 1.6兼容。

+0

假設我想製作一個imageView,它可以在xml中有新的「StateDrawable」,我該怎麼做? – 2013-08-11 12:28:49

+6

很好的答案,但我可以建議一個改進。有'StateSet.stateSetMatches'方法,它檢查你可繪製的狀態是否匹配想要的狀態(而不是手動迭代數組) – 2013-10-12 23:52:13

10

無法直接對drawable應用colorfilter來解決此問題。對我而言,工作是將圖像作爲位圖獲取,使用相同的度量創建一個空的第二個圖像,爲第二個圖像定義一個畫布,將該顏色過濾器應用於繪畫對象並在第二個位圖上繪製第一個位圖。最後,從新的位圖創建一個BitmapDrawable,就完成了。下面是代碼

ImageButton imageButton = (ImageButton)findViewById(R.id.aga); 

    Bitmap one = BitmapFactory.decodeResource(getResources(), R.drawable.pen_circle); 
    Bitmap oneCopy = Bitmap.createBitmap(one.getWidth(), one.getHeight(), Config.ARGB_8888); 

    Canvas c = new Canvas(oneCopy); 
    Paint p = new Paint(); 
    p.setColorFilter(new LightingColorFilter(Color.CYAN, 1)); 
    c.drawBitmap(one, 0, 0, p); 

    StateListDrawable states = new StateListDrawable(); 
    states.addState(new int[] {android.R.attr.state_pressed}, new BitmapDrawable(oneCopy)); 
    states.addState(new int[] { }, imageButton.getDrawable()); 
    imageButton.setImageDrawable(states); 
+0

你的解決方案適合我! – Merlevede 2015-09-08 05:25:34

9

這是我的課,砍死支持ColorFilter:

用法:

final Drawable icon = getResources().getDrawable(iconResId); 
final Drawable filteredIcon = // this is important 
     icon.getConstantState().newDrawable(); 
final FilterableStateListDrawable selectorDrawable = 
     new FilterableStateListDrawable(); 
selectorDrawable.addState(ICON_STATE_SELECTED, filteredIcon, 
     new PorterDuffColorFilter(mIconOverlayColor, PorterDuff.Mode.SRC_ATOP)); 
selectorDrawable.addState(ICON_STATE_DEFAULT, icon); 

正如你所看到的ColorFilter不直接作用於繪製,它關聯同時向選擇器Drawable添加狀態。

這裏有什麼重要的是,

  • 您需要創建從恆邦新繪製的,否則你會修改常量狀態,因此在你的活動,可繪製的任何實例。
  • 您需要使用我的自定義addState方法,它具有與框架方法addState相同的名稱,但我添加了一個附加參數(ColorFilter)。這個方法在框架超類中不存在!

代碼(骯髒的,但對我的工作):

/** 
* This is an extension to {@link android.graphics.drawable.StateListDrawable} that workaround a bug not allowing 
* to set a {@link android.graphics.ColorFilter} to the drawable in one of the states., it add a method 
* {@link #addState(int[], android.graphics.drawable.Drawable, android.graphics.ColorFilter)} for that purpose. 
*/ 
public class FilterableStateListDrawable extends StateListDrawable { 

    private int currIdx = -1; 
    private int childrenCount = 0; 
    private SparseArray<ColorFilter> filterMap; 

    public FilterableStateListDrawable() { 
     super(); 
     filterMap = new SparseArray<ColorFilter>(); 
    } 

    @Override 
    public void addState(int[] stateSet, Drawable drawable) { 
     super.addState(stateSet, drawable); 
     childrenCount++; 
    } 

    /** 
    * Same as {@link #addState(int[], android.graphics.drawable.Drawable)}, but allow to set a colorFilter associated to this Drawable. 
    * 
    * @param stateSet - An array of resource Ids to associate with the image. 
    *     Switch to this image by calling setState(). 
    * @param drawable -The image to show. 
    * @param colorFilter - The {@link android.graphics.ColorFilter} to apply to this state 
    */ 
    public void addState(int[] stateSet, Drawable drawable, ColorFilter colorFilter) { 
     // this is a new custom method, does not exist in parent class 
     int currChild = childrenCount; 
     addState(stateSet, drawable); 
     filterMap.put(currChild, colorFilter); 
    } 

    @Override 
    public boolean selectDrawable(int idx) { 
     if (currIdx != idx) { 
      setColorFilter(getColorFilterForIdx(idx)); 
     } 
     boolean result = super.selectDrawable(idx); 
     // check if the drawable has been actually changed to the one I expect 
     if (getCurrent() != null) { 
      currIdx = result ? idx : currIdx; 
      if (!result) { 
       // it has not been changed, meaning, back to previous filter 
       setColorFilter(getColorFilterForIdx(currIdx)); 
      } 
     } else if (getCurrent() == null) { 
      currIdx = -1; 
      setColorFilter(null); 
     } 
     return result; 
    } 

    private ColorFilter getColorFilterForIdx(int idx) { 
     return filterMap != null ? filterMap.get(idx) : null; 
    } 
} 

我已經開了一個bug一下:https://code.google.com/p/android/issues/detail?id=60183

UPDATE:該錯誤已被固定在框架中,因爲棒棒糖我想。 我覺得修復承諾是:https://android.googlesource.com/platform/frameworks/base/+/729427d%5E!/

或在Github上:https://github.com/android/platform_frameworks_base/commit/729427d451bc4d4d268335b8dc1ff6404bc1c91e

我的解決方法應該仍然工作後棒棒糖,它只是不被谷歌使用的修補程序。

+0

這工作得很好,但是明智的做法是指定你沒有重寫addState,但是你創建了一個具有相同名稱但有三個參數的新函數; addState(state,drawable,colorfilter) – user1354603 2015-04-16 08:30:08

+0

你是對的,我加了一個註釋,謝謝 – 2015-04-17 08:45:04

+0

謝謝,順便說一句,我認爲你是正確的這個錯誤被固定在棒棒糖上。我正在將您的解決方案用於預棒棒糖設備,併爲新設備使用RippleDrawable。 – user1354603 2015-04-20 14:23:53

1

這是我的Mopper代碼的變體。這個想法是,當用戶觸摸它時,ImageView會獲取顏色過濾器,並且當用戶停止觸摸時,將會移除顏色過濾器。

class PressedEffectStateListDrawable extends StateListDrawable { 

    private int selectionColor; 

    public PressedEffectStateListDrawable(Drawable drawable, int selectionColor) { 
     super(); 
     this.selectionColor = selectionColor; 
     addState(new int[] { android.R.attr.state_pressed }, drawable); 
     addState(new int[] {}, drawable); 
    } 

    @Override 
    protected boolean onStateChange(int[] states) { 
     boolean isStatePressedInArray = false; 
     for (int state : states) { 
      if (state == android.R.attr.state_pressed) { 
       isStatePressedInArray = true; 
      } 
     } 
     if (isStatePressedInArray) { 
      super.setColorFilter(selectionColor, PorterDuff.Mode.MULTIPLY); 
     } else { 
      super.clearColorFilter(); 
     } 
     return super.onStateChange(states); 
    } 

    @Override 
    public boolean isStateful() { 
     return true; 
    } 
} 

用法:

Drawable drawable = new FastBitmapDrawable(bm); 
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5)); 
1

這裏是我的@Malachiasz代碼的變化,這可以讓你選擇狀態和顏色組合的任何應用到基礎繪製。

public class ColorFilteredStateDrawable extends StateListDrawable { 

    private final int[][] states; 
    private final int[] colors; 

    public ColorFilteredStateDrawable(Drawable drawable, int[][] states, int[] colors) { 
     super(); 
     drawable.mutate(); 
     this.states = states; 
     this.colors = colors; 
     for (int i = 0; i < states.length; i++) { 
      addState(states[i], drawable); 
     } 
    } 

    @Override 
    protected boolean onStateChange(int[] states) { 
     if (this.states != null) { 
      for (int i = 0; i < this.states.length; i++) { 
        if (StateSet.stateSetMatches(this.states[i], states)) { 
         super.setColorFilter(this.colors[i], PorterDuff.Mode.MULTIPLY); 
         return super.onStateChange(states); 
        } 
      } 
      super.clearColorFilter(); 
     } 
     return super.onStateChange(states); 
    } 

    @Override 
    public boolean isStateful() { 
     return true; 
    } 
}