自定义View+属性动画实战 — 灵动的锦鲤
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义View+属性动画实战 — 灵动的锦鲤相关的知识,希望对你有一定的参考价值。
参考技术A通过自定义View+属性动画 实现一个会动鱼
分析:
1.画一条鱼
2.鱼原地动
3.鱼向点击处游动
鱼分为:鱼头(圆) + 身体(两条直线+两条贝塞尔曲线) + 鱼鳍(一条直线+一个贝塞尔)+尾巴(两三角)+节肢*2 (梯形+两圆)
先把鱼水平朝右,画一个坐标系,鱼的重心为坐标系中心
头圆半径的4.19倍,这个其实是自己定的,5f,6f都行,就是只改变鱼的长度,用鱼头半径做初始单位有利于改变整个鱼的大小。
初中知识:
所以可以得出: 入参一个点、两点长度、对于x轴的角度。返回值 一个坐标
定义画笔
mPaint.setDither(true);防抖动
mPaint.setAntiAlias(true); 抗锯齿
画鱼头:找到鱼头圆心,入参:重心、鱼身长一半、鱼的朝向(默认180跟重心一个方向)
画鱼鳍
鱼鳍是一个直线+一个二阶贝塞尔曲线,所以重点就是求出三个点:鱼鳍左点、右点、贝塞尔控制点
通过鱼头的圆心求,距离 0.9 * R ,角度110 。通过那个公式就能求出来 = 右鱼鳍点
左鱼鳍点 = 右鱼鳍点、距离、角度-180
贝塞尔控制点 = (这个完全靠自己试,只影响鱼鳍的胖瘦) 右鱼鳍点、距离 * 1.8f、角度 115
试贝塞尔的网站: cubic-bezier.com/#.17,.67,.8…
三个点都有了,绘制鱼鳍:
绘制之前要将其他绘制重置: mPath.reset();
然后mPath.moveTo()移动到第一个点
mPath.lineTo()画直线
mPath.quadTo()画二阶贝塞尔曲线,入参第二个点、第三个点
最后 canvas.drawPath(mPath,mPaint);直接画出来
最后一个点不用封闭,系统自动会封闭。
别的部分也都差不多,根据那个公式,通过参考点求出另一个点,然后求出各个部分的点,最后连线画就行
然后
mPath.reset()、 mPath.moveTo、 mPath.lineTo、canvas.drawPath
画身体
求出这6个点就行
鱼原地摆动需要属性动画ValueAnimator
属性动画,给一个值,一直变,
如: ValueAnimator.ofFloat(0,1f); 就是将一个float值 从0f变到1f
setRepeatCount设置重复次数
setRepeatMode设置重复模式
setInterpolator设置插值器
Interpolator(插值器)系统提供了很多插值器:先加速在减速、加速、匀速、周期运动、先回退再加速、最后弹一下等等。如果不够用可以自定义。
在监听里面能取到当前的值,然后通过 invalidateSelf();刷新重绘
通过这个变化的值,去改变鱼头的角度,这能实现鱼的摆动(比如鱼头摆动5度,节肢1摆动10度,节肢2摆动20度)这样不同的幅度就让鱼动起来。
鱼尾是通过改变三角的大小来实现的,幅度规律应该跟节肢2一样。
鱼尾是上节(节肢1)带动(节肢2)来运动的,而且是周期规律性的运动
说到周期性运动,又很平滑。sin、cos刚好是这样的
所以可以用sin代替0~1f的动画,这样更加平滑。cos刚好比sin多一个象限,那么节肢2刚好可以被节肢1带着。
鱼整条都是定义在FishDrawable的,用FishRelativeLayout,addView去将ImageView加进来FishDrawable
在onTouchEvent里记录下点击(X,Y),然后根据属性动画去改变圆半径和透明度
ObjectAnimator
ObjectAnimator extends ValueAnimator
效果是一样的,将一个属性从 X值改到Y值。可以不用写监听了addListener
入参1.要改变的对象,2.要改变的值(必须实现set方法),3.初始值,4.终点值 -- (或者路径Path)
本质其实是利用反射将 第二个参数的set、get方法里的值给改了。所以一定得实现set、get,不然就报错
鱼的运行轨迹为 3阶贝塞尔曲线,所以关键就是确定4个点
重点就是控制点2的角度
求出夹角,还得算出与X轴的夹角也就是
// AB连线与X的夹角的tan值 - OB与x轴的夹角的tan值 float direction = (A.y - B.y) / (A.x - B.x) - (O.y - B.y) / (O.x - B.x);
完整各个点:
最后游动:
path.cubicTo()三阶贝塞尔曲线公式
通过ObjectAnimator属性动画改变ImageView的xy坐标。
通过fishDrawable.setFrequence(3f) 让鱼在游动的时候运动的更快
鱼头的朝向,转过来
鱼头的方向 = 运动轨迹的切线的方向,这样就能平滑的转过来了。
通过取到曲线执行的百分比得到
pathMeasure.getPosTan()
tanα值,然后转换成角度
完成
Android自定义View实战之仿百度加载动画,一种优雅的Loading方式
转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53470872
本文出自【DylanAndroid的博客】
- Android自定义View实战之仿百度加载动画一种优雅的Loading方式
- 第一个仿百度加载动画用ObjectAnimator属性动画操作ImageView的属性方法实现
- 第二个仿百度加载动画第二种实现方式用ValueAnimator原生的ondraw方法实现
- 第三个扔球动画-水平旋转动画
- 第四个扔球动画-垂直旋转动画
- GitHub
【Android自定义View实战】之仿百度加载动画,一种优雅的Loading方式
无意中看到了百度的加载动画,看起来非常优雅,打算亲手造一个。
仿百度加载动画的重要思路:当第一遍执行完毕后就让第一个停下来在中间位置,换原来中间位置的第三个开始执行动画,
以此类推,当第二遍执行完毕后第二个停下来,中间位置的开始执行动画。
第一个:仿百度加载动画,用ObjectAnimator属性动画操作ImageView的属性方法实现:
- 1.布局文件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="500px"
android:layout_height="500px"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_blue"
android:layout_width="wrap_content"
android:scaleType="matrix"
android:layout_height="wrap_content"
android:src="@mipmap/dot_blue"
android:layout_gravity="center" />
<ImageView
android:id="@+id/iv_yellow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="matrix"
android:src="@mipmap/dot_yellow"
android:layout_gravity="center" />
<ImageView
android:id="@+id/iv_red"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="matrix"
android:src="@mipmap/dot_red"
android:layout_gravity="center" />
</FrameLayout>
- 2.代码
package cn.bluemobi.dylan.baiduprogressbar;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
/**
* 仿百度优雅的加载动画
* Created by dylan on 2016-12-04.
*/
public class BaiduProgressBar extends FrameLayout
/**
* 开始执行的第一个动画的索引,
* 由于第一个和第二个同时当执行,
* 当第一遍执行完毕后就让第一个停下来在中间位置,换原来中间位置的第三个开始执行动画,
* 以此类推,当第二遍执行完毕后第二个停下来,中间位置的开始执行动画。
*/
private int startIndex = 0;
/**
* 交换执行动画的源图片数组
*/
private int[] src = new int[]R.mipmap.dot_yellow, R.mipmap.dot_red, R.mipmap.dot_blue;
/**
* 存放三个ImageView的的集合
*/
private List<ImageView> views = new ArrayList<>();
/**
* 让左边和右边动画同时执行的AnimatorSet对象
*/
private AnimatorSet animatorSet;
/**
* 动画所执行的最大半径(即中间点和最左边的距离)
*/
private int maxRadius = 200;
public BaiduProgressBar(Context context)
super(context);
init();
public BaiduProgressBar(Context context, AttributeSet attrs)
super(context, attrs);
init();
public BaiduProgressBar(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init();
/**
* 查找布局控件
*/
private void assignViews()
ImageView iv_blue = (ImageView) findViewById(R.id.iv_blue);
ImageView iv_yellow = (ImageView) findViewById(R.id.iv_yellow);
ImageView iv_red = (ImageView) findViewById(R.id.iv_red);
views.add(iv_yellow);
views.add(iv_red);
views.add(iv_blue);
/**
* 初始化
*/
private void init()
LayoutInflater.from(getContext()).inflate(R.layout.baidu_progress_bar, this, true);
assignViews();
startAnimator();
/**
* 开始执行动画
*/
private void startAnimator()
/**向左来回移动的X位移动画**/
ObjectAnimator objectAnimatorLeft = ObjectAnimator.ofFloat(views.get(0), "translationX", 0, -maxRadius, 0);
objectAnimatorLeft.setRepeatCount(-1);
objectAnimatorLeft.setDuration(1000);
/**向右来回移动的X位移动画**/
ObjectAnimator objectAnimatorRight = ObjectAnimator.ofFloat(views.get(1), "translationX", 0, maxRadius, 0);
objectAnimatorRight.setRepeatCount(-1);
objectAnimatorRight.setDuration(1000);
/**动画组合->让左右同时执行**/
animatorSet = new AnimatorSet();
animatorSet.play(objectAnimatorRight).with(objectAnimatorLeft);
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.start();
/**动画监听**/
objectAnimatorLeft.addListener(new Animator.AnimatorListener()
@Override
public void onAnimationStart(Animator animation)
@Override
public void onAnimationEnd(Animator animation)
@Override
public void onAnimationCancel(Animator animation)
@Override
public void onAnimationRepeat(Animator animation)
/**每次记录一下下次应该停止在中间的Image索引,然后和中间的交换**/
if (startIndex == 0)
sweep(0, 2);
startIndex = 1;
else
sweep(1, 2);
startIndex = 0;
);
/**
* 每次让先执行动画的目标和中间停止的动画目标交换
*
* @param a 最先执行的动画的索引
* @param b 在中间动画的索引
*/
private void sweep(int a, int b)
views.get(a).setImageResource(src[b]);
views.get(b).setImageResource(src[a]);
int temp = src[b];
src[b] = src[a];
src[a] = temp;
/**
* 在View销毁时停止动画
*/
@Override
protected void onDetachedFromWindow()
super.onDetachedFromWindow();
animatorSet.cancel();
第二个:仿百度加载动画第二种实现方式,用ValueAnimator+原生的ondraw()方法实现:
package cn.bluemobi.dylan.baiduprogressbar;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
/**
* 仿百度优雅的加载动画
* Created by dylan on 2016-12-04.
*/
public class BaiduProgressBar2 extends View
/**
* 开始执行的第一个动画的索引,
* 由于第一个和第二个同时当执行,
* 当第一遍执行完毕后就让第一个停下来在中间位置,换原来中间位置的第三个开始执行动画,
* 以此类推,当第二遍执行完毕后第二个停下来,中间位置的开始执行动画。
*/
private int sweepIndex = 0;
/**
* 交换执行动画的颜色数组
*/
private int[] colors = new int[]getResources().getColor(R.color.colorYellow),
getResources().getColor(R.color.colorRed),
getResources().getColor(R.color.colorBlue);
/**
* 动画所执行的最大偏移量(即中间点和最左边的距离)
*/
private Float maxWidth = 200f;
/**
* 三个圆的半径
*/
private Float radius = 30f;
/**
* 当前偏移的X坐标
*/
private Float currentX = 0f;
/**
* 画笔
*/
private Paint paint;
/**
* 属性动画
*/
private ValueAnimator valueAnimator;
public BaiduProgressBar2(Context context)
super(context);
startAnimator();
public BaiduProgressBar2(Context context, AttributeSet attrs)
super(context, attrs);
startAnimator();
public BaiduProgressBar2(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
startAnimator();
/**
* 用属性动画实现位移动画
*/
private void startAnimator()
valueAnimator = ValueAnimator.ofFloat(0f, maxWidth, 0);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
currentX = (Float) animation.getAnimatedValue();
invalidate();
);
valueAnimator.addListener(new Animator.AnimatorListener()
@Override
public void onAnimationStart(Animator animation)
@Override
public void onAnimationEnd(Animator animation)
@Override
public void onAnimationCancel(Animator animation)
@Override
public void onAnimationRepeat(Animator animation)
sweep(sweepIndex);
);
valueAnimator.setInterpolator(new LinearInterpolator());
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
valueAnimator.setRepeatCount(-1);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.setDuration(1000);
valueAnimator.start();
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
/**画左边的圆**/
paint.setColor(colors[0]);
canvas.drawCircle(centerX - currentX, centerY, radius, paint);
/**画右边的圆**/
paint.setColor(colors[1]);
canvas.drawCircle(centerX + currentX, centerY, radius, paint);
/**画中间的圆**/
paint.setColor(colors[2]);
canvas.drawCircle(centerX, centerY, radius, paint);
/**
* 每次让先执行动画的目标和中间停止的动画目标交换
*
* @param a 最先执行的动画的索引
*/
private void sweep(int a)
int temp = colors[2];
colors[2] = colors[a];
colors[a] = temp;
if (a == 0)
sweepIndex = 1;
else
sweepIndex = 0;
/**
* 在View销毁时停止动画
*/
@Override
protected void onDetachedFromWindow()
super.onDetachedFromWindow();
valueAnimator.cancel();
在经过以上的动画之后,突然在Loading设计思路分享中看到了两个比较酷炫的动画
主要思路图如下
第三个:扔球动画->水平旋转动画
package cn.bluemobi.dylan.baiduprogressbar;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by dylan on 2016-12-04.
*/
public class BaiduLoadingView extends FrameLayout
/**
* 存放三个小球的集合
*/
private List<ImageView> views = new ArrayList<>();
/**
* 同时播放动画的对象
*/
private AnimatorSet animatorSet;
public BaiduLoadingView(Context context)
super(context);
init();
public BaiduLoadingView(Context context, AttributeSet attrs)
super(context, attrs);
init();
public BaiduLoadingView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init();
private void assignViews()
ImageView iv_blue = (ImageView) findViewById(R.id.iv_blue);
ImageView iv_yellow = (ImageView) findViewById(R.id.iv_yellow);
ImageView iv_red = (ImageView) findViewById(R.id.iv_red);
views.add(iv_yellow);
views.add(iv_red);
views.add(iv_blue);
/**
* 初始化
*/
private void init()
LayoutInflater.from(getContext()).inflate(R.layout.baidu_progress_bar, this, true);
assignViews();
startAnimator();
private void startAnimator()
/**动画组合->让左右同时执行**/
animatorSet = new AnimatorSet();
animatorSet.play(startAnimator1()).with(startAnimator2()).with(startAnimator3());
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.start();
private ObjectAnimator startAnimator1()
/**对象的不同属性组合**/
PropertyValuesHolder objectAnimatorTranslation = PropertyValuesHolder.ofFloat("translationX", -100, -200, -100, 0, 100, 200, 100, 0, -100);
PropertyValuesHolder objectAnimatorScale = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 1, 1.5f, 1, 0.5f, 1, 1.5f, 1, 0.5f);
PropertyValuesHolder objectAnimatorScaleY = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 1, 1.5f, 1, 0.5f, 1, 1.5f, 1, 0.5f);
/**同时操作对象的两个属性动画**/
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(views.get(0), objectAnimatorTranslation, objectAnimatorScale, objectAnimatorScaleY);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(2000);
objectAnimator.start();
return objectAnimator;
private ObjectAnimator startAnimator2()
/**对象的不同属性组合**/
PropertyValuesHolder objectAnimatorTranslation = PropertyValuesHolder.ofFloat("translationX", 0, 100, 200, 100, 0, -100, -200, -100, 0);
PropertyValuesHolder objectAnimatorScale = PropertyValuesHolder.ofFloat("scaleX", 1, 0.5f, 1, 1.5f, 1, 0.5f, 1, 1.5f, 1);
PropertyValuesHolder objectAnimatorScaleY = PropertyValuesHolder.ofFloat("scaleY", 1, 0.5f, 1, 1.5f, 1, 0.5f, 1, 1.5f, 1);
/**同时操作对象的两个属性动画**/
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(views.get(1), objectAnimatorTranslation, objectAnimatorScale, objectAnimatorScaleY);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(2000);
objectAnimator.start();
return objectAnimator;
private ObjectAnimator startAnimator3()
/**对象的不同属性组合**/
PropertyValuesHolder objectAnimatorTranslation = PropertyValuesHolder.ofFloat("translationX", 100, 0, -100, -200, -100, 0, 100, 200, 100);
PropertyValuesHolder objectAnimatorScale = PropertyValuesHolder.ofFloat("scaleX", 1.5f, 1f, 0.5f, 1, 1.5f, 1, 0.5f, 1, 1.5f);
PropertyValuesHolder objectAnimatorScaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f, 1f, 0.5f, 1, 1.5f, 1, 0.5f, 1, 1.5f);
/**同时操作对象的两个属性动画**/
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(views.get(2), objectAnimatorTranslation, objectAnimatorScale, objectAnimatorScaleY);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(2000);
objectAnimator.start();
return objectAnimator;
/**
* 在View销毁时停止动画
*/
@Override
protected void onDetachedFromWindow()
super.onDetachedFromWindow();
animatorSet.cancel();
第四个:扔球动画->垂直旋转动画
package cn.bluemobi.dylan.baiduprogressbar;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by dylan on 2016-12-04.
*/
public class BaiduProgressLoading extends FrameLayout
/**
* 存放三个小球的集合
*/
private List<ImageView> views = new ArrayList<>();
public BaiduProgressLoading(Context context)
super(context);
init();
public BaiduProgressLoading(Context context, AttributeSet attrs)
super(context, attrs);
init();
public BaiduProgressLoading(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init();
private void assignViews()
ImageView iv_blue = (ImageView) findViewById(R.id.iv_blue);
ImageView iv_yellow = (ImageView) findViewById(R.id.iv_yellow);
ImageView iv_red = (ImageView) findViewById(R.id.iv_red);
views.add(iv_yellow);
views.add(iv_red);
views.add(iv_blue);
private void init()
LayoutInflater.from(getContext()).inflate(R.layout.baidu_progress_bar, this, true);
assignViews();
startAnimator1();
startAnimator2();
startAnimator3();
PointF point = new PointF();
private void startAnimator1()
ValueAnimator valueAnimator = ValueAnimator.ofFloat(90, 360);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float value = (Float) animation.getAnimatedValue();
/**
*
* 圆点坐标:(x0,y0)
* 半径:r
* 角度:a0
* 则圆上任一点为:(x1,y1)
* x1 = x0 + r * cos(ao * 3.14 /180 )
* y1 = y0 + r * sin(ao * 3.14 /180 )
*/
/**第四步,根据每个菜单真实角度计算其坐标值**/
point.x = (float) Math.cos(value * (Math.PI / 180)) * 100 - 100;
point.y = (float) -Math.sin(value * (Math.PI / 180)) * 100;
views.get(0).setTranslationX(point.x);
views.get(0).setTranslationY(point.y);
);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(750);
valueAnimator.start();
ValueAnimator valueAnimator2 = ValueAnimator.ofFloat(180, 0, -180);
valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float value = (Float) animation.getAnimatedValue();
/**
*
* 圆点坐标:(x0,y0)
* 半径:r
* 角度:a0
* 则圆上任一点为:(x1,y1)
* x1 = x0 + r * cos(ao * 3.14 /180 )
* y1 = y0 + r * sin(ao * 3.14 /180 )
*/
/**第四步,根据每个菜单真实角度计算其坐标值**/
point.x = (float) Math.cos(value * (Math.PI / 180)) * 100 + 100;
point.y = (float) -Math.sin(value * (Math.PI / 180)) * 100;
views.get(0).setTranslationX(point.x);
views.get(0).setTranslationY(point.y);
);
valueAnimator2.setInterpolator(new LinearInterpolator());
valueAnimator2.setDuration(1000);
valueAnimator2.setStartDelay(750);
valueAnimator2.start();
ValueAnimator valueAnimator3 = ValueAnimator.ofFloat(0, 90);
valueAnimator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float value = (Float) animation.getAnimatedValue();
/**
*
* 圆点坐标:(x0,y0)
* 半径:r
* 角度:a0
* 则圆上任一点为:(x1,y1)
* x1 = x0 + r * cos(ao * 3.14 /180 )
* y1 = y0 + r * sin(ao * 3.14 /180 )
*/
/**第四步,根据每个菜单真实角度计算其坐标值**/
point.x = (float) Math.cos(value * (Math.PI / 180)) * 100 - 100;
point.y = (float) -Math.sin(value * (Math.PI / 180)) * 100;
views.get(0).setTranslationX(point.x);
views.get(0).setTranslationY(point.y);
);
valueAnimator3.setInterpolator(new LinearInterpolator());
valueAnimator3.setDuration(250);
valueAnimator3.setStartDelay(1750);
valueAnimator3.start();
valueAnimator3.addListener(new Animator.AnimatorListener()
@Override
public void onAnimationStart(Animator animation)
@Override
public void onAnimationEnd(Animator animation)
startAnimator1();
@Override
public void onAnimationCancel(Animator animation)
@Override
public void onAnimationRepeat(Animator animation)
);
private void startAnimator2()
ValueAnimator valueAnimator2 = ValueAnimator.ofFloat(180, 0, -180);
valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float value = (Float) animation.getAnimatedValue();
/**
*
* 圆点坐标:(x0,y0)
* 半径:r
* 角度:a0
* 则圆上任一点为:(x1,y1)
* x1 = x0 + r * cos(ao * 3.14 /180 )
* y1 = y0 + r * sin(ao * 3.14 /180 )
*/
/**第四步,根据每个菜单真实角度计算其坐标值**/
point.x = (float) Math.cos(value * (Math.PI / 180)) * 100 + 100;
point.y = (float) -Math.sin(value * (Math.PI / 180)) * 100;
views.get(1).setTranslationX(point.x);
views.get(1).setTranslationY(point.y);
);
valueAnimator2.setInterpolator(new LinearInterpolator());
valueAnimator2.setDuration(1000);
valueAnimator2.start();
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 360);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float value = (Float) animation.getAnimatedValue();
/**
*
* 圆点坐标:(x0,y0)
* 半径:r
* 角度:a0
* 则圆上任一点为:(x1,y1)
* x1 = x0 + r * cos(ao * 3.14 /180 )
* y1 = y0 + r * sin(ao * 3.14 /180 )
*/
/**第四步,根据每个菜单真实角度计算其坐标值**/
point.x = (float) Math.cos(value * (Math.PI / 180)) * 100 - 100;
point.y = (float) -Math.sin(value * (Math.PI / 180)) * 100;
views.get(1).setTranslationX(point.x);
views.get(1).setTranslationY(point.y);
);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(1000);
valueAnimator.setStartDelay(1000);
valueAnimator.start();
valueAnimator.addListener(new Animator.AnimatorListener()
@Override
public void onAnimationStart(Animator animation)
@Override
public void onAnimationEnd(Animator animation)
startAnimator2();
@Override
public void onAnimationCancel(Animator animation)
@Override
public void onAnimationRepeat(Animator animation)
);
private void startAnimator3()
ValueAnimator valueAnimator = ValueAnimator.ofFloat(270, 180);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float value = (Float) animation.getAnimatedValue();
/**
*
* 圆点坐标:(x0,y0)
* 半径:r
* 角度:a0
* 则圆上任一点为:(x1,y1)
* x1 = x0 + r * cos(ao * 3.14 /180 )
* y1 = y0 + r * sin(ao * 3.14 /180 )
*/
/**第四步,根据每个菜单真实角度计算其坐标值**/
point.x = (float) Math.cos(value * (Math.PI / 180)) * 100 + 100;
point.y = (float) -Math.sin(value * (Math.PI / 180)) * 100;
views.get(2).setTranslationX(point.x);
views.get(2).setTranslationY(point.y);
);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(250);
valueAnimator.start();
ValueAnimator valueAnimator2 = ValueAnimator.ofFloat(0, 360);
valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float value = (Float) animation.getAnimatedValue();
/**
*
* 圆点坐标:(x0,y0)
* 半径:r
* 角度:a0
* 则圆上任一点为:(x1,y1)
* x1 = x0 + r * cos(ao * 3.14 /180 )
* y1 = y0 + r * sin(ao * 3.14 /180 )
*/
/**第四步,根据每个菜单真实角度计算其坐标值**/
point.x = (float) Math.cos(value * (Math.PI / 180)) * 100 - 100;
point.y = (float) -Math.sin(value * (Math.PI / 180)) * 100;
views.get(2).setTranslationX(point.x);
views.get(2).setTranslationY(point.y);
);
valueAnimator2.setInterpolator(new LinearInterpolator());
valueAnimator2.setDuration(1000);
valueAnimator2.setStartDelay(250);
valueAnimator2.start();
ValueAnimator valueAnimator3 = ValueAnimator.ofFloat(180, -90);
valueAnimator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float value = (Float) animation.getAnimatedValue();
/**
*
* 圆点坐标:(x0,y0)
* 半径:r
* 角度:a0
* 则圆上任一点为:(x1,y1)
* x1 = x0 + r * cos(ao * 3.14 /180 )
* y1 = y0 + r * sin(ao * 3.14 /180 )
*/
/**第四步,根据每个菜单真实角度计算其坐标值**/
point.x = (float) Math.cos(value * (Math.PI / 180)) * 100 + 100;
point.y = (float) -Math.sin(value * (Math.PI / 180)) * 100;
views.get(2).setTranslationX(point.x);
views.get(2).setTranslationY(point.y);
);
valueAnimator3.setInterpolator(new LinearInterpolator());
valueAnimator3.setDuration(750);
valueAnimator3.setStartDelay(1250);
valueAnimator3.start();
valueAnimator3.addListener(new Animator.AnimatorListener()
@Override
public void onAnimationStart(Animator animation)
@Override
public void onAnimationEnd(Animator animation)
startAnimator3();
@Override
public void onAnimationCancel(Animator animation)
@Override
public void onAnimationRepeat(Animator animation)
);
GitHub
以上是关于自定义View+属性动画实战 — 灵动的锦鲤的主要内容,如果未能解决你的问题,请参考以下文章
Android自定义View实战之仿百度加载动画,一种优雅的Loading方式
Android自定义View实战之仿百度加载动画,一种优雅的Loading方式