【问题标题】:StateListDrawable to switch colorfiltersStateListDrawable 切换颜色过滤器
【发布时间】:2011-08-26 11:47:58
【问题描述】:

我想创建自定义按钮以在 TabHost 中使用。我一直在尝试仅使用相同的图像资源(png),但根据状态更改滤色器。所以我做了这个位作为自定义按钮的布局:

<?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)。

问题是滤色器似乎被完全忽略了。在任何情况下我都看不到颜色的变化。

我还尝试通过在&lt;ImageView/&gt; 上设置android:tint 属性来获得相同的结果,但显然您不能在此处使用对&lt;selector&gt; 的引用,因为它会引发NumberFormatException

我看不出我做错了什么,因此我们将不胜感激。

【问题讨论】:

    标签: java android android-layout android-tabhost colorfilter


    【解决方案1】:

    好的,我从来没有让上面的代码工作,所以这就是我最终要做的。

    首先,我继承了 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 兼容。

    【讨论】:

    • 假设我希望制作一个可以在 xml 中包含新的“StateDrawable”的 imageView,我该怎么做?
    • 很好的答案,但我可以提出改进建议。有StateSet.stateSetMatches 方法,它检查您的可绘制状态是否与所需状态匹配(而不是手动迭代数组)
    【解决方案2】:

    也无法通过将滤色器直接应用于可绘制对象来解决此问题。对我有用的是将图像作为位图,创建一个具有相同度量的空第二个,为第二个定义画布,将该颜色过滤器应用于绘画对象并在第二个上绘制第一个位图。最后从新的 Bitmap 创建一个 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);
    

    【讨论】:

      【解决方案3】:

      这是我的课,被黑以支持 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 并未直接应用于可绘制对象,而是在向选择器可绘制对象添加状态时与之关联。

      这里最重要的是

      • 您需要从常量状态创建一个新的可绘制对象,否则您将修改常量状态,从而修改您的活动周围该可绘制对象的任何实例。
      • 您需要使用我的自定义 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;
          }
      }
      

      我已经打开了一个关于此的错误:https://code.google.com/p/android/issues/detail?id=60183

      更新:框架中的错误已得到修复,我认为是棒棒糖。 我认为修复提交是这样的:https://android.googlesource.com/platform/frameworks/base/+/729427d%5E!/

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

      我的解决方法在 Lollipop 之后应该仍然有效,只是没有使用 Google 的修复程序。

      【讨论】:

      • 这很好用,但是可能明智的做法是指定您没有覆盖 addState 而是创建了一个具有相同名称但具有三个参数的新函数; addState(state, drawable, colorfilter)
      • 谢谢,顺便说一句,我认为您对在棒棒糖上修复此错误是正确的。我将您的解决方案用于棒​​棒糖之前的设备,将 RippleDrawable 用于较新的设备。
      • @user1354603 我很高兴这对某人有所帮助,请问哪个应用程序正在使用它?我只是好奇:)
      • 这是一款即将发布的应用,Projectplace for Android。目前在 Playstore 上的应用程序已经过时了,所以请留意新应用程序。
      • 看起来是个很不错的应用 :)
      【解决方案4】:

      这是我对 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));
      

      【讨论】:

        【解决方案5】:

        这是我对@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;
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-11-20
          • 2021-05-10
          • 2014-05-12
          • 1970-01-01
          • 2021-09-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多