Android 补间动画Animation的实用应用

2023-12-22 19:38

本文主要是介绍Android 补间动画Animation的实用应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载请注明:
http://blog.csdn.net/sinat_30276961/article/details/49868409

上一篇,我总结了补间动画的各种特性,并写了个小的应用来使用这些特性。本篇,将在上篇的基础上,更进一步的使用Android的Animation,让你的应用不断炫起来~~

ok,闲话少说,开始正题~

先看一下效果:

第一个实例:
第一个实例

第二个实例:
第二个实例

先大致讲一下这两个实例用到了Animation的哪些特性。

第一个实例,我想大部分朋友都看到过了,最初是一个老外写的卫星菜单,然后有很多人尝试去实现,我也是其中一个。
这个实例中需要用到的特性是:
Animation中的透明度的渐变,平移和旋转。外加一个动画保持fillAfter。

第二个实例用到了Animation的渐变的最核心的特性,那就是值从0到1变化,并可以通过插值器进行过程控制。

接下来,我一个个来讲解下。

卫星菜单实现

要实现卫星菜单,有两种方案,一个是通过Android平台里的动画效果来实现,另一个是单纯的用画来实现。

这两个方案中,第二个方案是不可取的,虽然实现也不是很复杂,但是相比较第一种,还是挺复杂的。

第一个方案又能有两种实现方式:用补间动画和属性动画。
因为本篇是讲补间动画的应用,所以这里就用补间动画来实现。

这里,需要考虑的是补间动画虽然可以实现动画效果,但是view本身没有做相应的属性改变,所以,如果仅仅创建一组可动画的菜单view,然后动画到了最外围,去点击时是无效的。那怎么解决呢?

我们可以创建两组菜单view,一组负责动画,一组负责响应点击。这样一来,这两组view的位置就可以确定了:负责动画的view都放在最初始的地方;负责响应的view放在动画结束的地方。然后只要控制下view的可见不可见就可以了。

接着是动画效果的选择。
每个动画view在展开时,需要哪些动画呢?一个是自身旋转;一个是平移。然后插值器要选择OvershootInterpolator,达到一个稍微过头再回来的效果。动画view收回时,就是上述的反动画效果。注意,这里需要设置fillAfter=true,原因就不用我多说了吧。
然后是菜单点击选择时,动画view需要哪些动画呢?被点击的菜单view需要两个动画效果:一个是透明度渐变,因为最后要消失;一个是放大。没被选择的菜单view也需要两个:一个是透明度渐变,还有一个是缩小。注意,这里fillAfter=false,因为动画完之后,希望它们回到最初始的地方。
最后是控制打开关闭的菜单view需要的动画,很简单,旋转。

需要注意的就这些。

接下去开始实现:

先定义一些属性:

<resources><attr name="num" format="integer"/><attr name="radius" format="dimension"/><declare-styleable name="UseTweenAnimationView"><attr name="num"/><attr name="radius"/></declare-styleable>
</resources>

这里,我定义了两个属性:菜单的数量;菜单的半径。当然你可以多定义一些,比方说动画时间等等。

然后是初始化:

public class UseTweenAnimationView extends ViewGroup {/*** mRadius = mMenuRadius *  RADIUS_TIME;*/private static final int RADIUS_TIME = 8;/*** 每个菜单的默认半径(dp)*/private static final int DEF_MENU_RADIUS = 25;/*** 动画时间*/private static final int DURATION = 1000;private int mWidth;private int mHeight;/*** 每个菜单的半径*/private int mMenuRadius;/*** 每个菜单相对于界面所在的半径*/private int mRadius;/*** 菜单数量*/private int mMenuNum;/*** 标记菜单打开关闭情况*/private boolean mMenuClosed = true;/*** 标记是否在动画中*/private boolean mIsAnimation = false;public UseTweenAnimationView(Context context) {this(context, null);}public UseTweenAnimationView(Context context, AttributeSet attrs) {super(context, attrs);TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.UseTweenAnimationView);mMenuNum = ta.getInteger(R.styleable.UseTweenAnimationView_num, 5);mMenuRadius = ta.getDimensionPixelSize(R.styleable.UseTweenAnimationView_radius,(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25, getResources().getDisplayMetrics()));mRadius = mMenuRadius * RADIUS_TIME;ta.recycle();// 控件的宽高留个菜单直径的余量mWidth = mRadius + mMenuRadius * 4;mHeight = mWidth;// 根据数量,创建动画菜单,这里偷懒直接用textviewfor (int i = 0; i < mMenuNum; i++) {TextView menuView = new TextView(context);menuView.setBackgroundResource(R.drawable.round_bg);menuView.setText(String.valueOf(i + 1));menuView.setTextColor(Color.WHITE);menuView.setGravity(Gravity.CENTER);LayoutParams menuViewLP = new LayoutParams(mMenuRadius * 2,mMenuRadius * 2);menuView.setLayoutParams(menuViewLP);addView(menuView);}// 根据数量,创建用于点击响应菜for (int i = 0; i < mMenuNum; i++) {TextView menuView = new TextView(context);menuView.setBackgroundResource(R.drawable.round_bg);menuView.setText(String.valueOf(i + 1));menuView.setTextColor(Color.WHITE);menuView.setGravity(Gravity.CENTER);LayoutParams menuViewLP = new LayoutParams(mMenuRadius * 2,mMenuRadius * 2);menuView.setLayoutParams(menuViewLP);menuView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {clickMenuItem(v);}});addView(menuView);}// 创建开关菜单ImageView controlView = new ImageView(context);controlView.setImageResource(R.drawable.add);LayoutParams lp = new LayoutParams(mMenuRadius * 2,mMenuRadius * 2);controlView.setLayoutParams(lp);controlView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if (!mIsAnimation) {mIsAnimation = true;animControlView(v);toggleMenu();}}});addView(controlView);}

代码可能有点长,不过不复杂。继承自ViewGroup这无可厚非,因为要包含很多view。然后,定义了一些变量值,这里的mIsAnimation这个是为了防止用户在动画还没结束时就点击。
控件的宽和高是一样长的,都为mRadius加上两个菜单view的直径。多了一个菜单view直径的预留是为了给overshoot插值器和放大动画效果预留空间。

然后是先添加动画菜单,再添加响应菜单,最后添加控制开关菜单。这里可以看到都设置了LayoutParams。早加晚加都要加,干脆就放这里了。

菜单view点击响应的clickMenuItem和控制菜单的点击响应animControlView和toggleMenu后面会再贴出来。

ok,接下去是测量尺寸和放置控件

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);final LayoutParams lp = child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}setMeasuredDimension(mWidth, mHeight);}/*** 这里偷懒了,明确大小** @param spec* @param childDimension* @return*/private int getChildMeasureSpec(int spec, int childDimension) {return MeasureSpec.makeMeasureSpec(childDimension, MeasureSpec.EXACTLY);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {if (changed) {// 先放动画菜单for (int i = 0; i < mMenuNum; i++) {View child = getChildAt(i);child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());child.setVisibility(View.INVISIBLE);}// 再放用于响应的菜单// 计算一个角度final double angle = Math.PI / 2 / (mMenuNum - 1);for (int i = 0; i < mMenuNum; i++) {View child = getChildAt(i + mMenuNum);int cl = 0;int ct = 0;if (i == 0) {cl = 0;ct = mRadius;} else if (i == mMenuNum - 1) {cl = mRadius;ct = 0;} else {cl = (int) (mRadius * Math.sin(angle * i));ct = (int) (mRadius * Math.cos(angle * i));}child.layout(cl, ct, cl + mMenuRadius * 2, ct + mMenuRadius * 2);child.setVisibility(View.INVISIBLE);}// 最后放开关菜单View controlView = getChildAt(mMenuNum * 2);controlView.layout(0, 0, controlView.getMeasuredWidth(), controlView.getMeasuredHeight());}}

这里,测量尺寸时,我偷懒了,每个view长宽直接用固定值,也就是最初读入进来的半径*2。然后整个控件的宽高在上面有讲过,就是mRadius+view的直径*2,预留一个view的直径。

然后是放置view。负责动画的view的位置很简单,就是0,0点;负责响应的view的位置稍微麻烦点。需要通过sin,cos去计算一下它们的x和y点。
为了方便讲解,我贴张图:
这里写图片描述

view1和view5的坐标很好计算,它们的x点分别是0和mRadius,y点分别是mRadius和0。然后是view2,view3,view4,它们的x和y需要通过弧度去计算,先算出90度被分为4个一样大小的小角度a,然后view2的x就是sina*mRadius,view3的x是sin(2a)*mRadius,以此类推,应该不难理解。

ok,尺寸量好,位置放好,接着就是设置响应了。
在上面已经有贴出调用点,就是初始化那里。这里就贴出详细定义的方法:

    private void animControlView(View v) {RotateAnimation rotateAnimation = new RotateAnimation(0, 270,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);rotateAnimation.setDuration(DURATION);rotateAnimation.setFillAfter(true);v.startAnimation(rotateAnimation);}

上面就是控制菜单点击的动画效果。
接着是toggleMenu

    private void toggleMenu() {final double angle = Math.PI / 2 / (mMenuNum - 1);for (int i = 0; i < mMenuNum; i++) {final View animChild = getChildAt(i);final View showChild = getChildAt(mMenuNum+i);int cl = 0;int ct = 0;if (i == 0) {cl = 0;ct = mRadius;} else if (i == mMenuNum - 1) {cl = mRadius;ct = 0;} else {cl = (int) (mRadius * Math.sin(angle * i));ct = (int) (mRadius * Math.cos(angle * i));}animChild.setVisibility(View.VISIBLE);if (!mMenuClosed) {showChild.setVisibility(View.INVISIBLE);}AnimationSet animationSet = new AnimationSet(true);animationSet.setInterpolator(new OvershootInterpolator(2f));TranslateAnimation translateAnimation = null;RotateAnimation rotateAnimation = null;if (mMenuClosed) {translateAnimation = new TranslateAnimation(0, cl, 0, ct);rotateAnimation = new RotateAnimation(0, 1440,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);} else {translateAnimation = new TranslateAnimation(cl, 0, ct, 0);rotateAnimation = new RotateAnimation(1440, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);}translateAnimation.setFillAfter(true);rotateAnimation.setFillAfter(true);// 设置一些延迟时间translateAnimation.setStartOffset((i * 100) / mMenuNum);rotateAnimation.setStartOffset((i * 100) / mMenuNum);animationSet.addAnimation(rotateAnimation);animationSet.addAnimation(translateAnimation);animationSet.setDuration(DURATION);final int position = i;animationSet.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {if (mMenuClosed) {animChild.setVisibility(View.INVISIBLE);showChild.setVisibility(View.VISIBLE);if (position == mMenuNum - 1) {mIsAnimation = false;mMenuClosed = false;}} else {animChild.setVisibility(View.INVISIBLE);showChild.setVisibility(View.INVISIBLE);if (position == mMenuNum - 1) {mIsAnimation = false;mMenuClosed = true;}}}@Overridepublic void onAnimationRepeat(Animation animation) {}});animChild.startAnimation(animationSet);}}

这里,要先计算出动画到哪个位置。当然,你也可以在onLayout那里把坐标保存下来,然后这里直接使用。确定好动画的路径,接着就是创建组合动画效果:旋转加平移。这里添加的顺序很重要:先添加旋转,再添加平移,至于为什么,你可以试试。

然后可以设置一些延迟,让菜单响应有个先后的感觉。然后监听一下动画,获取到动画结束回调,然后做处理。这里可能有朋友有疑问,为啥每个动画都做监听,给最后一个设置监听不就行了,因为最后一个结束了,也就意味着全部结束了。没错,这样思路是对的,不过,这样会有一个不好的效果,为啥呢?因为我在前面加了延时动画,如果给最后一个设置监听,然后在动画结束得到回调时再去遍历每个view,去隐藏,就会看起来不自然,所以只能给每个做动画监听,每次结束就马上把当前的动画view隐藏起来。至于为啥要隐藏,应该不用我说吧。

ok,最后就是点击菜单时的动画效果了:

    private void clickMenuItem(final View v) {AnimationSet animationSet = new AnimationSet(true);animationSet.setInterpolator(new AccelerateInterpolator(1));animationSet.setDuration(DURATION/2);ScaleAnimation scaleAnimation = new ScaleAnimation(1, 2, 1, 2,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);animationSet.addAnimation(scaleAnimation);animationSet.addAnimation(alphaAnimation);animationSet.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {v.setVisibility(View.INVISIBLE);}@Overridepublic void onAnimationRepeat(Animation animation) {}});v.startAnimation(animationSet);for (int i = 0; i < mMenuNum; i++) {final View showChild = getChildAt(i+mMenuNum);if (showChild != v) {AnimationSet as = new AnimationSet(true);as.setInterpolator(new AccelerateInterpolator(3));as.setDuration(DURATION/2);ScaleAnimation sa = new ScaleAnimation(1, 0, 1, 0,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);AlphaAnimation aa = new AlphaAnimation(1, 0);as.addAnimation(sa);as.addAnimation(aa);as.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {showChild.setVisibility(View.INVISIBLE);mMenuClosed = true;}@Overridepublic void onAnimationRepeat(Animation animation) {}});showChild.startAnimation(as);}}}

照例,使用组合动画,点击的view添加放大和透明的动画;其余view添加缩小和透明的动画。

至此,一个鲜活的卫星菜单就完成了,这样一路下来不难吧。

闪亮的文字

接着,我来讲讲闪亮的文字怎么实现。
这个的实现需要用到的核心技术是LinearGradient。什么是LinearGradient,就是线性渐变,通俗点讲,就是可以设置颜色的线性变化。比方说,我起点设置白色,终点设置黑色,然后设置到一个空图上,就会出现从白色渐变到黑色的颜色。

那为啥这个会和animation扯到一起呢?其实,这只是我的心血来潮。当然,你也可以不用animation。

既然扯到animation,那就讲一下怎么把Animation使用进去。大家都知道,Animation它的值变化过程是从0到1或者从1到0,然后插值器控制从0到1的变化过程。既然如此,那么我们就可以利用这0到1的自动产生值的特性,用比例去放大它,就能控制很多方式了。

不懂?我举个例子,我现在要实现月亮绕地球旋转的效果。我有很多种方案去实现月亮绕行的速度,有一个办法就是用animation。首先,我要知道月亮绕地球的轨迹的长度,然后通过animation从0到1变化的特性,把这个值再乘以月亮绕行轨迹长度,不就可以不用计算速度了吗。因为我给animation安个线性插值器,它的比例自然是线性增长,也就是匀速了。最妙的是,我觉得匀速缺乏美感,那安个OverShoot..啥的,你想咋地就咋地,不用你自己去设定这个变化过程,animation本身会提供给你值,多妙哉!

ok,闲话就说到这,我们来实现。

public class ShiningTextView extends TextView{/**  * 可以加入插值器,简单实现更多特效*/private Animation mAnimation;/**  * 线性颜色变化控制类*/private LinearGradient mLinearGradient;private Matrix mGradientMatrix;private int mWidth = 0;private int mHeight = 0;private float mOffset = 0;public ShiningTextView(Context context) {this(context, null);}public ShiningTextView(Context context, AttributeSet attrs) {super(context, attrs);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);if (mWidth == 0) {mWidth = getMeasuredWidth();if (mWidth > 0) {final Paint paint = getPaint();mGradientMatrix = new Matrix();mLinearGradient = new LinearGradient(-mWidth, 0, 0, 0, new int[] {0x33ffffff, 0xffffffff, 0x33ffffff}, new float[] {0, 0.5f, 1}, Shader.TileMode.CLAMP);paint.setShader(mLinearGradient);}}}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mGradientMatrix.setTranslate(mOffset, 0);mLinearGradient.setLocalMatrix(mGradientMatrix);}public void startShining(Interpolator interpolator, int duration, int repeatCount) {mAnimation = new Animation() {@Overrideprotected void applyTransformation(float interpolatedTime,Transformation t) {super.applyTransformation(interpolatedTime, t);mOffset = interpolatedTime*mWidth+mWidth;invalidate();}};mAnimation.setAnimationListener(new AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationRepeat(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {getPaint().setShader(null);}});mAnimation.setDuration(duration);mAnimation.setInterpolator(interpolator);mAnimation.setRepeatCount(repeatCount);mAnimation.setRepeatMode(Animation.RESTART);startAnimation(mAnimation);}
}

代码不多,我就直接贴出来了。
重点就是applyTransformation的应用。它传进来的interpolatedTime是从0到1变化的,这个变化在你设置的duration时间里,变化过程由interpolator插值器决定,很简单明了吧。

至此,这两个实例就讲到这。
感兴趣的朋友可以下载源码(用Android studio创建的):代码入口

这篇关于Android 补间动画Animation的实用应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/525213

相关文章

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

macOS无效Launchpad图标轻松删除的4 种实用方法

《macOS无效Launchpad图标轻松删除的4种实用方法》mac中不在appstore上下载的应用经常在删除后它的图标还残留在launchpad中,并且长按图标也不会出现删除符号,下面解决这个问... 在 MACOS 上,Launchpad(也就是「启动台」)是一个便捷的 App 启动工具。但有时候,应

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

Java中&和&&以及|和||的区别、应用场景和代码示例

《Java中&和&&以及|和||的区别、应用场景和代码示例》:本文主要介绍Java中的逻辑运算符&、&&、|和||的区别,包括它们在布尔和整数类型上的应用,文中通过代码介绍的非常详细,需要的朋友可... 目录前言1. & 和 &&代码示例2. | 和 ||代码示例3. 为什么要使用 & 和 | 而不是总是使

Python循环缓冲区的应用详解

《Python循环缓冲区的应用详解》循环缓冲区是一个线性缓冲区,逻辑上被视为一个循环的结构,本文主要为大家介绍了Python中循环缓冲区的相关应用,有兴趣的小伙伴可以了解一下... 目录什么是循环缓冲区循环缓冲区的结构python中的循环缓冲区实现运行循环缓冲区循环缓冲区的优势应用案例Python中的实现库

SpringBoot整合MybatisPlus的基本应用指南

《SpringBoot整合MybatisPlus的基本应用指南》MyBatis-Plus,简称MP,是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,下面小编就来和大家介绍一下... 目录一、MyBATisPlus简介二、SpringBoot整合MybatisPlus1、创建数据库和

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式