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.
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 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