Thursday, May 29, 2014

Know the performance, timing and speed of animation

Follow the former post "Animation follow touch path". The example show how to draw animation follow user touch path. Always one step in each onDraw(), without concern the device dpi, screen size... In some case, for example small screen with high dpi, or large screen with low dpi, the touch path will have different length, so it have different visual animation speed.

This example show how to retrieve screen dpi by with DisplayMetrics. May be you have to adjust speed (step and stepAngle in the code)  at run-time accordingly, depends on your application.

We always have to concern the performance (processing time) of our code. This example also show the processing time in onDraw, time between call of onDraw, and Frame Per Second (it approximate 60 fps). Remember you have to make sure your code cannot run longer than 1/60 second.

run on Nexus One

run on HTC One X

run on Nexus 7 (1st generation)

AnimationView.java
package com.example.androidanimationalongpath;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class AnimationView extends View {
 
 Paint paint, paintText;
 
 Bitmap bm;
 int bm_offsetX, bm_offsetY;
 
 Path animPath;
 PathMeasure pathMeasure;
 float pathLength;
 
 float step;   //distance each step
 float distance;  //distance moved
 float curX, curY;
  
 float curAngle;  //current angle
 float targetAngle; //target angle
 float stepAngle; //angle each step

 float[] pos;
 float[] tan;
 
 Matrix matrix;
 
 Path touchPath;
 
 long lastTime;

 public AnimationView(Context context) {
  super(context);
  initMyView();
 }

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

 public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  initMyView();
 }
 
 public void initMyView(){
  paint = new Paint();
  paint.setColor(Color.BLUE);
  paint.setStrokeWidth(1);
  paint.setStyle(Paint.Style.STROKE);
  
  paintText = new Paint();
  paintText.setColor(Color.RED);
  paintText.setStrokeWidth(1);
  paintText.setStyle(Paint.Style.FILL);
  paintText.setTextSize(26);
    
  bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
  bm_offsetX = bm.getWidth()/2;
  bm_offsetY = bm.getHeight()/2;
  
  animPath = new Path();
  
  pos = new float[2];
  tan = new float[2];
  
  matrix = new Matrix();
  
  touchPath = new Path();
  
  lastTime = System.currentTimeMillis();

 }

 @Override
 protected void onDraw(Canvas canvas) {
  if(animPath.isEmpty()){
   return;
  }
  
  long startNanos = System.nanoTime();
  long startMillis = System.currentTimeMillis();

  canvas.drawPath(animPath, paint);
  
  matrix.reset();
  
  if((targetAngle-curAngle)>stepAngle){
   curAngle += stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else if((curAngle-targetAngle)>stepAngle){
   curAngle -= stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else{
   curAngle=targetAngle;
   if(distance < pathLength){
    pathMeasure.getPosTan(distance, pos, tan);

    targetAngle = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    
    curX = pos[0]-bm_offsetX;
    curY = pos[1]-bm_offsetY;
    matrix.postTranslate(curX, curY);
    
    canvas.drawBitmap(bm, matrix, null);
    
    distance += step;
    
    invalidate();
   }else{
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    matrix.postTranslate(curX, curY);
    canvas.drawBitmap(bm, matrix, null);
   }
  }
  
  long endNanos = System.nanoTime();
  long betweenFrame = startMillis - lastTime;
  int fps = (int) (1000/betweenFrame);
  
  String strProcessingTime = "Processing Time (ns=0.000001ms) = " + (endNanos - startNanos);
  String strBetweenFrame = "Between Frame (ms) = " + betweenFrame;
  String strFPS = "Frame Per Second (approximate) = " + fps;
  
  lastTime = startMillis;
  canvas.drawText(strProcessingTime, 10, 30, paintText);
  canvas.drawText(strBetweenFrame, 10, 60, paintText);
  canvas.drawText(strFPS, 10, 90, paintText);
  canvas.drawText(String.valueOf(pathLength), 10, 120, paintText);

 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  
  int action = event.getAction();
  
  switch(action){
  case MotionEvent.ACTION_DOWN:
   touchPath.reset();
   touchPath.moveTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_MOVE:
   touchPath.lineTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_UP:
   touchPath.lineTo(event.getX(), event.getY());
   animPath = new Path(touchPath);
   
   pathMeasure = new PathMeasure(animPath, false);
   pathLength = pathMeasure.getLength();
   
   step = 1;
   distance = 0;
   curX = 0;
   curY = 0;
   
   stepAngle = 1; 
   curAngle = 0;
   targetAngle = 0;
   
   invalidate();
   
   break;
    
  }
  
  return true;
 }

}

package com.example.androidanimationalongpath;

import android.support.v7.app.ActionBarActivity;
import android.util.DisplayMetrics;
import android.widget.TextView;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  TextView textDispInfo = (TextView)findViewById(R.id.dispinfo);
  
  //get display dpi
  DisplayMetrics metrics = getResources().getDisplayMetrics();
  textDispInfo.setText(
   "xdpi = " + metrics.xdpi + "\n" +
   "ydpi = " + metrics.ydpi);
 }

}

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androidanimationalongpath.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <TextView
        android:id="@+id/dispinfo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <com.example.androidanimationalongpath.AnimationView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:background="@android:color/darker_gray" />

    </LinearLayout>

</LinearLayout>



download filesDownload the files.

APK is prepared for testing on your device.

Next:
Change speed of Animation follow touch path

More example of Drawing Path on canvas of custom View.


No comments: