Showing posts with label Custom View. Show all posts
Showing posts with label Custom View. Show all posts

Custom view to draw bitmap along path, calculate in background thread

Last post show a example of "Custom view to draw bitmap along path", with calculation run inside onDraw(). It's modified version to pre-calculate in back thread.


(remark: in last post, canvas.drawPath() is called inside onDraw(). It seem run very slow, removed in this example.)

Modify AnimationView.java
package com.blogspot.android_er.androidmovingbitmapalongpath;

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

import java.util.ArrayList;
import java.util.List;

public class AnimationView extends View {

List<AnimationThing> animationThingsList;
public AnimationView(Context context) {
super(context);
initAnimationView();
}

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

public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAnimationView();
}

private void initAnimationView(){
animationThingsList = new ArrayList<>();
}

public void insertThing(AnimationThing thing){
animationThingsList.add(thing);
}

//prepare calculation in background thread
public void preDraw(){
for (AnimationThing thing : animationThingsList){
if(thing.distance < thing.pathLength){
thing.pathMeasure.getPosTan(thing.distance, thing.pos, thing.tan);

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

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

@Override
protected void onDraw(Canvas canvas) {
for (AnimationThing thing : animationThingsList){
canvas.drawBitmap(thing.bm, thing.matrix, null);
}

invalidate();
}


/*
@Override
protected void onDraw(Canvas canvas) {
for (AnimationThing thing : animationThingsList){

//This code run slow!!!
//canvas.drawPath(thing.animPath, thing.paint);

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

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

canvas.drawBitmap(thing.bm, thing.matrix, null);

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

invalidate();
}
*/



}


MainActivity.java
package com.blogspot.android_er.androidmovingbitmapalongpath;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Path;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

AnimationView myAnimationView;
AniThread myAniThread;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myAnimationView = (AnimationView)findViewById(R.id.MyAnimationView);

prepareThings();
myAniThread = new AniThread(myAnimationView);
myAniThread.start();

}

private void prepareThings() {
Path animPath;
float step;
Bitmap bm;

bm = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);

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

step = 1;

AnimationThing thing = new AnimationThing(animPath, bm, step);
myAnimationView.insertThing(thing);

//The second thing
bm = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_add);

animPath.reset();
animPath.addCircle(400, 400, 300, Path.Direction.CW);
step = 3;
thing = new AnimationThing(animPath, bm, step);
myAnimationView.insertThing(thing);
}

class AniThread extends Thread{

AnimationView targetView;

public AniThread(AnimationView target) {
super();
targetView = target;
}

@Override
public void run() {
while (true){
targetView.preDraw();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
}


}


The other files, AnimationThing.java and activity_main.xml, refer last post.


download filesDownload the files .

Custom view to draw bitmap along path


Refer to my old exercise "Animation of moving bitmap along path", the bitmap and path are hard-coded inside custom view. It's a modified version; the thing to be drawn (bitmap/path) are held separated and referenced by custom view in a list, such that it is easy to insert more things.


Please notice:
- I don't thing it's a good approach to do this, I just show a interesting exercise.
- You should not do the calculation (such as move the bitmap) inside onDraw(), it's suggested to do it in another thread.

Create a new class AnimationThing.java. It hold the bitmap and path of individual thing.
package com.blogspot.android_er.androidmovingbitmapalongpath;

import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;

public class AnimationThing {

Paint paint;
Path animPath;
PathMeasure pathMeasure;
float pathLength;
Bitmap bm;
int bm_offsetX, bm_offsetY;
float step;
float distance;
float[] pos;
float[] tan;
Matrix matrix;

public AnimationThing(Paint paint,
Path animPath,
Bitmap bm,
float step) {
this.paint = paint;

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

this.bm = bm;
bm_offsetX = bm.getWidth()/2;
bm_offsetY = bm.getHeight()/2;

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

matrix = new Matrix();
}
}


Create our custom view, AnimationView .java.
package com.blogspot.android_er.androidmovingbitmapalongpath;

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

import java.util.ArrayList;
import java.util.List;

public class AnimationView extends View {

List<AnimationThing> animationThingsList;
public AnimationView(Context context) {
super(context);
initAnimationView();
}

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

public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAnimationView();
}

private void initAnimationView(){
animationThingsList = new ArrayList<>();
}

public void insertThing(AnimationThing thing){
animationThingsList.add(thing);
}

@Override
protected void onDraw(Canvas canvas) {
for (AnimationThing thing : animationThingsList){

//!!! Only the path of the last thing will be drawn on screen
canvas.drawPath(thing.animPath, thing.paint);

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

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

canvas.drawBitmap(thing.bm, thing.matrix, null);

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

invalidate();
}
}


Layout, activity_main.xml.
<?xml version="1.0" encoding="utf-8"?>
<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:padding="16dp"
android:orientation="vertical"
tools:context="com.blogspot.android_er.androidmovingbitmapalongpath.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.blogspot.android_er.androidmovingbitmapalongpath.AnimationView
android:id="@+id/MyAnimationView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F0F0F0"/>
</LinearLayout>


MainActivity.java
package com.blogspot.android_er.androidmovingbitmapalongpath;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

AnimationView myAnimationView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myAnimationView = (AnimationView)findViewById(R.id.MyAnimationView);

prepareThings();
}

