Android贝塞尔曲线实战 [复制链接]

2019-7-9 10:10
jjcodecode 阅读:379 评论:0 赞:0

BackgroundView

  • materialup上看到了一个设计图,正好最近在研究贝塞尔曲线,就用贝塞尔曲线实现了这么一个效果- 图形是用贝塞尔曲线画出来的,叶子的随风飘动动画也是用贝塞尔曲线实现的,希望对大家有帮助

  • 代码只是初略完成,很多细节还没优化,如对代码有疑问欢迎邮件给我 ssseasonnn@gmail.com

  • github地址BackgroundView

设计图原图地址

设计效果图:

图片描述

代码实现的效果图:

图片描述

具体实现细节

  • 经过分析,设计图涉及到的图形有以下几个:

  • 云朵

  • 山上的树木

  • 叶子

  • 设计到的动画有两个:

  • 移动的云朵

  • 随风飘动的叶子

这样一分解,就可以按着步骤一个一个来实现了

  • 首先先把图形准备好,有两种方式,一种是直接用图片,第二种是自己画,当然不是用手画,是用代码画
    这里我选择了用代码画的方式来实现,因为最近正在研究贝塞尔曲线,正好就拿来练手了。

关于贝塞尔曲线如果有朋友不清楚的,可以参考以下扫盲贴:Path之贝塞尔曲线贝塞尔曲线

还有以下文章是我学习中遇到的:

设计师教你怎么用最偷懒的方式画贝塞尔曲线贝塞尔曲线在线绘制工具

  • 这里我就以画云朵来举例,其余的都是一样的
    云朵比较简单,就是两条曲线和底下一条直线构成,利用photoshop或者AI(adobe illustrator)中的钢笔工具获取一个大致的数据点的坐标和控制点的坐标,就可以画出这么一个图形了。photoshop和AI中的钢笔工具其实就是贝塞尔曲线。

如何用photoshop的钢笔工具这里贴一个地址

教你如何使用钢笔工具

具体步骤:
先用AI钢笔工具画一个大概的云朵形状,注意事项看图就行了啊!

图片描述
然后找到控制点和数据点的坐标,控制点在ps或AI中叫手柄,数据点叫锚点
当你用直接选择工具移动到锚点时,会自动显示出锚点坐标,如图所示
钢笔工具
这里有一点需要注意的就是,但是放到手柄上就不会出现坐标了,那么怎么办呢
先在视图选项中把标尺显示出来:
钢笔工具
接下来就可以通过标尺来估读手柄的坐标了,不用太精确,小数什么的都可以不用管
钢笔工具

这样一来,程序所需要的数据点和控制点就可以确定了,接下来就是在代码中来画了:
这里我就直接贴代码了,具体涉及到Canvas和Path这些不懂的概念可以先百度和谷歌,这些Api函数都是很通俗易懂的,另外可以通过邮件来和我探讨

画贝塞尔曲线主要用到Path的两个方法:

    public void quadTo(float x1, float y1, float x2, float y2)

    public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 

第一个方法是用来画二阶贝塞尔曲线的,二阶贝塞尔曲线只需要一个控制点,所以(x1,y1) 是控制点的坐标,(x2,y2)是数据点的坐标。
第二个方法是用来画三阶贝塞尔曲线的,三阶贝塞尔曲线需要两个控制点,所以(x1,y1)和(x2,y2)是控制点的坐标,(x3,y3)是数据的的坐标。

     private void init() {    
  mPaint = new Paint();    
  mPaint.setColor(getResources().getColor(R.color.cloud));   
  mPaint.setStrokeWidth(8);    
  mPaint.setAntiAlias(true);   
  mPaint.setDither(true);    
  mPaint.setStyle(Paint.Style.FILL);    
  mPaint.setTextSize(60);    
  mPaint.setPathEffect(new CornerPathEffect(4));
   }

     @Override    
    protected void onDraw(Canvas canvas) {        
  super.onDraw(canvas);
  Path path = new Path();        
  path.moveTo(5.82f * mScaleW, 51.78f * mScaleH);       
  path.quadTo(24 * mScaleW, 28 * mScaleH, 51.2f * mScaleW, 34.4f * mScaleH);     
  path.cubicTo(51f * mScaleW, 14 * mScaleH, 113 * mScaleW, -3 * mScaleH, mWidth, 51.78f * mScaleH);
  path.lineTo(5.82f * mScaleW, 51.78f * mScaleH);        
  canvas.drawPath(path, mPaint);    
      }

