我有一个用于listView的复杂适配器。
每一行都有一些内部视图需要点击(并且点击次数),并且它们还有选择器(以显示触摸的效果)。
在某些情况下,需要非常频繁地调用notifyDataSetChanged()(例如每秒一次/两次),以显示listView项目的一些更改。
例如,考虑查看下载文件列表,向用户显示正在下载的每个文件的进度。
每次调用notifyDataSetChanged时,触摸事件在触摸的视图上都会丢失,因此用户可能会错过点击它,特别是错过了长按。
不仅如此,选择器也会失去其状态,所以如果你触摸它并看到效果,当调用notifyDataSetChanged时,视图会丢失它的状态,你会看到它好像没有被触及。 / p>
即使对于没有更新任何内容的视图(这意味着我只是为它们返回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添加/删除项目的情况,listView需要调用notifyDataSetChangedbeing。此外,它还使更新的视图失去了触摸状态。
编辑:即使是部分解决方案也不起作用。也许它导致整个listView的布局,导致其他视图失去其状态。
在调用notifyDataSetChanged()之后,如何让视图与触摸事件保持“同步”?
答案 0 :(得分:2)
您没有更新&#34; Recycled&#34;的点击监听器。视图。
将tv.setOnClickListener()
放在if (tv == null)
支票之外。
此外,您希望&#34;保持同步的属性&#34;应该在支持ListView的模型中。永远不要相信Views能够保存重要数据,它们只应反映模型中的数据。
class Item{
String name;
boolean enabled;
boolean checked
}
class ItemAdapter extends ArrayAdapter<Item>{
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
if(convertView == null){
// create new instance
}
// remove all event listeners
Item item = getItem(position);
// set view properties from item (some times, old event listeners will fire when changing view properties , so we have cleared event listeners above)
// setup new event listeners to update properties of view and item
}
}
答案 1 :(得分:0)
如上所述,您可以使用View.setOnTouchListener()
并抓住ACTION_DOWN
和ACTION_UP
事件。
对于选择器动画,您可以使用自定义颜色动画。以下是使用动画更改backgroundColor的示例。
Integer colorFrom = getResources().getColor(R.color.red);
Integer colorTo = getResources().getColor(R.color.blue);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
view.setBackgroundColor((Integer)animator.getAnimatedValue());
}
});
colorAnimation.start();
编辑(由OP,意思是线程创建者):基于上述的替代解决方案是使用touchListener来设置视图背景的状态。
它的缺点(虽然它对于大多数情况应该没问题)是如果列表获得更多/更少的项目,触摸会丢失(通过ACTION_CANCEL),所以,如果你也希望处理它,你可以使用这个事件并使用你自己的逻辑自己处理。
尽管解决方案有点奇怪且无法处理所有可能的状态,但它仍能正常工作。
以下是示例代码:
public static abstract class StateTouchListener implements OnTouchListener,OnClickListener
{
@Override
public boolean onTouch(final View v,final MotionEvent event)
{
final Drawable background=v.getBackground();
// TODO use GestureDetectorCompat if needed
switch(event.getAction())
{
case MotionEvent.ACTION_CANCEL:
background.setState(new int[] {});
v.invalidate();
break;
case MotionEvent.ACTION_DOWN:
background.setState(new int[] {android.R.attr.state_pressed});
v.invalidate();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
background.setState(new int[] {});
v.invalidate();
v.performClick();
onClick(v);
break;
}
return true;
}
}
和我的代码中的修复:
tv.setOnTouchListener(new StateTouchListener()
{
@Override
public void onClick(final View v)
{
android.util.Log.d("Applog","click!");
};
});
这应该替换我使用的setOnClickListener。