findViewById () повертає значення null для користувацького компонента в макеті XML, а не для інших компонентів


91

У мене є res/layout/main.xmlці елементи та інші:

<some.package.MyCustomView android:id="@+id/foo" (some other params) />
<TextView android:id="@+id/boring" (some other params) />

У моїй діяльності onCreate я роблю це:

setContentView(R.layout.main);
TextView boring = (TextView) findViewById(R.id.boring);
// ...find other elements...
MyCustomView foo = (MyCustomView) findViewById(R.id.foo);
if (foo == null) { Log.d(TAG, "epic fail"); }

Інші елементи знайдено успішно, але fooповертається нульовим. MyCustomView має конструктор, MyCustomView(Context c, AttributeSet a)а в Log.d(...)кінці цього конструктора успішно з'являється в logcat безпосередньо перед "епічним збоєм".

Чому дорівнює fooнулю?

Відповіді:


182

Тому що в конструкторі я мав super(context)замість super(context, attrs).

Має сенс, якщо ви не передасте атрибути, такі як ідентифікатор, тоді подання не матиме ідентифікатора і, отже, не буде знайти його за допомогою цього ідентифікатора. :-)


1
Завжди приємно мати можливість відповісти на власні запитання :) Обов’язково позначте своє як прийняту відповідь.
MattC

Справді. Зробить це, коли SO дозволить мені ("Ви можете прийняти власну відповідь через 2 дні.")
Кріс Бойл,

4
Крім того, чи не слід робити рядки, як (MyCustomView) foo = findViewById(R.id.foo);бути MyCustomView foo = (MyCustomView) findViewById(R.id.foo);?
Джеремі Логан

3
Була та сама проблема, у моєму випадку я забув setContentView () .. XD
Том Бріто

Є хороший приклад, як робити такі речі на vogella.com: vogella.com/articles/AndroidCustomViews/article.html
Wolkenjaeger

27

У мене така сама проблема, тому що в моєму власному поданні я перевизначив конструктор, але викликав суперконструктор з міткою attrs paramete. Тобто копіювати вставку)

Моя попередня версія конструктора:

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

Тепер у мене є:

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

І це працює!


Те саме питання тут. До речі, для мене це теж була помилка копіювання.
KurtCobain

Те саме сталося і зі мною. Найбільш правильна відповідь на Stackoverflow. Коли додати AttributeSet attrs назад, все в порядку.
spikeyang

Думаю, ми всі шукали один і той самий підручник зі спеціальним переглядом;) ця помилка зайняла у мене 0,5 години безглуздої налагодження ...
KrwawyKefir

Це спрацювало і у мене! Я боровся з цим досить довго. Дякую!
us_david

18

У мене була та сама проблема. Моєю помилкою було те, що я писав

        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View layout=inflater.inflate(R.layout.dlg_show_info, null);
        alertDlgShowInfo.setView(layout);
        TableRow trowDesc=(TableRow)findViewById(R.id.trowDesc);

і оскільки я використовував інфлятор для "завантаження" подання з XML-файлу, останній рядок був неправильним. Щоб його вирішити, мені довелося написати:

TableRow trowDesc=(TableRow)layout.findViewById(R.id.trowDesc);

Я написав своє рішення, на випадок, якщо хтось має таку ж проблему.


Чувак, ти геніальний. Це було єдине рішення, яке мені вдалося з усіх численних, які я читав у SO
Ігор Ганапольський

Це була проблема і для мене. Ого, який шматочок ... Мені знадобились би дні, щоб я це зрозумів самостійно. Дякую!
poshaughnessy

18

Здається, існує безліч причин. Я просто використовував "Очистити ..." в Eclipse, щоб вирішити подібну проблему. (FindViewByID раніше працював і з якихось причин почав повертати значення null.)


1
очевидно, основна проблема полягає в тому, що ідентифікатори R.java якимось чином порушуються або, можливо, не оновлюються. Я помітив це не тільки з ідентифікаторами, але й в інших випадках, наприклад, неправильний рядок, що відображається в TextView і т. Д. Насправді я не знаю, чому це відбувається.
медуза

Це надто довго давало мені горе - чистота справді виправила це для мене.
Ніколас М.Т. Елліотт

1
Прибирання, справді. Ого, це відмовно!
Тім Бюте

11

Те саме питання, але інше рішення: я не дзвонив

setContentView(R.layout.main)

ДО того, як я спробував знайти погляд, як зазначено тут


Я думаю, це рішення, якщо ви отримуєте елемент в іншому поданні замість поточного пов'язаного подання.
StarCub

4

Якщо у вас кілька версій макета (залежно від щільності екрану, версій SDK), переконайтеся, що всі вони містять елемент, який ви шукаєте.


2

У моєму випадку findViewById повертав нуль, оскільки мій власний вигляд виглядав приблизно так у головному XML:

        <com.gerfmarquez.seekbar.VerticalSeekBar  
            android:id="@+id/verticalSeekBar"
            android:layout_width="wrap_content" 
            android:layout_height="fill_parent" 
            />

і я дізнався, що коли я додав речі xmlns, це працювало так:

        <com.gerfmarquez.seekbar.VerticalSeekBar  
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/verticalSeekBar"
            android:layout_width="wrap_content" 
            android:layout_height="fill_parent" 
            />

2

Переконайтеся, що setContentView(R.layout.main)заява називається перед findViewById(...)заявою;