其余的几个图形也是类似的画法。

画好了图形,接下来就是处理动画效果了

云朵的平移动画很简单就不具体解释了,直接贴代码:
/**

  • 平移动画
    */

ObjectAnimator translationAnimation = ObjectAnimator.ofFloat(view, "x", width, -width);
translationAnimation.setDuration(duration);
translationAnimation.setStartDelay(startDelay);
translationAnimation.setRepeatCount(ValueAnimator.INFINITE);
translationAnimation.setRepeatMode(ValueAnimator.INFINITE);

    translationAnimation.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.INVISIBLE);
        }

        @Override
        public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
view.setVisibility(View.VISIBLE);
        }
    });
    translationAnimation.start();

接下来介绍一下叶子的动画,根据设计图观察可以看出,叶子有两个动画,一个是移动,一个是旋转,
旋转很简单,那么移动的动画就需要研究一下了。

首先叶子并不是平移,而是做曲线运动,而且是不规则的曲线运动,一看到曲线二字,立马想到贝塞尔曲线,
既然贝塞尔曲线可以画任意曲线,那么我事先画出一条叶子的运动曲线,然后根据这条运动曲线的坐标来更新叶子的位置,不就实现了吗。

图片描述

有了这么条曲线,接下来就是根据这条曲线来实现叶子动画了
首先用Path构造出这条曲线:

    Path path = new Path();
    path.moveTo(mData[0].x, mData[0].y * mScaleH);
    path.quadTo(mCtrl[0].x * mScaleW, mCtrl[0].y * mScaleH, mData[1].x * mScaleW, mData[1].y * mScaleH);
    path.cubicTo(mCtrl[1].x * mScaleW, mCtrl[1].y * mScaleH, mCtrl[2].x * mScaleW, mCtrl[2].y * mScaleH,
mData[2].x * mScaleW, mData[2].y * mScaleH);
    path.cubicTo(mCtrl[3].x * mScaleW, mCtrl[3].y * mScaleH, mCtrl[4].x * mScaleW, mCtrl[4].y * mScaleH, mData[3]
.x * mScaleW, mData[3].y * mScaleH);
    path.quadTo(mCtrl[5].x * mScaleW, mCtrl[5].y * mScaleH, mData[4].x * mScaleW, mData[4].y * mScaleH);

接下来就是如何根据这个Path来更新叶子的位置。
如果是Android 5.0 ,属性动画有一个直接的Api可以根据Path来创建动画:

    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "x", "y", path);
    objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
    objectAnimator.setRepeatMode(ValueAnimator.INFINITE);
    objectAnimator.setDuration(5000);
    objectAnimator.start();

为了兼容低版本,有另外一种方法,使用PathMeasure,关于PathMeasure可以见这篇文章.简单来说就是PathMeasure 把 Path “拉直”,然后给了我们一个接口(getLength)告诉我们path的总长度,然后我们想要知道具体某一点的坐标,只需要用相对的distance去取即可。
直接贴代码:

    /**
     * 贝塞尔曲线动画
     */
    final PathMeasure mPathMeasure = new PathMeasure(path, false); //false的意思是不封闭Path,不让Path形成一个环
    final float[] pointF = new float[2];

    ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
    valueAnimator.setDuration(duration);
    valueAnimator.setStartDelay(startDelay);
    valueAnimator.setInterpolator(interpolator);
    valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
    valueAnimator.setRepeatMode(ValueAnimator.INFINITE);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
// 获取当前点坐标封装到pointF
mPathMeasure.getPosTan(value, pointF, null);
view.setX(pointF[0]);
view.setTranslationY(pointF[1]);
        }
    });
    valueAnimator.start();

我来说两句
您需要登录后才可以评论 登录 | 立即注册
facelist
所有评论(0)
领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )