博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/smile_running

Material Design 给出了一套标准的设计方案和动画效果,在 Android 开发方面,越来越多的 App 几乎都向 Material Design 风格靠拢,当然也是因为 Google 给出的设计效果确实好。我个人也非常喜欢这种简约而不失大方的风格,接下来我将通过一个案例来学习一下 Material Design 的一个跳转动画效果吧。

首先呢,大家肯定好奇是怎样的效果,先看看我实现的效果:

可以看到,点击 Fab 就会跳转到另一个 Activity,这其中有一段过渡动画,它是一种水波纹扩散效果,下面就是我将动画持续时间加到了 2S,方便大家仔细看看

其实,说是水波纹效果也没错,但它实际上是一个圆,通过不断地修改圆的半径,以达到一种波纹扩散的视觉效果。那么,我们来看看它是如何实现的吧。

相信学过动画和自定义 View 的人来说,这个效果其实并不难,无非是 draw 一个圆,然后通过属性动画不断的增大它的半径即可。下面我们直接看代码吧。

package nd.no.xww.mdanimationdemo;import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;/*** @author xww* @desciption :* @date 2020/1/17* @time 14:13*/
public class RippleView extends View {private static final String TAG = "RippleView";private Paint paint;private float startX;private float startY;private float radius;private onRippleListener onRippleListener;private void init() {paint = new Paint();paint.setAntiAlias(true);paint.setStyle(Paint.Style.FILL);paint.setDither(true);paint.setColor(getResources().getColor(R.color.colorAccent));}// 通过 ObjectAnimator 来开启动画,需要反射方式去设置 radius,因此要 setter() 方法public void setRadius(float radius) {this.radius = radius;invalidate();}public RippleView(Context context) {this(context, null);}public RippleView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public RippleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}public void startRippleAnimation(RipplePosition position) {onRippleListener.rippleState(RippleState.RIPPLE_START);this.startX = position.getX();this.startY = position.getY();float side = (float) Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2));@SuppressLint("ObjectAnimatorBinding")ObjectAnimator animator = ObjectAnimator.ofFloat(this, "radius", 0, side);animator.setDuration(300);animator.setInterpolator(new AccelerateInterpolator());animator.start();animator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);onRippleListener.rippleState(RippleState.RIPPLE_END);}});}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawCircle(startX, startY, radius, paint);}public interface onRippleListener {void rippleState(int state);}public void addOnRippleListener(RippleView.onRippleListener onRippleListener) {this.onRippleListener = onRippleListener;}
}

如上代码就是我们的波纹效果,首先我们绘制一个圆,肯定需要知道它的圆心坐标以及半径。可以看到图中的动画起始处,其实是 Fab 空间的中间坐标,如下图:

那么,如何获取这个 x,y 坐标呢,其实很简单,通过 Fab 的 getWidth 和 getHeight 以及 getX 和 getY 方法配合即可。代码如下:

    @Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.fab:Intent intent = new Intent(this, PersonalActivity.class);int startX = (int) (v.getX() + v.getWidth() / 2);int startY = (int) (v.getY() + v.getHeight() / 2);RipplePosition position = new RipplePosition(startX, startY);intent.putExtra("position", position);startActivity(intent);// 取消系统默认的 Activity 跳转动画overridePendingTransition(0, 0);break;}}

获取的 position 后,然后传到另一个 Activity 即可。我们在另一个 Activity 中取得这个 position 值,然后调用

ripple_view.startRippleAnimation(position);

便可拿到圆心的坐标,然后开启动画,如下关键代码:

    public void startRippleAnimation(RipplePosition position) {onRippleListener.rippleState(RippleState.RIPPLE_START);this.startX = position.getX();this.startY = position.getY();float side = (float) Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2));@SuppressLint("ObjectAnimatorBinding")ObjectAnimator animator = ObjectAnimator.ofFloat(this, "radius", 0, side);animator.setDuration(300);animator.setInterpolator(new AccelerateInterpolator());animator.start();animator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);onRippleListener.rippleState(RippleState.RIPPLE_END);}});}

这里的 ObjectAnimator 通过反射的方式调用自身的 radius 方法,不断的改变 radius 的值,所以通过反射的方式,就必须实现如下的 setter 方法,否则将反射失败。

    // 通过 ObjectAnimator 来开启动画,需要反射方式去设置 radius,因此要 setter() 方法public void setRadius(float radius) {this.radius = radius;invalidate();}

动画执行过程中,每一次都需要重新绘制 View,所以要刷新一下,调用 invalidate() 方法。

好了,如上就是水波纹扩散动画的实现方式,这个效果我们以及初步完成了一半,接下来就是水波纹扩散过后的效果了,我把动画放慢了,再看看如何

首先呢,是头部的图片以及文字往下移动,然后中间那部分文字是从左往右平移的,最后是 RecyclerView 的一个 Item 加载时的动画效果,通过这几种动画组合在一起,以达到一种视觉上的效果,给用户良好的体验。

这几部分动画就比较简单了,通过平移、缩放和插值器结合一起完成。首先呢,需要注意的一点,这些动画都是在水波纹扩散动画之后才进行的,也就是我们需要监听水波纹动画结束,这里我们通过一个接口来监听,代码如下:

    public interface onRippleListener {void rippleState(int state);}public void addOnRippleListener(RippleView.onRippleListener onRippleListener) {this.onRippleListener = onRippleListener;}

这里的水波纹状态,初步设置为两种

package nd.no.xww.mdanimationdemo;/*** @author xww* @desciption :* @date 2020/1/17* @time 14:36*/
public class RippleState {public static final int RIPPLE_START = 1;public static final int RIPPLE_END = 2;
}

由于再水波纹扩散动画执行完成之前的时间段内,我们需要将那些图片、文字之类的 View 先进行隐藏,并且将这些 View 设置到平移之前的位置,这个很关键,因为它需要从起始处平移进来。

接着,在水波纹动画结束后,将 View 进行显示,并且此时开启平移动画,代码如下:

    @Overridepublic void rippleState(int state) {switch (state) {case RippleState.RIPPLE_START:rl_author.setVisibility(View.INVISIBLE);iv_photo.setVisibility(View.INVISIBLE);iv_photo.setTranslationY(-iv_photo.getHeight());tv_name.setVisibility(View.INVISIBLE);tv_name.setTranslationY(-tv_name.getHeight());tv_blog.setVisibility(View.INVISIBLE);tv_blog.setTranslationY(-tv_blog.getHeight());ll_articles.setVisibility(View.INVISIBLE);ll_articles.setTranslationX(-ll_articles.getWidth());rv_favor.setVisibility(View.INVISIBLE);ripple_view.setVisibility(View.VISIBLE);break;case RippleState.RIPPLE_END:rl_author.setVisibility(View.VISIBLE);iv_photo.setVisibility(View.VISIBLE);iv_photo.animate().translationY(0).setDuration(300).setInterpolator(new AccelerateInterpolator()).start();tv_name.setVisibility(View.VISIBLE);tv_name.animate().translationY(0).setDuration(300).setInterpolator(new AccelerateInterpolator()).start();tv_blog.setVisibility(View.VISIBLE);tv_blog.animate().translationY(0).setDuration(300).setInterpolator(new AccelerateInterpolator()).start();ll_articles.setVisibility(View.VISIBLE);ll_articles.animate().translationX(0).setDuration(300).setInterpolator(new AccelerateInterpolator()).start();rv_favor.setVisibility(View.VISIBLE);ripple_view.setVisibility(View.GONE);break;}}

这样的话,我们就基本完成了这个组合动画的实现了,还有就是在适配器中的动画,通过滑动 RecyclerView 时,会产生一种抖动的效果,关键代码如下:

    @Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int i) {holder.imageView.setImageResource(mData.get(i));AnimatorSet animatorSet = new AnimatorSet();ObjectAnimator scaleX = ObjectAnimator.ofFloat(holder.imageView, "scaleX", 0.95f, 1f);ObjectAnimator scaleY = ObjectAnimator.ofFloat(holder.imageView, "scaleY", 0.95f, 1f);animatorSet.playTogether(scaleX, scaleY);animatorSet.setDuration(300);animatorSet.setInterpolator(new AnticipateOvershootInterpolator());animatorSet.start();}

通过对 X 和 Y 的缩放,配合插值器,就可以达到这样的效果。

不过,也许你会发现你已经掉入了一个坑中,你会发现水波纹扩散动画,其实它是无法显示出来的。这个是什么原因呢,我们来分析一下。

我们在 onCreate() 方法中,直接开启水波纹动画,代码如下

    @Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_personal);initView();position = (RipplePosition) getIntent().getSerializableExtra("position");data = new ArrayList<>();random = new Random();for (int i = 0; i < 30; i++) {data.add(pics[random.nextInt(9)]);}rv_favor.setLayoutManager(new GridLayoutManager(this, 3));PictureAdapter adapter = new PictureAdapter(data);rv_favor.setAdapter(adapter);ripple_view.addOnRippleListener(this);ripple_view.startRippleAnimation(position);}

我们直接在 onCreate 中调用 ripple_view.startRippleAnimation(position) 是不行的,因为 View Tree 的绘制过程中,我们无法保证这个 View 在 onDraw 方法执行之前开启动画效果,所以它就无法显示出来。

那么,如何保证在 onDraw 方法执行之前将动画开启呢,这个 android 给我们提供了一个 api,我们可以通过如下代码,进行监听

        ripple_view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {@Overridepublic boolean onPreDraw() {ripple_view.getViewTreeObserver().removeOnPreDrawListener(this);ripple_view.startRippleAnimation(position);return true;}});

此方法,就是当 onDraw 准备开始绘制的时候回调这个监听,所以我们要将 ripple_view.startRippleAnimation(position) 放在 onDraw 之前开启动画,并且这个监听需要进行移除,否则将一直处于监听中,阻塞主线程。好了,到此这个动画效果算是实现完成了,不过太多的动画效果势必会占用较大的性能。

Android Material Design 之 Activity 跳转水波纹扩散动画相关推荐

  1. android 水波纹扩散动画,[Android]多层波纹扩散动画——自定义View绘制

    之前整理过一些属性动画的基本操作,这一段时间的动画相关需求都安然度过了.直到这次-- 一.另一种动画需求 多数交互中的动画都是让单个页面元素动起来,这种就很适合用属性动画实现.但是对于 多个元素.非页 ...

  2. css —— 按钮水波纹扩散动画效果实现

    一. 效果 ​​​​​​​ 二. 代码 <div class="feature"><div class="feature-box">&l ...

  3. Android点击水波纹扩散效果整理(附带一个自定义的水波纹效果控件)

    很久很久没有写博客了,说来也有点惭愧.正好最近整理自己的项目工程目录,看到一些值得分享的控件,准备在之后的几篇博客中准备把它们陆续搬运上来. 这篇博客准备整理一下Android Material De ...

  4. Android Material Design TabLayout属性app:tabMode和app: tabGravity

    Android Material Design TabLayout属性app:tabMode和app: tabGravity Android Material Design 中的TabLayout有两 ...

  5. Android Material Design简单使用 http://www.cnblogs.com/android-blogs/p/5632103.html

    Android Material Design简单使用 吐槽 作为一个 Android developer,没有什么比拿着 UI 设计的一堆 iOS 风格的设计 来做需求更恶心的了,基本所有空间都要照 ...

  6. Android Material Design按钮样式

    本文翻译自:Android Material Design Button Styles I'm confused on button styles for material design. 我对材质设 ...

  7. Android Material Design :LinearLayoutCompat添加分割线divider

     Android Material Design :LinearLayoutCompat添加分割线divider Android Material Design 扩展支持包中的LinearLayo ...

  8. android夜间模式揭露动画,Android Material Design系列之夜间模式

    今天我们讲讲夜间模式的实现,这篇文章的名字应该叫:<Android Material Design系列之夜间模式>.在Android 5.0 之后,实现夜间模式并非很难了,支持的5.0库提 ...

  9. Android Material Design按钮样式设计

    Today we'll dive deep into Android Buttons in Material Design and develop an application that showca ...

最新文章

  1. python学习第四课
  2. Y君:天天增删改查,又能怎么样?
  3. CEC tile configuration of Launchpad shell is returned by http request
  4. 白话经典算法系列之一 冒泡排序的三种实现
  5. Spark GraphX算法 - Connected Components(连通分支)算法
  6. ubuntu 10.04 虚拟机建立tftp服务器
  7. Web工程师必备的可视化工具
  8. 第一次提交本地代码到github上
  9. 经典神经网络 -- VGG : 设计原理与pytorch实现
  10. hp t410微型计算机使用,HP 发表新款 t410 AIO Smart Zero 精简型电脑,仅需网络线即可作为电源驱动使用...
  11. ppt设置外观样式_ppt怎么设置幻灯片的背景一样?
  12. Delphi FastReport组件下载,包含多个版本,自己选择
  13. 【H3C模拟器】华三交换机配置IRF堆叠
  14. Ubuntu20.04安装中国版firefox
  15. python中mapping_python-学习-ORM中遇到的 mapping 详解并再总结字典dict
  16. 【MongoDB】mongodb | 安装 | 使用 | mdb
  17. 深读5G发展的趋势后带给我的感受
  18. 基于单片机的点光源控制系统
  19. 亚洲领军汽车产业展会Automechanika Shanghai开幕丨Xtecher 前线
  20. 解决:Cause: java.sql.SQLException: Incorrect integer value: ‘xxx‘ for column ‘xxx‘ at row 1

热门文章

  1. 短视频防止侵权之路——今抖云创
  2. ashx PHP文件 优劣,ashx的文件如何控制安全性
  3. Java实现 蓝桥杯VIP 算法提高 分分钟的碎碎念
  4. 咖啡豆香味开始与气候和生长地区
  5. SuperBenchmarker 压测工具
  6. yyds!用飞桨玩明日方舟
  7. 银行卡验证(验证是否存在,卡号类型,归属行)
  8. 《惢客创业日记》2019.02.11(周一) 终于和咸鱼见面了
  9. ios 内购正式环境_iOS开发-2017苹果内购最新教程
  10. C语言鹏哥学习笔记(初识)