引言
这是作者第一个CSDN的文章,写的不好的地方,请大家多多提提意见哈。今天写的文章是关于动画的,前几天公司要求我写一个观看直播点击送花与主播收到花朵的效果。
思路
好了看见了效果之后想必大家都应该知道肯定想到要用Animator(4.0增加的动画类),想必很多人都接触到了,但是刚接触Android的兄弟可能就没怎么使用这个。网上有很多文章对这个类的介绍,所以这里就不注重解释了。
主要的思路如下:
1.确定起点(主播是随机起点)与终点位置;
2.为其添加动画特效,从起点运动到终点,然后销毁View。
代码
我就不拿以前的代码了,我一边敲一边说,首先创建个项目(不多说了哈),然后我们创建一个工具类FlowerAnimation.java。我们就对这个类疯狂撸吧。
固定起点->固定终点特效实现
private static final String TAG = 'FlowerAnimation';
//上下文
private Context mContext;
//生成的View添加在的ViewGroup
private ViewGroup rootView;
private ViewGroup.LayoutParams layoutParams;
//资源文件
private Drawable[] drawables;
//插值器
private Interpolator[] interpolators;
上面代码的注释很清楚了,大家一看就明白了 ,TGA这是为了测试打印log使用的(AS快捷键-logt+enter)。
public FlowerAnimation(Context mContext, ViewGroup rootView) {
this.mContext = mContext;
this.rootView = rootView;
init();
}
private void init() {
drawables = new Drawable[8];
drawables[0] = mContext.getResources().getDrawable(R.mipmap.flower_01);
drawables[1] = mContext.getResources().getDrawable(R.mipmap.flower_02);
drawables[2] = mContext.getResources().getDrawable(R.mipmap.flower_03);
drawables[3] = mContext.getResources().getDrawable(R.mipmap.flower_04);
drawables[4] = mContext.getResources().getDrawable(R.mipmap.flower_05);
drawables[5] = mContext.getResources().getDrawable(R.mipmap.flower_06);
drawables[6] = mContext.getResources().getDrawable(R.mipmap.flower_07);
drawables[7] = mContext.getResources().getDrawable(R.mipmap.flower_08);
interpolators = new Interpolator[4];
interpolators[0] = new LinearInterpolator();//线性
interpolators[1] = new AccelerateInterpolator();//加速
interpolators[2] = new DecelerateInterpolator();//减速
interpolators[3] = new AccelerateDecelerateInterpolator();//先加速后减速
layoutParams = new ViewGroup.LayoutParams(DensityUtil.dip2px(mContext, 50), DensityUtil.dip2px(mContext, 50));
}
上面就是一些初始化的东西了,大家看看就好。
准备工作做好了,我们写最主要的方法,那就是生成动画了,上代码
/**
/**
* 开启动画
*
* @param view 执行动画的view
* @param startP 起点 如果传null 默认从view位置开始
* @param stopP 终点
*/
public void startAnim(@NonNull final View view, @Nullable PointF startP, @NonNull PointF stopP) {
if (startP == null) {
startP = new PointF(view.getX(), view.getY());
}
//透明度变化
ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(view, 'alpha', 0, 1);
animatorAlpha.setDuration(200);
//位移动画
ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, 'translationX', startP.x, stopP.x);
animatorX.setDuration(1000);
ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, 'translationY', startP.y, stopP.y);
animatorY.setDuration(1000);
//生成动画集合
AnimatorSet set = new AnimatorSet();
//开启透明度动画然后执行位移动画
set.play(animatorAlpha).before(animatorX).with(animatorY);
//加入植入器
set.setInterpolator(interpolators[rand.nextInt(interpolators.length)]);
//添加动画监听事件 为了移除view 防止造成oom
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
rootView.removeView(view);
}
});
set.start();
}
/**
* 开启动画
*
* @param view 执行动画的view
* @param stopP 终点
*/
public void startAnim(@NonNull final View view, @NonNull PointF stopP) {
startAnim(view, null, stopP);
}
/**
* 添加花朵
*
* @param startPoint
*/
public void addFlower(@NonNull PointF startPoint, @NonNull PointF stopP) {
ImageView flower = new ImageView(mContext);
flower.setX(startPoint.x);
flower.setY(startPoint.y);
Drawable drawable = drawables[rand.nextInt(drawables.length)];
flower.setBackground(drawable);
rootView.addView(flower, layoutParams);
startAnim(flower, startPoint, stopP);
}
好了,我们看到,生成这个动画需要View(这是必然的)终点也是必然,起点就无所谓了。每一步的注释都写的很清楚,ObjectAnimator这个类功能很强大(4.0+)。下面开始写个界面来看看效果啦。界面代码我直接上代码 不讲解了!对了有关于view位置的问题大家直接去看别的文章吧,很多的哈,我这里贴个我认为不错的:
http://blog.csdn.net/jason0539/article/details/42743531
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//终点坐标imageView
private ImageView endFlowerIv;
//开启动画按钮 也是起点坐标
private Button startFlowerBt;
private FlowerAnimation flowerAnimation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
endFlowerIv = (ImageView) findViewById(R.id.main_end_flower_iv);
startFlowerBt = (Button) findViewById(R.id.main_start_flower_bt);
startFlowerBt.setOnClickListener(this);
flowerAnimation = new FlowerAnimation(this, (ViewGroup) findViewById(R.id.activity_main));
}
@Override
public void onClick(View v) {
flowerAnimation.addFlower(new PointF(v.getX(), v.getY()), new PointF(endFlowerIv.getX(), endFlowerIv.getY()));
}
android:id='@+id/activity_main'
android:layout_width='match_parent'
android:layout_height='match_parent'>
<>
android:id='@+id/main_end_flower_iv'
android:layout_width='50dp'
android:layout_height='50dp'
android:layout_gravity='center_horizontal'
android:background='@mipmap/flower_01' />
<>
android:id='@+id/main_start_flower_bt'
android:layout_width='50dp'
android:layout_height='50dp'
android:layout_gravity='center_horizontal|bottom'
android:background='@mipmap/flower_01' />
下面是效果图
这个问题想必大家在上面的基础上就完全可以写出来 直接把代码贴上来与效果
/**
* 添加花朵 随即生成起点(rootView范围)
*
* @param stopP 终点
*/
public void addFlowerByScope(@NonNull PointF stopP) {
float x = rand.nextFloat() * rootView.getWidth();
float y = rand.nextFloat() * rootView.getHeight();
addFlower(new PointF(x, y), stopP);
}
/**
* 添加花朵 随即生成起点
*
* @param stopP 终点
* @param scopeP 范围 随即生成的点将会按照此范围随即取值
*/
public void addFlowerByScope(@NonNull PointF stopP, @NonNull PointF scopeP) {
float x = rand.nextFloat() * scopeP.x;
float y = rand.nextFloat() * scopeP.y;
addFlower(new PointF(x, y), stopP);
}
界面的点击事件换成对应的方法
@Override
public void onClick(View v) {
flowerAnimation.addFlowerByScope(new PointF(endFlowerIv.getX(), endFlowerIv.getY()));
}
到此,你会发现已经完成主播接到花朵的效果(就是随即从各个地方出现花朵飞到花朵出)以上就是主播界面显示的效果了。代码比较简单,下面实现观众点击送花的效果。
思路呢?其实跟上面差不多,观众送花的效果类似固定点到固定点的效果(类似哈哈),为什么说类似呢?因为从图上可以看到,路径是不同的,很明显发现 观众送花的效果的路径是随即的(乱飘)。这里就引出来了ValueAnimator 这个东西你会发现他是ObjectAnimator的父类。
ValueAnimator 顾名思义哈 就是针对数值的动画,他能帮我完成什么呢?
比如我我想让一个数值从0-10 时间是10s,我们来写写看我们新建一个ValueAnimActivity.java来实现观众的界面,顺便在里面测试demo。
public class ValueAnimActivity extends AppCompatActivity implements View.OnClickListener {
private TextView countTv;
private Button startBt;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_value);
initView();
}
private void initView() {
countTv = (TextView) findViewById(R.id.value_count_tv);
startBt = (Button) findViewById(R.id.value_start_bt);
startBt.setOnClickListener(this);
}
@Override
public void onClick(View v) {
startValueAnim();
}
private void startValueAnim() {
//从0-10 时间10s
ValueAnimator countAnim = ValueAnimator.ofInt(0, 10)
.setDuration(10000);
countAnim.setInterpolator(new LinearInterpolator());
countAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
countTv.setText(animation.getAnimatedValue().toString());
}
});
countAnim.start();
}
}
android:layout_width='match_parent'
android:layout_height='match_parent'
android:orientation='vertical'>
<>
android:id='@+id/value_count_tv'
android:layout_width='wrap_content'
android:layout_height='wrap_content'
android:layout_gravity='center'
android:textSize='30dp' />
<>
android:id='@+id/value_start_bt'
android:layout_width='wrap_content'
android:layout_height='wrap_content'
android:layout_gravity='center_horizontal|bottom'
android:text='start' />
代码跟布局都很简单 我们简单写了个ValueAnimator的例子 直接看效果
那怎么实现我们的效果呢?这里就用到一个方法
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
他能让一个对象进行改变,那怎么改变呢?其实随便是什么?都要跟时间挂钩(重要),为什么跟时间挂钩,这还要解释么?onAnimationUpdate回调的方法你打印log你会发现他会调用N多次,10S回调了496次,可想而知我们对象改变也会跟时间有关系,那么我们看看TypeEvaluator(类型评估者)这是一个接口,我们来看看它要我们实现的方法
public T evaluate(float fraction, T startValue, T endValue);
这个方法看到之后,后面2个我肯定知道是什么意思,动画的起始值,那第一个是什么?(英文翻译是分数)咱们说过肯定跟时间有关的,那么这个是不是就是时间呢?看了官方解释之后,这个意思就是当前完成动画的百分比。
还不懂?那好我们还看看官方有没有默认给我们实现的类,一看,有很多,我们直接拿来一个用看看效果,上代码
private void startValueAnim1() {
//从0-10 时间10s
ValueAnimator countAnim = ValueAnimator.ofObject(new IntEvaluator() {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
Log.e(TAG, 'evaluate() called with: fraction = [' + fraction + '], startValue = [' + startValue + '], endValue = [' + endValue + ']');
return super.evaluate(fraction, startValue, endValue);
}
}, 0, 10)
.setDuration(10000);
countAnim.setInterpolator(new LinearInterpolator());
countAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
countTv.setText(animation.getAnimatedValue().toString());
}
});
countAnim.start();
}
下面重点来了,如果要实现这种效果,那就要一个公式(贝塞尔曲线)这个当初我也是一头雾水啊,先不管直接套用公式就行了(先上一个图片)
这个公式只要理解我们给出4个点,他就算出当前的点。这里有4个点,但是我们只有2个点,另外2个点是为了控制曲线的走向,我随即取就可以咯。好了,我们先不管点,先把TypeEvaluator写好
/**
* 自定义的估值器
*/
public static class MyTypeEvaluator implements TypeEvaluator
{ private PointF pointF1, pointF2;
public MyTypeEvaluator(PointF pointF1, PointF pointF2) {
this.pointF1 = pointF1;
this.pointF2 = pointF2;
}
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float timeLeft = 1.0f - fraction;
PointF pointF = new PointF();//结果
pointF.x = timeLeft * timeLeft * timeLeft * (startValue.x)
+ 3 * timeLeft * timeLeft * fraction * (pointF1.x)
+ 3 * timeLeft * fraction * fraction * (pointF2.x)
+ fraction * fraction * fraction * (endValue.x);
pointF.y = timeLeft * timeLeft * timeLeft * (startValue.y)
+ 3 * timeLeft * timeLeft * fraction * (pointF1.y)
+ 3 * timeLeft * fraction * fraction * (pointF2.y)
+ fraction * fraction * fraction * (endValue.y);
return pointF;
}
}
只是简单的套用公式,就不用多讲了直接复制就好,要不然能看的眼花。
下面放上取中间控制点的代码
注释也简单,我觉得很好弄懂,下面上主要代码
下面是最终效果啦
到这里差不多可以结束了。想必大家可能会问,如果动画还没执行完就退出了,那就内存泄漏了啊。所以我再来个方法。
好了,跑起来没有问题了。
源码地址:https://github.com/CFlingchen/CSDN1
程序员大咖整理发布,转载请联系作者获得授权
联系客服