‏إظهار الرسائل ذات التسميات Custom View. إظهار كافة الرسائل
‏إظهار الرسائل ذات التسميات Custom View. إظهار كافة الرسائل

الثلاثاء، 10 يونيو 2014

Use CountDownTimer to do something repeatly, second example.

It is another approach to do the same job as the previous example "Use CountDownTimer to do something repeatly, updating custom view". Instead of implementing CountDownTimer inside custom view as self running function of custom view, this example implement CountDownTimer in MainActivity, and handle all the timing functions. The custom view simple display the graphic only.


MainActivity.java
package com.example.androidcountdowntimer;

import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity{

CountDownTimer countDownTimer;

int state;
final static int STATE_IDLE = 0;
final static int STATE_RED = 1;
final static int STATE_GREEN = 2;
final static int STATE_BLUE = 3;

TextView myState, myCounter;
Button btnStart;

SpotView mySpotView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mySpotView = (SpotView)findViewById(R.id.myspotview);

myState = (TextView) findViewById(R.id.mystate);

myCounter = (TextView) findViewById(R.id.mycounter);
btnStart = (Button) findViewById(R.id.start);
btnStart.setOnClickListener(btnStartOnClickListener);

state = STATE_IDLE;

}

OnClickListener btnStartOnClickListener =
new OnClickListener(){

@Override
public void onClick(View v) {

if(countDownTimer!=null){
countDownTimer.cancel();
}

state = STATE_RED;

countDownTimer = new CountDownTimer(3000, 500) {

@Override
public void onTick(long millisUntilFinished) {
myCounter.setText("onTick: " + millisUntilFinished);
}

@Override
public void onFinish() {
if(state == STATE_RED){
state = STATE_GREEN;
mySpotView.setSpots(false, true, false);
myState.setText("GREEN");
}else if(state == STATE_GREEN){
state = STATE_BLUE;
mySpotView.setSpots(false, false, true);
myState.setText("BLUE");
}else if(state == STATE_BLUE){
state = STATE_RED;
mySpotView.setSpots(true, false, false);
myState.setText("RED");
}

countDownTimer.start();
}
};

mySpotView.setSpots(true, false, false);
myState.setText("RED");
countDownTimer.start();
}
};

}

SpotView.java
package com.example.androidcountdowntimer;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class SpotView extends View{

boolean spotRedOn, spotGreenOn, spotBlueOn;

Paint paintRed, paintGreen, paintBlue;

public SpotView(Context context) {
super(context);
init();
}

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

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

private void init(){

paintRed = new Paint();
paintRed.setColor(Color.RED);
paintRed.setStrokeWidth(1);
paintRed.setStyle(Paint.Style.FILL);

paintGreen = new Paint();
paintGreen.setColor(Color.GREEN);
paintGreen.setStrokeWidth(1);
paintGreen.setStyle(Paint.Style.FILL);

paintBlue = new Paint();
paintBlue.setColor(Color.BLUE);
paintBlue.setStrokeWidth(1);
paintBlue.setStyle(Paint.Style.FILL);

spotRedOn = spotGreenOn = spotBlueOn = false;
}


public void setSpots(boolean r, boolean g, boolean b){
spotRedOn = r;
spotGreenOn = g;
spotBlueOn = b;
invalidate();
}

@Override
protected void onDraw(Canvas canvas) {

float w = getWidth();
float h = getHeight();

if(spotRedOn){
canvas.drawCircle(w/4, h/2, 100, paintRed);
}

if(spotGreenOn){
canvas.drawCircle(w/2, h/2, 100, paintGreen);
}

if(spotBlueOn){
canvas.drawCircle(3*w/4, h/2, 100, paintBlue);
}
}

}

Keep using the same layout in previous example.

الاثنين، 9 يونيو 2014

Use CountDownTimer to do something repeatly, updating custom view.

This example show how to change color spots in custom view repeatly with CountDownTimer. We also implement callback interface on MainActivity.java, such that our custom view can callback MainActivity to pass something.

May be it is not a good practice to achieve such a job, just a example to use CountDownTimer.


MainActivity.java
package com.example.androidcountdowntimer;

