Wednesday, June 18, 2014

Implement arrow-like PathDashPathEffect

Last example "Implement running dash path, using PathDashPathEffect" with path of circle. It's modified to create custom path to show running arrow-like PathDashPathEffect.


Modify MyView.java in last example.
package com.example.androiddrawpath;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathDashPathEffect;
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;

 Path dashPath;
 float phase;
 float advance;

 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();
  
  dashPath = new Path();
  dashPath.moveTo(5, -5);
  dashPath.lineTo(45, -5);
  dashPath.lineTo(40, 0);
  dashPath.lineTo(45, 5);
  dashPath.lineTo(5, 5);
  dashPath.lineTo(0, 0);
  dashPath.close();
  
  phase = 0.0f;
  advance = 60.0f;
 }
 
 @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);
  
  phase++;
  PathDashPathEffect pathDashPathEffect = 
   new PathDashPathEffect(dashPath, advance, phase, PathDashPathEffect.Style.MORPH);
  
  Paint paintDash = myShape.getPaint();
  paintDash.setPathEffect(pathDashPathEffect);
  
  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);
  
  invalidate();
  
 }
 
 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;
 }

}


Other files have no changed, re-list here:

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

}

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

}

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.

More example of Drawing Path on canvas of custom View.

No comments: