Чем именно атрибут XML android: onClick отличается от setOnClickListener?

416

Из того, что я прочитал, вы можете назначить onClickобработчик кнопке двумя способами.

Использование android:onClickатрибута XML, в котором вы просто используете имя открытого метода с подписью void name(View v)или использование setOnClickListenerметода, в котором вы передаете объект, реализующий OnClickListenerинтерфейс. Последний часто требует анонимный класс, который лично мне не нравится (личный вкус) или определение внутреннего класса, который реализует OnClickListener.

Используя атрибут XML, вам просто нужно определить метод вместо класса, поэтому мне было интересно, можно ли сделать то же самое с помощью кода, а не в макете XML.

emitrax
источник
4
Я прочитал вашу проблему, и я думаю, что вы застряли в том же месте, как я. Я наткнулся на очень хорошее видео, которое очень помогло мне в решении моей проблемы. Найдите видео по следующей ссылке: youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be Я надеюсь, что это вам тоже поможет :)
user2166292
9
Для тех, кто хочет сэкономить время, просматривая видео, размещенное в комментарии выше, он просто демонстрирует, как две кнопки могут иметь одинаковый метод для своего onClickатрибута в файле макета. Это сделано благодаря параметру View v. Вы просто проверяете if (v == findViewById(R.id.button1)) и т.д ..
CodyBugstein
13
@ Я бы подумал, что лучше использовать v.getId() == R.id.button1, так как вам не нужно находить фактический контроль и делать сравнение. И вы можете использовать switchвместо многих ifs.
Сами Кухмонен
3
Этот урок вам очень поможет. нажмите здесь
c49
Использование xml android: onClick вызывает сбой.

Ответы:

604

Нет, это невозможно с помощью кода. Android просто реализует OnClickListenerдля вас, когда вы определяете android:onClick="someMethod"атрибут.

Эти два фрагмента кода одинаковы, просто реализованы двумя различными способами.

Реализация кода

Button btn = (Button) findViewById(R.id.mybutton);

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        myFancyMethod(v);
    }
});

// some more code

public void myFancyMethod(View v) {
    // does something very interesting
}

Выше приведена реализация кода OnClickListener. И это реализация XML.

Реализация XML

<?xml version="1.0" encoding="utf-8"?>
<!-- layout elements -->
<Button android:id="@+id/mybutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Click me!"
    android:onClick="myFancyMethod" />
<!-- even more layout elements -->

В фоновом режиме Android делает только код Java, вызывая ваш метод по событию click.

Обратите внимание, что с указанным выше XML Android будет искать onClickметод myFancyMethod()только в текущей активности. Это важно помнить, если вы используете фрагменты, так как, даже если вы добавляете вышеупомянутый XML с использованием фрагмента, Android не будет искать onClickметод в .javaфайле фрагмента, использованного для добавления XML.

Еще одна важная вещь, которую я заметил. Вы упомянули, что не предпочитаете анонимные методы . Ты хотел сказать, что тебе не нравятся анонимные занятия .

Октавиан А. Дамиан
источник
4
Я не гуру Java, но да, я имел в виду анонимный класс. Спасибо за ответ. Очень ясно.
emitrax
118
Обратите внимание, что если вы используете XML onclick, вы должны поместить метод onclick ( myFancyMethod()) в текущее действие. Это важно, если вы используете фрагменты, так как программный способ установки слушателей onclick, вероятно, будет иметь метод, обрабатывающий щелчки в onCreateView () фрагмента ..., где он не будет найден, если на него ссылаются из XML.
Питер Айтай
12
Да, метод должен быть публичным.
Октавиан А. Дамиан
12
Интересно то, что выполнение этого в коде позволяет скрыть доступ к методу, сделав метод закрытым, в то время как способ xml приводит к раскрытию метода.
bgse
5
Тот факт, что функция (в XML-подходе) должна быть в Activity, важен не только при рассмотрении фрагментов, но и пользовательских представлений (содержащих кнопку). Если у вас есть пользовательское представление, которое вы повторно используете в нескольких действиях, но хотите использовать один и тот же метод onClick для всех случаев, метод XML не самый удобный. Вам нужно будет включить этот onClickMethod (с тем же телом) в каждое действие, которое использует ваше пользовательское представление.
Бартек Липински
87

Когда я увидел верхний ответ, я понял, что моя проблема не в том, чтобы поместить параметр (View v) в причудливый метод:

public void myFancyMethod(View v) {}

При попытке получить доступ к нему из XML, следует использовать

android:onClick="myFancyMethod"/>