import com.example.androidcountdowntimer.SpotView.SpotCallBack;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity
implements SpotCallBack{

TextView myState, myCounter;
Button btnStart;

SpotView mySpotView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mySpotView = (SpotView)findViewById(R.id.myspotview);
mySpotView.setCallback(this);

myState = (TextView) findViewById(R.id.mystate);

myCounter = (TextView) findViewById(R.id.mycounter);
btnStart = (Button) findViewById(R.id.start);
btnStart.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
mySpotView.startRun();
}
});

}

@Override
public void cb_onTick(String msg) {
myCounter.setText("onTick: " + msg);
}

@Override
public void cb_onFinish(String msg) {
myState.setText(msg);
}

}

SpotView.java
package com.example.androidcountdowntimer;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.CountDownTimer;
import android.util.AttributeSet;
import android.view.View;

public class SpotView extends View{

//used to pass back something to MainActivity
interface SpotCallBack {
void cb_onTick(String msg);
void cb_onFinish(String msg);
}

SpotCallBack spotCallback;

CountDownTimer countDownTimer;

int state;
final static int STATE_IDLE = 0;
final static int STATE_RED = 1;
final static int STATE_GREEN = 2;
final static int STATE_BLUE = 3;

Paint paintRed, paintGreen, paintBlue;

public SpotView(Context context) {
super(context);
init();
}

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

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

private void init(){
state = STATE_IDLE;

paintRed = new Paint();
paintRed.setColor(Color.RED);
paintRed.setStrokeWidth(1);
paintRed.setStyle(Paint.Style.FILL);

paintGreen = new Paint();
paintGreen.setColor(Color.GREEN);
paintGreen.setStrokeWidth(1);
paintGreen.setStyle(Paint.Style.FILL);

paintBlue = new Paint();
paintBlue.setColor(Color.BLUE);
paintBlue.setStrokeWidth(1);
paintBlue.setStyle(Paint.Style.FILL);
}

public void setCallback(SpotCallBack cb){
spotCallback = cb;
}

public void startRun(){
state = STATE_RED;

if(countDownTimer!=null){
countDownTimer.cancel();
}

countDownTimer = new CountDownTimer(3000, 500) {

@Override
public void onTick(long millisUntilFinished) {
spotCallback.cb_onTick(String.valueOf(millisUntilFinished));
}

@Override
public void onFinish() {
if(state == STATE_RED){
state = STATE_GREEN;
spotCallback.cb_onFinish("GREEN");
}else if(state == STATE_GREEN){
state = STATE_BLUE;
spotCallback.cb_onFinish("BLUE");
}else if(state == STATE_BLUE){
state = STATE_RED;
spotCallback.cb_onFinish("RED");
}
countDownTimer.start();
invalidate();
}
};

countDownTimer.start();
spotCallback.cb_onFinish("RED");
invalidate();
}

@Override
protected void onDraw(Canvas canvas) {

float w = getWidth();
float h = getHeight();

if(state == STATE_RED){
canvas.drawCircle(w/4, h/2, 100, paintRed);
}else if(state == STATE_GREEN){
canvas.drawCircle(w/2, h/2, 100, paintGreen);
}else if(state == STATE_BLUE){
canvas.drawCircle(3*w/4, h/2, 100, paintBlue);
}
}

}

/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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.example.androidcountdowntimer.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" />

<Button
android:id="@+id/start"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Start CountDownTimer" />
<TextView
android:id="@+id/mystate"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/mycounter"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<com.example.androidcountdowntimer.SpotView
android:id="@+id/myspotview"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

</LinearLayout>

Next example:
- Another approach to handle CountDownTimer in MainActivity, and the custom view handle display only.

السبت، 7 يونيو 2014

Animation follow touch path forward and backward

Further modify the example of "Change speed of Animation follow touch path" to add feature of animation direction, forward and backward.


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;

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;

static int FORWARD = 1;
static int BACKWARD = -1;
int direction;

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);

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();

step = 1; //default
stepAngle = 1; //default

direction = FORWARD; //default
}

@Override
protected void onDraw(Canvas canvas) {
if(animPath.isEmpty()){
return;
}

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((direction==FORWARD && distance < pathLength)
||(direction==BACKWARD && distance > 0)){

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 * direction;

invalidate();
}else{
matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
matrix.postTranslate(curX, curY);
canvas.drawBitmap(bm, matrix, null);
}
}

}

@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;

direction = FORWARD;

invalidate();

break;

}

return true;
}

public void setSpeed(int sp){
step = sp;
stepAngle = sp;
}

public void replayForward(){
direction = FORWARD;
invalidate();
}

public void replayBackward(){
direction = BACKWARD;
invalidate();
}

}

/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" />

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

<Button
android:id="@+id/replayfor"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="Replay/Forward" />
<Button
android:id="@+id/replayback"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="Replay/Backward" />

</LinearLayout>

<SeekBar
android:id="@+id/speedbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="10"
android:progress="1"/>

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

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

</LinearLayout>

</LinearLayout>

MainActivity.java
package com.example.androidanimationalongpath;

import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity {

AnimationView animationView;
SeekBar speedBar;
Button btnForward, btnbackward;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

animationView = (AnimationView)findViewById(R.id.animationview);
speedBar = (SeekBar)findViewById(R.id.speedbar);
speedBar.setOnSeekBarChangeListener(speedBarOnSeekBarChangeListener);
animationView.setSpeed(speedBar.getProgress()); //set default speed

btnForward = (Button)findViewById(R.id.replayfor);
btnForward.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View v) {
animationView.replayForward();
}});

btnbackward = (Button)findViewById(R.id.replayback);
btnbackward.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View v) {
animationView.replayBackward();
}});
}

OnSeekBarChangeListener speedBarOnSeekBarChangeListener =
new OnSeekBarChangeListener(){

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
//offset from SeekBar 0~19 to step 1~10
animationView.setSpeed(progress);
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
};

}


download filesDownload the files.

More example of Drawing Path on canvas of custom View.


الجمعة، 6 يونيو 2014

Change speed of Animation follow touch path

This exercise change the speed of the former example of "Animation follow touch path".


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;

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;

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);

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();

step = 1; //default
stepAngle = 1; //default
}

@Override
protected void onDraw(Canvas canvas) {
if(animPath.isEmpty()){
return;
}

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);
}
}

}

@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;
}

public void setSpeed(int sp){
step = sp;
stepAngle = sp;
}

}

/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" />

<SeekBar
android:id="@+id/speedbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="10"
android:progress="1"/>

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

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

</LinearLayout>

</LinearLayout>

MainActivity.java
package com.example.androidanimationalongpath;

import android.support.v7.app.ActionBarActivity;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity {

AnimationView animationView;
SeekBar speedBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

animationView = (AnimationView)findViewById(R.id.animationview);
speedBar = (SeekBar)findViewById(R.id.speedbar);
speedBar.setOnSeekBarChangeListener(speedBarOnSeekBarChangeListener);
animationView.setSpeed(speedBar.getProgress()); //set default speed
}

OnSeekBarChangeListener speedBarOnSeekBarChangeListener =
new OnSeekBarChangeListener(){

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
//offset from SeekBar 0~19 to step 1~10
animationView.setSpeed(progress);
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}};


}


download filesDownload the files.

More example of Drawing Path on canvas of custom View.

الاثنين، 2 يونيو 2014

Draw rotated path by rotating canvas

This example show how to draw rotated path by roatating canvas.

  //Save and rotate canvas 
canvas.save();
canvas.rotate(rotate, x, y);

canvas.drawPath(...);

//restore canvas
canvas.restore();


Modify MyView.java in last example "Rotate Path with Matrix", all other files, MainActivity.java, MyShape.java and /res/layout/activity_main.xml, keep no change.

package com.example.androiddrawpath;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

public class MyView extends View {

MyShape myShape;
float ratioRadius, ratioInnerRadius;
int numberOfPoint = 3; //default

float rotate;

//corresponding to UI element
TextView textLayerInfo;

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

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

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

public void initMyView(){
myShape = new MyShape();
}

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

long starting = System.nanoTime();

int w = getWidth();
int h = getHeight();

if((w==0) || (h==0)){
return;
}

float x = (float)w/2.0f;
float y = (float)h/2.0f;
float radius, innerRadius;
if(w > h){
radius = h * ratioRadius;
innerRadius = h * ratioInnerRadius;
}else{
radius = w * ratioRadius;
innerRadius = w * ratioInnerRadius;
}

myShape.setStar(x, y, radius, innerRadius, numberOfPoint);

//Save and rotate canvas
canvas.save();
canvas.rotate(rotate, x, y);

canvas.drawPath(myShape.getPath(), myShape.getPaint());

//restore canvas
canvas.restore();

long end = System.nanoTime();

String info = "myView.isHardwareAccelerated() = " + isHardwareAccelerated() + "\n"
+ "canvas.isHardwareAccelerated() = " + canvas.isHardwareAccelerated() + "\n"
+ "processing time (reference only) : " + String.valueOf(end - starting) + " (ns)";
textLayerInfo.setText(info);

}

public void setShapeRadiusRatio(float ratio){
ratioRadius = ratio;
}

public void setShapeInnerRadiusRatio(float ratio){
ratioInnerRadius = ratio;
}

public void setNumberOfPoint(int pt){
numberOfPoint = pt;
}

public void passElements(TextView textLayerInfo){
this.textLayerInfo = textLayerInfo;
}

public void setShapeRotate(int rot){
rotate = (float)rot;
}

}

More example of Drawing Path on canvas of custom View.

Rotate Path with Matrix

Example to rotate Path with Matrix:

  matrix.postRotate(rotate, x, y);
path.transform(matrix);

Modify to rotate the example in "Fill Path with color".


MainActivity.java
package com.example.androiddrawpath;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

public class MainActivity extends Activity {

SeekBar rotateBar;
TextView rotateText;

SeekBar radiusBar, innerRadiusBar;
MyView myView;

SeekBar ptBar;
TextView textPt;
final static int MIN_PT = 3;

RadioButton optLayerTypeNone, optLayerTypeSoftware, optLayerTypeHardware;
TextView textLayerInfo;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
radiusBar = (SeekBar) findViewById(R.id.radiusbar);
innerRadiusBar = (SeekBar)findViewById(R.id.innerradiusbar);
myView = (MyView) findViewById(R.id.myview);
float defaultRatio = (float) (radiusBar.getProgress())
/ (float) (radiusBar.getMax());
myView.setShapeRadiusRatio(defaultRatio);
float defaultInnerRatio = (float) (innerRadiusBar.getProgress())
/ (float) (innerRadiusBar.getMax());
myView.setShapeInnerRadiusRatio(defaultInnerRatio);

radiusBar.setOnSeekBarChangeListener(radiusBarOnSeekBarChangeListener);
innerRadiusBar.setOnSeekBarChangeListener(innerRadiusBarOnSeekBarChangeListener);

textPt = (TextView)findViewById(R.id.pttext);
ptBar = (SeekBar)findViewById(R.id.ptbar);
ptBar.setOnSeekBarChangeListener(ptBarOnSeekBarChangeListener);

optLayerTypeNone = (RadioButton)findViewById(R.id.typeNone);
optLayerTypeSoftware = (RadioButton)findViewById(R.id.typeSoftware);
optLayerTypeHardware = (RadioButton)findViewById(R.id.typeHardware);
textLayerInfo = (TextView)findViewById(R.id.typeinfo);

myView.passElements(textLayerInfo);
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

optLayerTypeNone.setOnCheckedChangeListener(optLayerTypeOnCheckedChangeListener);
optLayerTypeSoftware.setOnCheckedChangeListener(optLayerTypeOnCheckedChangeListener);
optLayerTypeHardware.setOnCheckedChangeListener(optLayerTypeOnCheckedChangeListener);

rotateText = (TextView)findViewById(R.id.rottext);
rotateBar = (SeekBar)findViewById(R.id.rotatebar);
rotateBar.setOnSeekBarChangeListener(rotateBarOnSeekBarChangeListener);
myView.setRotation(0); //set default rotate degree
};

OnCheckedChangeListener optLayerTypeOnCheckedChangeListener =
new OnCheckedChangeListener(){

@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if(optLayerTypeNone.isChecked()){
myView.setLayerType(View.LAYER_TYPE_NONE, null);
}else if(optLayerTypeSoftware.isChecked()){
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}else{
myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}

myView.invalidate();
}};

OnSeekBarChangeListener radiusBarOnSeekBarChangeListener =
new OnSeekBarChangeListener() {

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
float ratio = (float) (radiusBar.getProgress())
/ (float) (radiusBar.getMax());
myView.setShapeRadiusRatio(ratio);
myView.invalidate();
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}

};

OnSeekBarChangeListener innerRadiusBarOnSeekBarChangeListener =
new OnSeekBarChangeListener() {

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
float ratio = (float) (innerRadiusBar.getProgress())
/ (float) (innerRadiusBar.getMax());
myView.setShapeInnerRadiusRatio(ratio);
myView.invalidate();
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}

};

OnSeekBarChangeListener ptBarOnSeekBarChangeListener =
new OnSeekBarChangeListener() {

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
int pt = progress + MIN_PT;
textPt.setText("number of point in polygon: " + String.valueOf(pt));
myView.setNumberOfPoint(pt);
myView.invalidate();
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}

};

OnSeekBarChangeListener rotateBarOnSeekBarChangeListener =
new OnSeekBarChangeListener() {

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
int degree = progress-180;
rotateText.setText("rotate : " + degree + " degree");
myView.setShapeRotate(degree);
myView.invalidate();
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
};

}

MyView.java
package com.example.androiddrawpath;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

public class MyView extends View {

MyShape myShape;
float ratioRadius, ratioInnerRadius;
int numberOfPoint = 3; //default

float rotate;
Matrix matrix;

//corresponding to UI element
TextView textLayerInfo;

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

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

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

public void initMyView(){
myShape = new MyShape();
matrix = new Matrix();
}

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

long starting = System.nanoTime();

int w = getWidth();
int h = getHeight();

if((w==0) || (h==0)){
return;
}

float x = (float)w/2.0f;
float y = (float)h/2.0f;
float radius, innerRadius;
if(w > h){
radius = h * ratioRadius;
innerRadius = h * ratioInnerRadius;
}else{
radius = w * ratioRadius;
innerRadius = w * ratioInnerRadius;
}

myShape.setStar(x, y, radius, innerRadius, numberOfPoint);

//Rotate the path by angle in degree
Path path = myShape.getPath();
matrix.reset();
matrix.postRotate(rotate, x, y);
path.transform(matrix);

canvas.drawPath(path, myShape.getPaint());

long end = System.nanoTime();

String info = "myView.isHardwareAccelerated() = " + isHardwareAccelerated() + "\n"
+ "canvas.isHardwareAccelerated() = " + canvas.isHardwareAccelerated() + "\n"
+ "processing time (reference only) : " + String.valueOf(end - starting) + " (ns)";
textLayerInfo.setText(info);

}

public void setShapeRadiusRatio(float ratio){
ratioRadius = ratio;
}

public void setShapeInnerRadiusRatio(float ratio){
ratioInnerRadius = ratio;
}

public void setNumberOfPoint(int pt){
numberOfPoint = pt;
}

public void passElements(TextView textLayerInfo){
this.textLayerInfo = textLayerInfo;
}

public void setShapeRotate(int rot){
rotate = (float)rot;
}

}

MyShape.java
package com.example.androiddrawpath;

import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;

public class MyShape {

private Paint paint;
private Path path;

public MyShape() {
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(3);
//paint.setStyle(Paint.Style.STROKE);
//paint.setStyle(Paint.Style.FILL);
paint.setStyle(Paint.Style.FILL_AND_STROKE);

path = new Path();
}

public void setCircle(float x, float y, float radius, Path.Direction dir){
path.reset();
path.addCircle(x, y, radius, dir);
}

public void setStar(float x, float y, float radius, float innerRadius, int numOfPt){

double section = 2.0 * Math.PI/numOfPt;

path.reset();
path.moveTo(
(float)(x + radius * Math.cos(0)),
(float)(y + radius * Math.sin(0)));
path.lineTo(
(float)(x + innerRadius * Math.cos(0 + section/2.0)),
(float)(y + innerRadius * Math.sin(0 + section/2.0)));

for(int i=1; i<numOfPt; i++){
path.lineTo(
(float)(x + radius * Math.cos(section * i)),
(float)(y + radius * Math.sin(section * i)));
path.lineTo(
(float)(x + innerRadius * Math.cos(section * i + section/2.0)),
(float)(y + innerRadius * Math.sin(section * i + section/2.0)));
}

path.close();

}

public Path getPath(){
return path;
}

public Paint getPaint(){
return paint;
}

}

/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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.example.androiddrawpath.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:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="radius(%)"/>
<SeekBar
android:id="@+id/radiusbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="50" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="inner radius(%)"/>
<SeekBar
android:id="@+id/innerradiusbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="25" />
<TextView
android:id="@+id/pttext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="number of point in polygon: 3"/>
<SeekBar
android:id="@+id/ptbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="10"
android:progress="0" />

<TextView
android:id="@+id/rottext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="rotate :"/>
<SeekBar
android:id="@+id/rotatebar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="360"
android:progress="180" />

<RadioGroup
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton android:id="@+id/typeNone"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="LAYER_TYPE_NONE"/>
<RadioButton android:id="@+id/typeSoftware"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="LAYER_TYPE_SOFTWARE"/>
<RadioButton android:id="@+id/typeHardware"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="LAYER_TYPE_HARDWARE"/>
</RadioGroup>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/typeinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
<com.example.androiddrawpath.MyView
android:id="@+id/myview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

</LinearLayout>


download filesDownload the files.

Related:
- Draw rotated path by rotating canvas

More example of Drawing Path on canvas of custom View.

الخميس، 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.


الثلاثاء، 27 مايو 2014

Animation follow touch path

This example show how to implement animation of moving bitmap follow the touch moving path.


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;

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;

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);

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();
}

@Override
protected void onDraw(Canvas canvas) {

if(animPath.isEmpty()){
return;
}

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);
}
}

}

@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;
}

}

/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" />

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

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

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

</LinearLayout>

No change on MainActivity.java, refer HERE.


download filesDownload the files.

More example of Drawing Path on canvas of custom View.


الاثنين، 26 مايو 2014

Smooth turning along path

In last example of "Animation of moving bitmap along path", the bitmap turn suddenly in turning points of the path. This example modify to have a smooth turning.


Modify 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.View;
import android.widget.Toast;

public class AnimationView extends View {

Paint paint;

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;

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);

bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
bm_offsetX = bm.getWidth()/2;
bm_offsetY = bm.getHeight()/2;

animPath = new Path();
animPath.moveTo(100, 100);
animPath.lineTo(200, 100);
animPath.lineTo(300, 50);
animPath.lineTo(400, 150);
animPath.lineTo(100, 300);
animPath.lineTo(600, 300);
animPath.lineTo(100, 100);
animPath.close();

pathMeasure = new PathMeasure(animPath, false);
pathLength = pathMeasure.getLength();

Toast.makeText(getContext(), "pathLength: " + pathLength, Toast.LENGTH_LONG).show();

step = 1;
distance = 0;
curX = 0;
curY = 0;

stepAngle = 1;
curAngle = 0;
targetAngle = 0;

pos = new float[2];
tan = new float[2];

matrix = new Matrix();
}

@Override
protected void onDraw(Canvas canvas) {

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);
}else if((curAngle-targetAngle)>stepAngle){
curAngle -= stepAngle;
matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
matrix.postTranslate(curX, curY);
canvas.drawBitmap(bm, matrix, null);
}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;
}else{
distance = 0;
}
}

invalidate();
}

}

Other files, MainActivity.java and /res/layout/activity_main.xml, refer last exercise.

download filesDownload the files.

More example of Drawing Path on canvas of custom View.

Animation of moving bitmap along path

This example show how to animate a moving bitmap along path.


In order to get the position and angle of the animated bitmap in a distance along a path, we use the code:
  pathMeasure = new PathMeasure(animPath, false);
pathLength = pathMeasure.getLength();

...
pathMeasure.getPosTan(distance, pos, tan);

where pos, and tan are float[2] passed to retrieve the resulting position and tangent.

MainActivity.java.
package com.example.androidanimationalongpath;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

}

}

/res/layout/activity_main.xml, simple include our custom view in layout.
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
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" />

<com.example.androidanimationalongpath.AnimationView
android:layout_width="match_parent"
android:layout_height="match_parent"/>

</LinearLayout>

AnimationView.java, custom view.
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.View;
import android.widget.Toast;

public class AnimationView extends View {

Paint paint;

Bitmap bm;
int bm_offsetX, bm_offsetY;

Path animPath;
PathMeasure pathMeasure;
float pathLength;

float step; //distance each step
float distance; //distance moved

float[] pos;
float[] tan;

Matrix matrix;

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);

bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
bm_offsetX = bm.getWidth()/2;
bm_offsetY = bm.getHeight()/2;

animPath = new Path();
animPath.moveTo(100, 100);
animPath.lineTo(200, 100);
animPath.lineTo(300, 50);
animPath.lineTo(400, 150);
animPath.lineTo(100, 300);
animPath.lineTo(600, 300);
animPath.lineTo(100, 100);
animPath.close();

pathMeasure = new PathMeasure(animPath, false);
pathLength = pathMeasure.getLength();

Toast.makeText(getContext(), "pathLength: " + pathLength, Toast.LENGTH_LONG).show();

step = 1;
distance = 0;
pos = new float[2];
tan = new float[2];

matrix = new Matrix();
}

@Override
protected void onDraw(Canvas canvas) {

canvas.drawPath(animPath, paint);

if(distance < pathLength){
pathMeasure.getPosTan(distance, pos, tan);

matrix.reset();
float degrees = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
matrix.postRotate(degrees, bm_offsetX, bm_offsetY);
matrix.postTranslate(pos[0]-bm_offsetX, pos[1]-bm_offsetY);

canvas.drawBitmap(bm, matrix, null);

distance += step;
}else{
distance = 0;
}

invalidate();
}

}


download filesDownload the files.

Next:
- Smooth turning along path

More example of Drawing Path on canvas of custom View.

Updated@2016-08-09:
- Custom view to draw bitmap along path, in separate object

Draw star on canvas

Show how to draw star on canvas of custom View. More examples of drawing Path on canvas of custom View HERE.


MainActivity.java
package com.example.androiddrawpath;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

public class MainActivity extends Activity {

SeekBar radiusBar, innerRadiusBar;
MyView myView;

SeekBar ptBar;
TextView textPt;
final static int MIN_PT = 3;

RadioButton optLayerTypeNone, optLayerTypeSoftware, optLayerTypeHardware;
TextView textLayerInfo;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
radiusBar = (SeekBar) findViewById(R.id.radiusbar);
innerRadiusBar = (SeekBar)findViewById(R.id.innerradiusbar);
myView = (MyView) findViewById(R.id.myview);
float defaultRatio = (float) (radiusBar.getProgress())
/ (float) (radiusBar.getMax());
myView.setShapeRadiusRatio(defaultRatio);
float defaultInnerRatio = (float) (innerRadiusBar.getProgress())
/ (float) (innerRadiusBar.getMax());
myView.setShapeInnerRadiusRatio(defaultInnerRatio);

radiusBar.setOnSeekBarChangeListener(radiusBarOnSeekBarChangeListener);
innerRadiusBar.setOnSeekBarChangeListener(innerRadiusBarOnSeekBarChangeListener);

textPt = (TextView)findViewById(R.id.pttext);
ptBar = (SeekBar)findViewById(R.id.ptbar);
ptBar.setOnSeekBarChangeListener(ptBarOnSeekBarChangeListener);

optLayerTypeNone = (RadioButton)findViewById(R.id.typeNone);
optLayerTypeSoftware = (RadioButton)findViewById(R.id.typeSoftware);
optLayerTypeHardware = (RadioButton)findViewById(R.id.typeHardware);
textLayerInfo = (TextView)findViewById(R.id.typeinfo);

myView.passElements(textLayerInfo);
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

optLayerTypeNone.setOnCheckedChangeListener(optLayerTypeOnCheckedChangeListener);
optLayerTypeSoftware.setOnCheckedChangeListener(optLayerTypeOnCheckedChangeListener);
optLayerTypeHardware.setOnCheckedChangeListener(optLayerTypeOnCheckedChangeListener);
};

OnCheckedChangeListener optLayerTypeOnCheckedChangeListener =
new OnCheckedChangeListener(){

@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if(optLayerTypeNone.isChecked()){
myView.setLayerType(View.LAYER_TYPE_NONE, null);
}else if(optLayerTypeSoftware.isChecked()){
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}else{
myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}

myView.invalidate();
}};

OnSeekBarChangeListener radiusBarOnSeekBarChangeListener =
new OnSeekBarChangeListener() {

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
float ratio = (float) (radiusBar.getProgress())
/ (float) (radiusBar.getMax());
myView.setShapeRadiusRatio(ratio);
myView.invalidate();
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}

};

OnSeekBarChangeListener innerRadiusBarOnSeekBarChangeListener =
new OnSeekBarChangeListener() {

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
float ratio = (float) (innerRadiusBar.getProgress())
/ (float) (innerRadiusBar.getMax());
myView.setShapeInnerRadiusRatio(ratio);
myView.invalidate();
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}

};

OnSeekBarChangeListener ptBarOnSeekBarChangeListener =
new OnSeekBarChangeListener() {

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
int pt = progress + MIN_PT;
textPt.setText("number of point in polygon: " + String.valueOf(pt));
myView.setNumberOfPoint(pt);
myView.invalidate();
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {}

};

}

MyView.java
package com.example.androiddrawpath;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

public class MyView extends View {

MyShape myShape;
float ratioRadius, ratioInnerRadius;
int numberOfPoint = 3; //default

//corresponding to UI element
TextView textLayerInfo;

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

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

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

public void initMyView(){
myShape = new MyShape();
}

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

long starting = System.nanoTime();

int w = getWidth();
int h = getHeight();

if((w==0) || (h==0)){
return;
}

float x = (float)w/2.0f;
float y = (float)h/2.0f;
float radius, innerRadius;
if(w > h){
radius = h * ratioRadius;
innerRadius = h * ratioInnerRadius;
}else{
radius = w * ratioRadius;
innerRadius = w * ratioInnerRadius;
}

myShape.setStar(x, y, radius, innerRadius, numberOfPoint);
canvas.drawPath(myShape.getPath(), myShape.getPaint());

long end = System.nanoTime();

String info = "myView.isHardwareAccelerated() = " + isHardwareAccelerated() + "\n"
+ "canvas.isHardwareAccelerated() = " + canvas.isHardwareAccelerated() + "\n"
+ "processing time (reference only) : " + String.valueOf(end - starting) + " (ns)";
textLayerInfo.setText(info);

}

public void setShapeRadiusRatio(float ratio){
ratioRadius = ratio;
}

public void setShapeInnerRadiusRatio(float ratio){
ratioInnerRadius = ratio;
}

public void setNumberOfPoint(int pt){
numberOfPoint = pt;
}

public void passElements(TextView textLayerInfo){
this.textLayerInfo = textLayerInfo;
}

}

MyShape.java
package com.example.androiddrawpath;

import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;

public class MyShape {

private Paint paint;
private Path path;

public MyShape() {
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(3);
paint.setStyle(Paint.Style.STROKE);

path = new Path();
}

public void setCircle(float x, float y, float radius, Path.Direction dir){
path.reset();
path.addCircle(x, y, radius, dir);
}

public void setStar(float x, float y, float radius, float innerRadius, int numOfPt){

double section = 2.0 * Math.PI/numOfPt;

path.reset();
path.moveTo(
(float)(x + radius * Math.cos(0)),
(float)(y + radius * Math.sin(0)));
path.lineTo(
(float)(x + innerRadius * Math.cos(0 + section/2.0)),
(float)(y + innerRadius * Math.sin(0 + section/2.0)));

for(int i=1; i<numOfPt; i++){
path.lineTo(
(float)(x + radius * Math.cos(section * i)),
(float)(y + radius * Math.sin(section * i)));
path.lineTo(
(float)(x + innerRadius * Math.cos(section * i + section/2.0)),
(float)(y + innerRadius * Math.sin(section * i + section/2.0)));
}

path.close();

}

public Path getPath(){
return path;
}

public Paint getPaint(){
return paint;
}

}

/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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.example.androiddrawpath.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:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="radius(%)"/>
<SeekBar
android:id="@+id/radiusbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="50" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="inner radius(%)"/>
<SeekBar
android:id="@+id/innerradiusbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="25" />
<TextView
android:id="@+id/pttext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="number of point in polygon: 3"/>
<SeekBar
android:id="@+id/ptbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="10"
android:progress="0" />

<RadioGroup
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton android:id="@+id/typeNone"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="LAYER_TYPE_NONE"/>
<RadioButton android:id="@+id/typeSoftware"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="LAYER_TYPE_SOFTWARE"/>
<RadioButton android:id="@+id/typeHardware"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="LAYER_TYPE_HARDWARE"/>
</RadioGroup>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/typeinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
<com.example.androiddrawpath.MyView
android:id="@+id/myview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

</LinearLayout>


download filesDownload the files.

Next:
- Draw rounded corner star on canvas

More example of Drawing Path on canvas of custom View.