什么是属性动画?

属性动画可以通过直接更改 View 的属性来实现 View 动画。例如:

  1. 通过不断的更改 View 的坐标来实现让 View 移动的效果;
  2. 通过不断的更改 View 的背景来实现让 View 的背景渐变的效果;
  3. 通过不断的更改 View 的宽高来实现让 View 变形的效果;
  4. ...

由此可见,利用属性动画几乎可以处理任何的涉及到 View 的动画效果。

实战

具体的细节就不多说了,网上相应的教程也不少。这篇博客主要是来实现类似于美团外卖购物车的效果。

分析

首先分析购物车动画具体的细节:在滑动过程中,“购物车”向右移动,直至一半隐藏到右侧;在手指停留在屏幕中时,“购物车”还隐藏在右侧;当手指离开屏幕,“购物车”在一定时间后重新移动回来

以上的动画细节可以分析出和 RecycleView 的滚动事件息息相关,因此动画就应该在 RecycleView  的滚动事件中实现。

实现基本布局

上图的蓝色图片既是我们要处理的 View 。

给 RecycleView 加上滚动事件

接下来给 RecycleView 加上滚动事件,滑动或者飞翔时图片消失,当停止滑动时图片显示。

 // 给rv加上滚动事件
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:// 滚动中
case RecyclerView.SCROLL_STATE_SETTLING:// 飞翔中
iv.setVisibility(View.GONE);
break;
case RecyclerView.SCROLL_STATE_IDLE:// 停止滚动
iv.setVisibility(View.VISIBLE);
break;
}
}
});

效果图:

实现消失的动画

根据上面的图可以发现触发时机基本是没问题了,接下来要做的是让消失不突兀,加上消失的动画。

消失的实质是 View 的 x 坐标从当前位置一直往右直到变为隐藏了一半,下面让我们来实现这个效果:

 // 消失动画的基本属性(从iv当前的x坐标一直到出了屏幕右侧一半)
disappearAnimator = ValueAnimator.ofFloat(iv.getX(), (float) (Utils.getScreenWidth(this) - iv.getWidth() / 2.0));
disappearAnimator.setDuration(400);// 动画持续时间
disappearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {// Value更新事件
float curValue = (float) animation.getAnimatedValue();
iv.setX(curValue);
}
}); // 给rv加上滚动事件
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:// 滚动中
case RecyclerView.SCROLL_STATE_SETTLING:// 飞翔中
disappearAnimator.start();
break;
case RecyclerView.SCROLL_STATE_IDLE:// 停止滚动
iv.setVisibility(View.VISIBLE);
break;
}
}
});

然而发现实际上动画是这样的:

发现他是从最左边一直移动到了最右边,与我们的需求不符。

经调试发现,在 onCreate 的时候 iv 尚未初始化完毕,因此宽高以及坐标都还不能获取到。所以获取到的坐标以及宽度都是0。

所以可以在滚动事件中获取 iv 的坐标以及宽高,更改后的代码如下:

 // 给rv加上滚动事件
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
// 获取iv的坐标以及宽高
if (0 == originX) {
originX = iv.getX();
ivWidth = iv.getWidth();
} switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:// 滚动中
case RecyclerView.SCROLL_STATE_SETTLING:// 飞翔中
// 消失动画的基本属性(从iv当前的x坐标一直到出了屏幕右侧一半)
if (disappearAnimator == null) {
disappearAnimator = ValueAnimator.ofFloat(originX, (float) (screenWidth - ivWidth / 2.0));
disappearAnimator.setDuration(400);// 动画持续时间
disappearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {// Value更新事件
float curValue = (float) animation.getAnimatedValue();
iv.setX(curValue);
}
});
} disappearAnimator.start();
break;
case RecyclerView.SCROLL_STATE_IDLE:// 停止滚动
iv.setVisibility(View.VISIBLE);
break;
}
}
});

效果如下图:

实现出现的动画

既然已经实现了消失的动画,那出现的动画也就不难了。出现的实质是 View 的 x 坐标从右侧一半一直运动到原始位置。

 case RecyclerView.SCROLL_STATE_IDLE:// 停止滚动
