Элемент Android ListView: стабильные идентификаторы и все же получение неправильной позиции

К сожалению, я действительно думаю, что должен немного объяснить свою ситуацию.

Я пишу приложение для Android по древнему латинскому языку: мои цели - показать пользователям все спряжения латинских глаголов и дать им правильный вербальный анализ при поиске определенной изогнутой формы. Это мой манифест.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android_application.app_name"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ConjActivity"
            android:label="@string/app_name"
            android:parentActivityName=".MainActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.android_application.app_name.MainActivity"/>
        </activity>
    </application>

</manifest>

Просто знайте, что для достижения первой цели я автоматически создаю ВСЕ формы каждого глагола в виде строк, и каждое спряжение отображается в виде простого ListView с только одной строкой TextView в качестве элемента: один элемент = одна изогнутая словесная форма.

Теперь мне нужно настроить свои элементы, изменив несколько раз их textStyle, несколько раз их выравнивание и т. Д. Для этого я создал свой собственный ListAdapter следующим образом:

private static class ConjAdapter extends ArrayAdapter<String> {

    private ArrayList<Long> ids;
    private HashMap<String, Long> mIdMap;

    public ConjAdapter(Context context, int textViewResourceId, List<String> objects) {
        super(context, textViewResourceId, objects);
        ids = new ArrayList<Long>();
        mIdMap = new HashMap<String, Long>();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = super.getView(position, convertView, parent);
        ViewHolder holder;

        if(convertView==null){
            convertView = LayoutInflater.from(context).inflate(R.layout.simple_list_item_1, parent, false);
            holder = new ViewHolder();
            holder.txt = (TextView) convertView.findViewById(R.id.text1);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        long id = getItemId(position);

        if(sel_vb!=null){
            if(ids.contains(id)){ v.setBackgroundColor(context.getResources().getColor(R.color.row_bckgr_RED));
        } else {
                v.setBackgroundColor(Color.TRANSPARENT);
            }
        }

        for(Map.Entry<String, Long> map : mIdMap.entrySet()){
            if(id==map.getValue()){
                holder.txt.setText(map.getKey());
                break;
            }
        }

        String item = (String) holder.txt.getText();
        if(item.equals(context.getResources().getString(R.string.ind))||
                item.equals(context.getResources().getString(R.string.subj))||
                item.equals(context.getResources().getString(R.string.imp))||
                item.equals(context.getResources().getString(R.string.inf))||
                item.equals(context.getResources().getString(R.string.pt))||
                item.equals(context.getResources().getString(R.string.ger))||
                item.equals(context.getResources().getString(R.string.gerv))||
                item.equals(context.getResources().getString(R.string.sup))){
            holder.txt.setGravity(Gravity.CENTER);
            holder.txt.setTextSize(20f);
            holder.txt.setTypeface(null, Typeface.BOLD);
        } else if(item.equals(context.getResources().getString(R.string.pres))||
                item.equals(context.getResources().getString(R.string.impf))||
                item.equals(context.getResources().getString(R.string.fut))||
                item.equals(context.getResources().getString(R.string.pf))||
                item.equals(context.getResources().getString(R.string.ppf))||
                item.equals(context.getResources().getString(R.string.futant))){
            holder.txt.setTypeface(null, Typeface.ITALIC);
            holder.txt.setTextSize(19f);
        } else {
            holder.txt.setPadding(10, 0, 0, 0);
        }

        return convertView;
    }

    @Override
    public long getItemId(int position) {
        String item = getItem(position);
        return mIdMap.get(item);
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    static class ViewHolder {
        TextView txt;
    }

}

Проблема в том, что после 19-го элемента что-то идет не так, в том смысле, что я не получаю сообщений об ошибках и приложение не падает, но мой пользовательский код больше не работает, а элементы, которые должны иметь некоторые функции, наоборот, имеют другие. И эта ситуация ухудшается, когда я прокручиваю свой список вверх и вниз.

После того, что я прочитал, я действительно думаю, что эта проблема касается цели переработки переменной convertview, вызываемой getView() моего пользовательского адаптера.

И вот мой вопрос: почему это происходит даже со стабильными идентификаторами (которые я храню в mIdMap вместе со связанными строками элементов) и как я могу отсоединить свой элемент от неверной переменной позиции?

Обновить

Вот код, которым я заполняю mIdMap и идентификаторы:

HashMap<String, Long> tempMap = conjadapt.mIdMap;
for(int i=0, j=0; i<displ_conj.size(); i++, j++){
    tempMap.put(displ_conj.get(i), (long) j);
}
if(sel_vb!=null){
    for(Map.Entry<String, Long> map : tempMap.entrySet()){
        if(map.getKey().equals(sel_vb))
            conjadapt.ids.add(map.getValue());
    }
}

где displ_conj - ArrayList, в котором я храню свои данные. mIdMap хранит длинные переменные, потому что getItemId() должен возвращать long и с этим мне нужно кое-что сделать где-то еще.

2 ответа

Решение

Вы правы, это проблема рециклирования, и она не имеет ничего общего со стабильными идентификаторами. Как указывает самгак, вы не должны вызывать супер-вызов:

View v = super.getView(position, convertView, parent);

Это создаст совершенно новый View который вы затем изменяете цвет фона. Однако с тех пор View никогда не возвращается, оно сбрасывается на ветер после завершения вызова метода. Так что в основном ваш getView Метод делает некоторую дополнительную работу, которая никогда не используется. Вы должны иметь дело исключительно с convertView, Тем не менее, это не вызывает проблемы рециркуляции, которую вы видите.

Похоже, вы пытаетесь показать красный фон для sel_vb, Я предполагаю, что означает выбранный глагол? Знайте, что есть встроенный механизм для этого. ListViews поддерживать метод с именем setItemChecked (position, boolean). По сути, вы можете выделить любую позицию с помощью этого вызова метода. Обратите внимание, что вам нужно сначала включить режим выбора. Вы можете изменить синюю подсветку по умолчанию с помощью стилей или создав собственный макет вместо использования Android.

К сожалению, не так много документации, которая действительно объясняет, как включение стабильных идентификаторов влияет на ListView, Он используется только при включении режима выбора. Это помогает гарантировать, что правильный элемент выделен / проверен во время таких вещей, как настройки экрана или мутации адаптера, в то время как что-то выделено / проверено. В противном случае стабильные идентификаторы не имеют ничего общего с View поколение.

Трудно сказать, что именно вызывает причудливую переработку, но это определенно связано с тем, как вы заполняете текст с помощью Id-сопоставления. Конкретно этот парень:

    for(Map.Entry<String, Long> map : mIdMap.entrySet()){
        if(id==map.getValue()){
            holder.txt.setText(map.getKey());
            break;
        }
    }

Мое предложение будет полностью удалить всю логику Id из getView метод. Вместо этого заполните текст, выполнив:

holder.txt.setText(getItem(position));

Еще одно соображение производительности. Я предлагаю изучить различные типы представлений. Для вашего случая у вас может быть 3 различных типа представления на основе этой логики:

   String item = (String) holder.txt.getText();

    //First type
    if(item.equals(context.getResources().getString(R.string.ind))||
            item.equals(context.getResources().getString(R.string.subj))||
            item.equals(context.getResources().getString(R.string.imp))||
            item.equals(context.getResources().getString(R.string.inf))||
            item.equals(context.getResources().getString(R.string.pt))||
            item.equals(context.getResources().getString(R.string.ger))||
            item.equals(context.getResources().getString(R.string.gerv))||
            item.equals(context.getResources().getString(R.string.sup))){

    //Second type
    } else if(item.equals(context.getResources().getString(R.string.pres))||
            item.equals(context.getResources().getString(R.string.impf))||
            item.equals(context.getResources().getString(R.string.fut))||
            item.equals(context.getResources().getString(R.string.pf))||
            item.equals(context.getResources().getString(R.string.ppf))||
            item.equals(context.getResources().getString(R.string.futant))){

    } else {
       //Third type
    }

Затем вы можете создать три пользовательских макета для раздувания вместо того, чтобы полагаться на встроенный макет simple_list_item_1. Это улучшает переработку View и избавляет от раздувания, а затем модифицирует логику.

Решение

Благодаря @JaySoyer и его замечательным советам о проблемах с производительностью мне действительно удалось решить мою проблему, и я посчитал полезным поделиться кодом после моих исправлений.

Из того, что я узнал, проблема заключалась в том, что я слишком старался программно определять функции и слишком мало оставлял части XML. Решение действительно было связано с viewTypes. Переопределение getViewTypeCount() и getItemViewType() и раздувание одного макета для каждого viewType было ключом к моей проблеме.

В моем случае мне понадобилось 4 viewTypes (из-за наличия разных разделителей, разных textStyles и т. Д.), И теперь мой адаптер выглядит так:

private static class ConjAdapter extends ArrayAdapter<String> {

    private ArrayList<Long> ids;
    private HashMap<String, Long> mIdMap;
    private ViewHolder holder;

    private final int
            IND_MODE_TYPE = 0,
            ELSE_MODE_TYPE = 1,
            TENSE_TYPE = 2,
            FORM_TYPE = 3;


    public ConjAdapter(Context context, int textViewResourceId, List<String> objects) {
        super(context, textViewResourceId, objects);
        ids = new ArrayList<Long>();
        mIdMap = new HashMap<String, Long>();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if(convertView==null){
            if(getItemViewType(position)==IND_MODE_TYPE){
                convertView = LayoutInflater.from(context).inflate(R.layout.item_mode_ind, parent, false);
                holder = new ViewHolder();
                holder.txt = (TextView) convertView.findViewById(R.id.text1);
                convertView.setTag(holder);
            }else if(getItemViewType(position)==ELSE_MODE_TYPE){
                convertView = LayoutInflater.from(context).inflate(R.layout.item_mode_else, parent, false);
                holder = new ViewHolder();
                holder.txt = (TextView) convertView.findViewById(R.id.text1);
                holder.divider1 = (View) convertView.findViewById(R.id.conj_divider1);
                convertView.setTag(holder);
            } else if(getItemViewType(position)==TENSE_TYPE){
                convertView = LayoutInflater.from(context).inflate(R.layout.item_tense, parent, false);
                holder = new ViewHolder();
                holder.divider2 = (View) convertView.findViewById(R.id.conj_divider2);
                holder.txt = (TextView) convertView.findViewById(R.id.text1);
                convertView.setTag(holder);
            } else {
                convertView = LayoutInflater.from(context).inflate(R.layout.item_form, parent, false);
                holder = new ViewHolder();
                holder.txt = (TextView) convertView.findViewById(R.id.text1);
                convertView.setTag(holder);
            }
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        long id = getItemId(position);

        if(sel_vb!=null){
            if(ids.contains(id)){
                convertView.setBackgroundColor(context.getResources().getColor(R.color.row_bckgr_RED));
            } else {
                convertView.setBackgroundColor(Color.TRANSPARENT);
            }
        }

        holder.txt.setText(getItem(position));

        return convertView;
    }

    @Override
    public long getItemId(int position) {

        String item = getItem(position);
        return mIdMap.get(item);
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    @Override
    public int getItemViewType(int position) {
        String item = getItem(position);

        if(item.equals(context.getResources().getString(R.string.ind))){
            return IND_MODE_TYPE;
        } else if(item.equals(context.getResources().getString(R.string.subj))||
                item.equals(context.getResources().getString(R.string.imp))||
                item.equals(context.getResources().getString(R.string.inf))||
                item.equals(context.getResources().getString(R.string.pt))||
                item.equals(context.getResources().getString(R.string.ger))||
                item.equals(context.getResources().getString(R.string.gerv))||
                item.equals(context.getResources().getString(R.string.sup))){
            return ELSE_MODE_TYPE;
        } else if(item.equals(context.getResources().getString(R.string.pres))||
                item.equals(context.getResources().getString(R.string.impf))||
                item.equals(context.getResources().getString(R.string.fut))||
                item.equals(context.getResources().getString(R.string.pf))||
                item.equals(context.getResources().getString(R.string.ppf))||
                item.equals(context.getResources().getString(R.string.futant))){
            return TENSE_TYPE;
        } else {
            return FORM_TYPE;
        }
    }

    @Override
    public int getViewTypeCount() {
        return 4;
    }

    static class ViewHolder {
        TextView txt;
        View divider1, divider2;
    }

}

Как вы можете видеть, мне нужно было создать столько макетов, сколько указано количество типов viewType (правильно управляющих всеми необходимыми настройками через xml), и раздувать правильное после простой проверки фактического viewType, и все.

Надеюсь, что это было полезно!

Другие вопросы по тегам