Showing posts with label animation. Show all posts
Showing posts with label animation. Show all posts

Wednesday, August 10, 2016

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 .

Tuesday, August 9, 2016

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

Monday, October 19, 2015

Interpolator effect on ObjectAnimator


This post demo various Interpolator effect on ObjectAnimator. You can also download the demo APK, from the link on the bottom.


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

import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.os.Bundle;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.CycleInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    LinearLayout playGround;
    ImageView image;

    AccelerateDecelerateInterpolator accelerateDecelerateInterpolator;
    AccelerateInterpolator accelerateInterpolator;
    AnticipateInterpolator anticipateInterpolator;
    AnticipateOvershootInterpolator anticipateOvershootInterpolator;
    BounceInterpolator bounceInterpolator;
    CycleInterpolator cycleInterpolator;
    DecelerateInterpolator decelerateInterpolator;
    FastOutLinearInInterpolator fastOutLinearInInterpolator;
    FastOutSlowInInterpolator fastOutSlowInInterpolator;
    LinearInterpolator linearInterpolator;
    LinearOutSlowInInterpolator linearOutSlowInInterpolator;
    OvershootInterpolator overshootInterpolator;

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

        playGround = (LinearLayout)findViewById(R.id.playground);
        image = (ImageView)findViewById(R.id.image);
        image.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "Clicked!", Toast.LENGTH_SHORT).show();
            }
        });

        Button btnNull = (Button)findViewById(R.id.bNull);
        btnNull.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(null);
            }
        });

        Button btnAccelerateDecelerateInterpolator
                = (Button)findViewById(R.id.bAccelerateDecelerateInterpolator);
        btnAccelerateDecelerateInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(accelerateDecelerateInterpolator);
            }
        });

        Button btnAccelerateInterpolator = (Button)findViewById(R.id.bAccelerateInterpolator);
        btnAccelerateInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(accelerateInterpolator);
            }
        });

        Button btnAnticipateInterpolator = (Button)findViewById(R.id.bAnticipateInterpolator);
        btnAnticipateInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(anticipateInterpolator);
            }
        });

        Button btnAnticipateOvershootInterpolator
                = (Button)findViewById(R.id.bAnticipateOvershootInterpolator);
        btnAnticipateOvershootInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(anticipateOvershootInterpolator);
            }
        });

        Button btnBounceInterpolator = (Button)findViewById(R.id.bBounceInterpolator);
        btnBounceInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(bounceInterpolator);
            }
        });

        Button btnCycleInterpolator = (Button)findViewById(R.id.bCycleInterpolator);
        btnCycleInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(cycleInterpolator);
            }
        });

        Button btnDecelerateInterpolator = (Button)findViewById(R.id.bDecelerateInterpolator);
        btnDecelerateInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(decelerateInterpolator);
            }
        });

        Button btnFastOutLinearInInterpolator
                = (Button)findViewById(R.id.bFastOutLinearInInterpolator);
        btnFastOutLinearInInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(fastOutLinearInInterpolator);
            }
        });

        Button btnFastOutSlowInInterpolator
                = (Button)findViewById(R.id.bFastOutSlowInInterpolator);
        btnFastOutSlowInInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(fastOutSlowInInterpolator);
            }
        });

        Button btnLinearInterpolator = (Button)findViewById(R.id.bLinearInterpolator);
        btnLinearInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(linearInterpolator);
            }
        });

        Button btnOvershootInterpolator = (Button)findViewById(R.id.bOvershootInterpolator);
        btnOvershootInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(overshootInterpolator);
            }
        });

        Button btnLinearOutSlowInInterpolator
                = (Button)findViewById(R.id.bLinearOutSlowInInterpolator);
        btnLinearOutSlowInInterpolator.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimator(linearOutSlowInInterpolator);
            }
        });

        prepareInterpolator();
    }

    private void prepareInterpolator(){
        accelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
        accelerateInterpolator = new AccelerateInterpolator();
        anticipateInterpolator = new AnticipateInterpolator();
        anticipateOvershootInterpolator = new AnticipateOvershootInterpolator();
        bounceInterpolator = new BounceInterpolator();
        cycleInterpolator = new CycleInterpolator(2);
        decelerateInterpolator = new DecelerateInterpolator();
        fastOutLinearInInterpolator = new FastOutLinearInInterpolator();
        fastOutSlowInInterpolator = new FastOutSlowInInterpolator();
        linearInterpolator = new LinearInterpolator();
        linearOutSlowInInterpolator = new LinearOutSlowInInterpolator();
        overshootInterpolator = new OvershootInterpolator();
    }

    private void prepareObjectAnimator(TimeInterpolator timeInterpolator){
        //float w = (float)playGround.getWidth();
        float h = (float)playGround.getHeight();
        float propertyStart = 0f;
        float propertyEnd = -(h/2 - (float)image.getHeight()/2);
        String propertyName = "translationY";
        ObjectAnimator objectAnimator
                = ObjectAnimator.ofFloat(image, propertyName, propertyStart, propertyEnd);
        objectAnimator.setDuration(2000);
        objectAnimator.setRepeatCount(1);
        objectAnimator.setRepeatMode(ObjectAnimator.REVERSE);
        objectAnimator.setInterpolator(timeInterpolator);
        objectAnimator.start();
    }
}


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:orientation="horizontal"
    android:padding="16dp"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical">

        <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:id="@+id/playground"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:background="#E0E0E0"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/image"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:src="@mipmap/ic_launcher" />
        </LinearLayout>
    </LinearLayout>

    <ScrollView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1">

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

            <Button
                android:id="@+id/bNull"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Interpolator = null (LinearInterpolator)"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bAccelerateDecelerateInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="AccelerateDecelerateInterpolator (default)"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bAccelerateInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="AccelerateInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bAnticipateInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="AnticipateInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bAnticipateOvershootInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="AnticipateOvershootInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bBounceInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="BounceInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bCycleInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="CycleInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bDecelerateInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="DecelerateInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bFastOutLinearInInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="FastOutLinearInInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bFastOutSlowInInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="FastOutSlowInInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bLinearInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="LinearInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bLinearOutSlowInInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="LinearOutSlowInInterpolator"
                android:textAllCaps="false" />

            <Button
                android:id="@+id/bOvershootInterpolator"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="OvershootInterpolator"
                android:textAllCaps="false" />

        </LinearLayout>

    </ScrollView>

</LinearLayout>



download filesDownload the files (Android Studio Format) .

download filesDownload the demo APK.


~ Old example of Various effect of interpolator in Android Animation

Saturday, June 7, 2014

Animation follow touch path forward and backward

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


AnimationView.java
package com.example.androidanimationalongpath;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class AnimationView extends View {
 
 Paint paint;
 
 Bitmap bm;
 int bm_offsetX, bm_offsetY;
 
 Path animPath;
 PathMeasure pathMeasure;
 float pathLength;
 
 float step;   //distance each step
 float distance;  //distance moved
 float curX, curY;
  
 float curAngle;  //current angle
 float targetAngle; //target angle
 float stepAngle; //angle each step

 float[] pos;
 float[] tan;
 
 Matrix matrix;
 
 Path touchPath;
 
 static int FORWARD = 1;
 static int BACKWARD = -1;
 int direction;

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

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

 public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  initMyView();
 }
 
 public void initMyView(){
  paint = new Paint();
  paint.setColor(Color.BLUE);
  paint.setStrokeWidth(1);
  paint.setStyle(Paint.Style.STROKE);
    
  bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
  bm_offsetX = bm.getWidth()/2;
  bm_offsetY = bm.getHeight()/2;
  
  animPath = new Path();
  
  pos = new float[2];
  tan = new float[2];
  
  matrix = new Matrix();
  
  touchPath = new Path();
  
  step = 1;  //default
  stepAngle = 1; //default
  
  direction = FORWARD; //default
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if(animPath.isEmpty()){
   return;
  }
  
  canvas.drawPath(animPath, paint);
  
  matrix.reset();
  
  if((targetAngle-curAngle)>stepAngle){
   curAngle += stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else if((curAngle-targetAngle)>stepAngle){
   curAngle -= stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else{
   curAngle=targetAngle;
   
   if((direction==FORWARD && distance < pathLength)
    ||(direction==BACKWARD && distance > 0)){
    
    pathMeasure.getPosTan(distance, pos, tan);

    targetAngle = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    
    curX = pos[0]-bm_offsetX;
    curY = pos[1]-bm_offsetY;
    matrix.postTranslate(curX, curY);
    
    canvas.drawBitmap(bm, matrix, null);
    
    distance += step * direction;
    
    invalidate();
   }else{
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    matrix.postTranslate(curX, curY);
    canvas.drawBitmap(bm, matrix, null);
   }
  }

 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  
  int action = event.getAction();
  
  switch(action){
  case MotionEvent.ACTION_DOWN:
   touchPath.reset();
   touchPath.moveTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_MOVE:
   touchPath.lineTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_UP:
   touchPath.lineTo(event.getX(), event.getY());
   animPath = new Path(touchPath);
   
   pathMeasure = new PathMeasure(animPath, false);
   pathLength = pathMeasure.getLength();
   
   //step = 1;
   distance = 0;
   curX = 0;
   curY = 0;
   
   //stepAngle = 1; 
   curAngle = 0;
   targetAngle = 0;
   
   direction = FORWARD;
   
   invalidate();
   
   break;
    
  }
  
  return true;
 }
 
 public void setSpeed(int sp){
  step = sp;
  stepAngle = sp;
 }
 
 public void replayForward(){
  direction = FORWARD;
  invalidate();
 }
 
 public void replayBackward(){
  direction = BACKWARD;
  invalidate();
 }

}

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androidanimationalongpath.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        
        <Button 
            android:id="@+id/replayfor"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:text="Replay/Forward" />
        <Button 
            android:id="@+id/replayback"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:text="Replay/Backward" />
        
    </LinearLayout>

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

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

    </LinearLayout>

</LinearLayout>

MainActivity.java
package com.example.androidanimationalongpath;

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

public class MainActivity extends ActionBarActivity {
 
 AnimationView animationView;
 SeekBar speedBar;
 Button btnForward, btnbackward;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  animationView = (AnimationView)findViewById(R.id.animationview);
  speedBar = (SeekBar)findViewById(R.id.speedbar);
  speedBar.setOnSeekBarChangeListener(speedBarOnSeekBarChangeListener);
  animationView.setSpeed(speedBar.getProgress()); //set default speed
  
  btnForward = (Button)findViewById(R.id.replayfor);
  btnForward.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    animationView.replayForward();
   }});
  
  btnbackward = (Button)findViewById(R.id.replayback);
  btnbackward.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    animationView.replayBackward();
   }});
 }
 
 OnSeekBarChangeListener speedBarOnSeekBarChangeListener = 
  new OnSeekBarChangeListener(){

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

   @Override
   public void onStartTrackingTouch(SeekBar seekBar) {}

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

}


download filesDownload the files.

More example of Drawing Path on canvas of custom View.


Friday, June 6, 2014

Change speed of Animation follow touch path

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


AnimationView.java
package com.example.androidanimationalongpath;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class AnimationView extends View {
 
 Paint paint;
 
 Bitmap bm;
 int bm_offsetX, bm_offsetY;
 
 Path animPath;
 PathMeasure pathMeasure;
 float pathLength;
 
 float step;   //distance each step
 float distance;  //distance moved
 float curX, curY;
  
 float curAngle;  //current angle
 float targetAngle; //target angle
 float stepAngle; //angle each step

 float[] pos;
 float[] tan;
 
 Matrix matrix;
 
 Path touchPath;

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

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

 public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  initMyView();
 }
 
 public void initMyView(){
  paint = new Paint();
  paint.setColor(Color.BLUE);
  paint.setStrokeWidth(1);
  paint.setStyle(Paint.Style.STROKE);
    
  bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
  bm_offsetX = bm.getWidth()/2;
  bm_offsetY = bm.getHeight()/2;
  
  animPath = new Path();
  
  pos = new float[2];
  tan = new float[2];
  
  matrix = new Matrix();
  
  touchPath = new Path();
  
  step = 1;  //default
  stepAngle = 1; //default
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if(animPath.isEmpty()){
   return;
  }
  
  canvas.drawPath(animPath, paint);
  
  matrix.reset();
  
  if((targetAngle-curAngle)>stepAngle){
   curAngle += stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else if((curAngle-targetAngle)>stepAngle){
   curAngle -= stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else{
   curAngle=targetAngle;
   if(distance < pathLength){
    pathMeasure.getPosTan(distance, pos, tan);

    targetAngle = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    
    curX = pos[0]-bm_offsetX;
    curY = pos[1]-bm_offsetY;
    matrix.postTranslate(curX, curY);
    
    canvas.drawBitmap(bm, matrix, null);
    
    distance += step;
    
    invalidate();
   }else{
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    matrix.postTranslate(curX, curY);
    canvas.drawBitmap(bm, matrix, null);
   }
  }

 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  
  int action = event.getAction();
  
  switch(action){
  case MotionEvent.ACTION_DOWN:
   touchPath.reset();
   touchPath.moveTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_MOVE:
   touchPath.lineTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_UP:
   touchPath.lineTo(event.getX(), event.getY());
   animPath = new Path(touchPath);
   
   pathMeasure = new PathMeasure(animPath, false);
   pathLength = pathMeasure.getLength();
   
   //step = 1;
   distance = 0;
   curX = 0;
   curY = 0;
   
   //stepAngle = 1; 
   curAngle = 0;
   targetAngle = 0;
   
   invalidate();
   
   break;
    
  }
  
  return true;
 }
 
 public void setSpeed(int sp){
  step = sp;
  stepAngle = sp;
 }

}

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androidanimationalongpath.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

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

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

    </LinearLayout>

</LinearLayout>

MainActivity.java
package com.example.androidanimationalongpath;

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

public class MainActivity extends ActionBarActivity {
 
 AnimationView animationView;
 SeekBar speedBar;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

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

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

   @Override
   public void onStartTrackingTouch(SeekBar seekBar) {}

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

}


download filesDownload the files.

More example of Drawing Path on canvas of custom View.

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.


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

Friday, March 14, 2014

Create animation on SurfaceView in background Thread

Last post "Simple SurfaceView example" draw bitmap in surfaceCreated() callback. In this step, a customized Thread, MyThread, is implemented to draw the bitmap running across screen in background thread.


MyThread.java
package com.example.androidsurfaceview;

import android.graphics.Canvas;

public class MyThread extends Thread {
 
 MySurfaceView myView;
 private boolean running = false;

 public MyThread(MySurfaceView view) {
  myView = view;
 }
 
 public void setRunning(boolean run) {
        running = run;    
 }

 @Override
 public void run() {
  while(running){
   
   Canvas canvas = myView.getHolder().lockCanvas();
   
   if(canvas != null){
    synchronized (myView.getHolder()) {
     myView.drawSomething(canvas);
    }
    myView.getHolder().unlockCanvasAndPost(canvas);
   }
   
   try {
    sleep(30);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   
  }
 }

}

Modify MySurfaceView.java in last post.
package com.example.androidsurfaceview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MySurfaceView extends SurfaceView {
 
    private SurfaceHolder surfaceHolder;
    private Bitmap bmpIcon;
    private MyThread myThread;
    int xPos = 0;
    int yPos = 0;
    int deltaX = 5;
    int deltaY = 5;
    int iconWidth;
    int iconHeight;

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

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

 public MySurfaceView(Context context, 
   AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  init();
 }
 
 private void init(){
  
  myThread = new MyThread(this);
  
  surfaceHolder = getHolder();
  bmpIcon = BitmapFactory.decodeResource(getResources(), 
    R.drawable.ic_launcher);

  iconWidth = bmpIcon.getWidth();
  iconHeight = bmpIcon.getHeight();
  
  surfaceHolder.addCallback(new SurfaceHolder.Callback(){

   @Override
   public void surfaceCreated(SurfaceHolder holder) {
    myThread.setRunning(true);
    myThread.start();
   }

   @Override
   public void surfaceChanged(SurfaceHolder holder, 
     int format, int width, int height) {
    // TODO Auto-generated method stub
    
   }

   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
    boolean retry = true;
                myThread.setRunning(false);
                while (retry) {
                       try {
                             myThread.join();
                             retry = false;
                       } catch (InterruptedException e) {
                       }
                }
   }});
 }

 protected void drawSomething(Canvas canvas) {
  canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(bmpIcon, 
          getWidth()/2, getHeight()/2, null);
        
        xPos += deltaX;
        if(deltaX > 0){
         if(xPos >= getWidth() - iconWidth){
             deltaX *= -1;
            }
        }else{
         if(xPos <= 0){
             deltaX *= -1;
            }
        }
        
        yPos += deltaY;
        if(deltaY > 0){
         if(yPos >= getHeight() - iconHeight){
             deltaY *= -1;
            }
        }else{
         if(yPos <= 0){
             deltaY *= -1;
            }
        }

        canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(bmpIcon, 
          xPos, yPos, null);

 }

}

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

download filesDownload the files.

Next:
Draw bitmap programmatically for SurfaceView

Wednesday, December 4, 2013

Start main activity after splash screen with animation

Modify from last exercise, in this post, we will start another activity (MainActivity) after animation of AnimationDrawable.

Start MainActivity after splash screen with animation


To start another activity, simple call startActivity() with intent.
   Intent intent = new Intent(
     AndroidAnimationActivity.this, MainActivity.class);
   startActivity(intent);


Create a new MainActivity.java, it's the new activity to run after splash screen. Simple display anything now.
package com.exercise.AndroidAnimation;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ImageView;

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  ImageView image = new ImageView(this);
  image.setImageDrawable(
    getResources()
    .getDrawable(R.drawable.ic_launcher));
  setContentView(image);
 }
 
}


Modify run() method of MyTimerTask class in our splach screen activity, AndroidAnimationActivity.java in our example:
package com.exercise.AndroidAnimation;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.content.Intent;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.widget.ImageView;

public class AndroidAnimationActivity extends Activity {
    
 AnimationDrawable myAnimationDrawable;
 
 Timer timer;
 MyTimerTask myTimerTask;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ImageView myAnimation = (ImageView)findViewById(R.id.myanimation);
        myAnimationDrawable 
         = (AnimationDrawable)myAnimation.getDrawable();

        myAnimation.post(
          new Runnable(){

     @Override
     public void run() {
      myAnimationDrawable.start();
     }
          });
        
        //Calculate the total duration
        int duration = 0;
        for(int i = 0; i < myAnimationDrawable.getNumberOfFrames(); i++){
         duration += myAnimationDrawable.getDuration(i);
        }
        
        timer = new Timer();
        myTimerTask = new MyTimerTask();
        timer.schedule(myTimerTask, duration);
    }
    
    class MyTimerTask extends TimerTask {

  @Override
  public void run() {
   
   timer.cancel();
   /*
   runOnUiThread(new Runnable(){
    @Override
    public void run() {
     Toast.makeText(getApplicationContext(), 
       "Animation Stopped", 
       Toast.LENGTH_SHORT).show(); 
    }});
   */
   Intent intent = new Intent(
     AndroidAnimationActivity.this, MainActivity.class);
   startActivity(intent);
  }  
 }
}


Modify AndroidManifest.xml to add <activity> of ".MainActivity".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.exercise.AndroidAnimation"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AndroidAnimationActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
        </activity>
    </application>

</manifest>


From the demo video, it can be noted that:
- If the devie's configuration change (such as orientation) in MainActivity, it will still in MainActivity. Will not re-start the first activity.
- May be you have to special handle the BACK button depends on what you want, because of the first activity still in history stack in this simple implementation.

download filesDownload the files.

Tuesday, December 3, 2013

Handle end of animation for AnimationDrawable

I have a old exercise "Create frame animation with AnimationDrawable", which play animation repeatly without handle end of the animation. In this exercise, I will do something when the animation end.


AnimationDrawable have no any Listener for the end of animation, and also I cannot use the isRunning() method to determine the end of animation. Finally, I calculate the total duration of the animation using its getNumberOfFrames() and getDuration(). And then use Timer and TimerTask to schedule a Runnable() run when the time reached.

First of all, modify /res/anim/anim_android.xml to set android:oneshot="true", re-use the layout, main.xml, and the frame pictures. All of them can be found here.

Main code:
package com.exercise.AndroidAnimation;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.Toast;

public class AndroidAnimationActivity extends Activity {
    
 AnimationDrawable myAnimationDrawable;
 
