ValueAnimator

Animation in Android
Applying ValueAnimator we can animate any number of objects of any type at the same time using one instance of it. Also you can use not only the animated value but the fraction to be able to interpolate between other two values without creating a new instance of ValueAnimator. So great tool.

Usage:
Firstly we create an instance of ValueAnimator as

ValueAnimator animator =
     ValueAnimator.ofFloat(start value, end value);

Then we should add an update listener to it like below

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
   public void onAnimationUpdate(ValueAnimator valueAnimator) {
     float value = (float) animator.getAnimatedValue();
     //update your views here
     //do not forget to invalidate your views
   }
     });

Now we can set parameters of animator and start it in suitable place.

animator.setDuration(duration);
animator.start();

Note that we can play some animations together by creating an instance of AnimatorSet and using play() and with() functions. Also we can add an instance of AnimatorListener to provide proper functionality for events of the animation. See below example.
The following example shows how ValueAnimator is useful in animating several views each in several properties.
Example 1: Please create a new Android Studio project and use Empty template. Then edit the activity_main.xml (layout) file as shown below. Note that the background resource of buttons in this example is an SVG file which has been converted to ic_sun.xml file.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
android:id="@+id/lay1"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.msh_n.myp.valueanimator1.MainActivity">
<Button
android:id="@+id/button"
android:layout_width="70dp"
android:layout_height="70dp"
android:background="@drawable/ic_sun"
android:text=""
/>
</android.support.constraint.ConstraintLayout>

The MainActivity.java file is as follows.

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button btn;
Button[] buttons;
ConstraintLayout layout;
int whichAnimation;
int flowerX = 0;
int flowerY = 0;
double[] flowerXD = new double[5];
double[] flowerYD = new double[5];
double r = 200;
int duration = 200;
//int[] startDelay = {100, 80, 60, 80, 100};
//int[] exitDelay = {100, 80, 60, 80, 100};
int[] startDelay = {0,0,0,0,0};
int[] exitDelay = {0,0,0,0,0};
int w;
int h;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.button);
layout = (ConstraintLayout) findViewById(R.id.lay1);
// w = 1000;
// h = 1400;
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
w = layout.getMeasuredWidth();
h = layout.getMeasuredHeight();
for(int i = 0; i < 5; i++){
flowerXD[i] = w/2 + r*(Math.cos(Math.toRadians(72*(i))));
flowerYD[i] = h/2 + r*(Math.sin(Math.toRadians(72*(i))));
}
}
});
btn.setOnClickListener(this);
for(int i = 0; i < 5; i++){
flowerXD[i] = w/2 + r*(Math.cos(Math.toRadians(72*(i))));
flowerYD[i] = h/2 + r*(Math.sin(Math.toRadians(72*(i))));
}
initializeBtns();
}
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.button:
if(whichAnimation == 0){
for(Button btn: buttons){
btn.setVisibility(View.VISIBLE);
}
for(int i = 0; i < 5; i++){
playEnter(buttons[i], i);
}
whichAnimation = 1;
}else{
for(int i = 0; i < 5; i++){
playExit(buttons[i], i);
}
whichAnimation = 0;
}
}
}
private void playEnter(final Button b, final int position){
/**
* Animator that animates buttons x and y position simultaneously with size
*/
AnimatorSet buttonAnimator = new AnimatorSet();
/**
* ValueAnimator to update x position of a button
*/
int destinationX = flowerX +(int) flowerXD[position];
ValueAnimator btnAnimatorX = ValueAnimator.ofFloat(flowerX, destinationX);
btnAnimatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
b.setX((float) valueAnimator.getAnimatedValue());
b.requestLayout();
}
});
int destinationY = flowerY + (int) flowerYD[position];
ValueAnimator btnAnimatorY = ValueAnimator.ofFloat(flowerY, destinationY);
btnAnimatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
b.setY((float) valueAnimator.getAnimatedValue());
b.requestLayout();
}
});
ValueAnimator btnAnimatorSize = ValueAnimator.ofFloat(5f, 100f);
btnAnimatorSize.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float width =(float) valueAnimator.getAnimatedValue();
//b.setLayoutParams(new ConstraintLayout.LayoutParams((int) width, (int) width));
b.getLayoutParams().width = (int) width;
b.getLayoutParams().height = (int) width;
b.requestLayout();
}
});
btnAnimatorX.setDuration(duration);
btnAnimatorY.setDuration(duration);
btnAnimatorSize.setDuration(duration);
/**
* Add both x and y position update animation in
* animator set
*/
buttonAnimator.play(btnAnimatorX).with(btnAnimatorY).with(btnAnimatorSize);
buttonAnimator.setStartDelay(startDelay[position]);
buttonAnimator.start();
}
private void playExit(final Button b, int position){
/**
* Animator that animates buttons x and y position simultaneously with size
*/
AnimatorSet buttonAnimator = new AnimatorSet();
int destinationX = flowerX +(int) flowerXD[position];
ValueAnimator btnAnimatorX = ValueAnimator.ofFloat(destinationX, flowerX);
btnAnimatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
b.setX((float) valueAnimator.getAnimatedValue());
b.requestLayout();
}
});
int destinationY = flowerY + (int) flowerYD[position];
ValueAnimator btnAnimatorY = ValueAnimator.ofFloat(destinationY, flowerY);
btnAnimatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
b.setY((float) valueAnimator.getAnimatedValue());
b.requestLayout();
}
});
ValueAnimator btnAnimatorSize = ValueAnimator.ofFloat(100f, 5f);
btnAnimatorSize.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float width =(float) valueAnimator.getAnimatedValue();
//b.setLayoutParams(new ConstraintLayout.LayoutParams((int) width, (int) width));
b.getLayoutParams().width = (int) width;
b.getLayoutParams().height = (int) width;
b.requestLayout();
}
});
btnAnimatorX.setDuration(duration);
btnAnimatorY.setDuration(duration);
btnAnimatorSize.setDuration(duration);
/**
* Add both x and y position update animation in
* animator set
*/
buttonAnimator.play(btnAnimatorX).with(btnAnimatorY).with(btnAnimatorSize);
buttonAnimator.setStartDelay(exitDelay[position]);
buttonAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
for(Button btn: buttons){
btn.setVisibility(View.INVISIBLE);
}
}
@Override
public void onAnimationCancel(Animator animator) {
for(Button btn: buttons){
btn.setVisibility(View.INVISIBLE);
}
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
buttonAnimator.start();
}
public void initializeBtns(){
buttons = new Button[5];
for(int i = 0; i < buttons.length; i++){
buttons[i] = new Button(MainActivity.this);
//buttons[i].setX(flowerX);
//buttons[i].setY(flowerY);
buttons[i].setLayoutParams(new ConstraintLayout.LayoutParams(5,5));
buttons[i].setText(String.valueOf(i + 1));
buttons[i].setBackgroundResource(R.drawable.ic_sun);
buttons[i].setTextSize(20);
buttons[i].setVisibility(View.INVISIBLE);
((ConstraintLayout) findViewById(R.id.lay1)).addView(buttons[i]);
}
}
}

And it is the result!


Example 2: Here we use a custom view (something like the custom view created in custom view) and make it to play a simple animation. Please create a new Android Studio project and use Empty template and refer to custom view for details of the custom view. We should create a new java file PieView.java shown below.

