Использование индекса цикла в анонимном классе Android OnClickListener для создания сетки ImageButtons

Я создаю Android-приложение для карточной игры. По сути, у меня это так, что на экране одновременно отображаются четыре ряда по три карты, каждая карта представлена ​​нажимаемой ImageButton. Моя проблема в том, что я хочу использовать цикл for, чтобы я мог создать OnClickListeners для этих двенадцати карт, имея при этом способ отслеживать, на какой числовой карте мы находимся, чтобы я мог правильно отслеживать, какая кнопка нажата. В приведенном ниже коде я использую счетчик, чтобы отслеживать, сколько карт было сдвинуто, и pressed_index, чтобы отслеживать три сдвинутых карты. Если счетчик не равен трем, я меняю цветовой фильтр на ImageButton, чтобы он выглядел нажатым. Однако, когда я устанавливаю анонимный класс, я не могу использовать переменную x, потому что это не разрешено в анонимном классе. Мне нужно установить pressed_index на значение текущей карты, иначе я не смогу узнать, какая из 12 карт выдвигается.

ImageButton[] hand = new ImageButton[12];
int[] pressed_index = new int[3];
int counter = 0;
for (int x = 0; x < 12; x++) {
    hand[x] = (ImageButton)findViewById(R.id.//card_1, card_2, etc.);
    hand[x].setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
                pressed_index[counter] = x;
                counter++;
                if (counter != 3) {
                    ((ImageView) v).setColorFilter(Color.argb(150, 155, 155, 155));
                }
            }
    });
}

Альтернатива, как у меня сейчас, и единственный способ сделать это иначе, это жестко закодировать все двенадцать методов и физически ввести каждое число. Однако это очень неэффективно и очень затрудняет обновление кода. Проблема правильной установки идентификатора не так уж плоха, я просто получаю идентификатор из строки, построенной из x, например...

int ID = getResources().getIdentifier(("card_"+(x+1)),"id","com.example.project");
hand[x] = (ImageButton)findViewById(ID);

... аналогично решению, найденному здесь: Android: использование findViewById () со строкой / в цикле Тем не менее, проблема использования x до сих пор остается для меня неразрешимой, даже после долгих исследований. Через такие сообщения, как Доступ к переменным из onclicklistener и Android: доступ к глобальной переменной внутри onClickListener, кажется, можно передать только final переменных, и одно распространенное исправление для создания отдельного класса вместо использования анонимного класса. Это, однако, не привело меня далеко, так как, когда я начал писать отдельный класс для OnClickListener, я столкнулся с той же проблемой, так как я не мог передать переменную для создания OnClickListener, чтобы сопоставить ее с правильным карта. Пожалуйста, дайте мне знать, если у вас есть какие-либо идеи относительно того, как я могу это исправить.


person mattpic    schedule 02.08.2013    source источник
comment
Кстати, хотя это не касается самой проблемы - здесь значение 12 должно быть вынесено за переменную/константу; в противном случае это просто магическое число   -  person    schedule 19.05.2016


Ответы (1)


Обычная Java/Android идиома была бы

ImageButton[] hand = new ImageButton[12];
int[] pressed_index = new int[3];
int counter = 0;
for (int x = 0; x < 12; x++) {
    final int x_ = x;
    hand[x] = (ImageButton)findViewById(R.id.//card_1, card_2, etc.);
    hand[x].setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
             pressed_index[counter] = x_;
             counter++;
             if (counter != 3) {
                 ((ImageView) v).setColorFilter(Color.argb(150, 155, 155, 155));
             }
        }
    });
}

то есть вы вводите конечную переменную помощника, назначаете ей итератор, а затем ссылаетесь на финальный помощник в анонимном внутреннем классе; вы можете сказать, что таким образом "захватываете" переменную. Хотя это очень похоже на то, как вы закрываете переменные в лямбда-синтаксисе, обвиняйте дизайн Java в таком явном привкусе кода вздох.

В качестве альтернативы обратите внимание, что, хотя ссылки на переменные, используемые в анонимном классе, должны быть окончательными, это не означает, что вы не можете изменить объект, на который указывает ссылка; например

final Foo foo = new Foo();
bar.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            foo.doBaz();
        }
});

является полностью допустимым кодом - вы можете свободно изменять состояние foo в своем методе doBaz(). Черт, вы даже можете (серьезный запах кода - не пытайтесь повторить это дома!)

ImageButton[] hand = new ImageButton[12];
int[] pressed_index = new int[3];
int counter = 0;
for (int[] x = new int[]{0}; x[0] < 12; x[0]++) {
    hand[x[0]] = (ImageButton)findViewById(R.id.//card_1, card_2, etc.);
    hand[x[0]].setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
                pressed_index[counter] = x[0];
                counter++;
                if (counter != 3) {
                    ((ImageView) v).setColorFilter(Color.argb(150, 155, 155, 155));
                }
        }
    });
}
person Community    schedule 18.01.2014
comment
также обратите внимание, что в Java 8 есть термин «эффективно окончательный», и вы также можете использовать лямбда-выражения/замыкания без необходимости явных финалов, поэтому дизайн теперь наконец-то улучшается. - person ; 24.05.2014