// 出现动画的基本属性(从屏幕右侧一半到原始位置)
if (appearAnimator == null) {
appearAnimator = ValueAnimator.ofFloat((float) (screenWidth - ivWidth / 2.0), originX);
appearAnimator.setDuration(400);// 动画持续时间
appearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {// Value更新事件
float curValue = (float) animation.getAnimatedValue();
iv.setX(curValue);
}
});
} appearAnimator.start();
break;

效果图如下:

但是发现如果频繁的滑动暂停的话动画会冲突,因此需要做一些判定,如果动画正在运行则不再重新开始动画。改动后的代码如下:

 switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:// 滚动中
case RecyclerView.SCROLL_STATE_SETTLING:// 飞翔中
// 消失动画的基本属性(从iv当前的x坐标一直到出了屏幕右侧一半)
if (disappearAnimator == null) {
disappearAnimator = ValueAnimator.ofFloat(originX, (float) (screenWidth - ivWidth / 2.0));
disappearAnimator.setDuration(400);// 动画持续时间
disappearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {// Value更新事件
float curValue = (float) animation.getAnimatedValue();
iv.setX(curValue);
}
});
} // 如果消失动画还未开始执行并且iv的位置在原始位置,则执行
if (!disappearAnimator.isStarted() && originX == iv.getX()) {
disappearAnimator.start();
}
break;
case RecyclerView.SCROLL_STATE_IDLE:// 停止滚动
// 出现动画的基本属性(从屏幕右侧一半到原始位置)
if (appearAnimator == null) {
appearAnimator = ValueAnimator.ofFloat((float) (screenWidth - ivWidth / 2.0), originX);
appearAnimator.setDuration(400);// 动画持续时间
appearAnimator.setStartDelay(700);// 延迟时间
appearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {// Value更新事件
float curValue = (float) animation.getAnimatedValue();
iv.setX(curValue);
}
});
} // 如果出现动画还未开始执行,则执行
if (!appearAnimator.isStarted()) {
appearAnimator.start();
}
break;
}

但是发现还是会有冲突,经检测,发现是出现动画和消失动画互相干扰的缘故。当滑动已暂停但出现动画还未执行完毕,此时重新滑动会触发消失动画

因此需要给出现动画加一个延迟,并且如果处于非暂停状态需要取消出现动画。(也许美团外卖暂停一段时间才开始出现的原因就是防止用户不停的滑动暂停吧。)

修改后的代码如下:

case RecyclerView.SCROLL_STATE_DRAGGING:// 滚动中
case RecyclerView.SCROLL_STATE_SETTLING:// 飞翔中
// 消失动画的基本属性(从iv当前的x坐标一直到出了屏幕右侧一半)
if (disappearAnimator == null) {
disappearAnimator = ValueAnimator.ofFloat(originX, (float) (screenWidth - ivWidth / 2.0));
disappearAnimator.setDuration(400);// 动画持续时间
disappearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {// Value更新事件
float curValue = (float) animation.getAnimatedValue();
iv.setX(curValue);
}
});
} // 如果此时出现动画已开始,则取消
if (appearAnimator != null && appearAnimator.isStarted()) {
appearAnimator.cancel();
} // 如果消失动画还未开始执行并且iv的位置在原始位置,则执行
if (!disappearAnimator.isStarted() && originX == iv.getX()) {
disappearAnimator.start();
}
break;

最终效果如图所示:

总结

  • 如果要实现其他的效果,例如淡入淡出等同理就可以实现;
  • 多个动画对统一个 View 做变换时一定要注意动画之间的冲突;
  • 属性动画+函数方程可以实现一些丰富多变的效果,待研究;
  • 本文的实现还是比较简陋,最好的效果是动画可以被打断,由于比较麻烦,所以没有实现。

Github地址:属性动画初战

大家如果有什么疑问或者建议可以通过评论或者邮件的方式联系我,欢迎大家的评论~

