nullPointer, когда findViewById() в SimpleCursorAdapter

Я использовал SimpleCursorAdapter с файлом xml с определенными в нем представлениями:

<LinearLayout ...>
    <ImageView android:id="@+id/listIcon" />
    <TextView android:id="@+id/listText" />
</LinearLayout>

Моя цель состояла в том, чтобы программно установить цвет текста TextView и цвет фона LinearLayout (то есть каждой строки в ListView); цвет возвращается из базы данных.

Например, я получал NPE при попытке манипулировать TextView после того, как он нашел его без жалоб:

TextView tv = (TextView) findViewById(R.id.listText);
tv.setTextColor(color); // NPE on this line

Что справедливо; если в списке несколько записей, разумно предположить, что "R.id.listText" не будет работать. Поэтому я расширил адаптер SimpleCursor:

public View getView(int position, View convertView, ViewGroup parent) {
    View row = super.getView(position, convertView, parent);
    TextView text = (TextView) row.findViewById(R.id.listText);
    // ImageView icon = (ImageView) row.findViewById(R.id.listIcon);

    // If there's an icon defined
    if (mIcon_id != 0) {
        // icon.setImageResource(mIcon_id);
    }

    // If text color defined
    if (mTextColor != 0) {
        text.setTextColor(mTextColor);
    }

    // If background color set
    if (mBackgroundColor != 0) {
        row.setBackgroundColor(mBackgroundColor);
    }
    return(row);
}

И я получаю две разные ошибки:

  • Аналогичный NPE вызывается в "text.setTextColor(mTextColor)"
  • Если строки с ImageView раскомментированы, я получаю "ClassCastException: android.widget.TextView", где я вызываю "row.findViewById(R.id.listIcon) "

Для справки: я пытался использовать образец кода Commonsware, применяя его к моей ситуации. ссылка (pdf)


Изменено на это:

public View getView(int position, View convertView, ViewGroup parent) {
    convertView = super.getView(position, convertView, parent);

    if (convertView == null) convertView = View.inflate(mContext, R.layout.theme_item, null);
    TextView text = (TextView) convertView.findViewById(R.id.listText_tv);
    ImageView icon = (ImageView) convertView.findViewById(R.id.listIcon_iv);

    // If there's an icon defined
    if (mIcon_id != 0) {
        icon.setImageResource(mIcon_id);
    }

    // If text color defined
    if (mTextColor != 0) {
        text.setTextColor(mTextColor);
    }

    // If background color set
    if (mBackgroundColor != 0) {
        convertView.setBackgroundColor(mBackgroundColor);
    }
    bindView(convertView, mContext, mCursor);
    return(convertView);
}

Теперь я получаю ClassCastException в следующем действии (при щелчке элемента списка). В следующем действии ничего не изменилось; это сработало при использовании SimpleListAdapter для списка, в котором были записи (при нажатии которых приводило к Activity2), поэтому я думаю, что я все еще делаю что-то неправильно в этом расширенном классе.


person ataulm    schedule 14.03.2011    source источник
comment
Помимо ClassCastException, работает ли этот код, если ваш курсор возвращает только одну запись?   -  person Quintin Robinson    schedule 14.03.2011
comment
@Quintin Я не смог это проверить, я вернулся к старой версии и попробовал новую с приведенными ниже предложениями, которые сработали. Знаете ли вы, почему в этом случае могло быть выброшено исключение ClassCastException? При нажатии на запись в списке, который теперь работает (цвета изменены), он должен открыть новое действие - теперь я получаю исключение ClassCastException в этом (обычное действие, без списков), чего раньше не было.   -  person ataulm    schedule 15.03.2011
comment
*Не пробовал Project › Clean, так что попробую, когда приступлю к работе; поиск в SO показывает, что сгенерированный файл R может выйти из синхронизации, что вызовет эту проблему.   -  person ataulm    schedule 15.03.2011
comment
Project › Clean устранил ClassClassExceptions :)   -  person ataulm    schedule 15.03.2011


Ответы (4)


Неправда, что convertView всегда будет существующим экземпляром; вы должны проверить, является ли он нулевым, а затем создать его экземпляр. Если нет, вы можете изменить его так же, как вы это сделали.

Это должно быть так:

public View getView(int position, View convertView, ViewGroup parent) {
    if(convertView == null)
        convertView = //inflate your row here
    View row = convertView;
    //Manipulate the row here
    return(row);
}
person Marcos Vasconcelos    schedule 14.03.2011
comment
Спасибо, чувак. Я предполагаю, что это решение эквивалентно тому, как адаптер обычно повторно использует невидимые виды? (т.е. были прокручены вне поля зрения пользователя) - person ataulm; 15.03.2011
comment
Именно так они повторно используют представления, чтобы не раздувать каждый раз и экономить время процесса и память. - person Marcos Vasconcelos; 15.03.2011

Я бы изменил метод getView:

public View getView(int position, View convertView, ViewGroup parent) {
    convertView = View.inflate(getContext(), R.layout.myLayout, null);
    TextView text = (TextView) convertView.findViewById(R.id.listText);
    ImageView icon = (ImageView) convertView.findViewById(R.id.listIcon);

    // If there's an icon defined
    if (mIcon_id != 0) {
      icon.setImageResource(mIcon_id);
    }

    // If text color defined
    if (mTextColor != 0) {
      text.setTextColor(mTextColor);
    }

    // If background color set
    if (mBackgroundColor != 0) {
      convertView.setBackgroundColor(mBackgroundColor);
    }

    return convertView;
}
person Eric Nordvik    schedule 14.03.2011

Я думаю, что вы получаете NPE, потому что пытаетесь создать текстовое представление и изображение в представлении, где их нет.

Если вы хотите заполнить ListView записями из базы данных, в своей деятельности вы определяете main.xml с ListView:

<LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:id="@+id/listView1">
</ListView>

and in the onCreate method you set the view to this xml with setContentView(R.layout.main);. Then you create your cursor to your database and your custom adapter:

    MySimpleCursorAdapter adapter = new MySimpleCursorAdapter(this, R.layout.entry,
                names, new String[] {Phones.NAME, Phones.NUMBER}, new int[] {
                R.id.listIcon, R.id.listText});
    startManagingCursor(cursor);
    ListView listView = (ListView) findViewById(R.id.listView1);
    listView.setAdapter(adapter);

и вы определяете entry.xml с вашими listIcon и listText, на которые указывает адаптер. В моем примере я запрашиваю имена и номера из списка контактов.

В вашем пользовательском адаптере вы должны без проблем получить доступ к своему текстовому и графическому представлению внутри getView или bindView.

Здесь у вас есть и пример для получить все контакты в вашем списке контактов с их изображением, именем и номером, но используя ListActivity вместо активности, и только один xml с двумя текстовыми представлениями и изображением. Если вы используете ListActivity, вам не нужно использовать ListView, и вам не нужно устанавливать представление содержимого в действии.

Я надеюсь, что это помогает!

person Racker    schedule 14.03.2011

Не забудьте поставить: layout_width и layout_heigth для каждого из ваших представлений.

person Tsunaze    schedule 14.03.2011