浅谈属性动画简单使用之实现爱的贝塞尔曲线浪漫告白效果(三)

谁说程序员不浪漫的啊,每次看到别人在黑程序员心中就有一种无奈,只是他们看到的是程序员不好的一面,今天我将用这个案例告诉那些人,程序猿也是一个很浪漫,很有情调的人。在程序员心中他们只想做最高效的事情,没有什么比效率更重要了。那就开始今天程序猿的告白之旅。

我们都知道属性动画有个强大的地方,它实现让某个控件按照我们指定的运动轨迹来运动。也就是说它可以按照一个抛物线来运动,也可以按照一个线性的线来运动,还可以按照我们今天所讲的贝塞尔曲线的轨迹来运动。为什么他可以按照某一个轨迹来运动呢??首先我们来分析一下今天这个Demo的实现原理吧

分析:我们要实现一颗爱心在整个布局底部中间位置动态产生,产生的效果过程中需要三种动画:X方向的缩放动画,Y方向的缩放动画,整个透明度的动画。产生后动画的爱心,必须按照贝塞尔曲线的轨迹来上升移动,并且在移动的过程中,透明度慢慢减小,直至爱心到达布局顶部正好消失。那怎么去实现呢?

大致的思路如下:首先前面三种的动画很简单,通过一个AnimatorSet动画集合,将那三种动画放在一起,然后通过一个playTogether(...)让他们同时动画即可搞定。由于我们需要动态增加该布局中爱心。所以最好是写一个自定义的ViewGroup继承于Relative,然后在布局中固定好爱心布局位置,当然你不用自定义一个View直接操纵也可以的。然后给爱心产生过程中添加最初三种属性动画。到这里我们的第一步就完成了。然后接着就是实现贝塞尔曲线轨迹,我们知道在属性动画中它可以任意操作一个属性的变化,并且在属性动画中有个ValueAnimator它可以得到一个动态变化的值范围,他本身不能作用于某个动画,但他确是实现动画的本质。ObjectAnimator则是它的子类,只是在他的基础上进行再一次的封装。说完这个不得不说TypeEvaluator<T>它是一个估值器的接口,他可以动态计算出该轨迹中任意一个点的位置坐标,说白他可以得到该曲线的轨迹。实现贝塞尔曲线思路是这样的:分别定义起点和终点,然后通过TypeEvaluator<PointF>估值器来得到到整条的曲线轨迹中每个点坐标,然后通过动画的监听事件不断获得最新的点的坐标,把该坐标实时更新到爱心控件的X,Y坐标即可。从实现按曲线移动的效果。

由以上贝塞尔曲线原理图,来进一步分析各个点,并且给出贝塞尔曲线公式:

贝塞尔曲线是一种应用非常广曲线,可以它应用到各个领域包括一些大型的工业设计,计算机图形绘制等领域:

P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。

公式:

P0点:无论是哪个爱心,P0都为起点始终都是一样的。

P1点:在X轴上范围:0~loveLayout.mWidth,如果要保证P1点在P2点的下面,那么P1点的Y轴上范围是:1/2mHeight~mHeight(注:mHeight是整个布局的高度)

P2点:在X轴上范围0~loveLayout.mWidth,需要保证P2点在P1上面,那么P2点的Y轴上的范围是:0~1/2mHeight

P3点:仔细发现每颗爱心达到顶部,所以只是X轴的坐标是不一样,Y轴上坐标都是0

好了,我们经过很长一段时间的分析,基本上都分析清楚了,现在即可开始我们的编码了:

布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#FFF"
 >
<com.mikyou.myview.LoveLayout
    android:id="@+id/id_love_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

</com.mikyou.myview.LoveLayout>
</LinearLayout>

自定义ViewGroup的LoveLayout

package com.mikyou.myview;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.mikyou.loveforyou.R;
import com.mikyou.tools.BezierEvalutor;