Android 属性动画实战的更多相关文章

  1. Android属性动画

    这几天看郭神的博客 Android属性动画完全解析(上),初识属性动画的基本用法之后,我自己突然想实现一种动画功能,就是我们在携程网.阿里旅行等等手机APP端买火车票的时候,看到有选择城市,那么就有出 ...

  2. 【转】android 属性动画之 ObjectAnimator

    原文网址:http://blog.csdn.net/feiduclear_up/article/details/39255083 前面一篇博客讲解了 android 简单动画之 animtion,这里 ...

  3. Android属性动画之ValueAnimation

    ValueAnimation是ObjectAnimation类的父类,经过前几天的介绍,相信大家对ObjectAnimation有了 一定的认识,今天就为大家最后介绍一下ValueAnimation, ...

  4. Android属性动画完全解析(下)

    转载:http://blog.csdn.net/guolin_blog/article/details/44171115 大家好,欢迎继续回到Android属性动画完全解析.在上一篇文章当中我们学习了 ...

  5. Android属性动画完全解析(上),初识属性动画的基本用法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43536355 在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系 ...

  6. Android属性动画完全解析(中)

    转载:http://blog.csdn.net/guolin_blog/article/details/43536355 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法,当然也是 ...

  7. Android属性动画完全解析(上)

    Android属性动画完全解析(上) 转载:http://blog.csdn.net/guolin_blog/article/details/43536355 在手机上去实现一些动画效果算是件比较炫酷 ...

  8. Android 属性动画(Property Animation) 全然解析 (下)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38092093 上一篇Android 属性动画(Property Animatio ...

  9. Android 属性动画 源码解析 深入了解其内部实现

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/42056859,本文出自:[张鸿洋的博客] 我参加了博客之星评选,如果你喜欢我的博 ...

随机推荐

  1. 微服务SpringCloud之服务调用与负载均衡

    上一篇我们学习了服务的注册与发现,本篇博客是在上一篇的基础上学习服务的调用.上一博客主要创建了Eureka的服务端和一个Client,该Client包含了一个Controller用来提供对外服务供外部 ...

  2. asyncio系列之sleep()实现

    先来看个例子,自己实现的模拟耗时操作 例1 import types import select import time import socket import functools class Fu ...

  3. 一套简单的web即时通讯——第三版

    前言 接上版,本次版本做了如下优化: 1.新增同意.拒绝添加好友后做线上提示: 2.新增好友分组,使用工具生成后台API,新增好友分组功能,主要功能有:添加分组.重命名分组名称.删除分组 3.新增好友 ...

  4. 2018.11.2 2018NOIP冲刺之最短公共父串

    很有意思的一个题 试题描述 给定字符串A和字符串B,要求找一个最短的字符串,使得字符串A和B均是它的子序列. 输入 输入包含两行,每行一个字符串,分别表示字符串A和字符串B.(串的长度不超过30) 输 ...

  5. 在windowx的Hyper-v 安装CentOS系统

    博客写的很少,一方面是因为我觉得目前很多博客都是相互抄袭,或者有很多部分都是重复的内容.而我自己再去写同样的内容的画,有点浪费时间. 所以,如果我要写,我希望是写一些与众不同,或者重复率比较低的内容, ...

  6. HDU 4444:Walk(思维建图+BFS)***

    http://acm.hdu.edu.cn/showproblem.php?pid=4444 题意:给出一个起点一个终点,给出n个矩形的两个对立顶点,问最少需要拐多少次弯可以从起点到达终点,如果不能输 ...

  7. 动态代理模拟实现aop

    AOP实现起来代码相当简单.主要核心是动态代理和反射. 一.接口类: public interface MethodDao { public void sayHello(); } 二.接口实现类: p ...

  8. 聊聊C语言的预编译指令include

    "include"相信大家不会陌生,在我们写代码时,开头总会来一句"include XXX".include是干嘛用的,很多教材都提到了,因此这里不会再详细解释 ...

  9. 提升布局性能____Making ListView Scrolling Smooth

    listview是一个比较重要的UI组件,一切影响UI的操作,比如适配器从磁盘.网络或者数据库中加载数据的操作,最好都放在子线程中完成.子线程可以使用thread,不过那样比较老土,官方推荐使用Asy ...

  10. [Spring+SpringMVC+Mybatis]框架学习笔记(六):事务

    第7讲 事务 7.1 事务的概念 事务是一系列作为一个逻辑单元来执行的操作集合. 它是数据库维护数据一致性的单位,它讲数据库从一个一致状态,转变为新的另外一个一致状态.说的简单一点就是:如果一组处理步 ...