android 飘心动画(直播点赞)效果(二)---贝塞尔曲线的实现
上篇文章 android 飘心动画(直播点赞)效果 只有代码,没有相关的说明。因为我自己也没有看懂,所以参照网上另一篇关于贝塞尔曲线实现 飘心动画的效果,目的就是 便于理解上篇文章代码的思路,然后写个关于飘心动画的自己的理解。
下面是我参照的文章:一步一步教你实现Periscope点赞效果,—文章出自简书。 我也是是依葫芦画瓢,所以就定义为转载的文章,只是文章里面加了些自己理解的东西。效果图如下:
1.定义飘心的布局
这个我相信大家很容易想到使用RelativeLayout,对,没错,那么我们先定义一个Layout吧,继承自RelativeLayout,并且重载构造函数,并定义一些变量.
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import com.myapplication2.app.R;
import java.util.Random;
/**
* http://www.jianshu.com/p/03fdcfd3ae9c
* https://github.com/AlanCheen/PeriscopeLayout/blob/master/library/src/main/java/me/yifeiyuan/library/PeriscopeLayout.java
*
* 参考实现的自定义飘心动画的布局
* time:2016年8月31日10:10:34
* @see android.widget.RelativeLayout
*/
public class PeriscopeLayout extends RelativeLayout{
private int dHeight; //爱心的高度
private int dWidth; //爱心的宽度
private int mHeight; //自定义布局的高度
private int mWidth; //自定义布局的宽度
private LayoutParams layoutParams;
private Random random = new Random(); //用于获取随机心的随机数
private Drawable[] drawables; //存放初始化图片的数组
/**
* 是在java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个
*/
public PeriscopeLayout(Context context) {
super(context);
init();
}
/**
* 这个是在xml创建但是没有指定style的时候被调用
*/
public PeriscopeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* 这个是在xml创建但是 有指定style的时候被调用
*/
public PeriscopeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化布局和随机心型图片
*/
private void init(){
//初始化显示的图片,暂时使用3 种图片
drawables = new Drawable[3];
//getResources().getDrawable 过期的替代方法 ContextCompat.getDrawable(getContext(),R.drawable.heart3);
// Drawable red = getResources().getDrawable(R.drawable.heart3);
Drawable red = ContextCompat.getDrawable(getContext(),R.drawable.heart3);
Drawable yellow = ContextCompat.getDrawable(getContext(),R.drawable.heart8);
Drawable blue = ContextCompat.getDrawable(getContext(),R.drawable.heart6);
drawables[0] = red;
drawables[1] = yellow;
drawables[2] = blue;
//获取图的宽高 用于后面的计算
//注意 我这里3张图片的大小都是一样的,所以我只取了一个
dHeight = red.getIntrinsicHeight();
dWidth = red.getIntrinsicWidth();
//定义心型图片出现的位置,底部 水平居中
layoutParams = new LayoutParams(dWidth,dHeight);
layoutParams.addRule(CENTER_HORIZONTAL,TRUE);
layoutParams.addRule(ALIGN_PARENT_BOTTOM,TRUE);
}
/**
* http://blog.csdn.net/pi9nc/article/details/18764863
* 自定义布局 onMeasure 的作用
* 获取控件的实际高度
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//注意!! 获取本身的宽高 需要在测量之后才有宽高
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
1.1 处理心性图片的位置
//定义心型图片出现的位置,底部 水平居中
layoutParams = new LayoutParams(dWidth,dHeight);
layoutParams.addRule(CENTER_HORIZONTAL,TRUE);
layoutParams.addRule(ALIGN_PARENT_BOTTOM,TRUE);
1.2 用一个数组存放默认的心型的图片
//getResources().getDrawable 过期的替代方法 ContextCompat.getDrawable(getContext(),R.drawable.heart3);
Drawable red = ContextCompat.getDrawable(getContext(),R.drawable.heart3);
……
…..
drawables[0] = red;
drawables[1] = yellow;
drawables[2] = blue;
1.3 直接通过随机数的方法就可以获取随机的图片
drawables[random.nextInt(3)]//表示0-2的随机数,注意,3是取不到的,是个开区间
1.4 onMeasure方法的说明
getWidth(): View在设定好布局后整个view 的宽度。
getMeasuredWidth(): 对View上的內容进行测量后得到到的View內容占据的宽度。
具体的说明如下:
http://blog.csdn.net/pi9nc/article/details/18764863
2.实现缩放和透明度变化的动画
直接通过 ObjectAnimator 即属性动画实现缩放和透明度变化的动画效果
在PeriscopeLayout.java 文件里面添加对应的放就可
2.1 属性动画 的实现
/**
* 通过属性动画 实现爱心图片的缩放和透明度变化的动画效果
* target 就是爱心图片的view
*/
private AnimatorSet getEnterAnimtor(final View target){
ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, 0.2f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, 0.2f, 1f);
AnimatorSet enter = new AnimatorSet();
enter.setDuration(500);
enter.setInterpolator(new LinearInterpolator());//线性变化
enter.playTogether(scaleX,scaleY);
enter.setTarget(target);
return enter;
}
/**
* 动画结束后,remove
*/
private class AnimEndListener extends AnimatorListenerAdapter {
private View target;
public AnimEndListener(View target) {
this.target = target;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
//因为不停的add 导致子view数量只增不减,所以在view动画结束后remove掉
removeView((target));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
2.2 提供外部方法的实现
/**
* 提供外部实现点击效果,只有缩放和变淡的效果
*/
public void addFavorWithoutBiz(){
ImageView imageView = new ImageView(getContext());
//随机心型颜色
imageView.setImageDrawable(drawables[random.nextInt(3)]);
imageView.setLayoutParams(layoutParams);
addView(imageView);
Animator set = getEnterAnimtor(imageView);
set.addListener(new AnimEndListener(imageView));
set.start();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
2.3 无贝塞尔曲线的效果
3.实现贝塞尔曲线效果
三次方公式
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。
曲线的参数形式为:
现代的成象系统,如PostScript、Asymptote和Metafont,运用了以贝兹样条组成的三次贝兹曲线,用来描绘曲线轮廓。
公式中需要四个P,P0,是我们的起点,P3是终点,P1,P2是途径的两个点
而t则是我们的一个因子,取值范围是0-1
3.1 自定义 BezierEvaluator 实现 TypeEvaluator
import android.animation.TypeEvaluator;
import android.graphics.PointF;
/**
* 我们自定义一个BezierEvaluator 实现 TypeEvaluator
* 由于我们view的移动需要控制x y 所以就传入PointF 作为参数,是不是感觉完全契合
*/
public class BezierEvaluator implements TypeEvaluator<PointF> {
private PointF pointF1;
private PointF pointF2;
public BezierEvaluator(PointF pointF1,PointF pointF2){
this.pointF1 = pointF1;
this.pointF2 = pointF2;
}
@Override
public PointF evaluate(float time, PointF startValue,
PointF endValue) {
float timeLeft = 1.0f - time;
PointF point = new PointF();//结果
point.x = timeLeft * timeLeft * timeLeft * (startValue.x)
+ 3 * timeLeft * timeLeft * time * (pointF1.x)
+ 3 * timeLeft * time * time * (pointF2.x)
+ time * time * time * (endValue.x);
point.y = timeLeft * timeLeft * timeLeft * (startValue.y)
+ 3 * timeLeft * timeLeft * time * (pointF1.y)
+ 3 * timeLeft * time * time * (pointF2.y)
+ time * time * time * (endValue.y);
return point;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
3.2 定义贝塞尔曲线的动画实现
/**
* 贝塞尔曲线的动画实现
*/
private ValueAnimator getBezierValueAnimator(View target) {
//初始化一个BezierEvaluator
BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1));
//第一个PointF传入的是初始点的位置
ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight-20), new PointF(random.nextInt(getWidth()), 0));//随机
animator.addUpdateListener(new BezierListenr(target));
animator.setTarget(target);
animator.setDuration(3000);
return animator;
}
/**
* 获取中间的两个点
*/
private PointF getPointF(int scale) {
PointF pointF = new PointF();
pointF.x = random.nextInt((mWidth - 50));//减去50 是为了控制 x轴活动范围,看效果 随意~~
//再Y轴上 为了确保第二个点 在第一个点之上,我把Y分成了上下两半 这样动画效果好一些 也可以用其他方法
pointF.y = random.nextInt((mHeight - 150)) / scale;
return pointF;
}
/**
* 只有在回调里使用了计算的值,才能真正做到曲线运动
*/
private class BezierListenr implements ValueAnimator.AnimatorUpdateListener {
private View target;
public BezierListenr(View target) {
this.target = target;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//这里获取到贝塞尔曲线计算出来的的x y值 赋值给view 这样就能让爱心随着曲线走啦
PointF pointF = (PointF) animation.getAnimatedValue();
target.setX(pointF.x);
target.setY(pointF.y);
// alpha动画
target.setAlpha(1 - animation.getAnimatedFraction());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
注意:如果发现初始位置不对,存在抖动现象
getBezierValueAnimator 函数中
ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight-20), new PointF(random.nextInt(getWidth()), 0));//随机第一个pointF 的点来控制位置
3.3 增加调用方法
public void addFavor() {
ImageView imageView = new ImageView(getContext());
imageView.setImageDrawable(drawables[random.nextInt(3)]);
imageView.setLayoutParams(layoutParams);
addView(imageView);
Animator set = getAnimator(imageView);
set.addListener(new AnimEndListener(imageView));
set.start();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
3.4 效果展示
4.具体使用
xml 代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/grey"
android:alpha="0.5">
<com.myapplication2.app.newsdemo.view.bizHeartview.PeriscopeLayout
android:id="@+id/heart_layout"
android:layout_alignParentRight="true"
android:layout_width="100dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/member_send_good"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginRight="30dp"
android:layout_marginBottom="10dp"
android:background="@drawable/live_like_icon"
/>
</RelativeLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
5.参考资料
http://www.jianshu.com/p/03fdcfd3ae9c
https://github.com/AlanCheen/PeriscopeLayout
6.总结
发现有些东西只看一遍,没什么效果。只有自己动手才知道里面的困难,虽然是一步步仿照过来的,但是也学到了一些东西,例如自定义view相关的知识点,以及属性动画,插补器的使用,因为遇到不懂得地方需要自己去查资料,而查资料的过程就是学习的过程。
上一篇: Day32:反射