Android Material Design 之 Activity 跳转水波纹扩散动画
博主声明:
转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。
本文首发于此 博主:威威喵 | 博客主页: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 跳转水波纹扩散动画相关推荐
- android 水波纹扩散动画,[Android]多层波纹扩散动画——自定义View绘制
之前整理过一些属性动画的基本操作,这一段时间的动画相关需求都安然度过了.直到这次-- 一.另一种动画需求 多数交互中的动画都是让单个页面元素动起来,这种就很适合用属性动画实现.但是对于 多个元素.非页 ...
- css —— 按钮水波纹扩散动画效果实现
一. 效果 二. 代码 <div class="feature"><div class="feature-box">&l ...
- Android点击水波纹扩散效果整理(附带一个自定义的水波纹效果控件)
很久很久没有写博客了,说来也有点惭愧.正好最近整理自己的项目工程目录,看到一些值得分享的控件,准备在之后的几篇博客中准备把它们陆续搬运上来. 这篇博客准备整理一下Android Material De ...
- Android Material Design TabLayout属性app:tabMode和app: tabGravity
Android Material Design TabLayout属性app:tabMode和app: tabGravity Android Material Design 中的TabLayout有两 ...
- Android Material Design简单使用 http://www.cnblogs.com/android-blogs/p/5632103.html
Android Material Design简单使用 吐槽 作为一个 Android developer,没有什么比拿着 UI 设计的一堆 iOS 风格的设计 来做需求更恶心的了,基本所有空间都要照 ...
- Android Material Design按钮样式
本文翻译自:Android Material Design Button Styles I'm confused on button styles for material design. 我对材质设 ...
- Android Material Design :LinearLayoutCompat添加分割线divider
Android Material Design :LinearLayoutCompat添加分割线divider Android Material Design 扩展支持包中的LinearLayo ...
- android夜间模式揭露动画,Android Material Design系列之夜间模式
今天我们讲讲夜间模式的实现,这篇文章的名字应该叫:<Android Material Design系列之夜间模式>.在Android 5.0 之后,实现夜间模式并非很难了,支持的5.0库提 ...
- Android Material Design按钮样式设计
Today we'll dive deep into Android Buttons in Material Design and develop an application that showca ...
最新文章
- python学习第四课
- Y君:天天增删改查,又能怎么样?
- CEC tile configuration of Launchpad shell is returned by http request
- 白话经典算法系列之一 冒泡排序的三种实现
- Spark GraphX算法 - Connected Components(连通分支)算法
- ubuntu 10.04 虚拟机建立tftp服务器
- Web工程师必备的可视化工具
- 第一次提交本地代码到github上
- 经典神经网络 -- VGG : 设计原理与pytorch实现
- hp t410微型计算机使用,HP 发表新款 t410 AIO Smart Zero 精简型电脑,仅需网络线即可作为电源驱动使用...
- ppt设置外观样式_ppt怎么设置幻灯片的背景一样?
- Delphi FastReport组件下载,包含多个版本,自己选择
- 【H3C模拟器】华三交换机配置IRF堆叠
- Ubuntu20.04安装中国版firefox
- python中mapping_python-学习-ORM中遇到的 mapping 详解并再总结字典dict
- 【MongoDB】mongodb | 安装 | 使用 | mdb
- 深读5G发展的趋势后带给我的感受
- 基于单片机的点光源控制系统
- 亚洲领军汽车产业展会Automechanika Shanghai开幕丨Xtecher 前线
- 解决:Cause: java.sql.SQLException: Incorrect integer value: ‘xxx‘ for column ‘xxx‘ at row 1