Android欢迎界面圆点切换粘连特效

需求效果图

需要开发一个动画特效,欢迎界面切换时,底部的指示点需要有移动粘连的效果。由于对粘连特效感兴趣,所以决定自己动手来实现。
先来看一下效果图(不是动图):
Android欢迎界面圆点切换粘连特效_第1张图片

功能分析

  • 自定义View:实现整体的动画效果,使用Canvas绘制
  • 粘连特效:使用Path路径绘制

具体实现

自定义View

自定义View,大家应该非常熟悉,这理解不做介绍了。以下代码是主要使用的方法:

public class MyView extends View {

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context,  AttributeSet attrs) {
        super(context, attrs);        
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

}

粘连特效

粘连特效的实现,主要就是贝塞尔曲线的绘制。

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。百度百科

贝塞尔曲线分为二阶,三阶。现在只需要实现二阶曲线就好。
在Android中,二阶贝塞尔曲线的实现利用的是Path类的quadTo方法。

void    quadTo(float x1, float y1, float x2, float y2)
        Add a quadratic bezier from the last point, approaching control point (x1,y1), and ending at (x2,y2).

就是说(x1,y1)是贝塞尔曲线的控制点,(x2,y2)是结束点。

绘制贝塞尔曲线

Android欢迎界面圆点切换粘连特效_第2张图片
绘制贝塞尔曲线至少需要起点,控制点,结束点三个点位。
对上图来说,我们只需要得到P1,P5,P4三个点的坐标,或者得到P2,P5,P3三个点的坐标,就能绘制出一条贝塞尔曲线。
我们可以利用Path类绘制一个闭合的路径图,所以我们还需要把P1,P2连接起来,把P3,P4连接起来。

求点的坐标

求点的坐标之前,我们是知道两个圆的圆心坐标以及圆的半径大小。
所以利用三角函数可以得出五个点位的坐标,代码如下:

private void Calculation(float pax, float pay, float pbx, float pby) {
        double a = Math.atan((pbx - pax) / (pby - pay));
        double sin = Math.sin(a);
        double cos = Math.cos(a);
        p1.Y = (float) (pay + (sin * 100));
        p1.X = (float) (pax - (cos * 100));

        p2.X = (float) (pax + cos * 100);
        p2.Y = (float) (pay - sin * 100);

        p3.X = (float) (pbx - cos * 100);
        p3.Y = (float) (pby + sin * 100);

        p4.X = (float) (pbx + cos * 100);
        p4.Y = (float) (pby - sin * 100);

        p5.X = (pax + pbx) / 2;
        p5.Y = (pay + pby) / 2;
    }

得到五个点位的坐标,就可以开始组合Path的路径了。

绘制路径

在组合Path路径之前,需要介绍一个Path的方法:

void    lineTo(float x, float y)
        Add a line from the last point to the specified point (x,y).

意思就是说连接Path设置的上一个点的坐标,默认为(0,0)

path.reset();
path.moveTo(p1.X, p1.Y);
path.quadTo(p5.X, p5.Y, p3.X, p3.Y);
path.lineTo(p4.X, p4.Y);
path.quadTo(p5.X, p5.Y, p2.X, p2.Y);
path.lineTo(p1.X, p1.Y);

连接成一个闭合的路径。

//绘制贝塞尔曲线
canvas.drawPath(path, paint);

调用drawPath方法开始绘制Path,就能绘制出贝塞尔曲线。

整体移动特效

以上介绍了主要的功能实现,粘连特效的形成。下面开始介绍滑动效果。

  • 对象化
    我把每个圆点抽象为一个对象,并实现他们本身的绘制方法。
public class CirclePoint {

    private P p;
    private Paint paint;

    public CirclePoint() {
        initPaint();
    }

    public void setP(P p) {
        this.p = p;
    }

    public P getP() {
        return p;
    }

    //绘制方法,绘制的画布与颜色
    public void onDraw(Canvas canvas, int color) {
        if (p == null) return;
        paint.setColor(color);
        canvas.drawCircle(p.X, p.Y, p.radius, paint);
    }

    //初始化画笔
    private void initPaint() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setStrokeWidth(2);
    }
}
  • 绘制固定圆点(以下称之为定圆)
    创建定圆