private void prepareThings(){
Paint paint;
Path animPath;
float step;
Bitmap bm;

paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);

bm = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);

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

step = 1;

AnimationThing thing = new AnimationThing(paint, animPath, bm, step);
myAnimationView.insertThing(thing);

//The second thing
bm = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_add);

animPath.reset();
animPath.addCircle(400, 400, 300, Path.Direction.CW);
step = 3;
thing = new AnimationThing(paint, animPath, bm, step);
myAnimationView.insertThing(thing);
}
}



download filesDownload the files .

Next:
Custom view to draw bitmap along path, calculate in background thread

Implement custom View to display Animated GIF

It's a improved version of the example "Play animated GIF using android.graphics.Movie, with Movie.decodeStream(InputStream)".


- Add function of Run/Stop, and Repeat setting
- Instead of disable hardware acceleration in AndroidManifest.xml, the custom AnimatedGifView disable hardware acceleration by calling setLayerType(), for API Level 11.

Our custom view, AnimatedGifView.gif
package com.example.androidgif;

import java.io.InputStream;

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

public class AnimatedGifView extends View {

private InputStream gifInputStream;
private Movie gifMovie;
private int movieWidth, movieHeight;
private long movieDuration;
private long movieRunDuration;
private long lastTick;
private long nowTick;

private boolean repeat = true;
private boolean running = true;

public void setRepeat(boolean r) {
repeat = r;
}

public void setRunning(boolean r) {
running = r;
}

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

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

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

private void init(Context context) {

// Turn OFF hardware acceleration
// API Level 11
setLayerType(View.LAYER_TYPE_SOFTWARE, null);

setFocusable(true);
gifInputStream = context.getResources().openRawResource(
R.drawable.android_er);

gifMovie = Movie.decodeStream(gifInputStream);
movieWidth = gifMovie.width();
movieHeight = gifMovie.height();
movieDuration = gifMovie.duration();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(movieWidth, movieHeight);
}

public int getMovieWidth() {
return movieWidth;
}

public int getMovieHeight() {
return movieHeight;
}

public long getMovieDuration() {
return movieDuration;
}

@Override
protected void onDraw(Canvas canvas) {

if(gifMovie == null){
return;
}

nowTick = android.os.SystemClock.uptimeMillis();
if (lastTick == 0) { // first time
movieRunDuration = 0;
}else{
if(running){
movieRunDuration += nowTick-lastTick;
if(movieRunDuration > movieDuration){
if(repeat){
movieRunDuration = 0;
}else{
movieRunDuration = movieDuration;
}
}
}
}

gifMovie.setTime((int)movieRunDuration);
gifMovie.draw(canvas, 0, 0);

lastTick = nowTick;
invalidate();

}
}

MainActivity.java
package com.example.androidgif;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.ToggleButton;

public class MainActivity extends ActionBarActivity {

TextView textViewInfo;
AnimatedGifView gifView;

CheckBox cbRepeat;
ToggleButton tbRun;

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

gifView = (AnimatedGifView) findViewById(R.id.gifview);
textViewInfo = (TextView) findViewById(R.id.textinfo);

String stringInfo = "";
stringInfo += "Duration: " + gifView.getMovieDuration() + "\n";
stringInfo += "W x H: " + gifView.getMovieWidth() + " x "
+ gifView.getMovieHeight() + "\n";

textViewInfo.setText(stringInfo);

cbRepeat = (CheckBox)findViewById(R.id.repeat);
cbRepeat.setOnCheckedChangeListener(new OnCheckedChangeListener(){

@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
gifView.setRepeat(isChecked);
}});

tbRun = (ToggleButton)findViewById(R.id.run);
tbRun.setOnCheckedChangeListener(new OnCheckedChangeListener(){

@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
gifView.setRunning(isChecked);
}});
}

}

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"
tools:context="com.example.androidgif.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" />

<CheckBox
android:id="@+id/repeat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Repeat"
android:checked="true"/>
<ToggleButton
android:id="@+id/run"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOn="Stop"
android:textOff="Run"
android:checked="true"/>

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

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/android_er"
/>

<com.example.androidgif.AnimatedGifView
android:id="@+id/gifview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

<TextView
android:id="@+id/textinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="info..." />

</LinearLayout>



download filesDownload the files.