高仿网易新闻栏目动画效果

tyktfj0910 的博客地址:http://blog.csdn.net/tyk0910


效果预览


今天准备用RecyclerView来实现网易新闻Tabs的动态效果。先看效果图:




点击下面的RecyclerView的item,会有一个view的移动的动画;动画完成以后,下面的RecyclerView会有一个item删除的动画,对应上面的RecyclerView有一个item增加的动画;然后拖动上面RecyclerView的item可以进行排序,左右滑动可以进行删除。整体效果还是和网易新闻的Tabs很像,细节上处理稍微有点不一样。网易上面的item是点击删除,我这里处理成了滑动删除。


点击事件


接口回调是处理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) {
       //逻辑处理
   } }


移动动画


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类型的参数,这里传入 和 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的倍数,就移动到下一行,不然就添加在后面。大家看示例动态图可以发现不一样。


增加删除动画


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的增加删除动画使用的是默认的动画。记得先更新数据,在加上动画,不然会出错!!!



拖动排序与滑动删除


这里使用了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());    } }

自定义一个类继承实现 ItemTouchHelper.Callback 接口,实现里面的三个方法,分别是设置拖动与侧滑的方向,拖动时回调的方法,侧滑时回调的方法。 然后将参数传递给 makeMovementFlags(dragFlags, swipeFlags) 中。


如果我们设置了非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的拖动排序与滑动删除就已经完成。


本篇文章只对核心代码进行了讲解,完整的项目源码大家可以点击最下方的 阅读原文 进行下载。





如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:


阅读原文



如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值