GridView 上的动画在滚动时变得混乱
Animation on GridView gets messed up on scrolling
我想做的是点击时 View
摇动 再次点击时停止摇动,我做到了,但是当我滚动时GridView 的一切都变得一团糟,另一个未被点击的 View
上的摇动动画。
这里有一个更清楚的解释:http://i.imgur.com/IDv7vJ6.gif
我不知道为什么其他人的观点在动摇。
这是我的代码:
public class PeopleFragmentGridAdapter extends BaseAdapter {
private List<Person> people = new ArrayList<>();
private Context context;
private Map<Integer, Boolean> animating;
private Map<Integer, ObjectAnimator> animators;
public PeopleFragmentGridAdapter(Context context, List<Person> people) {
this.context = context;
this.people.addAll(people);
this.animating = new HashMap<>();
this.animators = new HashMap<>();
}
@Override
public int getCount() {
return people.size();
}
@Override
public Person getItem(int position) {
return people.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder ViewHolder;
final Person p = people.get(position);
if (convertView == null) {
convertView = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.fragment_people_grid_peoples_item, null);
ViewHolder = new ViewHolder();
ViewHolder.peopleGridItem = (PeopleGridItem) convertView.findViewById(R.id.fragment_people_grid_peoples_item_item);
convertView.setTag(ViewHolder);
} else {
ViewHolder = (ViewHolder) convertView.getTag();
}
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
p.setSelected(!p.isSelected());
notifyDataSetChanged();
}
});
if (animators.get(p.getId()) == null)
animators.put(p.getId(), ObjectAnimator.ofFloat(convertView, "rotation", 0, -2, 0, 2, 0));
if (animating.get(p.getId()) == null)
animating.put(p.getId(), false);
if (p.isSelected()) {
if (!animating.get(p.getId())) {
animating.put(p.getId(), true);
animators.get(p.getId()).setTarget(convertView);
animators.get(p.getId()).setRepeatCount(ObjectAnimator.INFINITE);
animators.get(p.getId()).setDuration(300);
animators.get(p.getId()).start();
}
} else {
animators.get(p.getId()).setTarget(convertView);
animating.put(p.getId(), false);
animators.get(p.getId()).end();
animators.get(p.getId()).cancel();
}
return convertView;
}
}
有什么问题吗?
其他视图抖动的原因是 Android 实现 GridView 的方式。为了保留内存,它会回收视图,以便只有那些显示在屏幕上(或即将显示)的视图才会保存在内存中。任何被回收的视图都被放入一个未使用的视图池中,这些视图可以在将来重用。这是参数 "convertView" 获取其视图的地方。
这意味着您的工作是在屏幕外保持对象的状态。
if (convertView == null) {
convertView = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.fragment_people_grid_peoples_item, null);
ViewHolder = new ViewHolder();
ViewHolder.peopleGridItem = (PeopleGridItem) convertView.findViewById(R.id.fragment_people_grid_peoples_item_item);
convertView.setTag(ViewHolder);
} else {
ViewHolder = (ViewHolder) convertView.getTag();
}
输入 else 语句——其中 convertView != null——表示您收到了一个回收视图,而不是膨胀一个新视图。正是在这里,你应该防止回收视图继续抖动。
编辑: 我仔细查看了您的代码,我认为您实际上考虑到了我之前提到的内容(但没有成功),但我要离开它以防它帮助别人。在你的情况下,我认为问题是你没有停止你的 ObjectAnimator。
if (animators.get(p.getId()) == null)
animators.put(p.getId(), ObjectAnimator.ofFloat(convertView, "rotation", 0, -2, 0, 2, 0));
这里如果当前人物的动画师为空。你创造一个新的。假设您单击了 person1。 ObjectAnimator1 开始为 person1 制作动画。您向下滚动,此视图将重新用于 person13。 ObjectAnimator13 被创建,然后被告知结束并取消。无论 ObjectAnimator13 说什么,ObjectAnimator1 仍然在为被重用的视图设置动画。
要解决这个问题,您可能需要将 Person 的当前 ID 存储在 ViewHolder 中,这样您可以做的第一件事就是结束任何以前存在的动画。可能更好的方法是将 ObjectAnimator 存储在 ViewHolder 中,然后不需要在 HashMap 中搜索它。
使用标签比使用哈希图更容易做到这一点...在每个 getView 调用中,验证是否选择了此人——如果没有,则取消动画。并且由于 convertviews 被回收,您必须在每次 每次 getView 调用时重置它们的状态(在需要的地方启动动画,在不需要的地方取消)。
public void getView(final int position, View convertView, ViewGroup parent) {
// inflate convertviews if necessary
Person person = people.get(position);
ObjectAnimator anim = (ObjectAnimator) convertView.getTag(R.id.tag_row_animation);
if (person.isSelected()) {
if (anim == null) {
anim = getAnimator(convertView);
anim.setTarget(convertView);
anim.start();
convertView.setTag(R.id.tag_row_animation, anim);
}
} else {
if (anim != null) anim.cancel();
convertView.setTag(R.id.tag_row_animation, null);
}
convertView.setTag(R.id.tag_list_person, person);
convertView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
Person person = (Person) v.getTag(R.id.tag_list_person);
person.toggleSelected();
notifyDataSetChanged();
}
});
return convertView;
}
public ObjectAnimator getAnimator(View v) {
ObjectAnimator a = ObjectAnimator.ofFloat(v, "rotation", 0, -2, 0, 2, 0);
a.setDuration(300);
a.setRepeatCount(ObjectAnimator.INFINITE);
return a;
}
至于您的解决方案不起作用的原因:由于您的哈希图正在保存对 convertview 的引用,并且 convertview 每 n 个位置被回收一次(n = 屏幕上的位置数,给定或取 1 或 2 ), 他们会为每个位置 % n == 0 摇动,因为你设置摇动的前一个 convertview 现在要为 n 个位置之外的网格单元摇动,因为 convertview 被回收。
我想做的是点击时 View
摇动 再次点击时停止摇动,我做到了,但是当我滚动时GridView 的一切都变得一团糟,另一个未被点击的 View
上的摇动动画。
这里有一个更清楚的解释:http://i.imgur.com/IDv7vJ6.gif
我不知道为什么其他人的观点在动摇。
这是我的代码:
public class PeopleFragmentGridAdapter extends BaseAdapter {
private List<Person> people = new ArrayList<>();
private Context context;
private Map<Integer, Boolean> animating;
private Map<Integer, ObjectAnimator> animators;
public PeopleFragmentGridAdapter(Context context, List<Person> people) {
this.context = context;
this.people.addAll(people);
this.animating = new HashMap<>();
this.animators = new HashMap<>();
}
@Override
public int getCount() {
return people.size();
}
@Override
public Person getItem(int position) {
return people.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder ViewHolder;
final Person p = people.get(position);
if (convertView == null) {
convertView = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.fragment_people_grid_peoples_item, null);
ViewHolder = new ViewHolder();
ViewHolder.peopleGridItem = (PeopleGridItem) convertView.findViewById(R.id.fragment_people_grid_peoples_item_item);
convertView.setTag(ViewHolder);
} else {
ViewHolder = (ViewHolder) convertView.getTag();
}
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
p.setSelected(!p.isSelected());
notifyDataSetChanged();
}
});
if (animators.get(p.getId()) == null)
animators.put(p.getId(), ObjectAnimator.ofFloat(convertView, "rotation", 0, -2, 0, 2, 0));
if (animating.get(p.getId()) == null)
animating.put(p.getId(), false);
if (p.isSelected()) {
if (!animating.get(p.getId())) {
animating.put(p.getId(), true);
animators.get(p.getId()).setTarget(convertView);
animators.get(p.getId()).setRepeatCount(ObjectAnimator.INFINITE);
animators.get(p.getId()).setDuration(300);
animators.get(p.getId()).start();
}
} else {
animators.get(p.getId()).setTarget(convertView);
animating.put(p.getId(), false);
animators.get(p.getId()).end();
animators.get(p.getId()).cancel();
}
return convertView;
}
}
有什么问题吗?
其他视图抖动的原因是 Android 实现 GridView 的方式。为了保留内存,它会回收视图,以便只有那些显示在屏幕上(或即将显示)的视图才会保存在内存中。任何被回收的视图都被放入一个未使用的视图池中,这些视图可以在将来重用。这是参数 "convertView" 获取其视图的地方。
这意味着您的工作是在屏幕外保持对象的状态。
if (convertView == null) {
convertView = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.fragment_people_grid_peoples_item, null);
ViewHolder = new ViewHolder();
ViewHolder.peopleGridItem = (PeopleGridItem) convertView.findViewById(R.id.fragment_people_grid_peoples_item_item);
convertView.setTag(ViewHolder);
} else {
ViewHolder = (ViewHolder) convertView.getTag();
}
输入 else 语句——其中 convertView != null——表示您收到了一个回收视图,而不是膨胀一个新视图。正是在这里,你应该防止回收视图继续抖动。
编辑: 我仔细查看了您的代码,我认为您实际上考虑到了我之前提到的内容(但没有成功),但我要离开它以防它帮助别人。在你的情况下,我认为问题是你没有停止你的 ObjectAnimator。
if (animators.get(p.getId()) == null)
animators.put(p.getId(), ObjectAnimator.ofFloat(convertView, "rotation", 0, -2, 0, 2, 0));
这里如果当前人物的动画师为空。你创造一个新的。假设您单击了 person1。 ObjectAnimator1 开始为 person1 制作动画。您向下滚动,此视图将重新用于 person13。 ObjectAnimator13 被创建,然后被告知结束并取消。无论 ObjectAnimator13 说什么,ObjectAnimator1 仍然在为被重用的视图设置动画。
要解决这个问题,您可能需要将 Person 的当前 ID 存储在 ViewHolder 中,这样您可以做的第一件事就是结束任何以前存在的动画。可能更好的方法是将 ObjectAnimator 存储在 ViewHolder 中,然后不需要在 HashMap 中搜索它。
使用标签比使用哈希图更容易做到这一点...在每个 getView 调用中,验证是否选择了此人——如果没有,则取消动画。并且由于 convertviews 被回收,您必须在每次 每次 getView 调用时重置它们的状态(在需要的地方启动动画,在不需要的地方取消)。
public void getView(final int position, View convertView, ViewGroup parent) {
// inflate convertviews if necessary
Person person = people.get(position);
ObjectAnimator anim = (ObjectAnimator) convertView.getTag(R.id.tag_row_animation);
if (person.isSelected()) {
if (anim == null) {
anim = getAnimator(convertView);
anim.setTarget(convertView);
anim.start();
convertView.setTag(R.id.tag_row_animation, anim);
}
} else {
if (anim != null) anim.cancel();
convertView.setTag(R.id.tag_row_animation, null);
}
convertView.setTag(R.id.tag_list_person, person);
convertView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
Person person = (Person) v.getTag(R.id.tag_list_person);
person.toggleSelected();
notifyDataSetChanged();
}
});
return convertView;
}
public ObjectAnimator getAnimator(View v) {
ObjectAnimator a = ObjectAnimator.ofFloat(v, "rotation", 0, -2, 0, 2, 0);
a.setDuration(300);
a.setRepeatCount(ObjectAnimator.INFINITE);
return a;
}
至于您的解决方案不起作用的原因:由于您的哈希图正在保存对 convertview 的引用,并且 convertview 每 n 个位置被回收一次(n = 屏幕上的位置数,给定或取 1 或 2 ), 他们会为每个位置 % n == 0 摇动,因为你设置摇动的前一个 convertview 现在要为 n 个位置之外的网格单元摇动,因为 convertview 被回收。