转载请注意:http://blog.youkuaiyun.com/wjzj000/article/details/59112306
本菜开源的一个自己写的Demo,希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect (拆解GitHub上的优秀框架于一体,全部拆离不含任何额外的库导入)
https://github.com/zhiaixinyang/MyFirstApp(Retrofit+RxJava+MVP)
写在前面
来到学校接近一周的时间算是啥也没干,这篇博客便是打响武装反抗惰性学习第一枪。
之前写过一篇博客:http://blog.youkuaiyun.com/wjzj000/article/details/53929870
是关于官方的侧滑控件DrawerLayout,它本身的用法比较简单。只是我隐隐约约记得之前留了一个坑:那个可以动的控制侧滑菜单打开关闭的按钮。
正好结合这Path动画,把这个坑填上。
填坑
上篇博客中使用的有动画效果的箭头是来自:https://github.com/ChrisRenke/DrawerArrowDrawable
的开源作品,具体的使用方式,各位看官可以移步这位大神的GitHub上,一睹傲视武林一统江湖的王者之霸气。
这里简单梳理一下源码,然后进去本次博客想记录的核心Path动画。
public DrawerArrowDrawable(Resources resources, boolean rounded) {
this.rounded = rounded;
float density = resources.getDisplayMetrics().density;
float strokeWidthPixel = STROKE_WIDTH_DP * density;
halfStrokeWidthPixel = strokeWidthPixel / 2;
linePaint = new Paint(SUBPIXEL_TEXT_FLAG | ANTI_ALIAS_FLAG);
linePaint.setStrokeCap(rounded ? ROUND : BUTT);
linePaint.setColor(BLACK);
linePaint.setStyle(STROKE);
linePaint.setStrokeWidth(strokeWidthPixel);
int dimen = (int) (DIMEN_DP * density);
bounds = new Rect(0, 0, dimen, dimen);
Path first, second;
JoinedPath joinedA, joinedB;
// 按钮的第一条线的相关路径操作
first = new Path();
first.moveTo(5.042f, 20f);
// 三阶贝塞尔曲线:说白了就是给定四个坐标点(moveTo为起始点,rCubicTo分别代表第一个控制点,第二个控制点,结束的点),系统通过贝塞尔方程画出一个线来。
first.rCubicTo(8.125f, -16.317f, 39.753f, -27.851f, 55.49f, -2.765f);
second = new Path();
second.moveTo(60.531f, 17.235f);
second.rCubicTo(11.301f, 18.015f, -3.699f, 46.083f, -23.725f, 43.456f);
//将路径缩放到给定的屏幕密度。(让按钮大小适配屏幕)
scalePath(first, density);
scalePath(second, density);
joinedA = new JoinedPath(first, second);
//省略大量类似代码
}
从这里我们基本可以看出,作者是直接使用多个Path来完成效果。
其中通过PathMeasure每个Path上的每个点的坐标位置,然后不断的重新绘制线段。
作者通过,如下代码进行回调,请求重绘:
new DrawerLayout.SimpleDrawerListener() {
@Override public void onDrawerSlide(View drawerView, float slideOffset) {
offset = slideOffset;
if (slideOffset >= .995) {
flipped = true;
drawerArrowDrawable.setFlip(flipped);
}
//省略部分代码
public void setFlip(boolean flip) {
this.flip = flip;
invalidateSelf();
}
回调产生以后,就会调用作者封装的draw方法:
private void draw(Canvas canvas) {
pathA.getPointOnLine(parameter, coordsA);
pathB.getPointOnLine(parameter, coordsB);
if (rounded) insetPointsForRoundCaps();
//coordsA当前路线的坐标,通过PathMeasure的getPosTan方法获得。
canvas.drawLine(coordsA[0], coordsA[1], coordsB[0], coordsB[1], linePaint);
}
通过PathMeasure的getPosTan获取中Path的多点坐标,然后重新绘制线段。完成动态效果。
OK,关于填坑,就到这里了。因为作者的源码比较的长。所以各位看官如果感兴趣的话,可以自己移步作者的GitHub之上就查看源码。
作者继承了android.graphics.drawable.Drawable来完成这个效果:
关于Drawable的官方解释:
Drawable是“可以绘制的东西”的一般抽象。通常你会处理Drawable作为绘制东西到屏幕上检索的资源类型; Drawable类提供了一个通用的API来处理可能采取各种形式的底层视觉资源。与View不同,Drawable没有任何功能来接收事件或与用户交互。
Path动画
接下来的内容是结合ValueAnimator结合Path实现动态的Path效果。
先看一下效果:
直接上代码:
初始化Path和Paint:
paint = new Paint();
paint.setColor(Color.DKGRAY);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.STROKE);
path = new Path();
path.moveTo(50, 50);
for (int i = 0; i < 4; i++) {
path.lineTo(450 - i * 50, 50 + i * 50);
path.lineTo(450 - i * 50, 450 - i * 50);
path.lineTo(50 + i * 50, 450 - i * 50);
path.lineTo(50 + i * 50, 100 + i * 50);
}
setPathEffect:
PathMeasure measure = new PathMeasure(path, false);
length = measure.getLength();
animator = ValueAnimator.ofFloat(0.0f, 1.0f);
animator.setDuration(5000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
//这里使用了一个DashPathEffect的技巧
paint.setPathEffect(createPathEffect(length, fraction));
postInvalidate();
}
});
animator.start();
private static PathEffect createPathEffect(float pathLength, float phase) {
return new DashPathEffect(new float[]{pathLength, pathLength},
pathLength - phase * pathLength);
}
我们在new DashPathEffect的时候传入的间隔值就是Path的全长,因此这里是没有间断的。我们控制Path的动画,实际是控制DashPathEffect的偏移,也就是pathLength - phase * pathLength,这个值是从Path全长随动画的进度逐渐减小到0。
对应到我们的效果之上就是Path从什么都没有,然后知道全部展现。(因为此时的偏移为0)
onDraw()中很简单,就是调用drawPath:
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
c.drawPath(path, paint);
}
原理非常的简单,我们可以理解是使用了DashPathEffect的错位效果。我们都知道DashPathEffect的一般制作效果是波浪线。
而这里我们使用它的偏移属性,做到了线段追逐的效果。
因为需要动态更新效果,所以使用动画进行控制。
PS:相关源码基本都存放于我的这个开源项目之中:
https://github.com/zhiaixinyang/PersonalCollect
尾声
OK,坑到这也填的差不多了。
学习虽好,但不要贪杯。有情不必终老,暗香浮动恰好。
最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp