Задний план
У меня есть сложный адаптер для listView.
В каждой строке есть несколько внутренних представлений, которые должны быть интерактивными (и обрабатывать клики), а также у них есть селекторы (чтобы показать эффект прикосновения).
в некоторых случаях notifyDataSetChanged() необходимо вызывать довольно часто (например, один раз/два раза в секунду), чтобы показать некоторые изменения в элементах listView.
В качестве примера рассмотрим список загружаемых файлов, где вы показываете пользователю ход загрузки каждого файла.
Эта проблема
Каждый раз, когда вызывается notifyDataSetChanged, событие касания теряется в сенсорном представлении, поэтому пользователь может пропустить щелчок по нему и особенно пропустить долгое нажатие на него.
Не только это, но и селектор также теряет свое состояние, поэтому, если вы коснетесь его и увидите эффект, когда вызывается notifyDataSetChanged, представление потеряет свое состояние, и вы увидите, что его не трогали.
Это происходит даже для представлений, в которых ничего не обновляется (это означает, что я просто возвращаю convertView для них).
Образец кода
Код ниже демонстрирует проблему. Это не исходный код, а очень короткий пример, чтобы было понятно, о чем я говорю.
Опять же, это не исходный код, поэтому я удалил использование ViewHolder и позаботился о щелчках для выполнения некоторых операций, чтобы упростить чтение и понимание. Но это все та же логика.
Вот код:
MainActivity.java
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ListView listView = (ListView) findViewById(R.id.listView);
final BaseAdapter adapter = new BaseAdapter() {
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
TextView tv = (TextView) convertView;
if (tv == null) {
tv = new TextView(MainActivity.this);
tv.setBackgroundDrawable(getResources().getDrawable(R.drawable.item_background_selector));
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
android.util.Log.d("AppLog", "click");
}
});
}
//NOTE: putting the setOnClickListener here won't help either.
final int itemViewType = getItemViewType(position);
tv.setText((itemViewType == 0 ? "A " : "B ") + System.currentTimeMillis());
return tv;
}
@Override
public int getItemViewType(final int position) {
return position % 2;
}
@Override
public long getItemId(final int position) {
return position;
}
@Override
public Object getItem(final int position) {
return null;
}
@Override
public int getCount() {
return 100;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(final int position) {
return false;
}
};
listView.setAdapter(adapter);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
// fake notifying
adapter.notifyDataSetChanged();
android.util.Log.d("AppLog", "notifyDataSetChanged");
handler.postDelayed(this, 1000);
}
}, 1000);
}
}
item_background_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"><shape>
<solid android:color="@android:color/holo_blue_light" />
</shape></item>
<item android:state_focused="true"><shape>
<solid android:color="@android:color/holo_blue_light" />
</shape></item>
<item android:drawable="@android:color/transparent"/>
</selector>
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.test.MainActivity"
tools:ignore="MergeRootFrame" >
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</FrameLayout>
Частичное решение
Можно обновить только необходимые представления, найдя представление и затем вызвав для него getView, но это обходной путь. Кроме того, это не будет работать в случае добавления/удаления элементов из listView, для которого необходимо вызвать notifyDataSetChanged. Кроме того, это также приводит к тому, что обновленный вид теряет свое касательное состояние.
РЕДАКТИРОВАТЬ: даже частичное решение не работает. Возможно, это вызывает макет всего listView, из-за которого другие представления теряют свои состояния.
Вопрос
Как я могу позволить представлениям оставаться «синхронизированными» с событиями касания после вызова notifyDataSetChanged() ?
onTouchEvent
? Возможно, он запускает команду, выполняя событиеACTION_UP
- person mapodev   schedule 02.07.2014ACTION_DOWN
. Событие длинного клика будет потеряно, но у вас есть хотя бы событие клика. - person mapodev   schedule 02.07.2014