시계 모드 그리기

프로젝트를 구성하고 시계 모드 서비스를 구현하는 클래스를 추가했으면 코드 작성을 시작하여 맞춤 시계 모드를 초기화하고 그릴 수 있습니다.

이 과정에서는 시계 모드 샘플의 예제를 통해 시스템에서 시계 모드 서비스를 사용하는 방법을 보여줍니다. 여기에 설명된 다양한 서비스 구현 방식(예: 초기화 및 기기 기능 감지)은 모든 시계 모드에 적용되므로 자신의 시계 모드에서 코드의 일부를 재사용할 수 있습니다.

그림 1. 시계 모드 샘플의 아날로그 및 디지털 시계 모드

시계 모드 초기화

시계 모드에 필요한 대부분의 리소스는 시스템이 서비스를 로드할 때 할당하고 초기화해야 합니다. 여기에는 비트맵 리소스 로드, 맞춤형 애니메이션을 실행하기 위한 타이머 객체 생성, 페인트 스타일 구성 및 기타 계산 수행이 포함됩니다. 일반적으로 이러한 작업은 한 번만 실행하고 결과를 다시 사용할 수 있습니다. 이렇게 하면 시계 모드의 성능이 향상되고 코드를 더 쉽게 유지 관리할 수 있습니다.

시계 모드를 초기화하려면 다음 단계를 따르세요.

  1. 맞춤 타이머, 그래픽 객체 및 기타 요소의 변수를 선언합니다.
  2. Engine.onCreate() 메서드에서 시계 모드 요소를 초기화합니다.
  3. Engine.onVisibilityChanged() 메서드에서 맞춤 타이머를 초기화합니다.

다음 섹션에서 각 단계를 자세히 설명합니다.

변수 선언

시스템이 서비스를 로드할 때 초기화하는 리소스는 구현 전반에서 다른 곳에서도 액세스할 수 있어야 재사용이 가능합니다. 그렇게 하려면 WatchFaceService.Engine 구현 시 이러한 리소스의 멤버 변수를 선언하면 됩니다.

다음 요소의 변수를 선언합니다.

그래픽 객체
구현 전략 만들기에 설명된 대로, 대부분의 시계 모드에는 시계 모드의 배경으로 사용되는 비트맵 이미지가 하나 이상 포함되어 있습니다. 시곗바늘 또는 시계 모드의 기타 디자인 요소를 나타내는 추가 비트맵 이미지를 사용할 수 있습니다.
주기 타이머
시스템은 시간이 변경되면 1분에 한 번 시계 모드에 알리지만, 일부 시계 모드는 맞춤형 시간 간격으로 애니메이션을 실행합니다. 이 경우 시계 모드를 업데이트하는 데 필요한 빈도로 재깍거리는 맞춤 타이머를 제공해야 합니다.
시간대 변경 receiver
사용자는 여행할 때 자신의 시간대를 조정할 수 있으며 시스템에서 이 이벤트를 브로드캐스트합니다. 서비스 구현 시 시간대가 변경될 때 알림을 받는 broadcast receiver를 등록하고 이에 따라 시간을 업데이트해야 합니다.

다음 스니펫은 이러한 변수를 정의하는 방법을 보여줍니다.

Kotlin

    private const val MSG_UPDATE_TIME = 0

    class Service : CanvasWatchFaceService() {
        ...
        inner class Engine : CanvasWatchFaceService.Engine() {

            private lateinit var calendar: Calendar

            // device features
            private var lowBitAmbient: Boolean = false

            // graphic objects
            private lateinit var backgroundBitmap: Bitmap
            private var backgroundScaledBitmap: Bitmap? = null
            private lateinit var hourPaint: Paint
            private lateinit var minutePaint: Paint

            // handler to update the time once a second in interactive mode
            private val updateTimeHandler: Handler = UpdateTimeHandler(WeakReference(this))

            // receiver to update the time zone
            private val timeZoneReceiver: BroadcastReceiver = object : BroadcastReceiver() {
                override fun onReceive(context: Context, intent: Intent) {
                    calendar.timeZone = TimeZone.getDefault()
                    invalidate()
                }
            }

            // service methods (see other sections)
            ...
        }
        ...
        private class UpdateTimeHandler(val engineReference: WeakReference<Engine>) : Handler() {
            override fun handleMessage(message: Message) {
                engineReference.get()?.apply {
                    when (message.what) {
                        MSG_UPDATE_TIME -> {
                            invalidate()
                            if (shouldTimerBeRunning()) {
                                val timeMs: Long = System.currentTimeMillis()
                                val delayMs: Long =
                                        INTERACTIVE_UPDATE_RATE_MS - timeMs % INTERACTIVE_UPDATE_RATE_MS
                                sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs)
                            }
                        }
                    }
                }
            }
        }
        ...
    }
    

