Learn more about Android animation

1, PropertyValuesHolder

Reading this article requires the foundation of Android attribute animation in the previous article, so that you can understand what to say next.

1. Understanding and use

PropertyValuesHolder is a method similar to ObjectAnimation, but with one target missing, it is the control to be executed. Look at the normal usage: all holders will be executed at the same time

public void  doPropertyValuesHolder(){
        //Define a rotation Holder
        PropertyValuesHolder rotationHolder=
                PropertyValuesHolder.ofFloat(
                        "rotation",
                        60f,40f,100f,-60f,40f,88f,77f);

        //Define a transparent Holder
        PropertyValuesHolder alphaHolder=
                PropertyValuesHolder.ofFloat(
                        "alpha",
                        0.01f,0.5f,1.0f,0.8f,0.2f,0.0f);
        
    	//Load into ObjectAnimator
        ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,rotationHolder,alphaHolder);
        objectAnimator.setDuration(3000);
        objectAnimator.start();
    }

2. Methods and parameters

You can see the parameters of this method:

ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)
PropertyValuesHolder ofFloat(String propertyName, float... values)

Object target is the control to display the animation

PropertyValuesHolder... values loads multiple propertyvaluesholders

String propertyName represents the parameter to be reflected, which is the same as that of ObjectAnimation

float... values are variable length parameters
This method also has the following pictures:

The ofObject() method is similar to ObjectAnimation, and also needs to customize the TypeEvaluator.

2, Keyframe

1. Understanding and use

Look at the name, is to understand the meaning of key frame. In the animation, do some operations in a certain frame, so as to achieve a more obvious effect of contrast effect.
Keyframes represent where an object should be at which point in time.
Specific use:

 public void  doPropertyValuesHolderKeyFrame(){

        //First keyframe1, starting from schedule 0.6, when the schedule is 60%, the value is 0.1f
        Keyframe keyframe1=Keyframe.ofFloat(0.6f,0.1f);

        //Middle keyframe2
        Keyframe keyframe2=Keyframe.ofFloat(0.1f,0.8f);

        //Tail keyframe3, ending with 50% progress, the value is 0.2f at this time
        Keyframe keyframe3=Keyframe.ofFloat(0.5f,0.2f);

        //Load into the Holder and set the method to reflect. This is reflected by setAlpha() method, which controls transparency
        PropertyValuesHolder alphaHolder=PropertyValuesHolder.ofKeyframe("alpha",keyframe1,keyframe2,keyframe3);

        //Load into Holder to ObjectAnimator or ValueAnimation
        ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,alphaHolder);
        objectAnimator.setDuration(3000);
        objectAnimator.start();
    }

2. Methods and parameters

Keyframe ofFloat(float fraction, float value)

float fraction represents progress

float value indicates the value under this progress

 PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)

String propertyName set method to reflect

Keyframe... values in keyframe

Keyframe's method is similar to other methods.

Keyframe set method, set progress, interpolator, numerical value.
When no interpolator is set, the default is linear interpolator

 keyframe1.setInterpolator(new LinearInterpolator()); //Default linear interpolator

3. Frame operation

Write a conclusion directly:

  • If frame 0 is removed, the first key is taken as the starting position
  • If the end frame is removed (progress is 1), the end position is the last keyframe
  • Using keyframe to build an animation requires at least 2 frames

3, ViewPropertyAnimator

1. Understanding and use

The animation can be defined quickly and some definitions can be omitted in the form of serial. When drawing the interface, the animation can be started, which saves more consumption than others.
For example:

 ballImageView.animate().alpha(0.5f).rotation(360f).scaleX(1.5f).translationX(100f);

2. Parameters and methods

You can see the return values of these methods, which are basically ViewPropertyAnimator


Refer to another table:

function Meaning
alpha(float value) Set transparency
scaleY(float value) Set the scale size in Y direction
scaleX(float value) Set the scale size in the X-axis direction
translationY(float value) Set movement value in Y direction
translationX(float value) Set movement value in X direction
rotation(float value) Set the degree of rotation around the Z axis
rotationX(float value) Set the degree of rotation around the x-axis
rotationY(float value) Set the degree of rotation around the Y axis
x(float value) The final position of the upper left corner relative to the parent container in the X-axis direction
y(float value) The final position of the upper left corner coordinate relative to the parent container in the Y-axis direction
alphaBy(float value) Set transparency increment
rotationBy(float value) Set rotation increment around Z axis
rotationXBy(float value) Set rotation increment around X oil
rotationYBy(float value) Set the increment of Y-axis rotation
translationXBy(float value) Set the increment of movement value in X direction
translationYBy(float value) Set the increment of movement value in Y direction
scaleXBy(float value) Set the scale size increment in the X direction
scaleYBy(float value) Set scale size increment in Y direction
xBy(float value) The position increment of the upper left corner coordinate relative to the parent container in the X-axis direction
yBy(float value) Position increment in Y direction relative to the upper left corner of the parent container
setlnterpolator(Timelnterpolator interpolator) Set interpolator
setStartDelay(long startDelay) Set start delay
setDuration(long duration) Animation duration

4, animateLayoutChanges

 android:animateLayoutChanges="true"
Add animation when Layout adds controls or removes controls, but only the default animation can be used.

 <LinearLayout
            android:animateLayoutChanges="true"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"/>

5, LayoutTransition

LayoutTransition can control the animation of ViewGroup, and can use customized animation.
Specific use:

 public void doLayoutTransition(){

        LinearLayout linearLayout=new LinearLayout(this);

        //1. Create an instance
        LayoutTransition transition=new LayoutTransition();

        //2. Create animation
        ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(null,"rotation",0f,90f,0f);

        //3. Set the form of animation
        transition.setAnimator(LayoutTransition.DISAPPEARING,objectAnimator);

        //4. Set LayoutTransition to ViewGroup
        linearLayout.setLayoutTransition(transition);
     
      	//5. Open source animation library NineOldAndroids
     
    }

setAnimator(int transitionType, Animator animator)

In this method, the transitionType has five options

Change ABCD animating animation applied by other elements that need to change because a new element is to be displayed in the container

_Change ﹣ separating ﹣ when an element in a container is going to disappear, the animation applied by other elements that need to change (many problems, not commonly used)

_CHANGING? Animation changes for the element being changed in the container

_Animation defined when the applying UU element appears in the container

_Animation defined when the separating UU element disappears in the container

6, PathMeasure

PathMeasure is similar to a calculator, which can calculate the coordinates and length of the target path

1. Initialization

  public void doPathMeasure(){
        Path path=new Path();

        //Initialization method 1
        PathMeasure pathMeasure1=new PathMeasure();
        pathMeasure1.setPath(path,true);

        //Initialization method 2
        PathMeasure pathMeasure2=new PathMeasure(path,false);
    }

setPath(Path path, boolean forceClosed)
Path is the target path to be calculated.
If forceClosed is closed, true will calculate the Path in the closed state, and false will calculate according to the original situation of Path.

2. Function call

Customize a view

public class PathView extends View {
    Path mPath;
    Paint mPaint;
    PathMeasure mPathMeasure;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPath=new Path();
        mPaint=new Paint();
        mPathMeasure=new PathMeasure();
    }

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

        canvas.translate(250,250); //Canvas move
        mPaint.setColor(Color.BLUE); //stroke color 
        mPaint.setStrokeWidth(5); //Brush thickness
        mPaint.setStyle(Paint.Style.STROKE); //Brush style

        mPath.moveTo(0,0);
        mPath.lineTo(0,100);
        mPath.lineTo(100,100);
        mPath.lineTo(100,0);

        mPathMeasure.setPath(mPath,true);
        Log.v("showLog",
                "getLength()=="+mPathMeasure.getLength()
                        +"  isClosed()=="+ mPathMeasure.isClosed()); //Result 400.0 true

        mPathMeasure.setPath(mPath,false);
        Log.v("showLog",
                "getLength()=="+mPathMeasure.getLength()
                        +"  isClosed()=="+ mPathMeasure.isClosed()); //Result 300.0 false

        canvas.drawPath(mPath,mPaint); //Draw path
    }
}

Painting effect:

2.1 PathMeasure.getLength()

The PathMeasure.getLength() function measures the length of the path

2.2 PathMeasure.isClosed()

The PathMeasure.isClosed() function returns whether to measure the closed state

2.3 PathMeasure.nextContour()

   		mPath.addRect(-50, -50, 50, 50, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPath.addRect(-100, -100, 100, 100, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPath.addRect(-120, -120, 120, 120, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPathMeasure.setPath(mPath, false);

        do {
            float len = mPathMeasure.getLength();
            Log.v("showLog", "len=" + len);
        } while (mPathMeasure.nextContour());

effect:

Print results:

len=400.0
len=800.0
len=960.0

PathMeasure.nextContour() gets in the same order as the Path you added

PathMeasure.getLength() just gets the length of the current path, not the whole length

2.3 getSegment()

Using the getSegment function requires disabling hardware acceleration to add to the constructor
setLayerType(LAYER_TYPE_SOFTWARE,null);

 		mPath.addRect(-50, -50, 50, 50, Path.Direction.CW);
        mPathMeasure.setPath(mPath,false); //Calculated path
        mPathMeasure.getSegment(0,150,mDstPath,true); //Intercept and add to mDstPath, add, not others
        canvas.drawPath(mPath, mPaint); //Draw the original path

        canvas.translate(200,0); //Canvas move
        mPaint.setColor(Color.RED);
        canvas.drawPath(mDstPath, mPaint); //Draw the added mDstPath
 boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)

The starting point of the interception of startD path is the point in the upper left corner

stopD intercept stop point

path added after dst interception

startWithMoveTo whether to save as is, true to save as is, false to connect the initial point and the end point, not necessarily the same shape as the original
The effect of the above code: the direction of the screenshot is related to the generation direction of the original path

2.4 example of dynamic circle drawing

code:

public class PathView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();

        mPaint.setColor(Color.BLUE); //stroke color 
        mPaint.setStrokeWidth(5); //Brush thickness
        mPaint.setStyle(Paint.Style.STROKE); //Brush style

        mPath.addCircle(100, 100, 50, Path.Direction.CW); //A complete circle
        mPathMeasure.setPath(mPath, true); //path to calculate

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //Progress 0 ~ 1
        animator.setRepeatCount(ValueAnimator.INFINITE); //Infinite cycle
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //Get current progress
                invalidate();//Redraw, execute onDraw() method again
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(100, 100); //Canvas move

        float stop=mPathMeasure.getLength()*mCurAnimValue; //A progress determines a intercept point
        mDstPath.reset();
        mPathMeasure.getSegment(0,stop,mDstPath,true); //Add a little bit

        canvas.drawPath(mDstPath,mPaint); //Every time there is a progress update, draw a small segment to intercept
    }
}

effect:

2.5 getPosTan()

Let's first look at the definition of the function:

boolean getPosTan(float distance, float pos[], float tan[]) 

float distance the actual length of the path

float pos [] coordinate value of the point. X and y pos[0]=x, pos[1]=y

float tan [] tangent value of the point. X and y pos[0]=x, pos[1]=y Tan < a = Y / X

2.6 example of arrow drawing circle

code:

public class PathView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;
    Bitmap mArrowBmp;
    float[] mPos;
    float[] mTan;
    int mCenterX,mCenterY;
    float mRadius;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();
        mPos=new float[2];
        mTan=new float[2];

        //Load arrow picture
        mArrowBmp= BitmapFactory.decodeResource(getResources(), R.drawable.arrow);

        mPaint.setColor(Color.BLUE); //stroke color 
        mPaint.setStrokeWidth(5); //Brush thickness
        mPaint.setStyle(Paint.Style.STROKE); //Brush style

        mPath.addCircle(540, 972, 486, Path.Direction.CW); //A complete circle
        mPathMeasure.setPath(mPath, true); //path to calculate

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //Progress 0 ~ 1
        animator.setRepeatCount(ValueAnimator.INFINITE); //Infinite cycle
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //Get current progress
                invalidate();//Redraw, execute onDraw() method again
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        /*
         * The minimum value of h,w is obtained;
         * >> 1 The shift is the same as / 2;
         * Multiply by 0.9f to represent 90% of the layout
         * */
        mRadius = (Math.min(h, w) >> 1) * 0.9f;

        // Central coordinate
        mCenterX = w / 2;
        mCenterY = h / 2;
        Log.v("showLog",mCenterX+"  "+mCenterY+"  "+mRadius);
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float stop=mPathMeasure.getLength()*mCurAnimValue; //A progress determines a intercept point

        mDstPath.reset();
        mPathMeasure.getSegment(0,stop,mDstPath,true); //Add a little bit

        canvas.drawPath(mDstPath,mPaint); //Every time there is a progress update, draw a small segment to intercept

        mPathMeasure.getPosTan(stop,mPos,mTan); //Obtain tangent value and coordinates of each point

        /**
         *    Math.atan2(mTan[1],mTan[0])Get the radian value of tan
         *    *180.0/Math.PI Convert to angle value
         * */
        float degrees=(float)(Math.atan2(mTan[1],mTan[0])*180.0/Math.PI);

        Matrix matrix=new Matrix();

        /**
         * Rotate the picture around the center point by a specified angle
         * postRotate(float degrees, float px, float py)
         * degrees Is the angle (px,py) is the center of the picture
         * */
        matrix.postRotate(degrees,mArrowBmp.getWidth()/2,mArrowBmp.getHeight()/2);

        /**
         * Move the picture from the default (0, 0) point to the front of the path
         * */
        matrix.postTranslate(mPos[0]-mArrowBmp.getWidth()/2,mPos[1]-mArrowBmp.getHeight()/2);

        //Draw pictures
        canvas.drawBitmap(mArrowBmp,matrix,mPaint);

    }
}

effect:

2.7 getMatrix()

Parameter type:

boolean getMatrix(float distance, Matrix matrix, int flags)

usage method:

  		//Calculate azimuth
        Matrix matrix = new Matrix();

		//Get location information
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG);

		//Get trimming information
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.TANGENT_MATRIX_FLAG); 

2.8 payment success examples

public class TickView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;
    int mCenterX, mCenterY;
    float mRadius;

    public TickView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();

        mPaint.setColor(Color.BLUE); //stroke color 
        mPaint.setStrokeWidth(5); //Brush thickness
        mPaint.setStyle(Paint.Style.STROKE); //Brush style

        mCenterX = 540;
        mCenterY = 972;
        mRadius = 486 / 2;

        /**
         * circular
         * */
        mPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW);

        /**
         * Check mark
         * */
        mPath.moveTo(mCenterX - mRadius / 2, mCenterY);
        mPath.lineTo(mCenterX, mCenterY + mRadius / 2);
        mPath.lineTo(mCenterX + mRadius / 2, mCenterY - mRadius / 3);

        mPathMeasure.setPath(mPath, false); //path to calculate

        ValueAnimator animator = ValueAnimator.ofFloat(0, 2); //Progress 0-1 is circle, 1-2 is checkmark
        animator.setRepeatCount(ValueAnimator.RESTART);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //Get current progress
                invalidate();//Redraw, execute onDraw() method again
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        /*
         * The minimum value of h,w is obtained;
         * >> 1 The shift is the same as / 2;
         * Multiply by 0.9f to represent 90% of the layout
         * */
        mRadius = (Math.min(h, w) >> 1) * 0.9f;

        // Central coordinate
        mCenterX = w / 2;
        mCenterY = h / 2;
        Log.v("showLog", mCenterX + "  " + mCenterY + "  " + mRadius);
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }


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

        if (mCurAnimValue < 1) {
            float stop = mPathMeasure.getLength() * mCurAnimValue;
            mPathMeasure.getSegment(0, stop, mDstPath, true);
        } else if (mCurAnimValue == 1) {
            mPathMeasure.getSegment(0, mPathMeasure.getLength(), mDstPath, true);
            mPathMeasure.nextContour();
        } else {
            float stop = mPathMeasure.getLength() * (mCurAnimValue - 1);
            mPathMeasure.getSegment(0, stop, mDstPath, true);
        }

        canvas.drawPath(mDstPath, mPaint);
    }
}

effect:

How many setbacks will we encounter in programming? Table give up, the end of the desert must be oasis.

Tags: Android Attribute calculator Programming

Posted on Thu, 07 May 2020 05:47:51 -0400 by Dan06