본문 바로가기
안드로이드 자바

[JAVA][Android] 원형 프로그레스바 타이머

by teamnova 2021. 5. 18.

안녕하세요 ~

 

오늘은 타이머를 만들어 볼건데요

 

그냥 타이머가 아니라 시간이 지남에따라, 원형 프로그레스 바가 움직이는 타이머를 만들어볼꺼에요

 

그럼 바로 시작하겠습니다 :)

 

 

스틱코드란 ?

stickode.com/mainlogin.html

 

STICKODE

 

stickode.com


 

 

 

 

 

이미지 세팅

 

우선 타이머를 만들면서 사용하게될 이미지들을 먼저 세팅해주겠습니다.

 

타이머 이미지 세팅

 

먼저 원형 프로세스 바를 만들어주기 위해 drawable에 리소스 파일을 만들어 줍니다.

 

 

보라색 원 프로세스 바

 

기본적으로 보여지는 보라색의 원형 프로세스바의 리소스 파일을 만들어 줍니다.

 

 

흰색 원 프로세스 바

해당 원 프로세스 바는 타이머의 시간이 지남에 따라 점점 나타나는 프로세스 바입니다. 이것으로 기존의 보라색 원이 시간이 지남에 따라 점점 지워지는 효과를 나타낼거에요.

 

 

다음으로 버튼 아이콘에 사용할 이미지들을 추가해주도록 하겠습니다.

 

타이머 버튼 아이콘

위 3개의 아이콘을 각각 타이머 스타트, 정지, 재시작 순으로 사용할거에요.

 

 

 

 

 

레이아웃 만들기

 

이제 본격적으로 메인 화면을 만들어 보도록 하겠습니다.

 

아래는 메인 화면과 해당 소스 코드입니다.

 

타이머 메인 화면

 

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=".MainActivity">

    <!-- 타이머 진행 바 -->
    <ProgressBar
        android:id="@+id/progressBarCircle"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="270dp"
        android:layout_height="270dp"
        android:layout_centerInParent="true"
        android:background="@drawable/drawable_circle_inner"
        android:indeterminate="false"
        android:max="100"
        android:progress="100"
        android:progressDrawable="@drawable/drawable_circle_outer"
        android:rotation="-90"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.25" />

    <EditText
        android:id="@+id/editTextMinute"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/textViewTime"
        android:layout_centerHorizontal="true"
        android:gravity="center"
        android:hint="minute"
        android:inputType="number"
        android:maxLength="15"
        android:maxLines="1"
        android:minEms="5"
        android:text="1"
        app:layout_constraintBottom_toTopOf="@+id/textViewTime"
        app:layout_constraintEnd_toEndOf="@+id/progressBarCircle"
        app:layout_constraintStart_toStartOf="@+id/progressBarCircle"
        app:layout_constraintTop_toTopOf="@+id/progressBarCircle"
        app:layout_constraintVertical_bias="0.85" />

    <TextView
        android:id="@+id/textViewTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="00:01:00"
        android:textColor="@color/black"
        android:textSize="40sp"
        app:layout_constraintBottom_toBottomOf="@+id/progressBarCircle"
        app:layout_constraintEnd_toEndOf="@+id/progressBarCircle"
        app:layout_constraintStart_toStartOf="@+id/progressBarCircle"
        app:layout_constraintTop_toTopOf="@+id/progressBarCircle" />


    <ImageView
        android:id="@+id/imageViewReset"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_below="@+id/textViewTime"
        android:layout_centerInParent="true"
        android:src="@drawable/ic_baseline_loop_24"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="@+id/progressBarCircle"
        app:layout_constraintEnd_toEndOf="@+id/progressBarCircle"
        app:layout_constraintStart_toStartOf="@+id/progressBarCircle"
        app:layout_constraintTop_toBottomOf="@+id/textViewTime"
        app:layout_constraintVertical_bias="0.45" />

    <ImageView
        android:id="@+id/imageViewStartStop"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_below="@+id/progressBarCircle"
        android:layout_centerHorizontal="true"
        android:src="@drawable/ic_baseline_play_circle_24"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressBarCircle" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

 

 

 

 

 

타이머 기능 구현

 

 

버튼으로 타이머를 컨트롤 해주기 때문에 뷰클릭 리스너를 implement 해줍니다. 그외에 타이머에서 사용할 변수들을 선언해주세요.

 

 

뷰와 리스너 초기화

 

뷰와 리스너들을 초기화해주는 기능을 넣어줍니다.

 

 

클릭 이벤트 설정

 

각 버튼을 눌렀을때, 동작할 기능들을 연결해 줍니다.

 

스틱코드를 사용해 원형 프로그레스 타이머의 기능을 추가해줍니다.

 

카운트 다운 리셋 기능 + 타이머 시작중 정지 기능
타이머 시간 설정 기능 + 카운트 다운 시간 계산 기능
타이머 정지 + 원형 프로그레스 값 설정 + 시간 계산

 

이렇게 필요한 기능을 전부 만들었습니다. 

 

아래는 전체 코드 소스입니다.

 

MainActivity.java

package com.example.circletimer;


import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private long timeCountInMilliSeconds = 1 * 60000;

    private enum TimerStatus {
        STARTED,
        STOPPED
    }

    private TimerStatus timerStatus = TimerStatus.STOPPED;

    private ProgressBar progressBarCircle;
    private EditText editTextMinute;
    private TextView textViewTime;
    private ImageView imageViewReset;
    private ImageView imageViewStartStop;
    private CountDownTimer countDownTimer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 뷰 초기화
        initViews();

        // 리스너 초기화
        initListeners();

    }

    /**
     *  뷰 초기화
     */
    private void initViews() {
        progressBarCircle =  findViewById(R.id.progressBarCircle);
        editTextMinute = findViewById(R.id.editTextMinute);
        textViewTime = findViewById(R.id.textViewTime);
        imageViewReset = findViewById(R.id.imageViewReset);
        imageViewStartStop = findViewById(R.id.imageViewStartStop);
    }

    /**
     * 리스너 초기화
     */
    private void initListeners() {
        imageViewReset.setOnClickListener(this);
        imageViewStartStop.setOnClickListener(this);
    }


    /**
     * 클릭 이벤트 설정
     *
     * @param view
     */
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.imageViewReset:
                reset();
                break;
            case R.id.imageViewStartStop:
                startStop();
                break;
        }
    }

    /**
     * 카운트 다운 시간을 리셋하고 재시작하는 기능
     */
    private void reset() {
        stopCountDownTimer();
        startCountDownTimer();
    }

     /**
      * 타이머가 시작하고 멈추는 기능
      */
     private void startStop() {

         if (timerStatus == TimerStatus.STOPPED) {


             setTimerValues();
             setProgressBarValues();

             imageViewReset.setVisibility(View.VISIBLE);
             imageViewStartStop.setImageResource(R.drawable.ic_baseline_stop_circle_24);
             editTextMinute.setEnabled(false);

             timerStatus = TimerStatus.STARTED;

             startCountDownTimer();

         } else {

             imageViewReset.setVisibility(View.GONE);
             imageViewStartStop.setImageResource(R.drawable.ic_baseline_play_circle_24);
             editTextMinute.setEnabled(true);

             timerStatus = TimerStatus.STOPPED;

             stopCountDownTimer();

         }

     }

     /**
      * 타이머에 시간이 설정 되어있는지 체크하크
      *  - 있는 경우 : 타이머에 시간 세팅
      *  - 없는 경우 : 시간을 설정해달라는 안내 토스트 메세지
      */
     private void setTimerValues() {
         int time = 0;
         if (!editTextMinute.getText().toString().isEmpty()) {

             time = Integer.parseInt(editTextMinute.getText().toString().trim());
         } else {

             Toast.makeText(getApplicationContext(), "시간을 설정해주세요", Toast.LENGTH_LONG).show();
         }

         timeCountInMilliSeconds = time * 60 * 1000;
     }

     /**
      * 카운트다운 시작 기능
      */
     private void startCountDownTimer() {

         countDownTimer = new CountDownTimer(timeCountInMilliSeconds, 1000) {
             @Override
             public void onTick(long millisUntilFinished) {

                 textViewTime.setText(hmsTimeFormatter(millisUntilFinished));

                 progressBarCircle.setProgress((int) (millisUntilFinished / 1000));

             }

             @Override
             public void onFinish() {

                 textViewTime.setText(hmsTimeFormatter(timeCountInMilliSeconds));

                 setProgressBarValues();

                 imageViewReset.setVisibility(View.GONE);

                 imageViewStartStop.setImageResource(R.drawable.ic_baseline_play_circle_24);

                 editTextMinute.setEnabled(true);

                 timerStatus = TimerStatus.STOPPED;
             }

         }.start();
         countDownTimer.start();
     }

     /**
      *  카운트 다운 정지 및 초기화
      */
     private void stopCountDownTimer() {
         countDownTimer.cancel();
     }

     /**
      * 원형 프로그레스 바에 값 세팅
      */
     private void setProgressBarValues() {

         progressBarCircle.setMax((int) timeCountInMilliSeconds / 1000);
         progressBarCircle.setProgress((int) timeCountInMilliSeconds / 1000);
     }


     /**
      * 밀리언 초를 시간으로 포멧해주는 기능
      *
      * @param milliSeconds
      * @return HH:mm:ss 시간 포멧
      */
     private String hmsTimeFormatter(long milliSeconds) {

         return String.format("%02d:%02d:%02d",
                 TimeUnit.MILLISECONDS.toHours(milliSeconds),
                 TimeUnit.MILLISECONDS.toMinutes(milliSeconds) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(milliSeconds)),
                 TimeUnit.MILLISECONDS.toSeconds(milliSeconds) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(milliSeconds)));


     }

}

 

 

 

 

 

테스트

 

테스트

 

정상적으로 잘 동작하는걸 확인할 수 있었습니다.

 

이상으로 원형 프로그레스 바 타이머 포스팅을 마치도록 하겠습니다.