 Timer timer;
 MyTimerTask myTimerTask;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ImageView myAnimation = (ImageView)findViewById(R.id.myanimation);
        myAnimationDrawable 
         = (AnimationDrawable)myAnimation.getDrawable();

        myAnimation.post(
          new Runnable(){

     @Override
     public void run() {
      myAnimationDrawable.start();
     }
          });
        
        //Calculate the total duration
        int duration = 0;
        for(int i = 0; i < myAnimationDrawable.getNumberOfFrames(); i++){
         duration += myAnimationDrawable.getDuration(i);
        }
        
        timer = new Timer();
        myTimerTask = new MyTimerTask();
        timer.schedule(myTimerTask, duration);
    }
    
    class MyTimerTask extends TimerTask {

  @Override
  public void run() {
   
   timer.cancel();
   runOnUiThread(new Runnable(){
    @Override
    public void run() {
     Toast.makeText(getApplicationContext(), 
       "Animation Stopped", 
       Toast.LENGTH_SHORT).show(); 
    }});
  }  
 }
}



download filesDownload the files.

Next:
Start main activity after splash screen with animation

Thursday, November 21, 2013

Bi-direction sliding ViewFlipper

Last exercise show a Example of ViewFlipper. It always slide from left to right, no matter you click previous or next button. In this exercise, we will implement bi-direction sliding. Slide to right if you click next button, slide left if you click previous button. This technique can also be applied on ViewAnimator, TextSwitcher, ImageSwitcher and ViewSwitcher.

Bi-direction sliding ViewFlipper

Create /res/anim/ folder, and create the following XMLs to define the animation.

slide_in_left.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromXDelta="-50%p" android:toXDelta="0"
            android:duration="1000"/>
 <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="1000" />
</set>


slide_in_right.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromXDelta="50%p" android:toXDelta="0"
            android:duration="1000"/>
 <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="1000" />
</set>


slide_out_left.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromXDelta="0" android:toXDelta="-50%p"
            android:duration="1000"/>
 <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
            android:duration="1000" />
</set>


slide_out_right.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromXDelta="0" android:toXDelta="50%p"
            android:duration="1000"/>
 <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
            android:duration="1000" />
</set>


Modify layout file, all child View of <ViewFlipper> have android:layout_width="match_parent".
<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=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

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

        <Button
            android:id="@+id/prev"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="previous" />

        <Button
            android:id="@+id/next"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="next" />
    </LinearLayout>

    <ViewFlipper
        android:id="@+id/viewflipper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

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

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical" >

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="- Button 2 -" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="LinearLayout 2" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical" >

            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Enter something" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="LinearLayout 3" />
        </LinearLayout>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ViewFlipper is a simple ViewAnimator that 
     will animate between two or more views that 
     have been added to it. Only one child is shown 
     at a time. If requested, can automatically 
     flip between each child at a regular interval." />
    </ViewFlipper>

</LinearLayout>


Main code;
package com.example.androidviewflipper;

import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ViewFlipper;

public class MainActivity extends Activity {

 Button buttonPrev, buttonNext;
 ViewFlipper viewFlipper;

 Animation slide_in_left, slide_out_right;
 Animation slide_in_right, slide_out_left;

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

  buttonPrev = (Button) findViewById(R.id.prev);
  buttonNext = (Button) findViewById(R.id.next);
  viewFlipper = (ViewFlipper) findViewById(R.id.viewflipper);

  slide_in_left = AnimationUtils.loadAnimation(this,
    R.anim.slide_in_left);
  slide_out_right = AnimationUtils.loadAnimation(this,
    R.anim.slide_out_right);
  
  slide_in_right = AnimationUtils.loadAnimation(this,
    R.anim.slide_in_right);
  slide_out_left = AnimationUtils.loadAnimation(this,
    R.anim.slide_out_left);
  
  //viewFlipper.setInAnimation(slide_in_left);
  //viewFlipper.setOutAnimation(slide_out_right);