itemWidth = ViewWidth / count;
itemCenter = itemWidth / 2;
circlePoints.clear();
for (int i = 0; i < count; i++) {
    CirclePoint circlePoint = new CirclePoint();
    P p = new P();
    p.X = itemCenter + itemWidth * i;
    p.Y = pointY;
    p.radius = circleRadius;
    circlePoint.setP(p);
    circlePoints.add(circlePoint);
}

然后在自定义View里面循环绘制每个定圆。

//绘制每个定圆
for (int i = 0; i < circlePoints.size(); i++) {
   circlePoints.get(i).onDraw(canvas, normalColor);      
}
  • 绘制移动的圆点(以下简称动圆)
    动圆的大小与定圆的大小相同,且它的默认位置与第一个定圆紧挨着,所以我设置动圆的圆心起始坐标为第一个定圆坐标顺X轴右移一个半径。
animP.X = itemCenter + circleRadius;
animP.Y = pointY;
animP.radius = circleRadius;
animCirclePoint.setP(animP);
  • 切换圆点
    再动圆不滑动时,且与定圆紧挨着,这是Path路径是一个矩形路径,而非贝塞尔曲线的曲线路径。
    所以Path路径,还有一种:
//计算方框Path
private void CalculationFramePath() {
   path.moveTo(p1.X, p1.Y);
   path.lineTo(p3.X, p3.Y);
   path.lineTo(p4.X, p4.Y);
   path.lineTo(p2.X, p2.Y);
   path.close();
}

在滑动过程中,动圆需要不断切换与之一起绘制形成Path闭合路径(贝塞尔曲线)的定圆,所以就需要计算动圆与当前形成闭合路径的定圆的两圆心的距离,当距离超过一定值后(当前设置为两定圆之间距离的一半减去偏移量),切换到下一个定圆。偏移量两个定圆中心点,往左右的偏移量。当动圆处于此位置时,动圆不与任何定圆产生Path闭合路径。
两圆心的距离:

circleDistance = Math.abs(Math.sqrt(Math.pow(pax - pbx, 2) + Math.pow(pay - pby, 2)));

滑动逻辑处理:

path.reset();//重置路径
        if (circleDistance <= circleRadius * 2) {
            //绘制闭合方框
            CalculationFramePath();
        } else if (circleDistance > circleRadius * 2 && circleDistance < itemCenter - offSet) {
            //绘制贝塞尔曲线
            CalculationBezierPath();
        } else if (circleDistance > itemCenter - offSet && circleDistance < itemCenter + offSet) {
            //取消绘制贝塞尔曲线
        } else if (circleDistance >= itemCenter + offSet) {
            //切换下一个圆,以此圆为基础计算Path路径,然后绘制
            if (currentIndex <= circlePoints.size() - 1) {
                if (pax > pbx) {//动圆位于当前圆的左侧
                    currentIndex = currentIndex - 1;
                } else {//动圆位于当前圆的右侧
                    currentIndex = currentIndex + 1;
                }
            }
        }

然后在OnDraw方法里面开始绘制定圆与动圆,然后计算滑动逻辑,绘制路径即可。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (circlePoints == null || circlePoints.size() <= 0) return;
        //绘制每个圆,根据当前的位置,处理绘制的颜色
        for (int i = 0; i < circlePoints.size(); i++) {
            if (currentIndex == i) {
                circlePoints.get(i).onDraw(canvas, selectColor);
            } else {
                circlePoints.get(i).onDraw(canvas, normalColor);
            }
        }
        //绘制移动的圆
        animCirclePoint.onDraw(canvas, selectColor);
        //计算Path路径
        CalculationData();
        //绘制Path路径
        canvas.drawPath(path, paint);
    }

在滑动过程中,只需要改变动圆的X坐标,并不会调用绘制方法就可以了。

public void setTranslateX(int x) {
   if (animCirclePoint != null && animCirclePoint.getP() != null) {
         animCirclePoint.getP().X = x / scale + itemCenter + circleRadius;
   }
   invalidate();
}

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

这里写图片描述

以上就是所有的内容,若有很多不当的地方,还请多多谅解(第一次写)。谢谢!
GitHub源码已上传 源码连接

你可能感兴趣的:(android-特效,android,path,动画,canvas)