Saturday, May 31, 2014

Efficient Android Threading: Asynchronous Processing Techniques for Android Applications

Efficient Android Threading: Asynchronous Processing Techniques for Android Applications

Multithreading is essential if you want to create an Android app with a great user experience, but how do you know which techniques can help solve your problem? This practical book describes many asynchronous mechanisms available in the Android SDK, and provides guidelines for selecting the ones most appropriate for the app you’re building.

Author Anders Goransson demonstrates the advantages and disadvantages of each technique, with sample code and detailed explanations for using it efficiently. The first part of the book describes the building blocks of asynchronous processing, and the second part covers Android libraries and constructs for developing fast, responsive, and well-structured apps.
  • Understand multithreading basics in Java and on the Android platform
  • Learn how threads communicate within and between processes
  • Use strategies to reduce the risk of memory leaks
  • Manage the lifecycle of a basic thread
  • Run tasks sequentially in the background with HandlerThread
  • Use Java’s Executor Framework to control or cancel threads
  • Handle background task execution with AsyncTask and IntentService
  • Access content providers with AsyncQueryHandler
  • Use loaders to update the UI with new data

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.


Wednesday, May 28, 2014

Fill Path with color

To fill path with color, simple replace the code paint.setStyle(Paint.Style.STROKE) to paint.setStyle(Paint.Style.FILL) or paint.setStyle(Paint.Style.FILL_AND_STROKE).


Modify MyShape.java in the example "Draw star on canvas" to fill the star.
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;
 }
 
}


More example of Drawing Path on canvas of custom View.

Round corner of path with CornerPathEffect

To make a path with rounded corner, simple call setPathEffect() with CornerPathEffect.

  float radius = 50.0f;
  CornerPathEffect cornerPathEffect =
   new CornerPathEffect(radius);
  paint.setPathEffect(cornerPathEffect);


Modify MyShape.java in the example "Draw star on canvas" to draw the star with rounded corner.
package com.example.androiddrawpath;

import android.graphics.Color;
import android.graphics.CornerPathEffect;
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);
  
  float radius = 50.0f;
  CornerPathEffect cornerPathEffect =
   new CornerPathEffect(radius);
  paint.setPathEffect(cornerPathEffect);
  
  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;
 }
 
}


More example of Drawing Path on canvas of custom View.

Tuesday, May 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.


Monday, May 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.

Saturday, May 24, 2014

Controlling Hardware Acceleration of individual View by calling setLayerType() method

In this example, we call setLayerType() method programatically, to set various layer type, by user selection. And show isHardwareAccelerated() of both myBiew and canvas. Also I count the processing time of our drawing in nano-second for reference, such that we can know how the hardware acceleration affect the performance.



Beginning in Android 3.0 (API level 11), you have more control on how and when to use layers with the View.setLayerType() method. This API takes two parameters: the type of layer you want to use and an optional Paint object that describes how the layer should be composited... ... ... A view can use one of three layer types:
  • LAYER_TYPE_NONE: The view is rendered normally and is not backed by an off-screen buffer. This is the default behavior.
  • LAYER_TYPE_HARDWARE: The view is rendered in hardware into a hardware texture if the application is hardware accelerated. If the application is not hardware accelerated, this layer type behaves the same as LAYER_TYPE_SOFTWARE.
  • LAYER_TYPE_SOFTWARE: The view is rendered in software into a bitmap.

You can disable hardware acceleration for an individual view at runtime with the following code:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Note: You currently cannot enable hardware acceleration at the view level. View layers have other functions besides disabling hardware acceleration.

reference: http://developer.android.com/guide/topics/graphics/hardware-accel.html#controlling

To varify if a View is Hardware Accelerated, we can call myView.isHardwareAccelerated() and/or Canvas.isHardwareAccelerated().
  • View.isHardwareAccelerated() returns true if the View is attached to a hardware accelerated window.
  • Canvas.isHardwareAccelerated() returns true if the Canvas is hardware accelerated
If you must do this check in your drawing code, use Canvas.isHardwareAccelerated() instead of View.isHardwareAccelerated() when possible.

reference: http://developer.android.com/guide/topics/graphics/hardware-accel.html#determining

Also have to be noticed is when hardware accelerated, some operation is unsupported, depends on API levels. That's why we have to disable Hardware Acceleration.

reference: http://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported



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;
	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);
		myView = (MyView) findViewById(R.id.myview);
		float defaultRatio = (float) (radiusBar.getProgress())
				/ (float) (radiusBar.getMax());
		myView.setShapeRadiusRatio(defaultRatio);

		radiusBar.setOnSeekBarChangeListener(radiusBarOnSeekBarChangeListener);
		
		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 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;
	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;
		if(w > h){
			radius = h * ratioRadius;
		}else{
			radius = w * ratioRadius;
		}
		
		myShape.setPolygon(x, y, radius, 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 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 setPolygon(float x, float y, float radius, 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)));
		
		for(int i=1; i<numOfPt; i++){
			path.lineTo(
				(float)(x + radius * Math.cos(section * i)), 
				(float)(y + radius * Math.sin(section * i)));
		}
		
		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: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>

In order to call setLayerType(int layerType, Paint paint), View.isHardwareAccelerated() and Canvas.isHardwareAccelerated (), android:minSdkVersion in AndroidManifest.xml have to be set ="11" in .

download filesDownload the files.


More examples of Draw Path on canvas of custom View.

Friday, May 23, 2014

Draw Path of polygon on canvas of custom View

This example works on last, to draw Path of equal-length polygon, on canvas.


MainActivity.java
package com.example.androiddrawpath;

import android.app.Activity;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

public class MainActivity extends Activity {

 SeekBar radiusBar;
 MyView myView;
 
 SeekBar ptBar;
 TextView textPt;
 final static int MIN_PT = 3;

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

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

 };

 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 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, our custom View.
package com.example.androiddrawpath;

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

public class MyView extends View {
 
 MyShape myShape;
 float ratioRadius;
 int numberOfPoint = 3; //default

 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);
  
  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;
  if(w > h){
   radius = h * ratioRadius;
  }else{
   radius = w * ratioRadius;
  }
  
  myShape.setPolygon(x, y, radius, numberOfPoint);
  canvas.drawPath(myShape.getPath(), myShape.getPaint());
 }
 
 public void setShapeRadiusRatio(float ratio){
  ratioRadius = ratio;
 }
 
 public void setNumberOfPoint(int pt){
  numberOfPoint = pt;
 }

}

MyShape.java, it is the object hold the Path and Paint to be drawn on our View.
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 setPolygon(float x, float y, float radius, 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)));
  
  for(int i=1; i<numOfPt; i++){
   path.lineTo(
    (float)(x + radius * Math.cos(section * i)), 
    (float)(y + radius * Math.sin(section * i)));
  }
  
  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: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" />
    
    <com.example.androiddrawpath.MyView 
        android:id="@+id/myview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


download filesDownload the files.

More examples of Draw Path on canvas of custom View.

Draw Path on canvas of custom View

This example show drawing Path of circle on canvas of custom View, user can adjust its ratio using twh SeekBar. It will be the base of coming exercises.


MainActivity.java
package com.example.androiddrawpath;

import android.app.Activity;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;

public class MainActivity extends Activity {

 SeekBar radiusBar;
 MyView myView;

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

  radiusBar.setOnSeekBarChangeListener(radiusBarOnSeekBarChangeListener);

 };

 OnSeekBarChangeListener radiusBarOnSeekBarChangeListener = 
  new OnSeekBarChangeListener() {

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

  @Override
  public void onStartTrackingTouch(SeekBar seekBar) {
  }

  @Override
  public void onStopTrackingTouch(SeekBar seekBar) {
  }

 };

}

MyView.java, our custom View.
package com.example.androiddrawpath;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path.Direction;
import android.util.AttributeSet;
import android.view.View;

public class MyView extends View {
 
 MyShape myShape;
 float ratioRadius;

 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);
  
  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;
  if(w > h){
   radius = h * ratioRadius;
  }else{
   radius = w * ratioRadius;
  }
  
  myShape.setCircle(x, y, radius, Direction.CCW);
  canvas.drawPath(myShape.getPath(), myShape.getPaint());
 }
 
 public void setShapeRadiusRatio(float ratio){
  ratioRadius = ratio;
 }

}

MyShape.java, it is the object hold the Path and Paint to be drawn on our View.
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 Path getPath(){
  return path;
 }
 
 public Paint getPaint(){
  return paint;
 }
 
}

Layout, /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" />
    
    <com.example.androiddrawpath.MyView 
        android:id="@+id/myview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

download filesDownload the files.


More example:



Thursday, May 22, 2014

Display available currencies java.util.Currency

This example list available currencies java.util.Currency. Please notice that the methods getAvailableCurrencies() and getDisplayName() used in this example introduced in API Level 19. So android:minSdkVersion in AndroidManifest.xml have to be set ="19".


package com.example.androidcurrency;

import java.util.ArrayList;
import java.util.Currency;
import java.util.List;
import java.util.Set;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity {

 ListView listCurrency;
 Set<Currency> availableCurrenciesSet;
 List<Currency> availableCurrenciesList;
 ArrayAdapter<Currency> adapter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  listCurrency = (ListView)findViewById(R.id.currencylist);
  
  //available from API Level 19
  availableCurrenciesSet = 
   Currency.getAvailableCurrencies();
  
  availableCurrenciesList = new ArrayList<Currency>(availableCurrenciesSet);
  adapter = new ArrayAdapter<Currency>(
    this, 
    android.R.layout.simple_list_item_1, 
    availableCurrenciesList);
  listCurrency.setAdapter(adapter);
  
  listCurrency.setOnItemClickListener(new OnItemClickListener(){

   @Override
   public void onItemClick(AdapterView<?> parent, View view,
     int position, long id) {
    Currency currency = (Currency) parent.getItemAtPosition(position);
    String currencyCode = currency.getCurrencyCode();
    String displayName = currency.getDisplayName();
    String symbol = currency.getSymbol();
    
    Toast.makeText(MainActivity.this, 
     displayName + "\n" +
     currencyCode + "\n" +
     symbol, 
     Toast.LENGTH_LONG).show();
   }});
 }


}

<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.androidcurrency.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" />
    
    <ListView 
        android:id="@+id/currencylist"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>



Related:
- Display currency symbols, hard coded.

Tuesday, May 20, 2014

How to Create a Basic Integrated Gear Application

The tutorial, by Samsung Developers, teaches developers how to build a basic sample application for SAP. This is a three-part series. It also teaches how to test the applications using the Emulator.
  • Part 1 is all about the development on the Android side.
  • Part 2 deals with the development on the Tizen side.
  • Part 3 is about the complete application for SAP, which is basically packaging of the Android part and the Tizen part.
    It also teaches how to test the applications using the Emulator.

Getting started with Qt 5.3.0 for WinRT

This tutorial video goes over the basic usage of Qt Creator and Visual Studio when developing Windows Phone and Windows Store Apps with Qt for WinRT. This includes application packaging steps required for uploading to the Windows Store and Windows Phone Store.

Qt, the leading cross-platform framework:
http://qt.digia.com
http://qt-project.com


Microsoft Surface Pro 3 - Launch Full Event Keynote

Microsoft announces Surface Pro 3 in NY City (Tuesday, May. 20), the tablet that can replace your laptop. It is the thinnest, lightest and most powerful Surface Pro yet.

Keynote with :
Satya Nadella, CEO. Microsoft
Panos Panay, Corp. Vice President, Surface™. Microsoft

Android WebView: display SVG using HTML and Javascript

This example show how to display SVG in WebView, using HTML and Javascript.


Scalable Vector Graphics (SVG) is an XML-based vector image format for two-dimensional graphics with support for interactivity and animation. The SVG specification is an open standard developed by the World Wide Web Consortium (W3C) since 1999. ~ Wikipedia.

You can convert graph file from PNG to SVG using inkscape. Save your svg file in /assets, same level of our html, mypage.html

Modify /assets/mypage.html from the former exercise of WebView.
<!DOCTYPE HTML>
<HTML>
<HEAD>
<script>

var p1;
var p2;
var canvas;
var context;
var cx;
var cy;
var x;
var y;
var canvasOffsetX;
var canvasOffsetY;

var imgSVG = new Image();
imgSVG.src = "test.svg";

function init(){
 p1 = document.getElementById('p1');
 p2 = document.getElementById('p2');
 p1.innerHTML=navigator.userAgent;
 
 canvas = document.getElementById('myCanvas');
 context = canvas.getContext('2d');
 canvasOffsetX = canvas.getBoundingClientRect().left;
 canvasOffsetY = canvas.getBoundingClientRect().top;
 
 canvas.width = window.innerWidth - (2 * canvasOffsetX);
 canvas.height = window.innerHeight - canvasOffsetY - 100;
 
 context.drawImage(imgSVG, 0, 0);
 
}

</script>
</HEAD>
<BODY onload="init()" style="border:5px solid #000000;">

<p id='p1'>un-init</p>

<canvas id='myCanvas' style="border:1px solid #FF0000;">
Canvas not support!
</canvas>

<p id='p2'></p>

</BODY>
</HTML>

Monday, May 19, 2014

Detect touch and draw circle on Android WebView with Javascript

Last exercise show a simple WebView with touch detection on Android WebView, it's modify to draw circle when user touch and move on HTML canvas.


Modify /assets/mypage.html from last exercise.
<!DOCTYPE HTML>
<HTML>
<HEAD>
<script>

var p1;
var p2;
var canvas;
var context;
var cx;
var cy;
var x;
var y;
var canvasOffsetX;
var canvasOffsetY;

function init(){
 p1 = document.getElementById('p1');
 p2 = document.getElementById('p2');
 p1.innerHTML=navigator.userAgent;
 
 canvas = document.getElementById('myCanvas');
 context = canvas.getContext('2d');
 canvasOffsetX = canvas.getBoundingClientRect().left;
 canvasOffsetY = canvas.getBoundingClientRect().top;
 
 canvas.width = window.innerWidth - (2 * canvasOffsetX);
 canvas.height = window.innerHeight - canvasOffsetY - 100;
 canvas.fillStyle="#a0a0a0";

 canvas.addEventListener('touchstart', touchstartListener, false);
 canvas.addEventListener('touchmove', touchmoveListener, false);
 canvas.addEventListener('touchend', touchendListener, false);
 canvas.addEventListener('touchenter', touchenterListener, false);
 canvas.addEventListener('touchleave', touchleaveListener, false);
 canvas.addEventListener('touchcancel', touchcancelListener, false);
 
}

function touchstartListener(event){
 cx = event.changedTouches[0].pageX - canvasOffsetX;
 cy = event.changedTouches[0].pageY - canvasOffsetY;
 p2.innerHTML= "touchstart - <br/>" 
  + cx + ":" + cy;
 event.preventDefault();
 
 context.rect(0, 0, canvas.width, canvas.height);
 context.fillStyle="white";
 context.fill();
}

function touchmoveListener(event){

 x = event.changedTouches[0].pageX - canvasOffsetX;
 y = event.changedTouches[0].pageY - canvasOffsetY;
 var deltax = x-cx;
 var deltay = y-cy;
 var radius = Math.sqrt(deltax*deltax + deltay*deltay);
 context.beginPath();
 context.arc(cx, cy, radius, 0, 2 * Math.PI, false);
 context.fillStyle = 'green';
 context.fill();
 context.lineWidth = 1;
 context.strokeStyle = '#003300';
 context.stroke();

 p2.innerHTML= "touchmove - <br/>" 
  + x + ":" + y;
 event.preventDefault();
}

function touchendListener(event){
 p2.innerHTML= "touchend - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchenterListener(event){
 p2.innerHTML= "touchenter - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchleaveListener(event){
 p2.innerHTML= "touchleave - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchcancelListener(event){
 p2.innerHTML= "touchcancel - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

</script>
</HEAD>
<BODY onload="init()" style="border:5px solid #000000;">

<p id='p1'>un-init</p>

<canvas id='myCanvas' style="border:1px solid #FF0000;">
Canvas not support!
</canvas>

<p id='p2'></p>

</BODY>
</HTML>

Sunday, May 18, 2014

IOIO Android Hardware Interface

IOIO is an open-source board designed for interfacing external hardware with Android.

Android WebView, detect touch events with Javascript.

This example show how to create hybrid web-app, using WebView. And detect the touch events with Javascript.


/assets/mypage.html, it will be loaded in our WebView, to perform the main function.
<!DOCTYPE HTML>
<HTML>
<HEAD>
<script>

var p1;
var p2;

function init(){
 p1 = document.getElementById('p1');
 p2 = document.getElementById('p2');
 p1.innerHTML=navigator.userAgent;
 var canvas = document.getElementById('myCanvas');
 var canvasOffsetX = canvas.getBoundingClientRect().left;
 var canvasOffsetY = canvas.getBoundingClientRect().top;
 
 canvas.width = window.innerWidth - (2 * canvasOffsetX);
 canvas.height = window.innerHeight - canvasOffsetY - 100;
 canvas.fillStyle="#a0a0a0";

 canvas.addEventListener('touchstart', touchstartListener, false);
 canvas.addEventListener('touchmove', touchmoveListener, false);
 canvas.addEventListener('touchend', touchendListener, false);
 canvas.addEventListener('touchenter', touchenterListener, false);
 canvas.addEventListener('touchleave', touchleaveListener, false);
 canvas.addEventListener('touchcancel', touchcancelListener, false);
 
}

function touchstartListener(event){
 p2.innerHTML= "touchstart - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchmoveListener(event){
 p2.innerHTML= "touchmove - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchendListener(event){
 p2.innerHTML= "touchend - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchenterListener(event){
 p2.innerHTML= "touchenter - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchleaveListener(event){
 p2.innerHTML= "touchleave - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchcancelListener(event){
 p2.innerHTML= "touchcancel - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

</script>
</HEAD>
<BODY onload="init()" style="border:5px solid #000000;">

<p id='p1'>un-init</p>

<canvas id='myCanvas' style="border:1px solid #FF0000;">
Canvas not support!
</canvas>

<p id='p2'></p>

</BODY>
</HTML>

/res/layout/activity_main.xml, with <WebView>.
<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"
    android:background="@android:color/background_dark"
    tools:context="com.example.androidhybridwebview.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" />

    <WebView
        android:id="@+id/mybrowser"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>

</LinearLayout>

package com.example.androidhybridwebview;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;

public class MainActivity extends Activity {

 WebView myBrowser;

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

  myBrowser = (WebView) findViewById(R.id.mybrowser);
  myBrowser.getSettings().setJavaScriptEnabled(true);
  myBrowser.loadUrl("file:///android_asset/mypage.html");
 }

}

Next:
Detect touch and draw circle on Android WebView with Javascript