今天准备用RecyclerView来实现网易新闻Tabs的动态效果。
点击下面的RecyclerView的item,会有一个view的移动的动画;动画完成以后,下面的RecyclerView会有一个item删除的动画,对应上面的RecyclerView有一个item增加的动画;然后拖动上面RecyclerView的item可以进行排序,左右滑动可以进行删除。
整体效果还是和网易新闻的Tabs很像,细节上处理稍微有点不一样。网易上面的item是点击删除,我这里处理成了滑动删除。
项目整体源码后面会给出下载链接,这里就只介绍重点部分的代码以及实现原理。
1.RecyclerView的点击事件
接口回调是处理RecyclerView点击事件很好的方式,分析一下下面RecyclerView的点击事件。点击item的时候,有一个移动的动画,所以我们需要点击item的view;动画完成以后,下面的RecyclerView有一个删除的动画,所以我们需要item的position。综上,我们的接口就出来了:
public interface onAllTabsListener { void allTabsItemClick(View view,int position); }
然后就是在点击RecyclerView的item的时候,传递参数:
viewHolder.txt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { listener.allTabsItemClick(viewHolder.itemView,position); } });
最后就是让Activity实现这个接口,接受传递的参数,进行逻辑的处理:
public class WangYiActivity extends AppCompatActivity implements AllTabsAdapter.onAllTabsListener {}
public void allTabsItemClick(final View view, final int position) {}
2.RecyclerView的移动动画
RecyclerView的item移动动画,我这里使用的是属性动画加上一阶贝塞尔曲线实现的。
科普时间(引用郭神博客):
ValueAnimator是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
其实RecyclerView的item移动动画的实现就是对view的x,y坐标不断赋值,不断更新,达到移动动画效果。
那怎么获取到view的x,y坐标呢,这里我们就需要Android中另外一个非常重要的类来实现了—-Path路径类(封装了贝塞尔曲线)。这里我们使用的是一阶贝塞尔曲线,看一下它的几个重要方法:
(1.)moveTo(float,float)
用于设置移动路径的起始点Point(x,y),对于android系统来说,屏幕的左上角的坐标是 (0,0) , 我们在做一些操作的时候默认基准点也是 (0,0)。Path 的moveTo 方法可以与此进行一个类比,就是为了改变 Path 的起始点。
(2.)lineTo(float x,float y)
上一个点以直线的方式连接到参数里的 (x,y)
(3.)路径测量PathMeasure
起点与终点拿到了,我们需要获取到x,与y的坐标,PathMeasure提供了以下的方法
float getLength() :测量path的距离
getPosTan(float distance, float[] pos, float[] tan) :传入一个距离distance(0<=distance<=getLength()),然后会计算当前距离的坐标点和切线,pos会自动填充上坐标,这个方法很重要。
现在坐标也能够获取到了,怎么实现呢:
1.调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimator的实例,ofFloat()方法当中允许传入多个float类型的参数,这里传入0和mPathMeasure.getLength()就表示将值从0平滑过渡到mPathMeasure.getLength()。
2.通过addUpdateListener()方法来添加一个动画的监听器,在动画执行的过程中会不断地进行回调,我们只需要在回调方法当中通过mPathMeasure.getPosTan()方法将当前的值取出并设置给view,就可以达到动画效果了。
3.addListener方法来监听动画完成以后的操作,数据的添加,删除等
public void allTabsItemClick(final View view, final int position) { final PathMeasure mPathMeasure; final float[] mCurrentPosition = new float[2]; int parentLoc[] = new int[2]; linearLayout.getLocationInWindow(parentLoc); int startLoc[] = new int[2]; view.getLocationInWindow(startLoc); final View startView = view; LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(view.getWidth(), view.getHeight()); allRecycle.removeView(view); linearLayout.addView(startView, params); final View endView; float toX, toY; int endLoc[] = new int[2]; //进行判断 int i = choseTabs.size(); if (i == 0) { toX = view.getWidth(); toY = view.getHeight(); } else if (i % 4 == 0) { endView = choseRecycle.getChildAt(i - 4); endView.getLocationInWindow(endLoc); toX = endLoc[0] - parentLoc[0]; toY = endLoc[1] + view.getHeight() - parentLoc[1]; } else { endView = choseRecycle.getChildAt(i - 1); endView.getLocationInWindow(endLoc); toX = endLoc[0] + view.getWidth() - parentLoc[0]; toY = endLoc[1] - parentLoc[1]; } Log.e("tag", allTabs.size() + "@"); Log.e("tag", choseTabs.size() + "@@"); float startX = startLoc[0] - parentLoc[0]; float startY = startLoc[1] - parentLoc[1]; Path path = new Path(); path.moveTo(startX, startY); path.lineTo(toX, toY); mPathMeasure = new PathMeasure(path, false); //属性动画实现 ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength()); valueAnimator.setDuration(500); // 匀速插值器 valueAnimator.setInterpolator(new LinearInterpolator()); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (Float) animation.getAnimatedValue(); // 获取当前点坐标封装到mCurrentPosition mPathMeasure.getPosTan(value, mCurrentPosition, null); startView.setTranslationX(mCurrentPosition[0]); startView.setTranslationY(mCurrentPosition[1]); } }); valueAnimator.start(); }
起点的坐标就是我们点击RecyclerView的item的坐标,根据传递过来的view进行计算的。终点的坐标我们这里进行了一下判断,根据上面RecyclerView的size进行判断。要是4的倍数,就移动到下一行,不然就添加在后面。大家看示例动态图可以发现不一样。
3.RecyclerView的增加删除动画
valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { //默认recyclerviewe的动画 allRecycle.setItemAnimator(new DefaultItemAnimator()); choseRecycle.setItemAnimator(new DefaultItemAnimator()); choseTabs.add(choseTabs.size(), allTabs.get(position)); allTabs.remove(position); //先更新数据 allAdapter.notifyDataSetChanged(); choseAdapter.notifyDataSetChanged(); //再更新动画 allAdapter.notifyItemRemoved(position); choseAdapter.notifyItemInserted(choseTabs.size()); linearLayout.removeView(startView); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } });
在移动动画完成以后,加上一个监听。这里RecyclerView的增加删除动画使用的是默认的动画。记得先更新数据,在加上动画,不然会出错!!!
4.RecyclerView的拖动排序与滑动删除
这里使用了RecyclerView的ItemTouchHelper类来实现了Item的拖动和删除功能,ItemTouchHelper是v7包下的一个类,专门用来配合RecyclerView实现滑动删除和拖拽功能的类。我们看看怎么使用。
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback { private onMoveAndSwipedListener mAdapter; public SimpleItemTouchHelperCallback(onMoveAndSwipedListener listener) { mAdapter = listener; } /** * 这个方法是用来设置我们拖动的方向以及侧滑的方向的 */ @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { //如果是ListView样式的RecyclerView //设置拖拽方向为上下左右都可以 final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; //设置侧滑方向为从左到右和从右到左都可以 final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; //将方向参数设置进去 return makeMovementFlags(dragFlags, swipeFlags); } /** * 当我们拖动item时会回调此方法 */ @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { //如果两个item不是一个类型的,我们让他不可以拖拽 if (viewHolder.getItemViewType() != target.getItemViewType()) { return false; } //回调adapter中的onItemMove方法 mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); return true; } /** * 当我们侧滑item时会回调此方法 */ @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { mAdapter.onItemDismiss(viewHolder.getAdapterPosition()); } }
(1)自定义一个类继承实现ItemTouchHelper.Callback接口,实现里面的三个方法,分别是设置拖动与侧滑的方向,拖动时回调的方法,侧滑时回调的方法。
然后将参数传递给 makeMovementFlags(dragFlags, swipeFlags)中
(2)如果我们设置了非0的dragFlags 与swipeFlags,那么当item被拖拽与侧滑的时候会不断的回调onMove与onSwiped方法,所以我们需要同时Adapter做出相应的改变,对mItems数据做出交换与删除的操作,因此我们需要一个回调接口来继续回调Adapter中的方法。
public interface onMoveAndSwipedListener { boolean onItemMove(int fromPosition , int toPosition); void onItemDismiss(int position); }
我们让TabsAdapter实现此接口,并且重写里面的方法
public class ChoseTabsAdapter extends RecyclerView.Adapter implements onMoveAndSwipedListener {}
重写拖动的方法,其实就是交换集合中指定元素的位置:
@Override public boolean onItemMove(int fromPosition, int toPosition) { //交换mItems数据的位置 Collections.swap(WangYiActivity.choseTabs, fromPosition, toPosition); //交换RecyclerView列表中item的位置 notifyItemMoved(fromPosition, toPosition); return true; }
滑动删除,就是拿到position进行数组的删除操作:
@Override public void onItemDismiss(int position) { //删除mItems数据 WangYiActivity.choseTabs.remove(position); //删除RecyclerView列表对应item notifyItemRemoved(position); }
再回到我们的SimpleItemTouchHelperCallback,在构造方法中将实现了onMoveAndSwipedListener接口的TabsAdapter 传进来。然后我们就在onMove()方法里获取当前拖拽的item和已经被拖拽到所处位置的item的ViewHolder,有了这2个ViewHolder,我们就可以拿到对应的position,然后调用传递过来的adapter中的onItemMove方法,这样adapter就会进行改变;在onSwiped()方法里面获取侧滑的item的position,然后调用传递过来的adapter中的onItemDismiss方法即可。
然后就是关联我们的ItemTouchHelper和RecyclerView:
//关联ItemTouchHelper和RecyclerView ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(choseAdapter); mItemTouchHelper = new ItemTouchHelper(callback); mItemTouchHelper.attachToRecyclerView(choseRecycle);
至此,RecyclerView的拖动排序与滑动删除就已经完成。
另外一些小细节,比如设置RecyclerView的间隔,获取RecyclerView的子item的view等,大家可以下载源码自己看看。
原文链接:http://www.apkbus.com/blog-822717-75864.html