  buttonPrev.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    viewFlipper.setInAnimation(slide_in_right);
    viewFlipper.setOutAnimation(slide_out_left);
    viewFlipper.showPrevious();
   }
  });

  buttonNext.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    viewFlipper.setInAnimation(slide_in_left);
    viewFlipper.setOutAnimation(slide_out_right);
    viewFlipper.showNext();
   }
  });
  ;
 }

}



Next: Using GestureDetector to detect user swipe, onFling()

Sunday, November 17, 2013

Example of ViewFlipper

android.widget.ViewFlipper is a simple ViewAnimator that will animate between two or more views that have been added to it. Only one child is shown at a time. If requested, can automatically flip between each child at a regular interval.

Example of ViewFlipper

<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=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

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

        <Button
            android:id="@+id/prev"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="previous" />

        <Button
            android:id="@+id/next"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="next" />
    </LinearLayout>

    <ViewFlipper
        android:id="@+id/viewflipper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

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

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical" >

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="- Button 2 -" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="LinearLayout 2" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical" >

            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Enter something" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="LinearLayout 3" />
        </LinearLayout>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ViewFlipper is a simple ViewAnimator that 
     will animate between two or more views that 
     have been added to it. Only one child is shown 
     at a time. If requested, can automatically 
     flip between each child at a regular interval." />
    </ViewFlipper>

</LinearLayout>

package com.example.androidviewflipper;

import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ViewFlipper;

public class MainActivity extends Activity {

 Button buttonPrev, buttonNext;
 ViewFlipper viewFlipper;

 Animation slide_in_left, slide_out_right;

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

  buttonPrev = (Button) findViewById(R.id.prev);
  buttonNext = (Button) findViewById(R.id.next);
  viewFlipper = (ViewFlipper) findViewById(R.id.viewflipper);

  slide_in_left = AnimationUtils.loadAnimation(this,
    android.R.anim.slide_in_left);
  slide_out_right = AnimationUtils.loadAnimation(this,
    android.R.anim.slide_out_right);

  viewFlipper.setInAnimation(slide_in_left);
  viewFlipper.setOutAnimation(slide_out_right);

  buttonPrev.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    viewFlipper.showPrevious();
   }
  });

  buttonNext.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    viewFlipper.showNext();
   }
  });
  ;
 }

}

Monday, November 11, 2013

Example of ViewSwitcher

android.widget.ViewSwitcher is a sub-class of ViewAnimator, switches between two views, and has a factory from which these views are created. You can either use the factory to create the views, or add them yourself.

A ViewSwitcher can only have two child views, of which only one is shown at a time. If you have more than two child views in ViewSwitch, java.lang.IllegalStateException of "Can't add more than 2 views to a ViewSwitcher" will happen.

Example of ViewSwitcher

<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=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

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

        <Button
            android:id="@+id/prev"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="previous" />

        <Button
            android:id="@+id/next"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="next" />
    </LinearLayout>

    <ViewSwitcher
        android:id="@+id/viewswitcher"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

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

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical" >

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="- Button 2 -" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="LinearLayout 2" />
        </LinearLayout>
    </ViewSwitcher>

</LinearLayout>

package com.example.androidviewswitcher;

import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ViewSwitcher;

public class MainActivity extends Activity {

 Button buttonPrev, buttonNext;
 ViewSwitcher viewSwitcher;

 Animation slide_in_left, slide_out_right;

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

  buttonPrev = (Button) findViewById(R.id.prev);
  buttonNext = (Button) findViewById(R.id.next);
  viewSwitcher = (ViewSwitcher) findViewById(R.id.viewswitcher);

  slide_in_left = AnimationUtils.loadAnimation(this,
    android.R.anim.slide_in_left);
  slide_out_right = AnimationUtils.loadAnimation(this,
    android.R.anim.slide_out_right);

  viewSwitcher.setInAnimation(slide_in_left);
  viewSwitcher.setOutAnimation(slide_out_right);

  buttonPrev.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    viewSwitcher.showPrevious();
   }
  });

  buttonNext.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    viewSwitcher.showNext();
   }
  });
  ;
 }

}


- Compare with ViewAnimator