public class PieView extends View {
// magnitude of rounding rectangle
int rx;
int ry;
String titleText;
int radius;
float angle;
Paint paintBackground;
Paint paintV;
Paint paintShadow;
Rect rect;
int backColor;
int viewColor;
int height =500;
int width=250;
int ori;
int actionBarHeight;
RectF rectCircle;
RectF rectOval;
public PieView(Context context){
//this(context, null, 0);
super(context);
}
public PieView(Context context, AttributeSet attrs){
//this(context, attrs, 0);
super(context, attrs);
init(context, attrs);
}
public PieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public PieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
public String getTitleText(){
return titleText;
}
public void setTitleText(String str){
titleText = str;
}
public int getRadius(){
return radius;
}
public float getAngle() {
return angle;
}
public void setAngle(float ang) {
this.angle = ang;
}
public int getRx(){
return rx;
}
public void setRx(int rx){
this.rx = rx;
}
public int getRy(){
return ry;
}
public void setRy(int ry){
this.ry = ry;
}
protected void init(Context context, @Nullable AttributeSet attrs){
angle = 5;
rx = 0;
ry = 0;
rect = new Rect();
// finding height of the action bar
TypedValue tv = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
actionBarHeight = getResources().getDimensionPixelSize(tv.resourceId);
paintBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
paintV = new Paint();
paintShadow = new Paint();
ori = ((Activity) context).getResources().getConfiguration().orientation;
if(attrs == null){
backColor = getResources().getColor(android.R.color.holo_purple);
viewColor = getResources().getColor(android.R.color.holo_green_light);
paintBackground.setColor(backColor);
paintV.setColor(viewColor);
paintShadow.setColor(getResources().getColor(android.R.color.holo_purple));
return;
}
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.PieView, 0, 0);
try {
titleText = a.getString(R.styleable.PieView_titleText);
backColor = a.getColor(R.styleable.PieView_backgroundColor,
getResources().getColor(android.R.color.holo_blue_light));
viewColor = a.getColor(R.styleable.PieView_viewColor,
getResources().getColor(android.R.color.holo_orange_dark));
} finally {
a.recycle();
}
paintBackground.setColor(backColor);
paintV.setColor(viewColor);
paintShadow.setColor(getResources().getColor(android.R.color.holo_purple));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
//height = h + actionBarHeight;
height = h;
//rectCircle = new RectF(width/2, 0, (3/4)*width, height/3);
rectCircle = new RectF(width/4, 0, 3*width/4, height/3);
rectOval = new
RectF(width/4, 0,width/2+width/3, height/3);
boolean isWidthLarger = width > height/3;
if(isWidthLarger){
radius = height/6;
}else{
radius = width/2;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int desiredWidth = 1000;
int desiredHeight = 1500;
int width;
int height;
//width
if(widthMode == MeasureSpec.EXACTLY){
//Must be this size
width = widthSize;
}else if(widthMode == MeasureSpec.AT_MOST){
//Can't be bigger than...
width = Math.min(widthSize, desiredWidth);
}else{
//Be whatever you want
width = desiredWidth;
}
//height
if(heightMode == MeasureSpec.EXACTLY){
//Must be this size
height = heightSize;
}else if(heightMode == MeasureSpec.AT_MOST){
//Can't be bigger than...
height = Math.min(heightSize, desiredHeight);
}else{
//Be whatever you want
height = desiredHeight;
}
//MUST CALL THIS
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
rect.left = 0;
rect.top = 0;
if(ori == Configuration.ORIENTATION_PORTRAIT) {
rect.bottom = height / 3;
rect.right = width;
RectF rect2 = new RectF();
rect2.left = 0;
rect2.right = width;
rect2.bottom = height;
rect2.top = 0;
canvas.drawRect(rect, paintBackground);
canvas.drawRoundRect(rectCircle, rx, ry, paintShadow);
canvas.drawArc(rectCircle, 0, angle, true, paintV);
canvas.drawCircle(width/2, height/6, radius/1.2f, paintShadow);
}else if(ori == Configuration.ORIENTATION_LANDSCAPE){
}
}
}
view raw PieView.java hosted with ❤ by GitHub

Then edit the activity_main.xml as below.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
android:animateLayoutChanges="true"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.msh_n.myp.customview1.MainActivity">
<com.msh_n.myp.customview1.PieView
android:id="@+id/pieView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toTopOf="@+id/button1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:titleText="Foreground color" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Click"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
</android.support.constraint.ConstraintLayout>

In the mainActivity.java one instance of ValueAnimator can simply do the animation properly.

public class MainActivity extends AppCompatActivity{
Button button;
Activity activity = this;
EditText edtTxt;
float newAngle;
float oldAngle;
ObjectAnimator animatorX;
ValueAnimator.AnimatorUpdateListener mListener;
int height;
int width;
int centerX;
int centerY;
int actionBarHeight;
int statusBarHeight;
int radius;
PieView pieView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button1);
pieView = (PieView) findViewById(R.id.pieView2);
final ValueAnimator animator = ValueAnimator.ofFloat(0f, 360f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) animator.getAnimatedValue();
pieView.setAngle(value);
pieView.setRx((int) value);
pieView.setRy((int) value);
pieView.invalidate();
}
});
animator.setDuration(5000);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
animator.start();
}
});
}
}

Now the result is something like below.


Comments