자바

    public class CanvasWatchFaceJava extends CanvasWatchFaceService {
        static final int MSG_UPDATE_TIME = 0;
        ...
        class Engine extends CanvasWatchFaceService.Engine {

            Calendar calendar;

            // device features
            boolean lowBitAmbient;

            // graphic objects
            Bitmap backgroundBitmap;
            Bitmap backgroundScaledBitmap;
            Paint hourPaint;
            Paint minutePaint;
            ...

            // handler to update the time once a second in interactive mode
            final Handler updateTimeHandler = new UpdateTimeHandler(new WeakReference<>(this));

            // receiver to update the time zone
            final BroadcastReceiver timeZoneReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    calendar.setTimeZone(TimeZone.getDefault());
                    invalidate();
                }
            };

            // service methods (see other sections)
            ...
        }
        ...
        private static class UpdateTimeHandler extends Handler {
            private WeakReference<Engine> engineReference;

            UpdateTimeHandler(WeakReference<Engine> engine) {
                this.engineReference = engine;
            }

            @Override
            public void handleMessage(Message message) {
                Engine engine = engineReference.get();
                if (engine != null) {
                    switch (message.what) {
                        case MSG_UPDATE_TIME:
                            engine.invalidate();
                            if (engine.shouldTimerBeRunning()) {
                                long timeMs = System.currentTimeMillis();
                                long delayMs = INTERACTIVE_UPDATE_RATE_MS
                                        - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
                                sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
                            }
                            break;
                    }
                }
            }
        }
        ...
    }
    

위의 예에서 맞춤 타이머는 스레드의 메시지 대기열을 사용하여 지연된 메시지를 보내고 처리하는 Handler 인스턴스로서 구현됩니다. 이 특정 시계 모드에서는 맞춤 타이머가 1초마다 재깍거립니다. 타이머가 재깍거리면 핸들러에서 invalidate() 메서드를 호출하고 시스템에서는 onDraw() 메서드를 호출하여 시계 모드를 다시 그립니다.

시계 모드 요소 초기화

시계 모드를 다시 그릴 때마다 재사용하는 비트맵 리소스, 페인트 스타일 및 기타 요소의 멤버 변수를 선언한 후에 시스템에서 서비스가 로드될 때 이러한 요소를 초기화해야 합니다. 이러한 요소를 한 번만 초기화하고 재사용하면 성능과 배터리 수명이 향상됩니다.

Engine.onCreate() 메서드에서 다음 요소를 초기화합니다.

  • 배경 이미지를 로드합니다.
  • 그래픽 객체를 그리는 스타일과 색상을 만듭니다.
  • 객체를 할당하여 시간을 계산합니다.
  • 시스템 UI를 구성합니다.

다음 스니펫은 이러한 요소를 초기화하는 방법을 보여줍니다.

Kotlin

    override fun onCreate(holder: SurfaceHolder?) {
        super.onCreate(holder)

        // configure the system UI (see next section)
        ...

        // load the background image
        backgroundBitmap = (resources.getDrawable(R.drawable.bg, null) as BitmapDrawable).bitmap

        // create graphic styles
        hourPaint = Paint().apply {
            setARGB(255, 200, 200, 200)
            strokeWidth = 5.0f
            isAntiAlias = true
            strokeCap = Paint.Cap.ROUND
        }
        ...

        // allocate a Calendar to calculate local time using the UTC time and time zone
        calendar = Calendar.getInstance()
    }
    

자바

    @Override
    public void onCreate(SurfaceHolder holder) {
        super.onCreate(holder);

        // configure the system UI (see next section)
        ...

        // load the background image
        Resources resources = AnalogWatchFaceService.this.getResources();
        Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null);
        backgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();

        // create graphic styles
        hourPaint = new Paint();
        hourPaint.setARGB(255, 200, 200, 200);
        hourPaint.setStrokeWidth(5.0f);
        hourPaint.setAntiAlias(true);
        hourPaint.setStrokeCap(Paint.Cap.ROUND);
        ...

        // allocate a Calendar to calculate local time using the UTC time and time zone
        calendar = Calendar.getInstance();
    }
    

배경 비트맵은 시스템이 시계 모드를 초기화할 때 한 번만 로드됩니다. 그래픽 스타일은 Paint 클래스의 인스턴스입니다. 시계 모드 그리기에 설명된 대로, 이러한 스타일을 사용하여 Engine.onDraw() 메서드 내에서 시계 모드의 요소를 그립니다.

맞춤 타이머 초기화

시계 모드 개발자는 기기가 대화형 모드에 있는 동안 필요한 빈도로 재깍거리는 맞춤 타이머를 제공하여 시계 모드의 업데이트 빈도를 결정합니다. 이를 통해 맞춤형 애니메이션 및 기타 시각적 효과를 만들 수 있습니다.

참고: 대기 모드에서는 시스템이 안정적으로 맞춤 타이머를 호출하지 않습니다. 대기 모드에서 시계 모드를 업데이트하려면 대기 모드에서 시계 모드 업데이트를 참조하세요.

매초 재깍거리는 AnalogWatchFaceService 클래스의 예제 타이머 정의가 변수 선언에 나와 있습니다. Engine.onVisibilityChanged() 메서드에서 다음 두 조건이 적용될 경우 맞춤 타이머를 시작하세요.

  • 시계 모드가 표시됩니다.
  • 기기가 대화형 모드입니다.

필요 시 AnalogWatchFaceService 클래스는 다음과 같이 타이머 틱을 예약합니다.

Kotlin

    private fun updateTimer() {
        updateTimeHandler.removeMessages(MSG_UPDATE_TIME)
        if (shouldTimerBeRunning()) {
            updateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME)
        }
    }

    fun shouldTimerBeRunning(): Boolean = isVisible && !isInAmbientMode
    

자바

    private void updateTimer() {
        updateTimeHandler.removeMessages(MSG_UPDATE_TIME);
        if (shouldTimerBeRunning()) {
            updateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
        }
    }

    boolean shouldTimerBeRunning() {
        return isVisible() && !isInAmbientMode();
    }
    

변수 선언에 나와 있는 대로 이 맞춤 타이머는 매초 재깍거립니다.

onVisibilityChanged() 메서드에서 필요 시 타이머를 시작하고 시간대 변경용 receiver를 다음과 같이 등록합니다.

Kotlin

    override fun onVisibilityChanged(visible: Boolean) {
        super.onVisibilityChanged(visible)

        if (visible) {
            registerReceiver()

            // Update time zone in case it changed while we weren't visible.
            calendar.timeZone = TimeZone.getDefault()
        } else {
            unregisterReceiver()
        }

        // Whether the timer should be running depends on whether we're visible and
        // whether we're in ambient mode, so we may need to start or stop the timer
        updateTimer()
    }
    

자바

    @Override
    public void onVisibilityChanged(boolean visible) {
        super.onVisibilityChanged(visible);

        if (visible) {
            registerReceiver();

            // Update time zone in case it changed while we weren't visible.
            calendar.setTimeZone(TimeZone.getDefault());
        } else {
            unregisterReceiver();
        }

        // Whether the timer should be running depends on whether we're visible and
        // whether we're in ambient mode, so we may need to start or stop the timer
        updateTimer();
    }
    

시계 모드가 표시되면 onVisibilityChanged() 메서드는 시간대 변경용 receiver를 등록합니다. 기기가 대화형 모드인 경우 이 메서드는 또한 맞춤 타이머를 시작합니다. 시계 모드가 표시되지 않으면 이 메서드는 맞춤 타이머를 중지하고 시간대 변경용 receiver의 등록을 취소합니다. registerReceiver()unregisterReceiver() 메서드는 다음과 같이 구현됩니다.

Kotlin

    private fun registerReceiver() {
        if (registeredTimeZoneReceiver) return
        registeredTimeZoneReceiver = true
        IntentFilter(Intent.ACTION_TIMEZONE_CHANGED).also { filter ->
            this@AnalogWatchFaceService.registerReceiver(timeZoneReceiver, filter)
        }
    }

    private fun unregisterReceiver() {
        if (!registeredTimeZoneReceiver) return
        registeredTimeZoneReceiver = false
        this@AnalogWatchFaceService.unregisterReceiver(timeZoneReceiver)
    }
    

자바

    private void registerReceiver() {
        if (registeredTimeZoneReceiver) {
            return;
        }
        registeredTimeZoneReceiver = true;
        IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
        AnalogWatchFaceService.this.registerReceiver(timeZoneReceiver, filter);
    }

    private void unregisterReceiver() {
        if (!registeredTimeZoneReceiver) {
            return;
        }
        registeredTimeZoneReceiver = false;
        AnalogWatchFaceService.this.unregisterReceiver(timeZoneReceiver);
    }
    

대기 모드에서 시계 모드 업데이트

대기 모드에서는 시스템이 1분마다 Engine.onTimeTick() 메서드를 호출합니다. 이 모드에서는 1분마다 한 번씩 시계 모드를 업데이트하면 충분합니다. 대화형 모드에서 시계 모드를 업데이트하려면 맞춤 타이머 초기화에 설명된 대로 맞춤 타이머를 제공해야 합니다.

대기 모드에서 대부분의 시계 모드 구현은 Engine.onTimeTick() 메서드에서 시계 모드를 다시 그릴 수 있도록 캔버스를 무효화합니다.

Kotlin

    override fun onTimeTick() {
        super.onTimeTick()

        invalidate()
    }
    

자바

    @Override
    public void onTimeTick() {
        super.onTimeTick();

        invalidate();
    }
    

시스템 UI 구성

시스템 UI 요소 수용에 설명된 대로, 시계 모드는 시스템 UI 요소를 간섭해서는 안 됩니다. 시계 모드의 배경이 밝거나 화면 하단에 정보가 표시되는 경우, 알림 카드의 크기를 구성하거나 배경 보호를 사용 설정해야 할 수 있습니다.

Wear OS by Google에서는 시계 모드가 활성 상태일 때 시스템 UI의 다음과 같은 부분을 구성할 수 있습니다.

  • 시스템이 시계 모드에서 시간을 가져올지를 지정합니다.
  • 주변의 단색 배경으로 시스템 표시기를 보호합니다.
  • 시스템 표시기의 위치를 지정합니다.

시스템 UI의 이러한 부분을 구성하려면 WatchFaceStyle 인스턴스를 만들어서 Engine.setWatchFaceStyle() 메서드에 전달합니다.

AnalogWatchFaceService 클래스는 시스템 UI를 다음과 같이 구성합니다.

Kotlin

    override fun onCreate(holder: SurfaceHolder?) {
        super.onCreate(holder)

        // configure the system UI
        setWatchFaceStyle(WatchFaceStyle.Builder(this@AnalogWatchFaceService)
                .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
                .setShowSystemUiTime(false)
                .build())
        ...
    }
    

자바

    @Override
    public void onCreate(SurfaceHolder holder) {
        super.onCreate(holder);

        // configure the system UI
        setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
                .setBackgroundVisibility(WatchFaceStyle
                                        .BACKGROUND_VISIBILITY_INTERRUPTIVE)
                .setShowSystemUiTime(false)
                .build());
        ...
    }
    

위의 코드 스니펫은 시스템 시간이 표시되지 않도록 구성합니다(이 시계 모드는 자체 방식으로 시간을 표현하기 때문임).

시계 모드 구현의 어느 시점에서나 시스템 UI의 스타일을 구성할 수 있습니다. 예를 들어, 사용자가 흰색 배경을 선택하면 시스템 표시기를 위한 배경 보호를 추가할 수 있습니다.

시스템 UI 구성에 관한 자세한 내용은 Wear API 참조 문서를 참조하세요.

읽지 않은 알림 표시기 관리

사용자들은 종종 읽지 않은 알림이 있을 경우 명확히 표시되는 것을 선호합니다. 따라서 읽지 않은 알림 표시기가 제공됩니다. 이 표시기는 화면 하단에 원으로 둘러싸인 점으로 나타납니다. 스트림에 읽지 않은 알림이 두 개 이상 있는 경우 표시됩니다.

읽지 않은 알림 표시기

그림 2. 읽지 않은 알림 표시기

참고: 읽지 않은 알림 표시기는 Wear 2.8.0 프로덕션 버전에서는 사용 설정되어 있지 않습니다. 개발자는 대신 최신 Wear 에뮬레이터를 사용하여 구현을 테스트하는 것이 좋습니다. 이 기능은 Wear의 다음 소비자 버전(버전 2.9.0)부터 기본적으로 표시됩니다.

