最近面试,面试官说了一个场景,大概类似于类似于两个点比如AB吧,从A到B这个运动轨迹要以抛物线的形式运动,说下实现的思路,因为我做过新手引导,通过获取到两个点的绝对坐标,这样通过动画来说,面试官说了,动画的话是一条直线,我当时立刻想到了贝塞尔曲线,我说用贝塞尔曲线来实现,他不说话了,貌似这个面试官是Java的,对安卓不是很了解,我觉得我面试的基本都回答的很不错,竟然后续也没通知了,额,心累啊,最近行情确实不好啊,可能缘分未到吧,那咱们今天就来实现下这个动画,既然面试官说了,Animation动画实现的是一条直线,那就来验证下是否是条直线.
先来看下效果图,基本上应该是这样的效果吧,那两条线是贝瑟尔曲线,不看那个线,看点击A时候的动画
一 分析场景
1.可以看到,面试官说的那种效果应该大概是这样的,但是他说Animation实现的是条直线,其实是不完全对的,如果单一的动画肯定是条线性的直线,但是通过组合动画可以模拟实现这个类抛物线的效果,思路就是让A到B水平X轴方向线性移动的同时,让其同时在Y轴方向有线性或加速移动;当然,贝塞尔曲线肯定也是可以的,咱先以这种的方式实现下
2.A和B(购物车)的都是TextView,两者的坐标很容易可以算出来,通过系统的getLocationInWindow,
/**
* <p>Computes the coordinates of this view in its window. The argument
* must be an array of two integers. After the method returns, the array
* contains the x and y location in that order.</p>
*
* @param outLocation an array of two integers in which to hold the coordinates
*/
public void getLocationInWindow(@Size(2) int[] outLocation) {
if (outLocation == null || outLocation.length < 2) {
throw new IllegalArgumentException("outLocation must be an array of two integers");
}
outLocation[0] = 0;
outLocation[1] = 0;
transformFromViewToWindowSpace(outLocation);
可以看下对这个方法的解释,返回的是2个长度的int类型的数组,ok,定义两个int类型的长度为2的数组
int [] firstPoint = new int[2]; int [] endPoint = new int[2];简单的封装下获取点的坐标的方法
private int[] getLocationXY(View view) { final int[] xy = new int[2]; view.getLocationInWindow(xy); return xy; }组合动画,主要的是要分析这个,动画我们是给控件设定,之前咱们已经得到了这个两点的坐标,则开始点和结束点坐标X 轴的差值,Y轴的差值可以算出来,咱们只考虑差值即可
private Animation createAnimation(int startX, int startY) { //组合动画集合 AnimationSet set = new AnimationSet(false); //开始点和结束点坐标X 轴的差值 Animation translationX = new TranslateAnimation(0, endPoint[0] - startX, 0, 0); //线性插值器 默认就是线性 translationX.setInterpolator(new LinearInterpolator()); //开始点和结束点坐标Y 轴的差值 Animation translationY = new TranslateAnimation(0, 0, 0, endPoint[1] - startY); //设置加速插值器 translationY.setInterpolator(new AccelerateInterpolator()); //还可以设置一些其他的动画 // Animation alpha = new AlphaAnimation(1,0.4f); set.addAnimation(translationX); set.addAnimation(translationY); // set.addAnimation(alpha); set.setDuration(500); return set; }则启动动画的方法如下:
@OnClick(R.id.animation_tv) void startAnim() { //A点在屏幕上的坐标 animationTv.getLocationInWindow(firstPoint); //购物车在屏幕上的坐标 bottomMoneyCarTv.getLocationInWindow(endPoint); Animation animation = createAnimation(firstPoint[0], firstPoint[1]); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { //// TODO: 2016/12/19 more action } @Override public void onAnimationRepeat(Animation animation) { } }); animationTv.startAnimation(animation); }
上面的是以组合动画的方式实现了这种类抛物线的动画.
二 使用贝塞尔曲线实现动画
1.贝塞尔曲线的公式就不列举了,看下一摘取网络上的图片
可以看到使用贝塞尔的时候,需要算出这个t的实时点,这个就是动画的轨迹
看下贝塞尔实现的效果图,我觉得和组合动画差不多
同样两个定点P0和P2很容易,即firstPoint和endPoint,下面是具体实现,其实也是模范代码了,注释已经很清楚了,不会的再去百度谷歌吧
//A点在屏幕上的坐标 animationTv.getLocationInWindow(firstPoint); // 得到父布局的起始点坐标(用于辅助计算动画开始/结束时的点的坐标,这个很重要,因为二次贝塞尔曲线就是3个定点,这个点相当于是给弧线定一个范围) int[] parentLocation = new int[2]; positionView.getLocationInWindow(parentLocation); //购物车在屏幕上的坐标 bottomMoneyCarTv.getLocationInWindow(endPoint); float startX = firstPoint[0] - parentLocation[0] + animationTv.getWidth() / 2; float startY = firstPoint[1] - parentLocation[1] + animationTv.getHeight() / 2; float toX = endPoint[0] - parentLocation[0] + bottomMoneyCarTv.getWidth() / 2; float toY = endPoint[1] - parentLocation[1]; // 开始绘制贝塞尔曲线 Path path = new Path(); // 移动到起始点(贝塞尔曲线的起点) path.moveTo(startX, 0); // 使用二阶贝塞尔曲线:注意第一个起始坐标越大,贝塞尔曲线的横向距离就会越大,一般按照下面的式子取即可 path.quadTo((startX + toX) / 2, startY, toX, toY); // mPathMeasure用来计算贝塞尔曲线的曲线长度和贝塞尔曲线中间插值的坐标,如果是true,path会形成一个闭环 mPathMeasure = new PathMeasure(path, false); // 属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值) ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength()); valueAnimator.setDuration(1000); valueAnimator.setRepeatCount(0); // 匀速线性插值器 valueAnimator.setInterpolator(new LinearInterpolator()); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // 当插值计算进行时,获取中间的每个值, // 这里这个值是中间过程中的曲线长度(下面根据这个值来得出中间点的坐标值) float value = (Float) animation.getAnimatedValue(); // 获取当前点坐标封装到mCurrentPosition // boolean getPosTan(float distance, float[] pos, float[] tan) : // 传入一个距离distance(0<=distance<=getLength()),然后会计算当前距离的坐标点和切线,pos会自动填充上坐标,这个方法很重要。 // mCurrentPosition此时就是中间距离点的坐标值 mPathMeasure.getPosTan(value, mCurrentPosition, null); // 移动的TextView的坐标设置为该中间点的坐标 animationTv.setTranslationX(mCurrentPosition[0]); animationTv.setTranslationY(mCurrentPosition[1]); } }); // 开始执行动画 valueAnimator.start(); // 动画结束后的处理 valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { //// TODO: 2016/12/19 } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } });
OK,这个就是面试给我的灵感吧,虽说不难,代码这东西,也是贵在实践加总结,找出适合自己的套路,解决问题就行!