Элемент 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, и все.
Надеюсь, что это было полезно!