Оттенок / тусклый рисунок при касании

Приложение, над которым я сейчас работаю, использует множество изображений ImageView в качестве кнопок. Графика на этих кнопках использует альфа-канал, чтобы сгладить края кнопки и сделать их неправильными. В настоящее время мы должны сгенерировать 2 графика для каждой кнопки (1 для выбранного/сфокусированного/нажатого состояния, а другой для невыбранного состояния по умолчанию) и использовать StateListDrawable, определенный в XML-файле для каждой кнопки.

Хотя это работает нормально, это кажется чрезвычайно расточительным, поскольку вся выбранная графика является просто окрашенными версиями невыбранных кнопок. На их создание требуется время (хотя и небольшое) и они занимают место в окончательном APK. Кажется, должен быть простой способ сделать это автоматически.

Идеальное решение, казалось бы, состоит в том, чтобы использовать ImageViews для каждой кнопки и указать в ее атрибуте tint ColorStateList. Этот подход имеет то преимущество, что для всех кнопок (с одинаковым оттенком) требуется только один XML ColorStateList. Однако это не работает. Как упоминалось здесь, ImageView генерирует исключение NumberFormatException, когда значение, предоставленное для tint, отличается от одного цвета.

Моей следующей попыткой было использовать LayerDrawable для выбранного рисунка. Внутри списка слоев у нас было бы исходное изображение в нижней части стека, закрытое полупрозрачным прямоугольником. Это работало на сплошных частях изображения кнопки. Однако края, которые должны были быть полностью прозрачными, теперь были того же цвета, что и верхний слой.

Кто-нибудь сталкивался с этой проблемой раньше и нашел разумное решение? Я хотел бы придерживаться XML-подходов, но, вероятно, буду кодировать простой подкласс ImageView, который будет выполнять требуемое окрашивание в коде.


person zienkikk    schedule 07.11.2011    source источник


Ответы (8)


Вы можете комбинировать StateListDrawable и LayerDrawable для этого.

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>
person sealskej    schedule 17.03.2013
comment
Как мне заставить его хорошо работать для файлов с 9 патчами, чтобы вся непрозрачная область изображения была затемнена при событиях касания? В настоящее время то, что он делает с изображениями с 9 патчами, заключается в изменении только части, которая определяется как содержимое изображения, а не самого изображения целиком... - person android developer; 21.09.2015

Для тех, кто столкнулся с подобной потребностью, решить это в коде довольно просто. Вот пример:

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);
    }
}

Это не закончено, но хорошо работает как доказательство концепции.

person zienkikk    schedule 09.11.2011
comment
Это, ИМХО, лучшее решение, и оно должно быть принятым ответом, поскольку оно работает хорошо и создает повторно используемый компонент, который можно использовать в любом макете, чтобы просто заменить ImageView. Хорошее решение! - person GreyBeardedGeek; 18.07.2013
comment
Спасибо, очень круто! Я немного изменил onTouchEvent следующим образом: else if ((event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) && mIsSelected) Это гарантирует, что оттенок исчезнет, ​​когда пользователь удерживает кнопку, проводит пальцем наружу и отпускает ее. - person Jaykob; 21.11.2013
comment
init() следует вызывать только в конструкторе TintableButton(Context context, AttributeSet attrs, int defStyle) - person Mark Pazon; 30.09.2014

Ваш ответ был превосходным :)

Однако вместо создания объекта кнопки я вызываю 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;

}

Хотя дает те же результаты

person htafoya    schedule 14.12.2011
comment
Это тоже работает. Причина, по которой я решил расширить необходимые классы, заключается в том, что я могу обрабатывать все это в макете XML после того, как класс будет готов. Таким образом, код, использующий эти тонируемые виджеты, не отличается от кода, использующего стандартные компоненты. - person zienkikk; 14.12.2011

Я думаю, что это решение простое!

Шаг 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>

Шаг 2: Создайте 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>

Шаг 3: Создайте 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>

Шаг 4: Попробуйте установить изображение в макете xml следующим образом:

android:src="@drawable/dim_image_view"
person Joao Ferreira    schedule 17.12.2015

У меня тоже нестандартные пуговицы, и мне нужен оттенок. В конце концов, я просто прибегнул к списку цветов штата в качестве фона 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>
person Code Poet    schedule 14.12.2011

Вы можете использовать класс, который я создал https://github.com/THRESHE/TintableImageButton. лучшее решение, но оно работает.

person Kostia Dombrovsky    schedule 24.02.2012

Ваш ответ тоже был превосходным;)

Я немного изменяю, это даст вам такой результат:

button_normalкнопка_нажата

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;
}
person Favas Kv    schedule 26.09.2014

Я думаю, что это решение достаточно простое:

    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;
        }
    });
person android developer    schedule 21.09.2015