引言
这是作者第一个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

来自:CSDN-CF凌晨http://blog.csdn.net/qq_27495349/article/details/53008975程序员大咖整理发布,转载请联系作者获得授权
|