1

Для мене проблема була вирішена, коли я додав папку res до Source у Java Build Path у налаштуваннях проекту.


1

Я зіткнувся з тим самим питанням ще деякий час, коли додав власний подання через макет XML, а потім спробував приєднати зворотний виклик деінде в програмі ...

Я створив власний вигляд і додав його до свого "layout_main.xml"

public class MUIComponent extends SurfaceView implements SurfaceHolder.Callback {
    public MUIComponent (Context context, AttributeSet attrs ) {
        super ( context, attrs );
    }
    // ..
}

І в основній діяльності я хотів приєднати кілька зворотних викликів і отримати посилання на елементи інтерфейсу з XML.

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // ...

        MUIInitializer muiInit = new MUIInitializer();
        muiInit.setupCallbacks(this);
        muiInit.intializeFields(this);
    }       
}

Ініціатор не робив нічого вигадливого, але будь-які зміни, які він намагався внести до власного подання (MUIComponent) або інших нестандартних елементів інтерфейсу, просто не відображались у програмі.

public class MUIInitializer {

    // ...

    public void setupCallbacks ( Activity mainAct ) {


        // This does NOT work properly
        // - The object instance returned is technically an instance of my "MUICompnent" view
        //   but it is a *different* instance than the instance created and shown in the UI screen
        // - Callbacks never get triggered, changes don't appear on UI, etc.
        MUIComponent badInst = (MUIComponent) mainAct.findViewById(R.id.MUIComponent_TESTSURF);


        // ...
        // This works properly

        LayoutInflater inflater = (LayoutInflater) mainAct.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View inflatedLayout = inflater.inflate ( R.layout.activity_main, null );

        MUIComponent goodInst = (MUIComponent) inflatedLayout.findViewById(R.id.MUIComponent_TESTSURF);


        // Add callbacks
        // ...
    }

}

Різниця між "badInst" та "goodInst" полягає в:

  • badInst використовує дію findViewByID
  • goodInst надуває макет і використовує завищений макет для пошуку

Помічений Вінсент має таке саме рішення ... і його відповідь коротша ... +1 замість цього :)
DevByStarlight

1

Це трапилося зі мною із спеціальним компонентом для Wear, але це загальна порада. Якщо ви використовуєте заглушку (таку, як я використовував WatchViewStub), ви не можете просто перевести дзвінок findViewById()куди завгодно. Все, що знаходиться всередині заглушки, повинно спочатку надуватися, що трапляється не просто після setContentView(). Таким чином, вам слід написати щось подібне, щоб почекати, поки це станеться:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_wear);
    final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
    stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
        @Override
        public void onLayoutInflated(WatchViewStub stub) {
            myCustomViewInst = (MyCustomView) findViewById(R.id.my_custom_view);
            ...

0

Моєю проблемою була помилка. Я писав android.id(крапка) замість android:id. : P

Очевидно, в моєму користувацькому компоненті xml немає перевірки синтаксису. :(


0

Була та сама проблема.

У мене було розташування з мало дітей. З конструктора одного з них я намагався отримати посилання (за допомогою context.findViewById) на іншу дочірню дитину. Це не спрацювало, оскільки друга дитина була визначена далі в макеті.

Я вирішив це так:

setContentView(R.layout.main);
MyView first = findViewById(R.layout.first_child);
first.setSecondView(findViewById(R.layout.second_child));

Це також спрацювало б, якби порядок дітей був протилежним, але я думаю, що це, як правило, слід робити як вище.


1
Загалом, ви не повинні використовувати findViewByIdконструктор a View, а навпаки, вводити код ініціалізації OnFinishInflate?
Sanjay Manohar

0

findViewById()Метод іноді повертається , nullколи корінь макета не має android:idатрибута. Майстер Eclipse для генерації макета xml-файлу не створюється автоматичноandroid:id атрибут для кореневого елемента.


0

У моєму випадку подання було в батьківському, а не в поданні, яке я намагався викликати. Тож у дочірньому поданні мені довелося зателефонувати:

RelativeLayout relLayout = (RelativeLayout) this.getParent();
View view1 = relLayout.findViewById(R.id.id_relative_layout_search);

0

Для мене спрацював «чистий» варіант.

У моєму випадку першопричиною є те, що вихідний код знаходиться на мережевій папці, а моя робоча станція та файловий сервер не синхронізовані належним чином і відхиляються на 5 секунд. Мітки часу на файлах, створених Eclipse, є минулими (оскільки вони присвоюються файловим сервером) із годинником робочої станції, що призводить до того, що Eclipse неправильно вирішує залежності між згенерованими та вихідними файлами. У цьому випадку, здається, працює "чисте", оскільки воно змушує повністю перебудувати замість поступової збірки, яка залежить від неправильних позначок часу.

Після того, як я зафіксував налаштування NTP на своїй робочій станції, проблема більше не виникала. Без належних налаштувань NTP це відбувалося б кожні кілька годин, оскільки годинник швидко дрейфував.


Додайте це до коментаря відповіді вище
Трунг Нгуєн

0

Щоб додати ще одну тривіальну помилку до відповідей, на яку слід звернути увагу:

Переконайтеся, що ви насправді редагуєте файл XML правильного макета ...


0

у мене була та сама проблема, тому що я забув оновити ідентифікатор подання у всіх папках макета.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.