版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、效果
爱心在底部中心慢慢变大出现,然后按随机的轨迹上升。
二、分析
这边有两种实现思路:
1.自绘控件,绘制的花(可以是 SVG or 很小的图片)按照贝塞尔曲线曲线路径绘制,Path+PathMeasure (小船的案例)http://blog.csdn.net/qq_18983205/article/details/76785251
2.控制View的动画,每一朵鲜花都是一个ImageView,可以通过属性动画控制曲线运动, ObjectAnimator。
如果说,在鲜花移动的过程中,还需要点击监听事件,进行人机交互的话,采用方法1则需要不停的计算鲜花区域来判断是否点击到鲜花。直接采用动画效果则没有这个问题。
三、添加图片
这边以自定义控件来实现。先来实现点击按钮添加爱心,且颜色随机。
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.xiaoyue.animatordemo.LoveLayout
android:id="@+id/loveLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="start"
android:text="赞一个" />
</RelativeLayout>
MainActivity
public class MainActivity extends ActionBarActivity {
private LoveLayout loveLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loveLayout = (LoveLayout)findViewById(R.id.loveLayout);
}
public void start(View v){
loveLayout.addLoveIcon();
}
}
LoveLayout
public class LoveLayout extends RelativeLayout {
//图片的宽高
private int dWidth;
private int dHeight;
//图片资源数组
private Drawable[] drawables = new Drawable[3];
//图片布局参数
private LayoutParams layoutParams;
//获取随机数
private Random random;
public LoveLayout(Context context) {
this(context, null);
}
public LoveLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
//加载图片数据
drawables[0] = getResources().getDrawable(R.drawable.blue);
drawables[1] = getResources().getDrawable(R.drawable.red);
drawables[2] = getResources().getDrawable(R.drawable.yellow);
//得到图片的原始高度
dWidth = drawables[0].getIntrinsicWidth();
dHeight = drawables[0].getIntrinsicHeight();
//初始化图片布局参数,添加到父容器底部、水平居中位置
layoutParams = new LayoutParams(dWidth, dHeight);
layoutParams.addRule(CENTER_HORIZONTAL);
layoutParams.addRule(ALIGN_PARENT_BOTTOM);
random = new Random();
}
public void addLoveIcon() {
ImageView imageView = new ImageView(getContext());
imageView.setImageDrawable(drawables[random.nextInt(3)]);
//将 imageView 添加到父容器底部、水平居中位置
imageView.setLayoutParams(layoutParams);
addView(imageView);
}
}
爱心是使用三张相同大小,不同颜色的图片,通过随机数来随机获取一个颜色。这些图片也比较简单,对性能有要求的也可以换成 SVG。
效果:
四、添加时的动画
在添加爱心的首,爱心是有小变到正常大小,在 X、Y 方向上都进行了缩放,采用 AnimatorSet。
private AnimatorSet getAnimator(ImageView imageView) {
ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 0.3f, 1f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0.3f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0.3f, 1f);
AnimatorSet addAnimator= new AnimatorSet();
addAnimator.playTogether(alpha, scaleX, scaleY);
addAnimator.setDuration(500);
return addAnimator;
}
在 addLoveIcon()中调用并启动:
//开始属性动画:平移、透明度渐变、缩放动画
AnimatorSet set = getAnimator(imageView);
set.start();
效果:
五、添加移动
估值器是通过改变算法从而改变值,插值器是控制计算的速度。所以,这边采用估值器进行实现移动的实现。
移动的轨迹使用贝三阶塞尔曲线,公式为:b(t)=p0*(1-t)(1-t)(1-t)+3*p1*t*(1-t)(1-t)+3*p2*t*t(1-t)+p3*t*t*t
1.自定义估值器
public class BezierEvaluator implements TypeEvaluator<PointF>{
//贝塞尔曲线的两个折点
private PointF pointF1;
private PointF pointF2;
//避免重复创建造成内存抖动
PointF point;
public BezierEvaluator(PointF pointF1, PointF pointF2) {
this.pointF1 = pointF1;
this.pointF2 = pointF2;
point = new PointF();
}
@Override
public PointF evaluate(float t, PointF point0, PointF point3) {
//t百分比:0~1
// b(t)=p0*(1-t)*(1-t)*(1-t)+3*p1*t*(1-t)*(1-t)+3*p2*t*t*(1-t)+p3*t*t*t
point.x = point0.x*(1-t)*(1-t)*(1-t)
+3*pointF1.x*t*(1-t)*(1-t)
+3*pointF2.x*t*t*(1-t)
+point3.x*t*t*t;
point.y = point0.y*(1-t)*(1-t)*(1-t)
+3*pointF1.y*t*(1-t)*(1-t)
+3*pointF2.y*t*t*(1-t)
+point3.y*t*t*t;
return point;
}
}
这边用到的估值器在计算的时候需要用到中间两个折点的坐标,为了引入这两个折点,这边直接新建一个类,而不再是使用匿名类。
2.移动动画
public ValueAnimator getMoveAnimator(final ImageView imageView){
//根据贝塞尔公式确定四个点(起始点p0,拐点1p1,拐点2p2,终点p3)
PointF pointF0 = new PointF((mWidth-dWidth)/2, mHeight-dHeight);
PointF pointF3 = new PointF(random.nextInt(mWidth), 0);
PointF pointF1 = getPointF(1);
PointF pointF2 = getPointF(2);
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(5000);
//设置起始点和终止点
valueAnimator.setObjectValues(pointF0, pointF3);
//设置估值器
BezierEvaluator bezierEvaluator = new BezierEvaluator(pointF1, pointF2);
valueAnimator.setEvaluator(bezierEvaluator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF f = (PointF) animation.getAnimatedValue();
imageView.setX(f.x);
imageView.setY(f.y);
//1~0 百分比
imageView.setAlpha(1-animation.getAnimatedFraction());
}
});
return valueAnimator;
}
private PointF getPointF(int i) {
PointF pointF = new PointF();
pointF.x = random.nextInt(mWidth);
//为了好看,尽量保证point2.y>point1.y
if(i==1){
pointF.y = random.nextInt(mHeight/2)+mHeight/2;
}else{
pointF.y = random.nextInt(mHeight/2);
}
return pointF;
}
这边估值器的使用就是上一篇讲的直接使用,只是这边使用的自定义估值器。在获取两个折点的时候,为了好看的效果,保证第一个折点在下半个屏幕,第二个折点在上半个屏幕。
3.添加到 Animator
在 getAnimator 方法里面获取到移动的动画,并使用 AnimatorSet 的混合执行,让移动动画在添加动画之后执行。
ValueAnimator moveAnimator = getMoveAnimator(imageView);
AnimatorSet set = new AnimatorSet();
//按序列执行
set.playSequentially(addAnimator, moveAnimator);
4.效果
六、添加回收
在上面只是把爱心的透明度变为了0,这时候如果不停点击,爱心会不停的堆积造成,而且在这边爱心是使用图片,会占用较大的内存。
public void addLoveIcon() {
final ImageView imageView = new ImageView(getContext());
imageView.setImageDrawable(drawables[random.nextInt(3)]);
//将 imageView 添加到父容器底部、水平居中位置
imageView.setLayoutParams(layoutParams);
addView(imageView);
//开始属性动画:平移、透明度渐变、缩放动画
AnimatorSet set = getAnimator(imageView);
//监听动画执行完毕,将iv移除或者复用
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
removeView(imageView);
}
});
set.start();
}
简单点就是添加监听,当动画执行完之后直接移除。如果说频率很高的话,可以考虑参考 ListView 的复用思想,做一个回收池进行复用。ListView 的复用思想:http://blog.csdn.net/qq_18983205/article/details/77995810
七、添加插值器
每一个爱心速度都一样,为了有更好的效果,可以添加一些插值器,让爱心的速度不一样。
在 init 方法里添加四个系统的插值器。
private void init() {
interpolators = new Interpolator[4];
interpolators[0] = new LinearInterpolator();//线性;
interpolators[1] = new AccelerateInterpolator();//加速;
interpolators[2] = new DecelerateInterpolator();//减速;
interpolators[3] = new AccelerateDecelerateInterpolator();//先加速后减速;
//加载图片数据
drawables[0] = getResources().getDrawable(R.drawable.blue);
drawables[1] = getResources().getDrawable(R.drawable.red);
drawables[2] = getResources().getDrawable(R.drawable.yellow);
//得到图片的原始高度
dWidth = drawables[0].getIntrinsicWidth();
dHeight = drawables[0].getIntrinsicHeight();
//初始化图片布局参数,添加到父容器底部、水平居中位置
layoutParams = new LayoutParams(dWidth, dHeight);
layoutParams.addRule(CENTER_HORIZONTAL);
layoutParams.addRule(ALIGN_PARENT_BOTTOM);
random = new Random();
};
在 getAnimator 方法里面添加设置随机插值器。
AnimatorSet set = new AnimatorSet();
//加速因子,使用插值器
set.setInterpolator(interpolators[random.nextInt(4)]);
//按序列执行
set.playSequentially(addAnimator, moveAnimator);
效果:
下面是完整的 LoveLayout 代码。
LoveLayout
public class LoveLayout extends RelativeLayout {
//自定义控件的宽高
private int mWidth;
private int mHeight;
//图片的宽高
private int dWidth;
private int dHeight;
//图片资源数组
private Drawable[] drawables = new Drawable[3];
//图片布局参数
private LayoutParams layoutParams;
//获取随机数
private Random random;
//插值器数组
private Interpolator[] interpolators ;
public LoveLayout(Context context) {
this(context, null);
}
public LoveLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
}
private void init() {
interpolators = new Interpolator[4];
interpolators[0] = new LinearInterpolator();//线性;
interpolators[1] = new AccelerateInterpolator();//加速;
interpolators[2] = new DecelerateInterpolator();//减速;
interpolators[3] = new AccelerateDecelerateInterpolator();//先加速后减速;
//加载图片数据
drawables[0] = getResources().getDrawable(R.drawable.blue);
drawables[1] = getResources().getDrawable(R.drawable.red);
drawables[2] = getResources().getDrawable(R.drawable.yellow);
//得到图片的原始高度
dWidth = drawables[0].getIntrinsicWidth();
dHeight = drawables[0].getIntrinsicHeight();
//初始化图片布局参数,添加到父容器底部、水平居中位置
layoutParams = new LayoutParams(dWidth, dHeight);
layoutParams.addRule(CENTER_HORIZONTAL);
layoutParams.addRule(ALIGN_PARENT_BOTTOM);
random = new Random();
}
public void addLoveIcon() {
final ImageView imageView = new ImageView(getContext());
imageView.setImageDrawable(drawables[random.nextInt(3)]);
//将 imageView 添加到父容器底部、水平居中位置
imageView.setLayoutParams(layoutParams);
addView(imageView);
//开始属性动画:平移、透明度渐变、缩放动画
AnimatorSet set = getAnimator(imageView);
//监听动画执行完毕,将iv移除或者复用
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
removeView(imageView);
}
});
set.start();
}
private AnimatorSet getAnimator(ImageView imageView) {
ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 0.3f, 1f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0.3f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0.3f, 1f);
AnimatorSet addAnimator = new AnimatorSet();
addAnimator.setDuration(500);
ValueAnimator moveAnimator = getMoveAnimator(imageView);
addAnimator.playTogether(alpha, scaleX,scaleY);
AnimatorSet set = new AnimatorSet();
//加速因子,使用插值器
set.setInterpolator(interpolators[random.nextInt(4)]);
//按序列执行
set.playSequentially(addAnimator, moveAnimator);
return set;
}
public ValueAnimator getMoveAnimator(final ImageView imageView){
//根据贝塞尔公式确定四个点(起始点p0,拐点1p1,拐点2p2,终点p3)
PointF pointF0 = new PointF((mWidth-dWidth)/2, mHeight-dHeight);
PointF pointF3 = new PointF(random.nextInt(mWidth), 0);
PointF pointF1 = getPointF(1);
PointF pointF2 = getPointF(2);
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(3000);
//设置起始点和终止点
valueAnimator.setObjectValues(pointF0, pointF3);
//设置估值器
BezierEvaluator bezierEvaluator = new BezierEvaluator(pointF1, pointF2);
valueAnimator.setEvaluator(bezierEvaluator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF f = (PointF) animation.getAnimatedValue();
imageView.setX(f.x);
imageView.setY(f.y);
//1~0 百分比
imageView.setAlpha(1-animation.getAnimatedFraction());
}
});
return valueAnimator;
}
private PointF getPointF(int i) {
PointF pointF = new PointF();
pointF.x = random.nextInt(mWidth);
//为了好看,尽量保证point2.y>point1.y
if(i==1){
pointF.y = random.nextInt(mHeight/2)+mHeight/2;
}else{
pointF.y = random.nextInt(mHeight/2);
}
return pointF;
}
}