graphics – Android drawing an animated line-ThrowExceptions

Exception or error:

I’m currently working with graphics and paths, and I can successufully display whatever I want.

But instead of drawing a line directly on my SurfaceView, I’d like to draw it progressively in an animation.

What I’ve done so far is to create a Path and then to use PathMeasure to retrieve the coordinates progressively along the path. Here is basically what I’ve done so far

PathMeasure pm = new PathMeasure(myPath, false);

    float position = 0;
    float end = pm.getLength();
    float[] coord = {0,0,0,0,0,0,0,0,0};

    while (position < end){
        Matrix m = new Matrix();
        // put the current path position coordinates into the matrix
        pm.getMatrix(position, m, PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);
        // put the matrix data into the coord array (coord[2] = x and coord[5] = y)
        m.getValues(coord);
        ????
        position += 1;

    }

The question marks is where I’m stuck. I want to draw the path progressively and see it animated on the screen. I couldn’t find much info about it on the internet, so any clue would be much appreciated if you have already come across the same situation. The final effect I want to create is like a pencil drawing progressively a text automatically.

How to solve:

Instead of creating a for loop, you can use the ObjectAnimator class to callback to one of your class’s methods every time you’d like to draw a bit more of the path.

import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
import android.util.Log;

public class PathView extends View
{
    Path path;
    Paint paint;
    float length;

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

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

    public PathView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
    }

    public void init()
    {
        paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(10);
        paint.setStyle(Paint.Style.STROKE);

        path = new Path();
        path.moveTo(50, 50);
        path.lineTo(50, 500);
        path.lineTo(200, 500);
        path.lineTo(200, 300);
        path.lineTo(350, 300);

        // Measure the path
        PathMeasure measure = new PathMeasure(path, false);
        length = measure.getLength();

        float[] intervals = new float[]{length, length};

        ObjectAnimator animator = ObjectAnimator.ofFloat(PathView.this, "phase", 1.0f, 0.0f);
        animator.setDuration(3000);
        animator.start();
    }

    //is called by animtor object
    public void setPhase(float phase)
    {
        Log.d("pathview","setPhase called with:" + String.valueOf(phase));
        paint.setPathEffect(createPathEffect(length, phase, 0.0f));
        invalidate();//will calll onDraw
    }

    private static PathEffect createPathEffect(float pathLength, float phase, float offset)
    {
        return new DashPathEffect(new float[] { pathLength, pathLength },
            Math.max(phase * pathLength, offset));
    }

    @Override
    public void onDraw(Canvas c)
    {
        super.onDraw(c);
        c.drawPath(path, paint);
    }
}

Then, just call init() to begin the animation, like this (or if you’d like it to start as soon as the view is inflated, put the init() call inside the constructors):

PathView path_view = (PathView) root_view.findViewById(R.id.path);
path_view.init();

Also see this question here, and this example, which I’ve based my code on.

###

I just have resolve this problem, here what I do:

private float[] mIntervals = { 0f, 0f };
private float drawSpeed = 2f;
private int currentPath = -1;
private PathMeasure mPathMeasure = new PathMeasure();
private ArrayList<Path> mListPath = new ArrayList<Path>(this.pathCount);


@Override
protected void onDraw(Canvas canvas) {
   if (mIntervals[1] <= 0f && currentPath < (pathCount - 1)) {
     // Set the current path to draw
     // getPath(int num) a function to return a path.
     Path newPath = this.getPath(mListPath.size());
     this.mListPath.add(newPath);
     this.mPathMeasure.setPath(newPath, false);
     mIntervals[0] = 0;
     mIntervals[1] = this.mPathMeasure.getLength();
   }

  if (mIntervals[1] > 0) {
     // draw the previous path
     int last = this.mListPath.size();
     for (int i = 0; i < last; i++) {
        canvas.drawPath(this.mListPath.get(i), mPaint);
     }
     // partially draw the last path
     this.mPaint.setPathEffect(new DashPathEffect(mIntervals, 0f));

     canvas.drawPath(this.mListPath.get(last), mPaint);

     // Update the path effects values, to draw a little more
     // on the path.
     mIntervals[0] += drawSpeed;
     mIntervals[1] -= drawSpeed;

     super.invalidate();
  } else {
     // The drawing have been done, draw it entirely
     for (int i = 0; i < this.mListPath.size(); i++) {
        canvas.drawPath(this.mListPath.get(i), mPaint);
     }
  }
}

This example, is an adaptation of what I’ve done (to simplify the example). Hope you will understand it. Since I’ve just made this function working, It lacks of optimizations and things like that.

Hope it will help 😉

###

here is an alternative solution that worked for me

 package com.sample;
 /**
* Created by Sumit
 */
public class PathView extends View {

Paint mPaint;
Path mPath;
int mStrokeColor;
float mStrokeWidth;

float mProgress = 0.0f;
float mLength = 0f;
float mTotal;

public PathView(Context context) {
    this(context, null);
    init();
}

public PathView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
    init();
}

public PathView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    mStrokeColor = Color.RED;
    mStrokeWidth = 8.0f;
    init();
}

private void init() {
    mPaint = new Paint();
    mPaint.setColor(mStrokeColor);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(mStrokeWidth);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setAntiAlias(true);
    mPaint.setDither(true);

    setPath(new Path());
    // setPath2(new Path());
}

public void setPath(Path p) {
    mPath = p;
    PathMeasure measure = new PathMeasure(mPath, false);
    mPathLength = measure.getLength();
}


public void setPath(List<float[][]> list) {
    Log.d("Path", "size " + list.size());
    Path p = new Path();
    p.moveTo(list.get(0)[0][0], list.get(1)[0][1]);

    for (int i = 1; i < list.size(); i++) {
        p.lineTo(list.get(i)[0][0], list.get(i)[0][1]);
        //if (i > 100)
            //p.moveTo(list.get(i)[0][0], list.get(i)[0][1]);
    }
    //p.setFillType(FillType.WINDING);
    setPath(p);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mTotal = (mLength - mLength * mProgress);
    PathEffect pathEffect = new DashPathEffect(new float[] { mLength, mLength }, mTotal);
    Log.d("Path Tag", "length =" + mLength + ", totla=" + mTotal);

    mPaint.setPathEffect(pathEffect);

    canvas.save();
    // canvas.translate(getPaddingLeft(), getPaddingTop());
    canvas.drawPath(mPath, mPaint);
    canvas.restore();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(widthMeasureSpec);

    int measuredWidth, measuredHeight;

    if (widthMode == MeasureSpec.AT_MOST)
        throw new IllegalStateException("Use MATCH_PARENT");
    else
        measuredWidth = widthSize;

    if (heightMode == MeasureSpec.AT_MOST)
        throw new IllegalStateException("Use MATCH_PARENT");
    else
        measuredHeight = heightSize;

    setMeasuredDimension(measuredWidth, measuredHeight);
    setPath();
}

void setPath() {
    int cX = getWidth() / 2;
    int cY = getHeight() / 2;
    cY += 50;
    cX -= 50;
    List<float[][]> list = new ArrayList<float[][]>();

    for (int i = 0; i < 50; i++) {
        list.add(new float[][] { { cX--, cY++ } });
    }

    for (int i = 0; i < 100; i++) {
        list.add(new float[][] { { cX--, cY-- } });
    }

    for (int i = 0; i < 100; i++) {
        list.add(new float[][] { { cX++, cY-- } });
    }

    for (int i = 0; i < 200; i++) {
        list.add(new float[][] { { cX++, cY++ } });
    }

    for (int i = 0; i < 100; i++) {
        list.add(new float[][] { { cX++, cY-- } });
    }
    for (int i = 0; i < 100; i++) {
        list.add(new float[][] { { cX--, cY-- } });
    }

    for (int i = 0; i < 100; i++) {
        list.add(new float[][] { { cX--, cY++ } });
    }

    setPath(list);
}

}

and use

 final PathView pathView = (PathView) findViewById(R.id.path_view);
 pathView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            ObjectAnimator anim = ObjectAnimator.ofFloat(view, "percentage", 0.0f, 1.0f);
            anim.setDuration(2000);
            anim.setInterpolator(new LinearInterpolator());
            anim.setRepeatCount(Animation.INFINITE);
            anim.start();
        }
    });

###

You will have to add this view to the layout, setting height to 1 and width to match parent. The line will be animated from left to right. The later line will be placed over the first one.

                public class AnimatorLineView extends RelativeLayout {

                    private View animatorLineView;
                    private View simpleLineView;
                    View animatorLine;
                    private int colorBeforeAnimation;
                    private int colorAfterAnimation;
                    private int colorForErrorLine;

                    public AnimatorLineView(Context context) {
                        super(context);
                        init();
                        startAnimation();
                    }

                    public AnimatorLineView(Context context, AttributeSet attrs) {
                        super(context, attrs);
                        init();
                        initAttributes(context, attrs);
                        setColors();
                        startAnimation();
                    }

                    public AnimatorLineView(Context context, AttributeSet attrs, int defStyleAttr) {
                        super(context, attrs, defStyleAttr);
                        init();
                        initAttributes(context, attrs);
                        setColors();
                        startAnimation();
                    }


                    private void setColors() {
                        simpleLineView.setBackgroundColor(colorBeforeAnimation);
                        animatorLine.setBackgroundColor(colorAfterAnimation);
                    }

                    public void init() {
                        animatorLineView = inflate(getContext(), R.layout.ainimator_line_view, this);
                        animatorLine = findViewById(R.id.simple_line);
                        simpleLineView = findViewById(R.id.animator_line);

                    }

                    public void setColor(int color) {
                        animatorLine.setBackgroundColor(color);
                    }

                    public void startAnimation() {
                        animatorLine.setVisibility(View.VISIBLE);
                        Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.enter_animation_underline);
                        animatorLine.startAnimation(animation);
                    }

                    public void showErrorLine(){
                        animatorLine.setBackgroundColor(colorForErrorLine);
                        animatorLine.setVisibility(View.VISIBLE);
                        Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.enter_animation_underline);
                        animatorLine.startAnimation(animation);
                    }

                    public void hideErrorLine(){
                        animatorLine.setBackgroundColor(colorAfterAnimation);
                        animatorLine.setVisibility(View.VISIBLE);
                        Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.enter_animation_underline);
                        animatorLine.startAnimation(animation);
                    }

                    private void initAttributes(Context context, AttributeSet attributeSet) {
                        TypedArray attr = getTypedArray(context, attributeSet, R.styleable.ProgressButton);
                        if (attr == null) {
                            return;
                        }
                        try {
                            colorBeforeAnimation = attr.getColor(R.styleable.AnimatorLineView_al_color_after_animation,ContextCompat.getColor(getContext(), R.color.animation_line_text_color));
                            colorAfterAnimation = attr.getColor(R.styleable.ProgressButton_pb_text_color, ContextCompat.getColor(getContext(), R.color.black_color));
                            colorForErrorLine = attr.getColor(R.styleable.ProgressButton_pb_text_color, ContextCompat.getColor(getContext(), R.color.error_msgs_text_color));
                        } finally {
                            attr.recycle();
                        }
                    }

                    protected TypedArray getTypedArray(Context context, AttributeSet attributeSet, int[] attr) {
                        return context.obtainStyledAttributes(attributeSet, attr, 0, 0);
                    }

                    public void resetColor(){
                        animatorLine.setBackgroundColor(colorAfterAnimation);
                        animatorLine.setVisibility(View.GONE);
                    }
                }

            <animator_line_view>

            <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <View
                    android:id="@+id/simple_line"
                    android:layout_width="match_parent"
                    android:layout_height="1.5dp"
                    android:background="#E0E0E0" />

                <View
                    android:id="@+id/animator_line"
                    android:layout_width="match_parent"
                    android:layout_height="1.5dp"
                    android:background="#000000"
                    android:visibility="gone" />

            </FrameLayout>

        <enter_animation_underline>

        <?xml version="1.0" encoding="utf-8"?>
        <set xmlns:android="http://schemas.android.com/apk/res/android"
            android:shareInterpolator="false">
            <translate
                android:fromXDelta="-100%" android:toXDelta="0%"
                android:fromYDelta="0%" android:toYDelta="0%"
                android:duration="@integer/animator_line_duration" />
        </set>
    ---- styles------
      <style name="animator_line">
            <item name="android:layout_width">match_parent</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="al_color_before_animation">#E0E0E0</item>
            <item name="al_color_after_animation">#0000000</item>
            <item name="al_error_line_color">#FF3352</item>
        </style>

        <declare-styleable name="AnimatorLineView">
            <attr name="al_color_before_animation" format="color" />
            <attr name="al_color_after_animation" format="color" />
            <attr name="al_error_line_color" format="color" />
        </declare-styleable>

-------- to be include in the xml
 <com.careem.acma.widget.AnimatorLineView
        android:id="@+id/animator_line"
        style="@style/animator_line" />

Leave a Reply

Your email address will not be published. Required fields are marked *