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.

No comments: