findViewById () zwraca wartość null dla niestandardowego komponentu w układzie XML, a nie dla innych komponentów


92

Mam w res/layout/main.xmltym te elementy i inne:

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

W moim działaniu onCreate robię to:

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

Pozostałe elementy zostały pomyślnie znalezione, ale foozwracają wartość null. MyCustomView ma konstruktora, MyCustomView(Context c, AttributeSet a)a Log.d(...)na końcu tego konstruktora pojawia się pomyślnie w logcat tuż przed „epicką porażką”.

Dlaczego jest foozerowa?

Odpowiedzi:


183

Ponieważ w konstruktorze super(context)zamiast super(context, attrs).

Ma to sens, jeśli nie przekażesz atrybutów, takich jak id, widok nie będzie miał identyfikatora i dlatego nie będzie można go znaleźć za pomocą tego identyfikatora. :-)


1
Zawsze miło móc odpowiadać na własne pytania :) Upewnij się, że również swoje oznaczasz jako zaakceptowaną odpowiedź.
MattC

W rzeczy samej. Zrobię to, gdy SO mi na to pozwoli („Możesz przyjąć własną odpowiedź w ciągu 2 dni.”)
Chris Boyle

4
Poza tym, czy nie powinieneś tak (MyCustomView) foo = findViewById(R.id.foo);wyglądać MyCustomView foo = (MyCustomView) findViewById(R.id.foo);?
Jeremy Logan

3
Miałem ten sam problem, w moim przypadku zapomniałem setContentView () .. XD
Tom Brito

Jest ładny przykład robienia takich rzeczy na vogella.com: vogella.com/articles/AndroidCustomViews/article.html
Wolkenjaeger

27

Mam ten sam problem, ponieważ w moim niestandardowym widoku nadpisałem konstruktora, ale wywołałem superkonstruktor bez parametru attrs. To jest kopiuj wklej)

Moja poprzednia wersja konstruktora:

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

Teraz mam:

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

I to działa!


Ten sam problem tutaj. Nawiasem mówiąc, był to również dla mnie błąd kopiowania wklejania.
KurtCobain

To samo przytrafiło się mnie. Najbardziej poprawna odpowiedź na Stackoverflow. Po dodaniu atrybutów AttributeSet wszystko jest w porządku.
spikeyang

Chyba wszyscy szukaliśmy tego samego samouczka z niestandardowym widokiem;) ten błąd zajął mi 0,5 godziny bezcelowego debugowania ...
KrwawyKefir

Dla mnie to też zadziałało! Walczyłem z tym przez jakiś czas. Dzięki!
us_david

18

Miałem ten sam problem. Mój błąd był taki: napisałem

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

a ponieważ użyłem inflatera do „załadowania” widoku z pliku XML, ostatnia linia była błędna. Aby go rozwiązać, musiałem napisać:

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

Napisałem swoje rozwiązanie na wypadek, gdyby ktoś miał ten sam problem.


Koleś, jesteś geniuszem. To było jedyne rozwiązanie, które zadziałało dla mnie ze wszystkich, które przeczytałem na SO
IgorGanapolsky

To też był problem dla mnie. Wow, co za kawałek ... Zajęłoby mi wiele dni, zanim sama to wymyśliła. Dziękuję Ci!
poshaughnessy

18

Wydaje się, że jest wiele powodów. Po prostu użyłem „Wyczyść ...” w Eclipse, aby rozwiązać podobny problem. (FindViewByID działał wcześniej iz jakiegoś powodu zaczął zwracać wartość null.)


1
Najwyraźniej podstawowym problemem jest to, że identyfikatory R.java w jakiś sposób ulegają uszkodzeniu lub mogą nie zostać zaktualizowane. Zauważyłem to nie tylko w przypadku identyfikatorów, ale także w innych przypadkach, np. Nieprawidłowy ciąg wyświetlany w TextView itp. Nie bardzo wiem jednak, dlaczego tak się dzieje.
meduzy

To przyprawiało mnie o zbyt długi smutek - sprzątanie rzeczywiście mi to naprawiało.
Nicholas MT Elliott

1
Rzeczywiście, porządki. Wow, to jest do bani!
Tim Büthe,

11

Ten sam problem, ale inne rozwiązanie: nie dzwoniłem

setContentView(R.layout.main)

ZANIM próbowałem znaleźć widok taki, jak tutaj podano


Myślę, że jest to rozwiązanie, jeśli otrzymujesz element w innym widoku zamiast bieżącego powiązanego widoku.
StarCub

4

Jeśli masz wiele wersji układu (w zależności od gęstości ekranu, wersji SDK), upewnij się, że wszystkie zawierają element, którego szukasz.


2

W moim przypadku findViewById zwracał wartość null, ponieważ mój widok niestandardowy wyglądał mniej więcej tak w głównym kodzie XML:

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

i dowiedziałem się, że kiedy dodałem rzeczy xmlns, działało to tak:

        <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

Upewnij się, że setContentView(R.layout.main)instrukcja wywołuje przed findViewById(...)instrukcją;


1

U mnie problem został rozwiązany, gdy dodałem folder res do źródła w ścieżce kompilacji Java w ustawieniach projektu.


1

Napotkałem ten sam problem jakiś czas temu, kiedy dodałem niestandardowy widok za pomocą układu XML, a następnie próbowałem dołączyć wywołanie zwrotne w innym miejscu w aplikacji ...

Utworzyłem widok niestandardowy i dodałem go do mojego „layout_main.xml”

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

W głównym ćwiczeniu chciałem dołączyć wywołania zwrotne i uzyskać odniesienia do elementów interfejsu użytkownika z 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);
    }       
}

Initilizer nie robił nic wymyślnego, ale wszelkie zmiany, które próbował wprowadzić w widoku niestandardowym (MUIComponent) lub innych niestandardowych elementach interfejsu użytkownika, po prostu nie pojawiały się w aplikacji.

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
        // ...
    }

}

Różnica między „badInst” a „goodInst” jest następująca:

  • badInst używa funkcji findViewByID działania
  • goodInst rozszerza układ i używa zawyżonego układu do wyszukiwania

Zauważyłem, że Vincent ma to samo rozwiązanie ... a jego odpowiedź jest krótsza ... zamiast tego
daje

1

Przydarzyło mi się to z niestandardowym komponentem do Wear, ale jest to ogólna rada. Jeśli używasz Stubu (takiego jak ja WatchViewStub), nie możesz po prostu wykonać połączenia w findViewById()dowolnym miejscu. Wszystko wewnątrz kikuta musi być najpierw napompowane, co nie dzieje się później setContentView(). Dlatego powinieneś napisać coś takiego, aby poczekać, aż to się stanie:

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

Mój problem to literówka. android.idZamiast tego napisałem (kropka) android:id. : P

Najwyraźniej nie ma sprawdzania składni w moim niestandardowym komponencie xml. :(


0

Miałem ten sam problem.

Miałem układ z kilkoma dziećmi. Z konstruktora jednego z nich próbowałem uzyskać odniesienie (za pomocą context.findViewById) do innego dziecka. To nie działało, ponieważ drugie dziecko zostało zdefiniowane dalej w układzie.

Rozwiązałem to w ten sposób:

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

Dobrze by działało, gdyby kolejność dzieci była odwrotna, ale myślę, że generalnie powinno się to robić tak jak powyżej.


1
Generalnie nie powinieneś używać findViewByIdw konstruktorze a View, ale raczej umieścić kod inicjalizacyjny w OnFinishInflate?
Sanjay Manohar,

0

findViewById()Metoda czasami powraca null, gdy korzeń układ ma android:idatrybut. Kreator Eclipse do generowania pliku xml układu nie generuje automatycznie android:idatrybutu dla elementu głównego.


0

W moim przypadku widok był u rodzica, a NIE w widoku, który próbowałem wywołać. Więc w widoku dziecka musiałem zadzwonić:

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

0

Opcja „czysty” zadziałała dla mnie.

W moim przypadku główną przyczyną jest to, że kod źródłowy znajduje się w udziale sieciowym, a moja stacja robocza i serwer plików nie zostały poprawnie zsynchronizowane i przesunęły się o 5 sekund. Sygnatury czasowe plików utworzonych przez Eclipse pochodzą z przeszłości (ponieważ są przypisywane przez serwer plików) i zapisują zegar stacji roboczej, co powoduje, że Eclipse nieprawidłowo rozwiązuje zależności między plikami wygenerowanymi a źródłowymi. W tym przypadku „czyste” wydaje się działać, ponieważ wymusza całkowitą przebudowę zamiast przyrostowej kompilacji, która opiera się na niewłaściwych sygnaturach czasowych.

Po naprawieniu ustawień NTP na mojej stacji roboczej problem nigdy więcej nie wystąpił. Bez odpowiednich ustawień NTP działo się to co kilka godzin, ponieważ zegary szybko dryfują.


Powinienem dodać to do komentarza odpowiedzi powyżej
Trung Nguyen

0

Aby dodać kolejny trywialny błąd do odpowiedzi, na które należy zwrócić uwagę:

Sprawdź, czy faktycznie edytujesz właściwy plik XML układu ...


0

Miałem ten sam problem, ponieważ zapomniałem zaktualizować identyfikator widoku we wszystkich moich folderach układu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.