Che*_*eng 21 android android-animation notifydatasetchanged android-recyclerview
目前,通过使用默认动画师android.support.v7.widget.DefaultItemAnimator
,这是我在排序过程中遇到的结果
DefaultItemAnimator动画视频: https ://youtu.be/EccI7RUcdbg
public void sortAndNotifyDataSetChanged() {
int i0 = 0;
int i1 = models.size() - 1;
while (i0 < i1) {
DemoModel o0 = models.get(i0);
DemoModel o1 = models.get(i1);
models.set(i0, o1);
models.set(i1, o0);
i0++;
i1--;
//break;
}
// adapter is created via adapter = new RecyclerViewDemoAdapter(models, mRecyclerView, this);
adapter.notifyDataSetChanged();
}
Run Code Online (Sandbox Code Playgroud)
但是,我更喜欢提供自定义动画,而不是排序时的默认动画(notifyDataSetChanged).旧物品将从右侧滑出,新物品将向上滑动.
预期的动画视频: https ://youtu.be/9aQTyM7K4B0
几年前,我通过使用LinearLayout
+ 达到了这个效果View
,因为那个时候我们还RecyclerView
没有.
这就是动画的设置方式
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f);
PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", 0f, (float) width);
ObjectAnimator animOut = ObjectAnimator.ofPropertyValuesHolder(this, alpha, translationX);
animOut.setDuration(duration);
animOut.setInterpolator(accelerateInterpolator);
animOut.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
final View view = (View) ((ObjectAnimator) anim).getTarget();
Message message = (Message)view.getTag(R.id.TAG_MESSAGE_ID);
if (message == null) {
return;
}
view.setAlpha(0f);
view.setTranslationX(0);
NewsListFragment.this.refreshUI(view, message);
final Animation animation = AnimationUtils.loadAnimation(NewsListFragment.this.getActivity(),
R.anim.slide_up);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.VISIBLE);
view.setTag(R.id.TAG_MESSAGE_ID, null);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(animation);
}
});
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animOut);
this.nowLinearLayout.setLayoutTransition(layoutTransition);
Run Code Online (Sandbox Code Playgroud)
而且,这就是动画被触发的方式.
// messageView is view being added earlier in nowLinearLayout
for (int i = 0, ei = messageViews.size(); i < ei; i++) {
View messageView = messageViews.get(i);
messageView.setTag(R.id.TAG_MESSAGE_ID, messages.get(i));
messageView.setVisibility(View.INVISIBLE);
}
Run Code Online (Sandbox Code Playgroud)
我想知道,如何在RecylerView中实现同样的效果?
RecyclerView
的默认方式.第二部分描述了在数据集更改后如何在动画中强制滑出/滑动所有项目的解决方案.要启用动画,您需要告诉RecyclerView
数据集如何更改(以便它知道应该运行什么类型的动画).这可以通过两种方式完成:
1)简单的版本:
我们需要设置adapter.setHasStableIds(true);
,并通过提供您的项目的ID public long getItemId(int position)
在你Adapter
的RecyclerView
.在RecyclerView
利用这些编号,以找出哪些项目中除去/添加/调用期间移动adapter.notifyDataSetChanged();
2)高级版本:adapter.notifyDataSetChanged();
您可以明确说明数据集的更改方式,而不是调用您.在Adapter
提供了几种方法,比如adapter.notifyItemChanged(int position)
,adapter.notifyItemInserted(int position)
......来描述数据集的变化
触发以反映数据集中的更改的动画由ItemAnimator
.管理.在RecyclerView
已经配备了一个不错的默认DefaultItemAnimator
.此外,可以使用自定义定义自定义动画行为ItemAnimator
.
右侧的幻灯片是在从数据集中删除项目时应播放的动画.应为添加到数据集的项目播放底部动画的幻灯片.如开头所述,我假设所有元素都向右滑动并从底部滑入.即使它们在数据集更改之前和之后都可见.通常RecyclerView
会播放更改/移动动画以保持可见的项目.但是,因为我们想要对所有项目使用删除/添加动画,所以我们需要欺骗适配器,使其认为更改后只有新元素,并且所有以前可用的项目都被删除.这可以通过为适配器中的每个项目提供随机ID来实现:
@Override
public long getItemId(int position) {
return Math.round(Math.random() * Long.MAX_VALUE);
}
Run Code Online (Sandbox Code Playgroud)
现在我们需要提供一个自定义ItemAnimator
来管理添加/删除项目的动画.所提出的结构SlidingAnimator
非常相似,android.support.v7.widget.DefaultItemAnimator
即具备RecyclerView
.另请注意,这是一个概念证明,应该在用于任何应用程序之前进行调整:
public class SlidingAnimator extends SimpleItemAnimator {
List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();
@Override
public void runPendingAnimations() {
final List<RecyclerView.ViewHolder> additionsTmp = pendingAdditions;
List<RecyclerView.ViewHolder> removalsTmp = pendingRemovals;
pendingAdditions = new ArrayList<>();
pendingRemovals = new ArrayList<>();
for (RecyclerView.ViewHolder removal : removalsTmp) {
// run the pending remove animation
animateRemoveImpl(removal);
}
removalsTmp.clear();
if (!additionsTmp.isEmpty()) {
Runnable adder = new Runnable() {
public void run() {
for (RecyclerView.ViewHolder addition : additionsTmp) {
// run the pending add animation
animateAddImpl(addition);
}
additionsTmp.clear();
}
};
// play the add animation after the remove animation finished
ViewCompat.postOnAnimationDelayed(additionsTmp.get(0).itemView, adder, getRemoveDuration());
}
}
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
pendingAdditions.add(holder);
// translate the new items vertically so that they later slide in from the bottom
holder.itemView.setTranslationY(300);
// also make them invisible
holder.itemView.setAlpha(0);
// this requests the execution of runPendingAnimations()
return true;
}
@Override
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
pendingRemovals.add(holder);
// this requests the execution of runPendingAnimations()
return true;
}
private void animateAddImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view);
anim
// undo the translation we applied in animateAdd
.translationY(0)
// undo the alpha we applied in animateAdd
.alpha(1)
.setDuration(getAddDuration())
.setInterpolator(new DecelerateInterpolator())
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
anim.setListener(null);
dispatchAddFinished(holder);
// cleanup
view.setTranslationY(0);
view.setAlpha(1);
}
@Override
public void onAnimationCancel(View view) {
}
}).start();
}
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view);
anim
// translate horizontally to provide slide out to right
.translationX(view.getWidth())
// fade out
.alpha(0)
.setDuration(getRemoveDuration())
.setInterpolator(new AccelerateInterpolator())
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
anim.setListener(null);
dispatchRemoveFinished(holder);
// cleanup
view.setTranslationX(0);
view.setAlpha(1);
}
@Override
public void onAnimationCancel(View view) {
}
}).start();
}
@Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
// don't handle animateMove because there should only be add/remove animations
dispatchMoveFinished(holder);
return false;
}
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
// don't handle animateChange because there should only be add/remove animations
if (newHolder != null) {
dispatchChangeFinished(newHolder, false);
}
dispatchChangeFinished(oldHolder, true);
return false;
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) { }
@Override
public void endAnimations() { }
@Override
public boolean isRunning() { return false; }
}
Run Code Online (Sandbox Code Playgroud)
这是最终结果:
此更新的解决方案不需要使用随机ID欺骗适配器,以便认为所有项目都已删除,并且只添加了新项目.如果我们应用2)高级版本 - 如何通知适配器有关数据集更改,我们可以告诉adapter
所有先前的项目已被删除并且所有新项目都已添加:
int oldSize = oldItems.size();
oldItems.clear();
// Notify the adapter all previous items were removed
notifyItemRangeRemoved(0, oldSize);
oldItems.addAll(items);
// Notify the adapter all the new items were added
notifyItemRangeInserted(0, items.size());
// don't call notifyDataSetChanged
//notifyDataSetChanged();
Run Code Online (Sandbox Code Playgroud)
之前提出的SlidingAnimator
内容仍然是动画更改的必要条件.
如果您不想在每种排序上重置滚动(GITHUB演示项目),可以看一下这个方向:
使用某种RecyclerView.ItemAnimator
,但不是重写animateAdd()
和 animateRemove()
功能,你可以实现animateChange()
和animateChangeImpl()
.排序后,您可以调用adapter.notifyItemRangeChanged(0, mItems.size());
triger动画.所以触发动画的代码看起来很简单:
for (int i = 0, j = mItems.size() - 1; i < j; i++, j--)
Collections.swap(mItems, i, j);
adapter.notifyItemRangeChanged(0, mItems.size());
Run Code Online (Sandbox Code Playgroud)
对于您可以使用的动画代码android.support.v7.widget.DefaultItemAnimator
,但此类具有私有,animateChangeImpl()
因此您必须复制粘贴的代码并更改此方法或使用反射.或者你可以ItemAnimator
像@Andreas Wenger
他的例子中那样创建自己的类SlidingAnimator
.这里的要点是为animateChangeImpl
你的代码实现Simmilar有2个动画:
1)向右滑动旧视图
private void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder oldHolder = changeInfo.oldHolder;
final View view = oldHolder == null ? null : oldHolder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view == null) return;
mChangeAnimations.add(oldHolder);
final ViewPropertyAnimatorCompat animOut = ViewCompat.animate(view)
.setDuration(getChangeDuration())
.setInterpolator(interpolator)
.translationX(view.getRootView().getWidth())
.alpha(0);
animOut.setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(oldHolder, true);
}
@Override
public void onAnimationEnd(View view) {
animOut.setListener(null);
ViewCompat.setAlpha(view, 1);
ViewCompat.setTranslationX(view, 0);
dispatchChangeFinished(oldHolder, true);
mChangeAnimations.remove(oldHolder);
dispatchFinishedWhenDone();
// starting 2-nd (Slide Up) animation
if (newView != null)
animateChangeInImpl(newHolder, newView);
}
}).start();
}
Run Code Online (Sandbox Code Playgroud)
2)向上滑动新视图
private void animateChangeInImpl(final RecyclerView.ViewHolder newHolder,
final View newView) {
// setting starting pre-animation params for view
ViewCompat.setTranslationY(newView, newView.getHeight());
ViewCompat.setAlpha(newView, 0);
mChangeAnimations.add(newHolder);
final ViewPropertyAnimatorCompat animIn = ViewCompat.animate(newView)
.setDuration(getChangeDuration())
.translationY(0)
.alpha(1);
animIn.setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(newHolder, false);
}
@Override
public void onAnimationEnd(View view) {
animIn.setListener(null);
ViewCompat.setAlpha(newView, 1);
ViewCompat.setTranslationY(newView, 0);
dispatchChangeFinished(newHolder, false);
mChangeAnimations.remove(newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
Run Code Online (Sandbox Code Playgroud)
这是带有工作滚动和类似动画的演示图像 https://i.gyazo.com/04f4b767ea61569c00d3b4a4a86795ce.gif https://i.gyazo.com/57a52b8477a361c383d44664392db0be.gif
编辑:
为了加快RecyclerView的性能,adapter.notifyItemRangeChanged(0, mItems.size());
您可能不希望使用以下内容:
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisible = layoutManager.findFirstVisibleItemPosition();
int lastVisible = layoutManager.findLastVisibleItemPosition();
int itemsChanged = lastVisible - firstVisible + 1;
// + 1 because we start count items from 0
adapter.notifyItemRangeChanged(firstVisible, itemsChanged);
Run Code Online (Sandbox Code Playgroud)