path 动画

Paint

Paint类包含关于如何绘制几何图形,文本和位图的样式和颜色信息。

//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了
mPaint.setAntiAlias(boolean aa);
设置绘画的风格,用于控制原始图形的几何解释(除了drawBitmap,它总是假定为Fill)。
//设置绘制的风格,用于控制原语的几何体是如何解释的(除了drawBitmap,它总是假定为Fill)。
mPaint.setStyle(Style style);
//设置描边的宽度。 在发线模式下将0传递给笔划。 细线总是绘制一个独立于Canva矩阵的像素。设置绘画的笔画宽度,每当绘画时使用风格是Stroke或StrokeAndFill
mPaint.setStrokeWidth(3);

//用这种风格绘制的几何和文本将被填充,忽略所有
中风相关的设置在油漆。
Paint.Style.FILL
//用这种风格绘制的几何和文本将描边,尊重油漆上的笔画相关的领域。
Paint.Style.STROKE
//用这种风格绘制的几何和文字将同时填充和描边,尊重油漆上的笔触相关的领域。 如果几何形状逆时针方向,此模式可能会产生意想不到的结果。 此限制不适用于FILL或STROKE。
Paint.Style.FILL_AND_STROKE

mPaint.setColor(mColorBg);

path

Path类封装了由直线段,二次曲线和三次曲线组成的复合(多个轮廓)几何路径。可以使用canvas.drawPath(path,paint)绘制,可以是填充或描边(基于绘制的样式) ,或者它可以用于剪辑或在路径上绘制文本。

//清除路径中的任何线条和曲线,使其为空。这不会改变填充类型的设置。
animPath.reset();
//从最后一个点到指定点(x,y)添加一条线。 如果此轮廓未进行moveTo()调用,则第一个点将自动设置为(0,0)。
x一行结尾的x坐标--y一行结尾的y坐标
animPath.lineTo(0, 0);

将下一个轮廓的起点设置为点(x,y)。
@参数x新轮廓起点的x坐标
@参数y新轮廓起点的y坐标
sPath.moveTo
向路径添加一个封闭的圆形轮廓
参数x要添加到路径的圆的中心的x坐标
参数y要添加到路径中的圆的中心的y坐标
radius要添加到路径的圆的半径
dir旋转圆的轮廓的方向
sPath.addCircle(50, 50, 60, Path.Direction.CW);
顺时针
Path.Direction.CW
逆时针
Path.Direction.CCW
创建一个空的PathMeasure对象。 要使用它来测量路径的长度,和/或沿着它找到位置和切线,请调用setPath。
请注意,一旦一个路径与度量对象相关联,如果路径随后被修改并且使用度量对象,那么它是未定义的。 如果修改路径,则必须使用路径调用setPath。
PathMeasure pathMeasure = new PathMeasure();
指定一个新的路径,或者为null,否则为空。
pathMeasure.setPath(sourcePath, false);
//返回当前轮廓的总长度,如果没有路径与此度量对象关联,则返回0。
pathMeasure.getLength()
移动到路径中的下一个轮廓。 如果存在则返回true,否则返回false。
pathMeasure.nextContour();

//经过上面这段计算duration代码的折腾 需要重新初始pathMeasure
pathMeasure.setPath(sourcePath, false);
  //每段path走完后,要补一下 某些情况会出现 animPath不满的情况(当然也可以设置一段,就可以达到进度条的效果)
*给定起止距离,返回到中间段。 如果段是零长度,则返回false,否则返回true。 startD和stopD被固定为合法值(0..getLength())。 如果startD> = stopD,则返回false(并保持dst不变)。如果startWithMoveTo为true,则使用moveTo开始该段。
      * <p>在{@link android.os.Build.VERSION_CODES#KITKAT}和更早的发行版上,得到的路径可能不会显示在硬件加速的画布上。 一个简单的解决方法是向这个路径添加一个单独的操作,比如<code> dst.rLineTo(0,0)</ code>。</ p>
 pathMeasure.getSegment(0, pathMeasure.getLength(), animPath, true);
给定一个起点和终点距离,返回中间段。 如果段是零长度,则返回false,否则返回true。 startD和stopD被固定为合法值(0..getLength())。
如果startD> = stopD,则返回false(并保持dst不变)。
如果startWithMoveTo为true,则使用moveTo开始片段。

在{@link android.os.Build.VERSION_CODES#KITKAT}和更早版本上,结果路径可能不会显示在硬件加速画布上。 一个简单的解决方法是向这个路径添加一个单独的操作,比如<code> dst.rLineTo(0,0)</ code>。</ p>
//animPath替换成mStonePath
animPath.set(mStonePath);

view

当这个视图的大小改变时,这在layout期间被调用。 如果刚刚添加到视图层次结构中,则使用旧值0调用。
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
       mPaddingLeft = getPaddingLeft();
        mPaddingTop = getPaddingTop();
    }

返回这个视图的左边填充。 如果有插入和启用滚动条,则此值可能还包括显示滚动条所需的空间。
        mPaddingLeft = getPaddingLeft();
        mPaddingTop = getPaddingTop();

invalidate

invalidate,请求重新draw,只会绘制调用者本身。
      * <p>这必须从UI线程调用。 要从非UI线程调用,请调用{@link #postInvalidate()}。
   public void invalidate() {
        invalidate(true);
    }
这是invalidate()工作实际发生的地方。 一个完整的invalidate()将导致绘图缓存失效,但是可以使用invalidateCache设置为false来调用此函数,以跳过不需要它的情况下的失效步骤(例如,保持与 相同的内容)。
     *
      * @param invalidateCache此视图的绘图缓存是否也应该失效。 对于完全无效,这通常是正确的,但是如果视图的内容或维度没有改变,则可以将其设置为假。
    void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

Canvas

Canvas类保存“draw”调用。 要绘制东西,你需要4个基本组件:一个位图来保存像素,一个Canvas来承载绘制调用(写入到位图),一个绘制基元(例如Rect,Path,文本,位图)和一个绘制 描述绘图的颜色和样式)。

//用指定的转换对当前矩阵进行预处理
canvas.translate(mPaddingLeft, mPaddingTop);
*使用指定的绘图绘制指定的路径。 该路径将根据画笔中的样式进行填充或框定。
     *
      * @param path要绘制的路径
      * @param paint用于绘制路径的油漆
canvas.drawPath(mSourcePath, mPaint);
canvas.drawPath(mAnimPath, mPaint);
绘制前景,mAnimPath不断变化,不断重绘View的话,就会有动画效果。

PathAnimView

一个路径动画的View,利用源Path绘制“底”,利用动画Path 绘制 填充动画
一个SourcePath 内含多段Path,循环取出每段Path,并做一个动画
需要做动画的源Path
用于绘制动画的Path
背景色
前景色

PathAnimHelper

介绍:一个自定义View Path动画的工具类
一个SourcePath 内含多段(一段)Path,循环取出每段Path,并做一个动画,

  /**
     * 一个SourcePath 内含多段Path,循环取出每段Path,并做一个动画
     * 自定义动画的总时间
     * 和是否循环
     *
     * @param view           需要做动画的自定义View
     * @param sourcePath     源Path
     * @param animPath       自定义View用这个Path做动画
     * @param totalDuaration 动画一共的时间
     * @param isInfinite     是否无限循环
     */
    protected void startAnim(View view, Path sourcePath, Path animPath, long totalDuaration, boolean isInfinite) {
        if (view == null || sourcePath == null || animPath == null) {
            return;
        }
        PathMeasure pathMeasure = new PathMeasure();
        //pathMeasure.setPath(sourcePath, false);
        //先重置一下需要显示动画的path
        animPath.reset();
        animPath.lineTo(0, 0);
        pathMeasure.setPath(sourcePath, false);
        //这里仅仅是为了 计算一下每一段的duration
        int count = 0;
        while (pathMeasure.getLength() != 0) {
            pathMeasure.nextContour();
            count++;
        }
        //经过上面这段计算duration代码的折腾 需要重新初始化pathMeasure
        pathMeasure.setPath(sourcePath, false);
        loopAnim(view, sourcePath, animPath, totalDuaration, pathMeasure, totalDuaration / count, isInfinite);
    }

    /**
     * 循环取出每一段path ,并执行动画
     *
     * @param animPath    自定义View用这个Path做动画
     * @param pathMeasure 用于测量的PathMeasure
     */
    protected void loopAnim(final View view, final Path sourcePath, final Path animPath, final long totalDuaration, final PathMeasure pathMeasure, final long duration, final boolean isInfinite) {
        //动画正在运行的话,先stop吧。万一有人要使用新动画呢,(正经用户不会这么用。)
        stopAnim();

        mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.setDuration(duration);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //Log.i("TAG", "onAnimationUpdate");
                //增加一个callback 便于子类重写搞事情
                onPathAnimCallback(view, sourcePath, animPath, pathMeasure, animation);
                //通知View刷新自己
                view.invalidate();
            }
        });

        mAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationRepeat(Animator animation) {
                //Log.w("TAG", "onAnimationRepeat: ");
                //每段path走完后,要补一下 某些情况会出现 animPath不满的情况
                pathMeasure.getSegment(0, pathMeasure.getLength(), animPath, true);

                //绘制完一条Path之后,再绘制下一条
                pathMeasure.nextContour();
                //长度为0 说明一次循环结束
                if (pathMeasure.getLength() == 0) {
                    if (isInfinite) {//如果需要循环动画
                        animPath.reset();
                        animPath.lineTo(0, 0);
                        pathMeasure.setPath(sourcePath, false);
                    } else {//不需要就停止(因为repeat是无限 需要手动停止)
                        animation.end();
                    }
                }
            }

        });
        mAnimator.start();
    }

根据ArrayList<float[]> path 解析

    /**
     * 根据ArrayList<float[]> path 解析
     *
     * @param path
     * @return
     */
    public static Path getPathFromArrayFloatList(ArrayList<float[]> path) {
        Path sPath = new Path();
        for (int i = 0; i < path.size(); i++) {
            float[] floats = path.get(i);
            sPath.moveTo(floats[0], floats[1]);
            sPath.lineTo(floats[2], floats[3]);
        }
        return sPath;
    }
    /**
     * 从R.array.xxx里取出点阵,
     *
     * @param context
     * @param arrayId
     * @param zoomSize
     * @return
     */
    public static Path getPathFromStringArray(Context context, int arrayId, float zoomSize) {
        Path path = new Path();
        String[] points = context.getResources().getStringArray(arrayId);
        for (int i = 0; i < points.length; i++) {
            String[] x = points[i].split(",");
            for (int j = 0; j < x.length; j = j + 2) {
                if (j == 0) {
                    path.moveTo(Float.parseFloat(x[j]) * zoomSize, Float.parseFloat(x[j + 1]) * zoomSize);
                } else {
                    path.lineTo(Float.parseFloat(x[j]) * zoomSize, Float.parseFloat(x[j + 1]) * zoomSize);
                }
            }
        }
        return path;
    }
        float[][] LETTERS = new float[][]{
                new float[]{
                        // A
                        24, 0, 1, 22,
                        1, 22, 1, 72,
                        24, 0, 47, 22,
                        47, 22, 47, 72,
                        1, 48, 47, 48
                },

                new float[]{
                        // B
                        0, 0, 0, 72,
                        0, 0, 37, 0,
                        37, 0, 47, 11,
                        47, 11, 47, 26,
                        47, 26, 38, 36,
                        38, 36, 0, 36,
                        38, 36, 47, 46,
                        47, 46, 47, 61,
                        47, 61, 38, 71,
                        37, 72, 0, 72,
                },....
};

进度条效果

    @Override
    public void onPathAnimCallback(View view, Path sourcePath, Path animPath, PathMeasure pathMeasure, ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        //获取一个段落
        float end = pathMeasure.getLength() * value;
        float begin = (float) (end - ((0.5 - Math.abs(value - 0.5)) * pathMeasure.getLength()));
        animPath.reset();
        animPath.lineTo(0, 0);
        pathMeasure.getSegment(begin, end, animPath, true);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容