public class LoveLayout extends RelativeLayout{
	//用于存放不同图片的爱心
	private Drawable firstDrawable;
	private Drawable secondDrawable;
	private Drawable threeDrawable;
	private int dHeight;//爱心的高度
	private int dWidth;//爱心的宽度
	private int mWidth;//整个布局的宽度
	private int mHeight;//整个布局的高度
	List<Drawable> mDrawablesList=new ArrayList<Drawable>();
	private LayoutParams params;
	private Random random=new Random();//定义一个随机数对象,用于表示P1,P2,P3点的X,Y坐标的在某个范围随机变化
	public LoveLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		initView();
	}

	private void initView() {
		firstDrawable=getResources().getDrawable(R.drawable.pic1);
		mDrawablesList.add(firstDrawable);
		secondDrawable=getResources().getDrawable(R.drawable.pic2);
		mDrawablesList.add(secondDrawable);
		threeDrawable=getResources().getDrawable(R.drawable.pic3);
		mDrawablesList.add(threeDrawable);
		//得到爱心图片的宽高
		dHeight=firstDrawable.getIntrinsicHeight();
		dWidth=firstDrawable.getIntrinsicWidth();
		params=new LayoutParams(dWidth, dHeight);
		//给爱心控件动态布局,使得爱心始终在布局最底部的中间位置
		params.addRule(CENTER_HORIZONTAL,TRUE);
		params.addRule(ALIGN_PARENT_BOTTOM, TRUE);
	}
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		//得到本布局的宽高
		mWidth=getMeasuredWidth();
		mHeight=getMeasuredHeight();
	}
	public void addLove(){//添加心
		ImageView mImageView=new ImageView(getContext());
		mImageView.setImageDrawable(mDrawablesList.get(random.nextInt(3)));//通过随机对象,随机在这三张爱心图片产生任意一张图片
		mImageView.setLayoutParams(params);
		addView(mImageView);
		//属性动画控制坐标
		AnimatorSet set= getAnimator(mImageView);//通过getAnimator得到整个爱心所有动画集合
		set.start();
	}
	//构造3个属性动画
	private AnimatorSet getAnimator(ImageView mImageView) {
		//1,alpha动画;2,
		ObjectAnimator alphaAnimator= ObjectAnimator.ofFloat(mImageView, "alpha", 0.3f,1.0f);
		ObjectAnimator scaleXAnimator= ObjectAnimator.ofFloat(mImageView, "scaleX", 0.2f,1.0f);
		ObjectAnimator scaleYAnimator= ObjectAnimator.ofFloat(mImageView, "scaleY", 0.2f,1.0f);
		AnimatorSet mAnimatorSet=new AnimatorSet();
		mAnimatorSet.setDuration(500);
		//三个动画同时集合
		mAnimatorSet.playTogether(alphaAnimator,scaleXAnimator,scaleYAnimator);
		mAnimatorSet.setTarget(mImageView);
		//贝塞尔曲线动画,不断修改ImageView的坐标,PointF(x,y)
		ValueAnimator bezierValueAnimator=getBeziValueAnimator(mImageView);//getBeziValueAnimator得到贝赛尔曲线轨迹位移动画
		AnimatorSet bezierAnimatorSet =new AnimatorSet();
		//按顺序播放动画
		bezierAnimatorSet.playSequentially(mAnimatorSet,bezierValueAnimator);//然后按顺序播放这些动画集合
		//bezierAnimatorSet.setDuration(3000);
		bezierAnimatorSet.setTarget(mImageView);

		return bezierAnimatorSet;//返回一个整体爱心所有动画的集合
	}
	/**
	 * @author mikyou
	 * getBeziValueAnimator
	 * 构造一个贝塞尔曲线动画
	 * */
	private ValueAnimator getBeziValueAnimator( final ImageView mImageView) {
		//贝塞尔曲线动画,不断修改ImageView的坐标,PointF(x,y)
		PointF pointF2=getPointF(2);//getPointF方法根据传进来的数字来标记四个点,P0,P1,P2,P3
		PointF pointF1=getPointF(1);
		PointF pointF0=new PointF(mWidth/2-dWidth/2, mHeight-dHeight);//创建P0点,起点
		PointF pointF3=new PointF(random.nextInt(mWidth), 0);//创建P3点,终点
		BezierEvalutor mBezierEvalutor=new BezierEvalutor(pointF1, pointF2);//创建一个估值器,然后并把P1,P2点传入
		/**
		 * @author zhongqihong
		 * 创建一个ValueAnimator,并把起点P0和终点P3传入,然后在BezierEvalutor重写的方法evalute中得到P0,P3
		 * 然后通过上一步利用BezierEvalutor构造器将P1,P2两个点传入,所以这就是说
		 * 在BezierEvalutor重写的方法evalute可以得到P0,P1,P2,P3点对象,然后通过贝塞尔的公式
		 * 即可计算出该轨迹上的任意一点的坐标,并实时返回一个PontF点的对象,然后通过addUpdateListener
		 * 监听事件实时获得最新点的坐标然后将这些最新点坐标去更新爱心的ImageVIew控件的X,Y坐标
		 * */
		ValueAnimator animator=ValueAnimator.ofObject(mBezierEvalutor, pointF0,pointF3);
		animator.addUpdateListener(new AnimatorUpdateListener() {

			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				PointF pointF=(PointF) animation.getAnimatedValue();	//通过addUpdateListener监听事件实时获得从mBezierEvalutor估值器对象evalute方法实时计算出最新点的坐标	。
				mImageView.setX(pointF.x);//然后去更新该爱心ImageView的X,Y坐标
				mImageView.setY(pointF.y);
				mImageView.setAlpha(1-animation.getAnimatedFraction());//getAnimatedFraction()就是mBezierEvalutor估值器对象中evaluate方法t即时间因子,从0~1变化,所以爱心透明度应该是从1~0变化正好到了顶部,t变为1,透明度变为0,即爱心消失
			}
		});
		animator.setTarget(mImageView);
		animator.setDuration(3000);
		return animator;
	}

	private PointF getPointF(int i) {
		PointF pointF=new PointF();
		pointF.x=random.nextInt(mWidth);//0~loveLayout.Width
		//为了美观,建议尽量保证P2在P1上面,那怎么做呢??
		//只需要将该布局的高度分为上下两部分,让p1只能在下面部分范围内变化(1/2height~height),让p2只能在上面部分范围内变化(0~1/2height),因为坐标系是倒着的;

		//0~loveLayout.Height/2
		if (i==1) {
			pointF.y=random.nextInt(mHeight/2)+mHeight/2;//P1点Y轴坐标变化
		}else if(i==2){//P2点Y轴坐标变化
			pointF.y=random.nextInt(mHeight/2);
		}

		return pointF;
	}

}

BezierEvalutor自定义估值器接口类:

package com.mikyou.tools;

import android.animation.TypeEvaluator;
import android.graphics.PointF;
/**
 * @author mikyou
 * 自定义估值器
 * */
public class BezierEvalutor implements TypeEvaluator<PointF> {
	PointF p1;
	PointF p2;
	public BezierEvalutor(PointF p1, PointF p2) {
		super();
		this.p1 = p1;
		this.p2 = p2;
	}
	@Override
	public PointF evaluate(float t, PointF p0, PointF p3) {
		//时间因子t: 0~1
		PointF point=new PointF();
		//实现贝塞尔公式:
		point.x=p0.x*(1-t)*(1-t)*(1-t)+3*p1.x*t*(1-t)*(1-t)+3*p2.x*(1-t)*t*t+p3.x*t*t*t;//实时计算最新的点X坐标
		point.y=p0.y*(1-t)*(1-t)*(1-t)+3*p1.y*t*(1-t)*(1-t)+3*p2.y*(1-t)*t*t+p3.y*t*t*t;//实时计算最新的点Y坐标
		return point;//实时返回最新计算出的点对象
	}

}

最后我们只需要在MainActivity中去调用那个LoveLayout类中的addLove方法即可,为了让他自动产生爱心,所以我就开启一个子线程来管理产生爱心,然后通过Handler对象来更新主线程中的UI。

package com.mikyou.loveforyou;

import com.mikyou.myview.LoveLayout;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {
	private LoveLayout mLoveLayout;
	private Handler handler=new Handler(){
		public void handleMessage(android.os.Message msg) {
			if (msg.what==0x123) {
				mLoveLayout.addLove();
			}
		};
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
	}

	private void initView() {
		mLoveLayout=(LoveLayout)findViewById(R.id.id_love_layout);
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				try {
					while(true){
						Thread.sleep(400);
						handler.sendEmptyMessage(0x123);
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}).start();
	}

}

最后运行效果:

Demo下载链接,拿去告白吧

时间: 2024-10-14 08:59:38

浅谈属性动画简单使用之实现爱的贝塞尔曲线浪漫告白效果(三)的相关文章

浅谈属性动画简单使用之实现卫星菜单(二)

大家对于卫星菜单应该都不陌生了,其实这个菜单如果能合适运用到我们的APP项目中,确实是一个不错的选择,交互性非常好.在写Demo之前我也上网搜了一些关于卫星菜单的实现,感觉好多人实现卫星菜单这种动画,采用的是补间动画,并且代码还不少,通过上一讲我们知道,补间动画不具备与用户交互的特点,就比如卫星菜单展开后,产生很多子菜单有很多的点击事件,会发现产生点击事件的位置不会随着补间动画而产生位置改变而改变,反而点击事件一直保存在初始的位置.当然补间动画也能做,那就需要产生动画后发生位置的改变,并且也需要

安卓开发_浅谈Android动画(四)

Property动画 概念:属性动画,即通过改变对象属性的动画. 特点:属性动画真正改变了一个UI控件,包括其事件触发焦点的位置 一.重要的动画类及属性值: 1.  ValueAnimator 基本属性动画类 方法 描述 setDuration(long duration) 设置动画持续时间的方法 setEvaluator(TypeEvaluator value) 设置插值计算的类型 setInterpolator(TimeInterpolator value) 设置时间插值器的类型 addUp

属性动画简单分析(二)

在<属性动画简单解析(一)>分析了属性动画ObjectAnimation的初始化流程: 1)通过ObjectAnimation的ofXXX方法,设置propertyName和values. 2)将propertyName和values封装成PropertyValueHolder对象:每个PropertyValueHolder对象持有values组成的帧序列对象KeyFrameSet对象: 3)将步骤2创建的PropertyValueHolder对象用ObjectAnimation的mValue

浅谈MD5及简单使用

原理简介: MD5即Message-Digest Algorithm 5(信息-摘要算法 第5版),用于确保信息传输完整一致.是计算机广泛使用的杂凑算法之一(又名:摘要算法.哈希算法),主流编程语言普遍已由MD5实现.将数据运算为另一固定长度值(十六进制的话:32位),是杂凑算法的基础原理,MD5的前身有MD2.MD3和MD4. MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串).除了MD5以外,

浅谈属性

  属性是OC2.0之后出来的新的语法,用来替代setter以及getter方法,使用属性可以快速创建setter以及getter方法的声明,setter以及getter方法的实现.另外还添加了对实例变量操作的安全处理 在.h文件 @property用来定义属性,NSString *属性的类型(和实例变量类型相同) name属性名和实例变量名相同)切记:@property只是自动声明setter以及getter  方法的声明 例如 @property NSString *name;   NSSt

Android属性动画简单剖析

运行效果图: 先看布局文件吧,activity_main.xml: 1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 and

浅谈Android 动画,带你进入动画的世界

背景: 其实我们Android 中大家都知道就那些东西,什么四大组件,activity,service,content provider,当然还有其东西,今天我也去总结了下,来说说Android 中动画这一模块,可能会有许多遗漏,希望大家见谅,多多给予补充. 一:老规矩了,先上效果图,没图没真相 二:Android Animation 内容的介绍 主要的内容包括以下 1.Animation      ------------ 动画 2.tween Animation     ----------

安卓开发_浅谈Android动画(配置文件实现)

动画效果,针对图片实现 现在学习四种基本的简单动画效果 一.Tween Animation共同属性 1.Duration:动画持续时间(毫秒单位) 2.fillAfter:设置为true,动画转化在动画结束后被应用 3.fillBefore:设置为true,动画转化在动画开始前被应用 4.interpolator:动画插入器(加速,减速插入器) 5.repeatCount:动画重复次数 6.repateMode:顺序重复/倒序重复 7.startOffset:动画之间的时间间隔 二.Animat

安卓开发_浅谈Android动画(三)

一.LayoutAnimation布局动画 用于为一个layout里面的控件,或者是一个ViewGroup里面的控件设置动画效果 在res-anim文件下新建一个动画xml文件 1 <?xml version="1.0" encoding="utf-8"?> 2 <set xmlns:android="http://schemas.android.com/apk/res/android" 3 android:interpolat