Надеюсь, что это помогает кому-то.

jp093121
источник
74

android:onClick для API уровня 4 и выше, поэтому, если вы ориентируетесь на <1.6, вы не сможете его использовать.

Джеймс
источник
33

Проверьте, если вы забыли опубликовать метод!

Руиво
источник
1
почему это обязательно сделать публичным?
eRaisedToX
3
@eRaisedToX Я думаю, это довольно ясно: если оно не публично, его нельзя вызвать из платформы Android.
m0skit0
28

Указание android:onClickатрибута приводит к внутреннему Buttonвызову экземпляра setOnClickListener. Следовательно, нет абсолютно никакой разницы.

Чтобы иметь четкое понимание, давайте посмотрим, как onClickатрибут XML обрабатывается платформой.

Когда файл макета раздувается, создаются все экземпляры, указанные в нем. В этом конкретном случае Buttonэкземпляр создается с помощью public Button (Context context, AttributeSet attrs, int defStyle)конструктора. Все атрибуты в теге XML считываются из пакета ресурсов и передаются как AttributeSetконструктору.

ButtonКласс наследуется от Viewкласса, что приводит Viewк вызову конструктора, который заботится о настройке обработчика обратного вызова click через setOnClickListener.

Атрибут onClick , определенный в attrs.xml , называется в View.java как R.styleable.View_onClick.

Вот код, View.javaкоторый выполняет большую часть работы за вас, звоня setOnClickListenerсам по себе.

 case R.styleable.View_onClick:
            if (context.isRestricted()) {
                throw new IllegalStateException("The android:onClick attribute cannot "
                        + "be used within a restricted context");
            }

            final String handlerName = a.getString(attr);
            if (handlerName != null) {
                setOnClickListener(new OnClickListener() {
                    private Method mHandler;

                    public void onClick(View v) {
                        if (mHandler == null) {
                            try {
                                mHandler = getContext().getClass().getMethod(handlerName,
                                        View.class);
                            } catch (NoSuchMethodException e) {
                                int id = getId();
                                String idText = id == NO_ID ? "" : " with id '"
                                        + getContext().getResources().getResourceEntryName(
                                            id) + "'";
                                throw new IllegalStateException("Could not find a method " +
                                        handlerName + "(View) in the activity "
                                        + getContext().getClass() + " for onClick handler"
                                        + " on view " + View.this.getClass() + idText, e);
                            }
                        }

                        try {
                            mHandler.invoke(getContext(), View.this);
                        } catch (IllegalAccessException e) {
                            throw new IllegalStateException("Could not execute non "
                                    + "public method of the activity", e);
                        } catch (InvocationTargetException e) {
                            throw new IllegalStateException("Could not execute "
                                    + "method of the activity", e);
                        }
                    }
                });
            }
            break;

Как видите, setOnClickListenerвызывается для регистрации обратного вызова, как мы делаем в нашем коде. Разница лишь в том, что он используется Java Reflectionдля вызова метода обратного вызова, определенного в нашей Деятельности.

Вот причина проблем, упомянутых в других ответах:

  • Метод обратного вызова должен быть общедоступным : поскольку Java Class getMethodон используется, ищутся только функции со спецификатором открытого доступа. В противном случае будьте готовы обработать IllegalAccessExceptionисключение.
  • При использовании Button с onClick во Fragment, обратный вызов должен быть определен в Activity : getContext().getClass().getMethod()вызов ограничивает поиск метода текущим контекстом, которым является Activity в случае Fragment. Следовательно, метод ищется в классе Activity, а не в классе Fragment.
  • Метод обратного вызова должен принимать параметр View : Так как Java Class getMethodищет метод, который принимает в View.classкачестве параметра.
Маниш Мулимани
источник
1
Для меня это была недостающая часть - Java использует Reflection, чтобы найти обработчик кликов, начинающийся с getContext (). Мне было немного загадочно, как щелчок распространяется от фрагмента к Деятельности.
Эндрю Кейссер
15

Здесь очень хорошие ответы, но я хочу добавить одну строку:

В android:onclickXML, Android использует Java-отражение за сценой, чтобы справиться с этим.

И, как объясняется здесь, отражение всегда замедляет работу. (особенно на Далвик В.М.). Регистрация onClickListenerэто лучший способ.

Крупал Шах
источник
5
Насколько это может замедлить работу приложения? :) Полмиллисекунды; даже не? По сравнению с фактическим надуванием макета, это как перо и кит
Конрад Моравский
14

Обратите внимание, что если вы хотите использовать функцию onClick XML, соответствующий метод должен иметь один параметр, тип которого должен соответствовать объекту XML.

Например, кнопка будет связана с вашим методом через строку имени: android:onClick="MyFancyMethod"но объявление метода должно показывать: ...MyFancyMethod(View v) {...

Если вы пытаетесь добавить эту функцию в пункт меню , он будет иметь такой же синтаксис в файле XML, но ваш метод будет объявлен как:...MyFancyMethod(MenuItem mi) {...

Антуан Лизе
источник
6

Другой способ настроить прослушиватели при нажатии - использовать XML. Просто добавьте атрибут android: onClick в свой тег.

Рекомендуется использовать атрибут xml «onClick» над анонимным Java-классом, когда это возможно.

Прежде всего, давайте посмотрим на разницу в коде:

Атрибут XML / атрибут onClick

Часть XML

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button1" 
    android:onClick="showToast"/>

Java часть

public void showToast(View v) {
    //Add some logic
}

Анонимный Java-класс / setOnClickListener

XML часть

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

Java часть

findViewById(R.id.button1).setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Add some logic
        }
});

Вот преимущества использования атрибута XML перед анонимным классом Java:

  • С помощью анонимного Java-класса мы всегда должны указывать идентификатор для наших элементов, но с атрибутом XML идентификатор можно опустить.
  • С помощью анонимного Java-класса мы должны активно искать элемент внутри представления (часть findViewById), но с атрибутом XML Android делает это за нас.
  • Как мы видим, анонимному Java-классу требуется как минимум 5 строк кода, но с атрибутом XML достаточно 3 строки кода.
  • С помощью анонимного Java-класса мы должны назвать наш метод «onClick», но с помощью атрибута XML мы можем добавить любое имя, которое захотим, что значительно улучшит читабельность нашего кода.
  • Атрибут xml «onClick» был добавлен Google во время выпуска API уровня 4, что означает, что это немного более современный синтаксис, а современный синтаксис почти всегда лучше.

Конечно, не всегда возможно использовать атрибут Xml, вот причины, по которым мы бы его не выбрали:

  • Если мы работаем с фрагментами. Атрибут onClick может быть добавлен только к действию, поэтому если у нас есть фрагмент, мы должны будем использовать анонимный класс.
  • Если мы хотим переместить слушателя onClick в отдельный класс (возможно, если он очень сложный и / или мы хотим использовать его повторно в разных частях нашего приложения), то мы не хотим использовать атрибут xml или.
Бестин Джон
источник
И обратите внимание, что функция, вызываемая с использованием атрибутов XML, всегда должна быть общедоступной, и если они объявлены как частные, это приведет к исключению.
Бестин Джон
5

С Java 8 вы, вероятно, могли бы использовать Method Reference для достижения того, что вы хотите.

Предположим, это ваш onClickобработчик события для кнопки.

private void onMyButtonClicked(View v) {
    if (v.getId() == R.id.myButton) {
        // Do something when myButton was clicked
    }
}

Затем вы передаете onMyButtonClickedссылку на метод экземпляра в таком setOnClickListener()вызове.

Button myButton = (Button) findViewById(R.id.myButton);
myButton.setOnClickListener(this::onMyButtonClicked);

Это позволит вам избежать явного определения анонимного класса самостоятельно. Однако я должен подчеркнуть, что Справочник по методам Java 8 на самом деле является просто синтаксическим сахаром. На самом деле он создает для вас экземпляр анонимного класса (так же как и лямбда-выражения), следовательно, с той же осторожностью, что и обработчик событий в стиле лямбда-выражения, применялся, когда вы приходите к отмене регистрации вашего обработчика событий. Эта статья объясняет это действительно приятно.

PS. Для тех, кому интересно, как я могу действительно использовать функцию языка Java 8 в Android, это любезно предоставлено библиотекой retrolambda .

onelaview
источник
5

Используя атрибут XML, вам просто нужно определить метод вместо класса, поэтому мне было интересно, можно ли сделать то же самое с помощью кода, а не в макете XML.

Да, Вы можете сделать свой fragmentили activityреализоватьView.OnClickListener

и когда вы инициализируете ваши новые объекты представления в коде, вы можете просто сделать mView.setOnClickListener(this);

и это автоматически устанавливает все объекты представления в коде для использования onClick(View v)метода, который есть у вас fragmentили чего- либо activityеще.

чтобы определить, какое представление вызвало onClickметод, вы можете использовать оператор switch для этого v.getId()метода.

Этот ответ отличается от ответа «Нет, это невозможно с помощью кода»

НКИ
источник
4
   Add Button in xml and give onclick attribute name that is the name of Method.
   <!--xml --!>
   <Button
  android:id="@+id/btn_register"
  android:layout_margin="1dp"
  android:onClick="addNumber"
  android:text="Add"
  />


    Button btnAdd = (Button) findViewById(R.id.mybutton); btnAdd.setOnClickListener(new View.OnClickListener() {
   @Override
    public void onClick(View v) {
      addNumber(v);
    }
    });

  Private void addNumber(View v){
  //Logic implement 
    switch (v.getId()) {
    case R.id.btnAdd :
        break;
     default:
        break;
    }}
Джит Пармар
источник
3

Поддерживая ответ Руиво, да, вы должны объявить метод как «общедоступный», чтобы иметь возможность использовать его в XML-клике Android - я разрабатываю приложение с таргетингом от API Level 8 (minSdk ...) до 16 (targetSdk ...).

Я объявил свой метод как закрытый, и он вызвал ошибку, просто объявив, что публичная работа великолепна.

Вакас Хасан
источник
похоже, что переменные, объявленные в классе активности хостинга, не могут быть использованы в рамках объявленного обратного вызова; Bundle Activity.mBundle сгенерирует исключение IllegalStateException / NullPointerException, если используется в myFancyMethod ().
Квазавр
2

Будьте осторожны, хотя android:onClickXML кажется удобным способом обработки кликов, setOnClickListenerреализация делает нечто большее, чем добавление onClickListener. В самом деле, это делает свойство view clickableистинным.

Хотя это не может быть проблемой в большинстве реализаций Android, согласно конструктору телефона, кнопка всегда по умолчанию имеет clickable = true, но другие конструкторы в некоторых моделях телефонов могут иметь clickable = false по умолчанию для представлений не Button.

Так что установки XML недостаточно, вам нужно все время думать, чтобы добавить android:clickable="true"не кнопку, и если у вас есть устройство, на котором по умолчанию кликабельно = true, и вы забудете хотя бы один раз поместить этот атрибут XML, вы не заметите проблема во время выполнения, но получит обратную связь на рынке, когда она будет в руках ваших клиентов!

Кроме того, мы никогда не можем быть уверены в том, как proguard будет запутывать и переименовывать атрибуты XML и метод класса, поэтому не на 100% безопасно, что у них никогда не будет ошибки в один прекрасный день.

Поэтому, если вы никогда не хотите иметь проблемы и никогда не думаете об этом, лучше использовать setOnClickListenerбиблиотеки или библиотеки типа ButterKnife с аннотацией@OnClick(R.id.button)

Ливио
источник
onClickАтрибут XML также устанавливает , clickable = trueпотому что он вызывает setOnClickListenerна Viewвнутренне
Флориан Walther
1

Предположим, вы хотите добавить событие клика как это main.xml

<Button
    android:id="@+id/btn_register"
    android:layout_margin="1dp"
    android:layout_marginLeft="3dp"
    android:layout_marginTop="10dp"
    android:layout_weight="2"
    android:onClick="register"
    android:text="Register"
    android:textColor="#000000"/>

В файле Java вы должны написать метод, подобный этому.

public void register(View view) {
}
Назрул Ислам
источник
0

Я пишу этот код в XML-файл ...

<Button
    android:id="@+id/btn_register"
    android:layout_margin="1dp"
    android:layout_marginLeft="3dp"
    android:layout_marginTop="10dp"
    android:layout_weight="2"
    android:onClick="register"
    android:text="Register"
    android:textColor="#000000"/>

И напишите этот код во фрагменте ...

public void register(View view) {
}
user2786249
источник
Это возможно во фрагменте.
user2786249
0

Лучший способ сделать это с помощью следующего кода:

 Button button = (Button)findViewById(R.id.btn_register);
 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //do your fancy method
            }
        });
Катарина Дабо
источник
Я не понимаю, как это отвечает на вопрос - спрашивающий хочет избежать создания анонимного класса и вместо этого использовать метод класса.
ajshort
0

Чтобы упростить вашу жизнь и избежать использования анонимного класса в setOnClicklistener (), реализуйте интерфейс View.OnClicklistener, как показано ниже:

Открытый класс YourClass расширяет CommonActivity реализует View.OnClickListener, ...

это позволяет избежать:

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        yourMethod(v);
    }
});

и идет прямо к:

@Override
public void onClick(View v) {
  switch (v.getId()) {
    case R.id.your_view:
      yourMethod();
      break;
  }
}
Висенте Домингос
источник