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' }