Open In App

How to Build a Simple Android App for Breathing Exercise?

Last Updated : 09 Feb, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

Android mobiles are very handy, and they provide the information instantaneously. Just assume that you are continuously flying or attending patients or participating in meetings etc., very often. During those times, there are possibilities of getting stressed and hence meditation is very much essential in this fast running lives. Here providing the source code for the breathing exercise app and surely that will be helpful to get more energetic and enthusiastic. A sample GIF is given below to get an idea about what we are going to do in this article. Note that we are going to implement this project using the Java language. 

Build a Simple Android App for Breathing Exercise Sample GIF

Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Java as the programming language.

Step 2: Before going to the coding section first you have to do some pre-task

Go to the app > res > values > colors.xml file and set the colors for your app.

XML




<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimaryLight">#5db839</color>
    <color name="colorPrimary">#0F9D58</color>
    <color name="colorPrimaryDark">#0F9D58</color>
    <color name="colorAccent">#FF4081</color>
</resources>


Go to the Gradle Scripts > build.gradle (Module: app) file and import the following dependencies and click the “Sync Now” on the above pop up.

compile fileTree(dir: ‘libs’, include: [‘*.jar’])

   androidTestCompile(‘com.android.support.test.espresso:espresso-core:2.2.2’, {

       exclude group: ‘com.android.support’, module: ‘support-annotations’

   })

Below is the complete code for the build.gradle (Module: app) file:

Java




apply plugin: 'com.android.application'
  
android {
    compileSdkVersion 27
    buildToolsVersion "27.0.3"
    defaultConfig {
        applicationId "com.example.breath"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
  
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:27.1.0'
    compile 'com.android.support:design:27.1.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
}


Step 3: Designing the UI Part

Working with the activity_main.xml file:

Go to the activity_main.xml file and refer to the following code. Below is the code for the activity_main.xml file.

XML




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
  
    <include layout="@layout/content_main" />
  
</LinearLayout>


Here, content_main.xml got included. Below is the code for it:

XML




<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/lt_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.example.breath.view.MainActivity">
  
    <View
        android:id="@+id/view_circle_outer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_marginRight="@dimen/activity_horizontal_margin"
        android:background="@drawable/bg_circle_outer"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
  
    <View
        android:id="@+id/view_circle_inner"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:background="@drawable/bg_circle_inner"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.501" />
  
    <TextView
        android:id="@+id/txt_status"
        android:layout_width="50dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="HOLD"
        android:textColor="#4dd1b2"
        android:textSize="@dimen/hold_text_size"
        app:layout_constraintBottom_toBottomOf="@+id/view_circle_inner"
        app:layout_constraintLeft_toLeftOf="@+id/view_circle_inner"
        app:layout_constraintRight_toRightOf="@+id/view_circle_inner"
        app:layout_constraintTop_toTopOf="@+id/view_circle_inner" />
  
</android.support.constraint.ConstraintLayout>


You can see in above code as “bg_circle_inner” and “bg_circle_outer“. That we need to create (Go to the drawable > right-click > New > Drawable Resource File) in the drawable folder.

Below is the code for the bg_circle_inner.xml file:

XML




<?xml version="1.0" encoding="utf-8"?>
<shape
    android:shape="oval">
    <solid android:color="@android:color/white" />
</shape>


Below is the code for the bg_circle_outer.xml file:

XML




<?xml version="1.0" encoding="utf-8"?>
<shape 
    android:shape="oval">
  
    <gradient
        android:angle="90"
        android:endColor="@color/colorPrimaryDark"
        android:startColor="@color/colorPrimaryLight" />
  
</shape>


Basically, bg_circle_outer.xml and bg_circle_inner.xml are the XML files that create oval shape and they are used in content_main.xml. They meant to show the “inhale” and “exhale” purpose.

Step 4: Working with the Java file

Working with the MainActivity.java file:

Go to the MainActivity.java file and refer to the following code. Below is the code for the MainActivity.java file. 

Java




import android.os.Bundle;
import android.os.Handler;
import android.support.constraint.ConstraintLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
  
import com.example.breath.R;
import com.example.breath.utils.Constants;
import com.example.breath.utils.SettingsUtils;
  
public class MainActivity extends AppCompatActivity implements SettingsDialog.SettingsChangeListener {
  
    private static final String TAG = MainActivity.class.getSimpleName();
  
    private ConstraintLayout contentLayout;
    private TextView statusText;
    private View outerCircleView, innerCircleView;
    private FloatingActionButton fab;
  
    private Animation animationInhaleText, animationExhaleText, animationInhaleInnerCircle, animationExhaleInnerCircle;
    private Handler handler = new Handler();
  
    private int holdDuration = 0;
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
  
        contentLayout = findViewById(R.id.lt_content);
  
        statusText = findViewById(R.id.txt_status);
        statusText.setText(Constants.INHALE);
  
        outerCircleView = findViewById(R.id.view_circle_outer);
        innerCircleView = findViewById(R.id.view_circle_inner);
  
        setupBackgroundColor();
  
        prepareAnimations();
        statusText.startAnimation(animationInhaleText);
        innerCircleView.startAnimation(animationInhaleInnerCircle);
    }
  
    private void setupBackgroundColor() {
        int backgroundResId = SettingsUtils.getBackgroundByPresetPosition(SettingsUtils.getSelectedPreset());
        setOuterCircleBackground(R.color.colorPrimaryDark);
    }
  
    private void setOuterCircleBackground(int backgroundResId) {
        outerCircleView.setBackgroundResource(backgroundResId);
    }
  
    private void setInhaleDuration(int duration) {
        animationInhaleText.setDuration(duration);
        animationInhaleInnerCircle.setDuration(duration);
    }
  
    private void setExhaleDuration(int duration) {
        animationExhaleText.setDuration(duration);
        animationExhaleInnerCircle.setDuration(duration);
    }
  
    private void prepareAnimations() {
        int inhaleDuration = SettingsUtils.getSelectedInhaleDuration();
        int exhaleDuration = SettingsUtils.getSelectedExhaleDuration();
        holdDuration = SettingsUtils.getSelectedHoldDuration();
  
        // Inhale - make large
        animationInhaleText = AnimationUtils.loadAnimation(this, R.anim.anim_text_inhale);
        animationInhaleText.setFillAfter(true);
        animationInhaleText.setAnimationListener(inhaleAnimationListener);
  
        animationInhaleInnerCircle = AnimationUtils.loadAnimation(this, R.anim.anim_inner_circle_inhale);
        animationInhaleInnerCircle.setFillAfter(true);
        animationInhaleInnerCircle.setAnimationListener(inhaleAnimationListener);
  
        setInhaleDuration(inhaleDuration);
  
        // Exhale - make small
        animationExhaleText = AnimationUtils.loadAnimation(this, R.anim.anim_text_exhale);
        animationExhaleText.setFillAfter(true);
        animationExhaleText.setAnimationListener(exhaleAnimationListener);
  
        animationExhaleInnerCircle = AnimationUtils.loadAnimation(this, R.anim.anim_inner_circle_exhale);
        animationExhaleInnerCircle.setFillAfter(true);
        animationExhaleInnerCircle.setAnimationListener(exhaleAnimationListener);
  
        setExhaleDuration(exhaleDuration);
  
    }
  
    private Animation.AnimationListener inhaleAnimationListener = new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
  
        }
  
        @Override
        public void onAnimationEnd(Animation animation) {
            Log.d(TAG, "inhale animation end");
            statusText.setText(Constants.HOLD);
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    statusText.setText(Constants.EXHALE);
                    statusText.startAnimation(animationExhaleText);
                    innerCircleView.startAnimation(animationExhaleInnerCircle);
                }
            }, holdDuration);
        }
  
        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    };
  
    private Animation.AnimationListener exhaleAnimationListener = new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
        }
  
        @Override
        public void onAnimationEnd(Animation animation) {
            Log.d(TAG, "exhale animation end");
            statusText.setText(Constants.HOLD);
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    statusText.setText(Constants.INHALE);
                    statusText.startAnimation(animationInhaleText);
                    innerCircleView.startAnimation(animationInhaleInnerCircle);
                }
            }, holdDuration);
        }
  
        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    };
  
    @Override
    public void onPresetChanged(int backgroundResId) {
        setOuterCircleBackground(backgroundResId);
    }
  
    @Override
    public void onInhaleValueChanged(int duration) {
        setInhaleDuration(duration);
    }
  
    @Override
    public void onExhaleValueChanged(int duration) {
        setExhaleDuration(duration);
    }
  
    @Override
    public void onHoldValueChanged(int duration) {
        holdDuration = duration;
    }
}


Create a new java class and name the file as SettingsUtils. Below is the code for the SettingsUtils.java class file. 

Java




public class SettingsUtils {
  
    public static int getBackgroundByPresetPosition(int position) {
        Preset preset = Preset.values()[position];
        return preset.getResId();
    }
  
    public static void saveSelectedPreset(int presetIndex) {
        BreathePreferences.getInstance().putInt(BreathePreferences.SELECTED_PRESET_KEY, presetIndex);
    }
  
    public static int getSelectedPreset() {
        int preset = BreathePreferences.getInstance().getInt(BreathePreferences.SELECTED_PRESET_KEY);
        return preset != -1 ? preset : Constants.DEFAULT_PRESET_INDEX;
    }
  
    public static void saveSelectedInhaleDuration(int duration) {
        BreathePreferences.getInstance().putInt(BreathePreferences.SELECTED_INHALE_DURATION_KEY, duration);
    }
  
    public static int getSelectedInhaleDuration() {
        int duration = BreathePreferences.getInstance().getInt(BreathePreferences.SELECTED_INHALE_DURATION_KEY);
        return duration != -1 ? duration : Constants.DEFAULT_DURATION;
    }
  
    public static void saveSelectedExhaleDuration(int duration) {
        BreathePreferences.getInstance().putInt(BreathePreferences.SELECTED_EXHALE_DURATION_KEY, duration);
    }
  
    public static int getSelectedExhaleDuration() {
        int duration = BreathePreferences.getInstance().getInt(BreathePreferences.SELECTED_EXHALE_DURATION_KEY);
        return duration != -1 ? duration : Constants.DEFAULT_DURATION;
    }
  
    public static void saveSelectedHoldDuration(int duration) {
        BreathePreferences.getInstance().putInt(BreathePreferences.SELECTED_HOLD_DURATION_KEY, duration);
    }
  
    public static int getSelectedHoldDuration() {
        int duration = BreathePreferences.getInstance().getInt(BreathePreferences.SELECTED_HOLD_DURATION_KEY);
        return duration != -1 ? duration : Constants.DEFAULT_DURATION;
    }
}


Create a new java class and name the file as BreathePreferences. Below is the code for the BreathePreferences.java class file. 

Java




import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
  
public class BreathePreferences {
  
    static final String SELECTED_PRESET_KEY = "selectedPreset";
    static final String SELECTED_INHALE_DURATION_KEY = "selectedInhaleDuration";
    static final String SELECTED_EXHALE_DURATION_KEY = "selectedExhaleDuration";
    static final String SELECTED_HOLD_DURATION_KEY = "selectedHoldDuration";
  
    private static final String BREATHE_PREFS = "BreathePreferences";
  
    private static BreathePreferences instance;
  
    private SharedPreferences prefs;
  
    private BreathePreferences(@NonNull Context context) {
        prefs = context.getSharedPreferences(BREATHE_PREFS, Context.MODE_PRIVATE);
    }
  
    public static void init(@NonNull Context context) {
        if (instance == null) {
            instance = new BreathePreferences(context);
        }
    }
  
    public static BreathePreferences getInstance() {
        if (instance == null) {
            Log.e(BreathePreferences.class.getSimpleName(), "Call init() first");
        }
  
        return instance;
    }
  
    public void putString(@NonNull String key, @NonNull String value) {
        prefs.edit().putString(key, value).apply();
    }
  
    public void putLong(@NonNull String key, long value) {
        prefs.edit().putLong(key, value).apply();
    }
  
    public void putInt(@NonNull String key, int value) {
        prefs.edit().putInt(key, value).apply();
    }
  
    public void putFloat(@NonNull String key, float value) {
        prefs.edit().putFloat(key, value).apply();
    }
  
    @Nullable
    public String getString(@NonNull String key) {
        return prefs.getString(key, null);
    }
  
    public long getLong(@NonNull String key) {
        return prefs.getLong(key, -1);
    }
  
    public int getInt(@NonNull String key) {
        return prefs.getInt(key, -1);
    }
  
    public float getFloat(@NonNull String key) {
        return prefs.getFloat(key, -1);
    }
  
    public void clearAll() {
        prefs.edit().clear().apply();
    }
}


Create a new java class and name the file as Constants. Below is the code for the Constants.java class file. 

Java




public class Constants {
  
    // Texts to show inside the breathing circle
    public static final String INHALE = "INHALE";
    public static final String EXHALE = "EXHALE";
    public static final String HOLD = "HOLD";
  
    // FAB button visibility delay
    public static int CONTENT_SHOW_DELAY_MS = 2000;
  
    // Defaults for @{@link SettingsUtils}
    public static final int DEFAULT_PRESET_INDEX = 0;
    public static final int DEFAULT_DURATION = 6000;
      
    // Value used to convert between animation
    // duration and seekbar unit
    public static final int MILLISECOND = 2000;
}


Create a new java Enum and name the file as Preset. Below is the code for the Preset.java class file. 

Java




import com.example.breath.R;
  
public enum Preset {
  
    WARM_FLAME(0, R.drawable.bg_circle_preset_warm_flame),
    NIGHT_FADE(1, R.drawable.bg_circle_preset_night_fade),
    WINTER_NEVA(2, R.drawable.bg_circle_preset_winter_neva),
    MORNING_SALAD(3, R.drawable.bg_circle_outer),
    SOFT_GRASS(4, R.drawable.bg_circle_preset_soft_grass);
  
    private final int settingsPosition;
    private final int resId;
  
    Preset(int settingsPosition, int resId) {
        this.settingsPosition = settingsPosition;
        this.resId = resId;
    }
  
    public int getResId() {
        return resId;
    }
  
}


Create a new java class and name the file as SettingsDialog. Below is the code for the SettingsDialog.java class file. 

Java




import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.view.View;
import android.widget.Button;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;
  
import com.example.breath.R;
import com.example.breath.utils.Constants;
import com.example.breath.utils.Preset;
import com.example.breath.utils.SettingsUtils;
  
public class SettingsDialog extends Dialog {
  
    private SettingsChangeListener listener;
  
    private RadioGroup radioGroup;
  
    public SettingsDialog(@NonNull Context context, @NonNull SettingsChangeListener listener) {
        super(context, R.style.Theme_SettingsDialog);
        this.listener = listener;
    }
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.v_overlay);
        getWindow().getAttributes().windowAnimations = R.style.Theme_SettingsDialog;
  
        radioGroup = findViewById(R.id.rg_gradients);
        SeekBar inhaleSeekBar = findViewById(R.id.seekBar_inhale);
        SeekBar exhaleSeekBar = findViewById(R.id.seekBar_exhale);
        SeekBar holdSeekBar = findViewById(R.id.seekBar_hold);
        Button closeButton = findViewById(R.id.btn_close);
  
        radioGroup.setOnCheckedChangeListener(checkedChangeListener);
        inhaleSeekBar.setOnSeekBarChangeListener(inhaleSeekBarChangeListener);
        exhaleSeekBar.setOnSeekBarChangeListener(exhaleSeekBarChangeListener);
        holdSeekBar.setOnSeekBarChangeListener(holdSeekBarChangeListener);
        closeButton.setOnClickListener(closeBtnClickListener);
  
        ((RadioButton) radioGroup.getChildAt(SettingsUtils.getSelectedPreset())).setChecked(true);
        inhaleSeekBar.setProgress(SettingsUtils.getSelectedInhaleDuration() / Constants.MILLISECOND);
        exhaleSeekBar.setProgress(SettingsUtils.getSelectedExhaleDuration() / Constants.MILLISECOND);
        holdSeekBar.setProgress(SettingsUtils.getSelectedHoldDuration() / Constants.MILLISECOND);
    }
  
    private RadioGroup.OnCheckedChangeListener checkedChangeListener = new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
            Preset selectedPreset;
            switch (checkedId) {
                case R.id.rb_1:
                    selectedPreset = Preset.WARM_FLAME;
                    break;
                case R.id.rb_2:
                    selectedPreset = Preset.NIGHT_FADE;
                    break;
                case R.id.rb_3:
                    selectedPreset = Preset.WINTER_NEVA;
                    break;
                case R.id.rb_4:
                    selectedPreset = Preset.MORNING_SALAD;
                    break;
                case R.id.rb_5:
                    selectedPreset = Preset.SOFT_GRASS;
                    break;
                default:
                    selectedPreset = Preset.WARM_FLAME;
                    break;
            }
            SettingsUtils.saveSelectedPreset(selectedPreset.ordinal());
            listener.onPresetChanged(selectedPreset.getResId());
        }
    };
  
    private SeekBar.OnSeekBarChangeListener inhaleSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            listener.onInhaleValueChanged(progress != 0 ? progress * Constants.MILLISECOND : Constants.MILLISECOND);
            SettingsUtils.saveSelectedInhaleDuration(progress != 0 ? progress * Constants.MILLISECOND :
                    Constants.MILLISECOND);
        }
  
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }
  
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    };
  
    private SeekBar.OnSeekBarChangeListener exhaleSeekBarChangeListener = new SeekBar
            .OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            listener.onExhaleValueChanged(progress != 0 ? progress * Constants.MILLISECOND : Constants.MILLISECOND);
            SettingsUtils.saveSelectedExhaleDuration(progress != 0 ? progress * Constants.MILLISECOND :
                    Constants.MILLISECOND);
        }
  
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }
  
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    };
  
    private SeekBar.OnSeekBarChangeListener holdSeekBarChangeListener = new SeekBar
            .OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            listener.onHoldValueChanged(progress * Constants.MILLISECOND);
            SettingsUtils.saveSelectedHoldDuration(progress * Constants.MILLISECOND);
        }
  
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }
  
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    };
  
    private View.OnClickListener closeBtnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            dismiss();
        }
    };
  
    public interface SettingsChangeListener {
        void onPresetChanged(int backgroundResId);
  
        void onInhaleValueChanged(int duration);
  
        void onExhaleValueChanged(int duration);
  
        void onHoldValueChanged(int duration);
    }
}


On execution of code, we can able to get the output as shown in the attached video.

Output:

Github Link: https://github.com/raj123raj/meditation



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads