触摸时调整/变暗可绘制对象

16

我目前正在开发的应用程序中,使用了很多ImageView作为按钮。这些按钮上的图形使用alpha通道来淡化按钮的边缘,使它们看起来不规则。目前,我们必须为每个按钮生成两个图形(一个用于选定/聚焦/按下状态,另一个用于默认未选定状态),并为每个按钮使用在XML文件中定义的StateListDrawable。

虽然这样做没有问题,但似乎非常浪费,因为所有选定的图形只是未选定按钮的着色版本。这些需要时间来生成(尽管很少),并在最终的APK中占用空间。似乎应该有一种简单的自动方式来解决这个问题。

理想的解决方案似乎是为每个按钮使用ImageView,并在其tint属性中指定一个ColorStateList。这种方法的优点是只需要一个XML ColorStateList即可用于所有共享相同tint的按钮。但是它不起作用。如此处所述,当tint的值不是单一颜色时,ImageView会抛出NumberFormatException。

我的下一个尝试是使用LayerDrawable作为选定的可绘制对象。在层列表中,我们将原始图像放在堆栈底部,然后用半透明矩形覆盖它。这对按钮图形的实心部分起作用。但是边缘应该完全透明,现在与顶层相同颜色。

有没有人遇到过这个问题并找到了合理的解决方案?我想坚持使用XML方法,但可能会编写一个简单的ImageView子类,在代码中进行所需的着色。

8个回答

17

对于那些遇到类似需求的人,用代码解决这个问题相当简洁。以下是一个示例:

public class TintableButton extends ImageView {

    private boolean mIsSelected;

    public TintableButton(Context context) {
        super(context);
        init();
    }   

    public TintableButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }   

    public TintableButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mIsSelected = false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN && !mIsSelected) {
            setColorFilter(0x99000000);
            mIsSelected = true;
        } else if (event.getAction() == MotionEvent.ACTION_UP && mIsSelected) {
            setColorFilter(Color.TRANSPARENT);
            mIsSelected = false;
        }

        return super.onTouchEvent(event);
    }
}

这还未完整,但作为概念证明很有效。


这是我个人认为最好的解决方案,应该被接受作为答案,因为它运行良好,并创建了一个可重复使用的组件,可以在任何布局中用来简单地替换 ImageView。不错的解决方案! - GreyBeardedGeek
1
谢谢,非常棒!我稍微修改了onTouchEvent,像这样:else if ((event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) && mIsSelected)这确保了当用户按住按钮并将手指拖出并释放时,色调会消失。 - Jaykob
init() 应该只在 TintableButton(Context context, AttributeSet attrs, int defStyle) 构造函数中被调用。 - Mark Pazon

7
您可以结合使用StateListDrawableLayerDrawable
public Drawable getDimmedDrawable(Drawable drawable) {
        Resources resources = getContext().getResources();
        StateListDrawable stateListDrawable = new StateListDrawable();
        LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{
                drawable,
                new ColorDrawable(resources.getColor(R.color.translucent_black))
        });
        stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, layerDrawable);
        stateListDrawable.addState(new int[]{android.R.attr.state_focused}, layerDrawable);
        stateListDrawable.addState(new int[]{android.R.attr.state_selected}, layerDrawable);
        stateListDrawable.addState(new int[]{}, drawable);
        return stateListDrawable;
}

我假设我们的colors.xml文件看起来像这样:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="translucent_black">#80000000</color>
</resources>

如何使9-patch文件正常工作,以便在触摸事件发生时,图像的整个非透明区域都会变暗?目前它对于9-patch图像所做的只是改变被定义为图像内容的部分,而不是整个图像本身... - android developer

6

你的回答非常好 :)

但是,与其创建一个按钮对象,我只需调用OnTouchlistener来改变每个视图的状态。

public boolean onTouch(View v, MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        Drawable background =  v.getBackground();
        background.setColorFilter(0xBB000000, PorterDuff.Mode.SCREEN);
        v.setBackgroundDrawable(background);
    } else if (event.getAction() == MotionEvent.ACTION_UP) {
        Drawable background =  v.getBackground();
        background.setColorFilter(null);
        v.setBackgroundDrawable(background);
    }

    return true;

}

虽然如此,但结果相同。


2
这也可以。我选择扩展必要的类的原因是在类准备好之后,我可以在布局XML中处理所有这些内容。这样使用这些可着色小部件的代码与使用标准组件的代码没有区别。 - zienkikk

3
我认为这个解决方案很简单! 步骤1:创建res/drawable/dim_image_view.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true" android:drawable="@drawable/dim_image_view_normal"/> <!-- focused -->
    <item android:state_pressed="true" android:drawable="@drawable/dim_image_view_pressed"/> <!-- pressed -->
    <item android:drawable="@drawable/dim_image_view_normal"/> <!--default -->
</selector>

第二步:创建res/drawable/dim_image_view_normal.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item><bitmap android:src="@mipmap/your_icon"/></item>
</layer-list>

步骤三:创建res/drawable/dim_image_view_pressed.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item><bitmap android:src="@mipmap/your_icon" android:alpha="0.5"></bitmap></item>
</layer-list>

第四步:尝试在xml布局中设置图像,例如:
android:src="@drawable/dim_image_view"

1

我也有不规则的按钮,需要着色。最终,我只是采用了状态颜色列表作为我的ImageButton背景。这给人一种按钮颜色在按下时发生变化的印象,非常直观且计算量较小。我知道这并不完全是一个着色,但它确实在通常是瞬间的情况下为用户提供了必要的视觉反馈。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:state_pressed="true"
        android:drawable="@android:color/darker_gray" />
    <item android:drawable="@android:color/transparent" />
</selector>

1

1

你的回答 也非常优秀 ;)

我稍微修改了一下,它会给你这样的结果:

button_normal button_pressed

Rect rect;
Drawable background;
boolean hasTouchEventCompleted = false;

@Override
public boolean onTouch(View v, MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(),
                v.getBottom());
        hasTouchEventCompleted = false;
        background = v.getBackground();
        background.setColorFilter(0x99c7c7c7,
                android.graphics.PorterDuff.Mode.MULTIPLY);
        v.setBackgroundDrawable(background);
    } else if (event.getAction() == MotionEvent.ACTION_UP
            && !hasTouchEventCompleted) {
        background.setColorFilter(null);
        v.setBackgroundDrawable(background);

    } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
        if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop()
                + (int) event.getY())) {
            // User moved outside bounds
            background.setColorFilter(null);
            v.setBackgroundDrawable(background);
            hasTouchEventCompleted = true;
        }
    }


    //Must return false, otherwise you need to handle Click events yourself.  
    return false;
}

0

我认为这个解决方案足够简单:

    final Drawable drawable = ... ;
    final int darkenValue = 0x3C000000;
    mButton.setOnTouchListener(new OnTouchListener() {
        Rect rect;
        boolean hasColorFilter = false;

        @Override
        public boolean onTouch(final View v, final MotionEvent motionEvent) {
            switch (motionEvent.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
                    drawable.setColorFilter(darkenValue, Mode.DARKEN);
                    hasColorFilter = true;
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (rect.contains(v.getLeft() + (int) motionEvent.getX(), v.getTop()
                            + (int) motionEvent.getY())) {
                        if (!hasColorFilter)
                            drawable.setColorFilter(darkenValue, Mode.DARKEN);
                        hasColorFilter = true;
                    } else {
                        drawable.setColorFilter(null);
                        hasColorFilter = false;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    drawable.setColorFilter(null);
                    hasColorFilter = false;
                    break;
            }
            return false;
        }
    });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接