기본적으로 표시기가 시계 모드에 추가됩니다. 표시기를 사용자가 사용할 수 있도록 두는 것이 좋습니다. 그러나 시계 모드에 이미 읽지 않은 알림이 표시되거나 새로운 표시기의 위치가 시계 모드의 요소와 충돌하는 경우 시스템 표시기가 표시되지 않도록 선택할 수 있습니다. 시계 스타일을 만들 때 다음 방법 중 하나를 사용합니다.

맞춤형 읽지 않은 알림 개수 제공 상태 표시줄에 읽지 않은 알림 개수 제공

그림 3. 맞춤형 알림 개수 또는 상태 표시줄 알림 개수

읽지 않은 알림 표시기를 시계 모드에 표시하도록 선택하는 경우 외부 링의 색상을 맞춤설정할 수 있습니다. WatchFaceStyle.Builder.setAccentColor를 호출하고 원하는 color를 지정합니다. 기본적으로 외부 링은 흰색입니다.

기기 화면에 관한 정보 가져오기

시스템은 기기가 저비트 대기 모드를 사용하는지 여부 및 화면에 번인 보호가 필요한지 여부와 같은 기기 화면의 속성을 결정할 때 Engine.onPropertiesChanged() 메서드를 호출합니다.

다음 코드 스니펫에서는 이러한 속성을 가져오는 방법을 보여줍니다.

Kotlin

    override fun onPropertiesChanged(properties: Bundle?) {
        super.onPropertiesChanged(properties)
        properties?.apply {
            lowBitAmbient = getBoolean(PROPERTY_LOW_BIT_AMBIENT, false)
            burnInProtection = getBoolean(PROPERTY_BURN_IN_PROTECTION, false)
        }
    }
    

자바

    @Override
    public void onPropertiesChanged(Bundle properties) {
        super.onPropertiesChanged(properties);
        lowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
        burnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION,
                false);
    }
    

시계 모드를 그릴 때 다음과 같은 기기 속성을 고려해야 합니다.

  • 저비트 대기 모드를 사용하는 기기의 경우 화면이 대기 모드의 각 색상에서 더 적은 수의 비트를 지원하므로, 기기가 대기 모드로 전환될 때 앤티앨리어싱 및 비트맵 필터링을 사용 중지해야 합니다.
  • 번인 보호가 필요한 기기의 경우, 대기 모드에서 흰색 픽셀의 큰 블록을 사용하지 말고 화면 가장자리 10픽셀 이내에 콘텐츠를 배치하지 않아야 합니다. 시스템에서 픽셀 번인을 방지하기 위해 주기적으로 콘텐츠를 이동하기 때문입니다.

저비트 대기 모드 및 번인 보호에 관한 자세한 내용은 특수 화면 최적화를 참조하세요. 비트맵 필터링을 사용 중지하는 방법에 관한 자세한 내용은 비트맵 필터링을 참조하세요.

모드 간 변경에 응답

기기가 대기 모드와 대화형 모드 간에 전환할 때 시스템은 Engine.onAmbientModeChanged() 메서드를 호출합니다. 서비스 구현은 모드 간 전환을 위해 필요한 사항을 조정한 후 시스템이 시계 모드를 다시 그릴 수 있도록 invalidate() 메서드를 호출해야 합니다.

다음 스니펫은 이 메서드의 구현 방법을 보여줍니다.

Kotlin

    override fun onAmbientModeChanged(inAmbientMode: Boolean) {

        super.onAmbientModeChanged(inAmbientMode)

        if (lowBitAmbient) {
            !inAmbientMode.also { antiAlias ->
                hourPaint.isAntiAlias = antiAlias
                minutePaint.isAntiAlias = antiAlias
                secondPaint.isAntiAlias = antiAlias
                tickPaint.isAntiAlias = antiAlias
            }
        }
        invalidate()
        updateTimer()
    }
    

자바

    @Override
    public void onAmbientModeChanged(boolean inAmbientMode) {

        super.onAmbientModeChanged(inAmbientMode);

        if (lowBitAmbient) {
            boolean antiAlias = !inAmbientMode;
            hourPaint.setAntiAlias(antiAlias);
            minutePaint.setAntiAlias(antiAlias);
            secondPaint.setAntiAlias(antiAlias);
            tickPaint.setAntiAlias(antiAlias);
        }
        invalidate();
        updateTimer();
    }
    

이 예제에서는 시스템이 시계 모드를 다시 그릴 수 있도록 일부 그래픽 스타일을 조정하고 캔버스를 무효화합니다.

