You can create a timer easily using classes like the Thread class and Handler class. Or if you want one with ready made UI then you can use a couple of libraries to inject a complete customizable timer. In this article, we want to look at how to create a timer using the aforementioned classes. We will also look at some ready made libraries to easen our work.
Questions this article helps answer
- How to create a timer easily in android.
- How to create a timer using handler.
- Best android timer libraries
Timer Examples
We will start by looking at examples of how to programmatically create a timer in android.
Eample 1: Kotlin Android - Create a timer using CoundownTimer class
This is an example written in Kotlin. A simple timer with features lik start, pause, reset is created. No special library is used. The CountDownTimer
class defined in the android.os
package is used.
Here is the demo of what is created:
Step 1: Dependencies
No special depdnency is needed for this project.
Step 2: Design Layout
The next step is to design your XML layout. In this example only a basic layout is designed. Simple widgets like textviews and buttons are what make up our timer.
activity_main.xml
Here is the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".MainActivity"
android:layout_gravity="center|top"
android:orientation="vertical">
<TextView
android:id="@+id/tv_main_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="timer"
android:textSize="30dp"
android:layout_gravity="center"
android:layout_marginTop="60dp"
android:layout_marginBottom="20dp"/>
<Button
android:onClick="on"
android:id="@+id/btn_main_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:textSize="20dp"
android:text="start"/>
<Button
android:onClick="on"
android:id="@+id/btn_main_pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:textSize="20dp"
android:text="pause"/>
<Button
android:onClick="on"
android:id="@+id/btn_main_rest"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:textSize="20dp"
android:text="restart"/>
</LinearLayout>
Step 3: Write Code
The code is written in kotlin. For example the following function will define the Timer format:
fun setTextTimer() {
var m = (timer / 1000) / 60
var s = (timer / 1000) % 60
var format = String.format("%02d:%02d", m, s)
tv_main_timer.setText(format)
}
This function on the other hand will reset the timer:
This other function will pause the timer:
While this function will start the timer:
private fun startTimer() {
countDownTimer = object : CountDownTimer(timer,1000){
// end of timer
override fun onFinish() {
Toast.makeText(this@MainActivity,"end timer",Toast.LENGTH_SHORT).show()
}
override fun onTick(millisUntilFinished: Long) {
timer = millisUntilFinished
setTextTimer()
}
}.start()
}
Here is the full code:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.CountDownTimer
import android.view.View
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
val start = 600_000L
var timer = start
lateinit var countDownTimer: CountDownTimer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// yhe first value
setTextTimer()
}
fun on(view: View) {
when(view.id){
R.id.btn_main_start -> startTimer()
R.id.btn_main_pause -> pauseTimer()
R.id.btn_main_rest -> restTimer()
}
}
// btn start
private fun startTimer() {
countDownTimer = object : CountDownTimer(timer,1000){
// end of timer
override fun onFinish() {
Toast.makeText(this@MainActivity,"end timer",Toast.LENGTH_SHORT).show()
}
override fun onTick(millisUntilFinished: Long) {
timer = millisUntilFinished
setTextTimer()
}
}.start()
}
// btn pause
private fun pauseTimer() {
countDownTimer.cancel()
}
// btn restart
private fun restTimer() {
countDownTimer.cancel()
timer = start
setTextTimer()
}
// timer format
fun setTextTimer() {
var m = (timer / 1000) / 60
var s = (timer / 1000) % 60
var format = String.format("%02d:%02d", m, s)
tv_main_timer.setText(format)
}
}
Run
Finally run the project.
Reference
Here is the code reference
No. | Link |
---|---|
1. | Download code |
2. | Browse code |
3. | Follow code author |
Example 2: Create a timer using the Handler class
Let's look at how to create a timer using Handler class and textViews.
Step 1: Dependencies
No special dependency is needed.
Step 2: Create Layout
That layout will have a bunch of textviews to show various sections of our timer:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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.parag.handlers.MainActivity">
<TextView
android:id="@+id/txt_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="10dp"
android:layout_centerHorizontal="true"
android:textStyle="bold"
android:textSize="20sp"
/>
<LinearLayout
android:layout_below="@+id/txt_view"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_start"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Start"
android:layout_margin="10dp"
style="?android:attr/buttonBarButtonStyle"
/>
<Button
android:id="@+id/btn_stop"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="stop"
android:layout_margin="10dp"
style="?android:attr/buttonBarButtonStyle"
/>
</LinearLayout>
</RelativeLayout>
Step 3: Write Code
Next write java code;
package com.parag.handlers;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends AppCompatActivity {
Button btnStart, btnStop;
TextView textView;
int counter = 0;
Timer timer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStart = (Button)findViewById(R.id.btn_start);
btnStop = (Button)findViewById(R.id.btn_stop);
textView = (TextView)findViewById(R.id.txt_view);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
counter += 1;
textView.post(new Runnable() {
@Override
public void run() {
textView.setText(""+counter);
}
});
}
};
timer.schedule(timerTask, 1000L, 1000L);
}
});
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
timer.cancel();
counter = 0;
textView.setText("0");
}
});
}
}
Run
Copy the above code and run it in android studio. Or download it form here. The code is written by this author.
Example 3: How to create a recyclerveiw with multiple CountDownTimers
This example has been created by the author as a solution for appropriately handling multiple countdown timers in a recyclerview in android. It is a solution that has been inspired by several questions in StackOverflow.Below are some of those questions;
Recyclerview with multiple countdown timers causes flickering
Multiple count down timers in RecyclerView flickering when scrolled
How to handle multiple countdown timers in RecyclerView?
and Handling countdown timers in recyclerview - android
Demo
Here are the demo screenshots of the final project:
Step 1: Create Project
Create an empty project in android studio.
Step 2: Dependencies
In this project butterknife is used for view binding. However this is not mandatory to the concept being taught and the library can be replaced or removed.
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
Step 3: Design Layouts
The two layouts needed are the recyclerview item layout and the main activity layout. The recyclerview item layout will contain an imageview and a textview next to it:
recycler_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="@+id/bck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
android:visibility="gone"></ImageView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/timestamp"
android:layout_centerInParent="true"
android:text="10"/>
</RelativeLayout>
The the main activity layout will contain the recyclerview.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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">
<!-- <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/timestamp"
android:text="nodata"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/timestamp_one"
android:layout_below="@id/timestamp"
android:text="nodata"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/timestamp_two"
android:layout_below="@id/timestamp_one"
android:text="nodata"/>-->
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"></android.support.v7.widget.RecyclerView>
</RelativeLayout>
Step 4: Create a Custom Runnable
Create a public class that implements the Runnable
interface:
Define the instance properties of this class:
public long millisUntilFinished = 40000;
public TextView holder;
Handler handler;
ImageView imageView;
Define the constructor of this class, making it receive parameters values and assign them to the above instance properties:
public CustomRunnable(Handler handler,TextView holder,long millisUntilFinished,ImageView imageView) {
this.handler = handler;
this.holder = holder;
this.millisUntilFinished = millisUntilFinished;
this.imageView = imageView;
}
Override the run()
method and construct a timer with seconds,minutes, hours and days, and set the resultant string to the textview:
@Override
public void run() {
/* do what you need to do */
long seconds = millisUntilFinished / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
String time = days+" "+"days" +" :" +hours % 24 + ":" + minutes % 60 + ":" + seconds % 60;
holder.setText(time);
millisUntilFinished -= 1000;
Log.d("DEV123",time);
imageView.setX(imageView.getX()+seconds);
/* and here comes the "trick" */
handler.postDelayed(this, 1000);
}
}
Here is the full code:
CustomRunnable.java
package com.mani.rc;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
public class CustomRunnable implements Runnable {
public long millisUntilFinished = 40000;
public TextView holder;
Handler handler;
ImageView imageView;
public CustomRunnable(Handler handler,TextView holder,long millisUntilFinished,ImageView imageView) {
this.handler = handler;
this.holder = holder;
this.millisUntilFinished = millisUntilFinished;
this.imageView = imageView;
}
@Override
public void run() {
/* do what you need to do */
long seconds = millisUntilFinished / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
String time = days+" "+"days" +" :" +hours % 24 + ":" + minutes % 60 + ":" + seconds % 60;
holder.setText(time);
millisUntilFinished -= 1000;
Log.d("DEV123",time);
imageView.setX(imageView.getX()+seconds);
/* and here comes the "trick" */
handler.postDelayed(this, 1000);
}
}
Step 5: Create a Custom Timer
It is a simple class with one Long
property:
Step 6: Create a RecyclerView Adapter
A reyclerview needs an adapter to inflate the model layout into a View
object and bind data to the inflated widgets. Here is the adapter able to handle the countdown timer;
RecyclerViewAdapter.java
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private static final String TAG = RecyclerViewAdapter.class.getSimpleName();
private Context context;
private List<CustomTimer> list;
private OnItemClickListener onItemClickListener;
private Handler handler = new Handler();
public RecyclerViewAdapter(Context context, List<CustomTimer> list,
OnItemClickListener onItemClickListener) {
this.context = context;
this.list = list;
this.onItemClickListener = onItemClickListener;
}
public void clearAll() {
handler.removeCallbacksAndMessages(null);
}
public class ViewHolder extends RecyclerView.ViewHolder {
// Todo Butterknife bindings
@BindView(R.id.timestamp) TextView timeStamp;
@BindView(R.id.bck) ImageView imageView;
CustomRunnable customRunnable;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
customRunnable = new CustomRunnable(handler,timeStamp,10000,imageView);
}
public void bind(final CustomTimer model, final OnItemClickListener listener) {
/*itemView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
listener.onItemClick(getLayoutPosition());
}
});*/
handler.removeCallbacks(customRunnable);
customRunnable.holder = timeStamp;
customRunnable.millisUntilFinished = 10000 * getAdapterPosition(); //Current time - received time
handler.postDelayed(customRunnable, 100);
}
}
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.recycler_item, parent, false);
ButterKnife.bind(this, view);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override public void onBindViewHolder(ViewHolder holder, int position) {
//CustomTimer item = list.get(position);
//Todo: Setup viewholder for item
holder.bind(null, onItemClickListener);
}
@Override public int getItemCount() {
return 100;//list.size();
}
public interface OnItemClickListener {
void onItemClick(int position);
}
}
Step 7: Create main activity
Finally put everything together in the only and launcher activity of this project:
MainActivity.java
public class MainActivity extends AppCompatActivity {
TextView holder,holderOne,holderTwo;
//CountDownTimer countDownTimer;
@BindView(R.id.recycler) RecyclerView recyclerView;
RecyclerViewAdapter recyclerViewAdapter;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
/* holder = (TextView) findViewById(R.id.timestamp);
holderOne = (TextView) findViewById(R.id.timestamp_one);
holderTwo = (TextView) findViewById(R.id.timestamp_two);*/
recyclerViewAdapter = new RecyclerViewAdapter(getApplicationContext(),null,null);
recyclerView.setAdapter(recyclerViewAdapter);
/*countDownTimer = new CustomTimer(10000, 500);
final CustomRunnable customRunnable = new CustomRunnable();
final CustomRunnable customRunnableOne = new CustomRunnable();
final CustomRunnable customRunnableTwo = new CustomRunnable();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
//countDownTimer.start();
handler.removeCallbacks(customRunnable);
customRunnable.holder = holder;
customRunnable.millisUntilFinished = 4000; //Current time - received time
handler.postDelayed(customRunnable, 100);
handler.removeCallbacks(customRunnableOne);
customRunnableOne.holder = holderOne;
customRunnableOne.millisUntilFinished = 50000; //Current time - received time
handler.postDelayed(customRunnableOne, 100);
handler.removeCallbacks(customRunnableTwo);
customRunnableTwo.holder = holderTwo;
customRunnableTwo.millisUntilFinished = 99000; //Current time - received time
handler.postDelayed(customRunnableTwo, 100);
}
});*/
}
@Override protected void onPause() {
super.onPause();
//countDownTimer.cancel();
/*handler.removeCallbacksAndMessages(null);
holder = null;*/
recyclerViewAdapter.clearAll();
}
/*private Handler handler = new Handler();
public class CustomRunnable implements Runnable {
public long millisUntilFinished = 40000;
public TextView holder;
@Override
public void run() {
*//* do what you need to do *//*
long seconds = millisUntilFinished / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
String time = days+" "+"days" +" :" +hours % 24 + ":" + minutes % 60 + ":" + seconds % 60;
holder.setText(time);
millisUntilFinished -= 1000;
Log.d("DEV123",time);
*//* and here comes the "trick" *//*
handler.postDelayed(this, 1000);
}
}
public class CustomTimer extends CountDownTimer{
public CustomTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
Log.d("DEV123","Creating new object");
}
public void onTick(long millisUntilFinished) {
long seconds = millisUntilFinished / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
String time = days+" "+"days" +" :" +hours % 24 + ":" + minutes % 60 + ":" + seconds % 60;
holder.setText(time);
Log.d("DEV123",time);
}
public void onFinish() {
holder.setText("Time up!");
}
}*/
}
Run
Finally run the project.
Reference
Below are code references:
Number | Link |
---|---|
1. | Download code |
2. | Follow code author |
Best timer libraries
let's now look at some third party libraries to create a timer.
(a). Solution 1: Use TickingTimer
TickingTimer is an Android Library to implement visual timer quickly, easily and effortlessly.
It's features include:
- Kotlin First
- It is Highly Customizable.
- It is lifecycle aware.
- It provides animations support.
Here's a demo:
Step 1: Install
- In the project-level
build.gradle
:
- In app-level
build.gradle
:
Step 2: How To Use?
- Add the timer in XML and specify width and height. TickingTimer currently doesn't support feature-specific XML attributes.
<com.parassidhu.tickingtimer.TickingTimer
android:id="@+id/timerView"
android:layout_width="250dp"
android:layout_height="250dp" />
- Start the timer in Java/Kotlin code:
or
Customizations
Function | Parameter(s) | Description |
---|---|---|
timerDuration() | duration (Int) | Duration for which timer should run |
backgroundTint() | color (Int?) | Colored tint for the background. If null is passed, tint is removed |
customBackground() | resource (@DrawableRes res: Int) | Custom background for the background provided as a drawable |
shape() | shape (Shape) | Set background shape from predefined: CIRCLE, ROUNDED and CIRCLE. Doesn't apply if customBackground is applied after calling this function |
textSize() | size (Int) | Timer duration text size in sp (scaled-pixels) |
textColor() | color (Int) | Timer duration text color |
textAppearance() | resId (Int) | Use for custom styling of the text. Pass the style resource Id |
onFinished() | lambda () | Lambda callback executed when timer finishes |
onTick() | lambda (Int) | Lambda executed on every second elapsed and provides remaining time |
timerAnimation() | animation (Animation) | Custom timer animation |
timerEndAnimation() | lambda (View) | Lambda exposes View and is executed on timer end. Perform end animation on the view here |
applyConfig() | config (Config?) | Pass a config object to apply above properties in one go |
cancel() | N/A | Cancels the timer. Automatically gets called on onDestroy() of the activity/fragment |
Saving Properties
To save the properties of the timer to reuse, you can use a Config object. Create config like this:
val config = TickingTimer.defaultConfig().apply {
timerDuration = 10
textSize = 50
textColor = Color.BLACK
shape = Shape.ROUNDED
customBackground = R.drawable.bg_grad_blue
}
and then apply to the timer while starting:
timerView.start(config)
Another way to use the config is to use applyConfig()
function:
timerView.start {
applyConfig(config)
shape(Shape.CIRCLE)
timerAnimation(ScaleAnimation(this@MainActivity, null))
customBackground(R.drawable.bg_grad_orange)
}
Remember that the properties defined after applyConfig()
overrides the config's properties for the specific timer.
Example
Here's a complete TickingTimer example:
MainActivity.kt
package com.parassidhu.tickingtimerapp
import android.graphics.Color
import android.os.Bundle
import android.view.animation.Animation
import android.view.animation.ScaleAnimation
import androidx.appcompat.app.AppCompatActivity
import com.parassidhu.tickingtimer.Shape
import com.parassidhu.tickingtimer.TickingTimer
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (System.currentTimeMillis() % 2L == 0L)
startView1()
else
startView2()
}
private fun startView1() {
val config = TickingTimer.defaultConfig().apply {
timerDuration = 10
textSize = 50
textColor = Color.BLACK
shape = Shape.ROUNDED
customBackground = R.drawable.bg_grad_blue
}
timerView1.start(config)
timerView2.start {
applyConfig(config)
shape(Shape.CIRCLE)
timerAnimation(ScaleAnimation(this@MainActivity, null))
customBackground(R.drawable.bg_grad_orange)
}
}
private fun startView2() {
val config = TickingTimer.defaultConfig().apply {
timerDuration = 10
textSize = 50
textColor = Color.parseColor("#70FFFFFF")
shape = Shape.ROUNDED
customBackground = R.drawable.bg_grad_green
}
timerView1.start(config)
timerView2.start {
applyConfig(config)
shape(Shape.CIRCLE)
timerAnimation(ScaleAnimation(this@MainActivity, null))
customBackground(R.drawable.bg_grad_pink)
}
}
}
activity_main.xml
And here's the layout:
<?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=".MainActivity">
<com.parassidhu.tickingtimer.TickingTimer
android:id="@+id/timerView1"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_margin="40dp"
app:layout_constraintBottom_toTopOf="@+id/timerView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<com.parassidhu.tickingtimer.TickingTimer
android:id="@+id/timerView2"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_margin="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timerView1" />
</androidx.constraintlayout.widget.ConstraintLayout>
Reference
Find complete reference here.