냐냐한 Dev Study/Android

안드로이드 간단 시계(날짜/시간) 만들기 (Java)

소소하냐 2022. 12. 27. 12:12

아주 간단하게, 디자인은 거의 생각하지 않고,

시계 동작 구현에 우선 순위를 두어 만들어 본 간단 시계입니다.

(추가 내용 : TextClock 을 추가하여, TextClock 동작과 간단 시계 동작을 확인합니다.)

 

좌 ) 간단 시계, 우) 간단 시계에 확인용으로 TextClock 추가

 

레이아웃 구성

- tv_time : 날짜 표시 TextView 

- tv_date : 시간 표시 TextView (24 시간 표시 형식) 

(추가 내용 : text_clock : 시간 표시 TextClock)

activity_main.xml 전체 코드 

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <!-- 날짜 표시 TextView  -->
    <TextView
        android:id="@+id/tv_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:text="2022.12.25 (일)"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- 시간 표시 TextView -->
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="00:00:00"
        android:textSize="50sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- 추가 내용 : TextClock -->
    <TextClock
        android:id="@+id/text_clock"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="150dp"
        android:textSize="50sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:format24Hour="HH:mm:ss"
        android:format12Hour="hh:mm:ss"
        />

</android.support.constraint.ConstraintLayout>

 

<TextClock />

  • 간단 시계가 정상적으로 동작하는 지 확인용입니다.
    (이 확인으로 간단 시계의 초가 미묘하게 맞지 않는 것을 확인했습니다.)
  • 제거 하려면, 위 코드에서 <TextClock /> 태그 내용 전체를 삭제하면 됩니다. 
  • java 코드 수정 없이, 이 태그 코드만으로도 시계가 표시됩니다.  
  • 시스템(휴대폰) 설정에 따라 24시간, 12시간 형식으로 보여집니다.
  • 시스템 설정 확인 (기기에 따라 다를 수 있음) : 설정 > 일반 > 날짜 및 시간 > 24시간 형식 사용 여부  
  • HH:mm:ss 포맷 설정에 대해 더 자세히 알려면 DateTimeFormatter 문서를 확인하세요.

 

[ DateTimeFormatter 중 시간 부분 ]

 

h clock-hour-of-am-pm (1-12) number 12
K hour-of-am-pm (0-11) number 0
k clock-hour-of-day (1-24) number 24
H hour-of-day (0-23) number 0

 

MainActivity.java 전체 코드

package com.example.clockver01;

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import java.util.Calendar;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

    final Handler handler = new Handler(Looper.getMainLooper());

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

        TextView tvDate = (TextView) findViewById(R.id.tv_date);
        TextView tvTime = (TextView) findViewById(R.id.tv_time);

        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Calendar current = Calendar.getInstance();

                        int year = current.get(Calendar.YEAR);
                        int month = current.get(Calendar.MONDAY);
                        int day = current.get(Calendar.DATE);
                        String dayWeek = current.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.KOREA);

                        int hour = current.get(Calendar.HOUR_OF_DAY);
                        int minute = current.get(Calendar.MINUTE);
                        int second = current.get(Calendar.SECOND);
                        
                        month = month + 1; // 월은 0~11, 1을 더해줘야 함

                        tvDate.setText(String.format("%04d.%02d.%02d (%s)", year, month, day, dayWeek));
                        tvTime.setText(String.format("%02d:%02d:%02d", hour, minute, second));
                    }
                });
            }
        };

        // 추가 : 0 millisecond 에 시작되도록 지연 시간 계산
        Calendar startTick = Calendar.getInstance();
        int delay = 1000 - startTick.get(Calendar.MILLISECOND);
        if(delay == 1000){
            delay = 0;
        }

        // 함수가 실행하면서 생긴 지연 시간을 무시
        timer.scheduleAtFixedRate(timerTask, delay, 1000);
        // 수정 전 : 선행 작업(가비지 컬렉션 등)으로 지연이 발생할 수 있음
        //timer.schedule(timerTask, 0, 1000);
    }
}

 

코드 간략 설명 

Timer 

- delay : 0  => 즉시 시작, 여기서는 0 millisecond에 시작되도록 위에서 delay 시간 계산 함 

- period : 1000 => 1초 간격

- scheduleAtFixedRate 사용 

Timer timer = new Timer();
// 함수가 실행하면서 생긴 지연 시간을 무시
timer.scheduleAtFixedRate(timerTask, delay, 1000);
// 수정 전 : 선행 작업(가비지 컬렉션 등)으로 지연이 발생할 수 있음
//timer.schedule(timerTask, 0, 1000);

 

 

참고 ) Timer class의 scheduleAtFixedRate 메서드 

public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, period);
    }

 

TimerTask

- Timer에서 처리할 내용 (날짜/시간 가져와서 화면에 표시하기)

TimerTask timerTask = new TimerTask() {
    @Override
    public void run() {
        // 처리할 내용
    }
};

 

Handler

- UI 작업은 백그라운드 스레드가 아닌 메인 스레드에서 처리해야하기 때문에 핸들러 사용하여 처리

final Handler handler = new Handler();

handler.post(new Runnable() {
    @Override
    public void run() {
        // 처리할 내용
    }
});

 

날짜/시간 표시

- Calendar class 사용

- 요일은 getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.KOREA) 

- %02d : 10 이하일 경우 앞에 0 붙도록 

- month = month + 1; : 월은 0 ~ 11 값을 반환. 1을 더해줘야 정상적으로 월 표시 가능 (2023.01.02 내용 추가) 

 

지연 시간 계산 

- 0 밀리초에 시작 되지 않아서 그런지 초가 미묘하게 빠르거나 느린 문제 발생. 이 부분 개선을 위한 코드

// 추가 : 0 millisecond 에 시작되도록 지연 시간 계산
Calendar startTick = Calendar.getInstance();
int delay = 1000 - startTick.get(Calendar.MILLISECOND);
if(delay == 1000){
    delay = 0;
}

 

 

2023.04.17 내용 추가입니다. 

지연 시간 계산 다시 제거

더 이상 해당 코드는 사용하고 있지 않지만, 지연 시간 계산이 필요없음을 알게 되었습니다. 

(이유는 모르겠지만 Kotlin에서는 지연 시간 계산으로도 시간이 맞지 않아서 다른 방법을 찾아보게 됐습니다.)

 

기존: 

1초(1000ms)마다 한 번씩 호출하게 되면, 호출 시점이 0ms가 아닐 경우 화면 갱신 시간은 계속 틀어진 채입니다. 

변경: 

사용자가 인식하지 못할 정도의 period로 자주 호출하여 갱신하면(예: 1000/30), 호출 시점이 0ms가 아니라도 잦은 갱신으로 자연스럽게 시간이 맞아떨어집니다. 

변경 소스: 

timer.scheduleAtFixedRate(timerTask, 0, 1000/30);

/* // 지연 시간 필요없음, 1초마다 호출이 아닌 (1000/30)초마다 호출
// 추가 : 0 millisecond 에 시작되도록 지연 시간 계산
Calendar startTick = Calendar.getInstance();
int delay = 1000 - startTick.get(Calendar.MILLISECOND);
if(delay == 1000){
    delay = 0;
}

// 함수가 실행하면서 생긴 지연 시간을 무시
timer.scheduleAtFixedRate(timerTask, delay, 1000);
// 수정 전 : 선행 작업(가비지 컬렉션 등)으로 지연이 발생할 수 있음
//timer.schedule(timerTask, 0, 1000); */