시계 모드 그리기

맞춤 시계 모드를 그릴 때 시스템은 Canvas 인스턴스가 있는 Engine.onDraw() 메서드 및 시계 모드를 그려야 하는 범위를 호출합니다. 범위는 일부 원형 기기 하단의 '턱'과 같은 인셋 영역을 고려합니다. 이 캔버스를 사용하여 다음과 같이 시계 모드를 직접 그릴 수 있습니다.

  1. 뷰가 바뀔 때마다 기기에 맞게 배경을 조정할 수 있도록 onSurfaceChanged() 메서드를 재정의합니다.

    Kotlin

        override fun onSurfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
            if (backgroundScaledBitmap?.width != width || backgroundScaledBitmap?.height != height) {
                backgroundScaledBitmap = Bitmap.createScaledBitmap(backgroundBitmap,
                        width, height, true /* filter */)
            }
            super.onSurfaceChanged(holder, format, width, height)
        }
        

    자바

        @Override
        public void onSurfaceChanged(
                SurfaceHolder holder, int format, int width, int height) {
            if (backgroundScaledBitmap == null
                    || backgroundScaledBitmap.getWidth() != width
                    || backgroundScaledBitmap.getHeight() != height) {
                backgroundScaledBitmap = Bitmap.createScaledBitmap(backgroundBitmap,
                        width, height, true /* filter */);
            }
            super.onSurfaceChanged(holder, format, width, height);
        }
        
  2. 기기가 대기 모드인지 아니면 대화형 모드인지를 확인합니다.
  3. 필요한 그래픽 계산을 실행합니다.
  4. 캔버스에 배경 비트맵을 그립니다.
  5. Canvas 클래스의 메서드를 사용하여 시계 모드를 그립니다.

다음 스니펫은 onDraw() 메서드를 구현하는 방법을 보여줍니다.

Kotlin

    override fun onDraw(canvas: Canvas, bounds: Rect) {
        val frameStartTimeMs: Long = SystemClock.elapsedRealtime()

        // Drawing code here

        if (shouldTimerBeRunning()) {
            var delayMs: Long = SystemClock.elapsedRealtime() - frameStartTimeMs
            delayMs = if (delayMs > INTERACTIVE_UPDATE_RATE_MS) {
                // This scenario occurs when drawing all of the components takes longer than an actual
                // frame. It may be helpful to log how many times this happens, so you can
                // fix it when it occurs.
                // In general, you don't want to redraw immediately, but on the next
                // appropriate frame (else block below).
                0
            } else {
                // Sets the delay as close as possible to the intended framerate.
                // Note that the recommended interactive update rate is 1 frame per second.
                // However, if you want to include the sweeping hand gesture, set the
                // interactive update rate up to 30 frames per second.
                INTERACTIVE_UPDATE_RATE_MS - delayMs
            }
            updateTimeHandler.sendEmptyMessageDelayed(MSG_CODE_UPDATE_TIME, delayMs)
        }
    }
    

자바

    @Override
    public void onDraw(Canvas canvas, Rect bounds) {
        long frameStartTimeMs = SystemClock.elapsedRealtime();

        // Drawing code here

        if (shouldTimerBeRunning()) {
            long delayMs = SystemClock.elapsedRealtime() - frameStartTimeMs;
            if (delayMs > INTERACTIVE_UPDATE_RATE_MS) {
                // This scenario occurs when drawing all of the components takes longer than an actual
                // frame. It may be helpful to log how many times this happens, so you can
                // fix it when it occurs.
                // In general, you don't want to redraw immediately, but on the next
                // appropriate frame (else block below).
                delayMs = 0;
            } else {
                // Sets the delay as close as possible to the intended framerate.
                // Note that the recommended interactive update rate is 1 frame per second.
                // However, if you want to include the sweeping hand gesture, set the
                // interactive update rate up to 30 frames per second.
                delayMs = INTERACTIVE_UPDATE_RATE_MS - delayMs;
            }
            updateTimeHandler.sendEmptyMessageDelayed(MSG_CODE_UPDATE_TIME, delayMs);
        }
    }
    

캔버스 인스턴스에서 그리기에 관한 자세한 내용은 캔버스 및 드로어블을 참조하세요.

WatchFace 샘플에는 onDraw() 메서드를 구현하는 방법의 예제로 참조할 수 있는 추가 시계 모드가 포함되어 있습니다.