July 6, 2021
How to develop a Flip Coin App in Android Studio
In this tutorial, you will learn how to develop a flip coin app in Android Studio.
Download the live app from https://play.google.com/store/apps/details?id=com.zakasoft.flip
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zakasoft.flip">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.java
package com.test.test;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.preference.PreferenceManager;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
long usage;
RandomFlipNumberGenerator randomFlipNumberGenerator;
ImageButton imageButton;
TextView txtStatus, txtNumberOfTimes;
private void Init() {
randomFlipNumberGenerator.GenerateFlipCoinNumber();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().getDecorView().setBackgroundColor(Color.WHITE);
imageButton = findViewById(R.id.imageButton);
txtStatus = findViewById(R.id.txtStatus);
txtStatus.setText("");
txtNumberOfTimes = findViewById(R.id.txtNumberOfTimes);
txtNumberOfTimes.setText("");
randomFlipNumberGenerator = new RandomFlipNumberGenerator();
Init();
imageButton.performClick();
usage = GetUse();
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int curSide = R.drawable.head;
Rotate3dAnimation animation;
ArrayList<Integer> number = new ArrayList<Integer>();
for (int i = 0; i < 2; ++i) number.add(i);
Collections.shuffle(number);
boolean stayTheSame = false;
int r = number.get(0);
stayTheSame = r == 1;
ImageView coinImage = findViewById(R.id.imageButton); // The ImageView with the coin
if (curSide == R.drawable.head) {
animation = new Rotate3dAnimation(coinImage, R.drawable.head, R.drawable.tail, 0, 180, 0, 0, 0, 0);
} else {
animation = new Rotate3dAnimation(coinImage, R.drawable.tail, R.drawable.head, 0, 180, 0, 0, 0, 0);
}
if (stayTheSame) {
animation.setRepeatCount(5); // must be odd (5+1 = 6 flips so the side will stay the same)
} else {
animation.setRepeatCount(6); // must be even (6+1 = 7 flips so the side will not stay the same)
}
animation.setDuration(200);
animation.setInterpolator(new LinearInterpolator());
coinImage.startAnimation(animation);
animation.setAnimationListener(new Animation.AnimationListener(){
@Override
public void onAnimationStart(Animation arg0) {
txtStatus.setText("");
}
@Override
public void onAnimationRepeat(Animation arg0) {
}
@Override
public void onAnimationEnd(Animation arg0) {
if(r==0)
{
txtStatus.setText("Tail");
}
else
{
txtStatus.setText("Head");
}
}
});
}
});
}
private class RandomFlipNumberGenerator {
public int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int GenerateFlipCoinNumber() {
Random random = new Random();
number = random.nextInt(1);
return number;
}
}
}
package com.zakasoft.flip;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.widget.ImageView;
public class Rotate3dAnimation extends Animation {
private final float fromXDegrees;
private final float toXDegrees;
private final float fromYDegrees;
private final float toYDegrees;
private final float fromZDegrees;
private final float toZDegrees;
private Camera camera;
private int width = 0;
private int height = 0;
private ImageView imageView;
private int curDrawable;
private int nextDrawable;
private int numOfRepetition = 0;
private float repeatCount;
public Rotate3dAnimation(ImageView imageView, int curDrawable, int nextDrawable, float fromXDegrees, float toXDegrees, float fromYDegrees, float toYDegrees, float fromZDegrees, float toZDegrees) {
this.fromXDegrees = fromXDegrees;
this.toXDegrees = toXDegrees;
this.fromYDegrees = fromYDegrees;
this.toYDegrees = toYDegrees;
this.fromZDegrees = fromZDegrees;
this.toZDegrees = toZDegrees;
this.imageView = imageView;
this.curDrawable = curDrawable;
this.nextDrawable = nextDrawable;
}
@Override
public void setRepeatCount(int repeatCount){
super.setRepeatCount(repeatCount);
this.repeatCount = repeatCount+1;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
this.width = width / 2;
this.height = height / 2;
camera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float xDegrees = fromXDegrees + ((toXDegrees - fromXDegrees) * interpolatedTime);
float yDegrees = fromYDegrees + ((toYDegrees - fromYDegrees) * interpolatedTime);
float zDegrees = fromZDegrees + ((toZDegrees - fromZDegrees) * interpolatedTime);
final Matrix matrix = t.getMatrix();
// ----------------- ZOOM ----------------- //
if ((numOfRepetition + interpolatedTime) / (repeatCount/2) <= 1){
imageView.setScaleX(1 + (numOfRepetition + interpolatedTime) / (repeatCount/2));
imageView.setScaleY(1 + (numOfRepetition + interpolatedTime) / (repeatCount/2));
} else if (numOfRepetition < repeatCount){
imageView.setScaleX(3 - (numOfRepetition + interpolatedTime) / (repeatCount/2));
imageView.setScaleY(3 - (numOfRepetition + interpolatedTime) / (repeatCount/2));
}
// ----------------- ROTATE ----------------- //
System.err.println(interpolatedTime);
if (interpolatedTime >= 0.5f) {
if (interpolatedTime == 1f){
int temp = curDrawable;
curDrawable = nextDrawable;
nextDrawable = temp;
numOfRepetition++;
} else {
imageView.setImageResource(nextDrawable);
}
xDegrees -= 180f;
} else if (interpolatedTime == 0) {
imageView.setImageResource(curDrawable);
}
camera.save();
camera.rotateX(-xDegrees);
camera.rotateY(yDegrees);
camera.rotateZ(zDegrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-this.width, -this.height);
matrix.postTranslate(this.width, this.height);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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.zakasoft.flip.MainActivity">
<ImageButton
android:id="@+id/imageButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="100dp"
android:layout_marginTop="100dp"
android:layout_marginEnd="100dp"
android:layout_marginBottom="100dp"
android:background="@android:color/white"
android:scaleType="fitCenter"
app:layout_constraintBottom_toTopOf="@+id/txtStatus"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/adView"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/head" />
<TextView
android:id="@+id/txtStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="TextView"
android:textColor="@color/colorPrimaryDark"
android:textSize="30sp"
app:layout_constraintBottom_toTopOf="@+id/txtNumberOfTimes"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/txtNumberOfTimes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral() // New line
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'com.google.gms:google-services:4.3.8'
}
}
allprojects {
repositories {
google()
mavenCentral() // New line
maven { url "https://jitpack.io" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
gradle module
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
defaultConfig {
applicationId "com.test.test"
minSdkVersion 19
targetSdkVersion 30
versionCode 2
versionName "2.0"
multiDexEnabled true
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.browser:browser:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.gms:play-services-ads:20.2.0' // for admob
implementation 'androidx.appcompat:appcompat:1.3.0' // for theme
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.browser:browser:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.gms:play-services-ads:20.2.0'
implementation 'com.android.volley:volley:1.2.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}

