You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WearOS表盘Complication无法渲染显示问题求助

问题

按照WearOS文档创建表盘,可正常绘制表盘内容,但尝试渲染Complication时无法显示。已排查以下点:

  • 通过createComplicationSlotsManager创建ComplicationSlots,使用无权限安全数据源(排除权限问题)
  • 修改Complication激活样式(怀疑颜色导致在黑背景不可见)无效
  • render函数中Complication处于启用状态(日志能打印)
  • 官方文档示例过时,现有工具无法适配当前API

完整实现代码:

package com.example.mywatchface

import android.graphics.*
import android.util.Log
import android.view.SurfaceHolder
import androidx.core.content.ContextCompat
import androidx.wear.watchface.*
import androidx.wear.watchface.complications.ComplicationSlotBounds
import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy
import androidx.wear.watchface.complications.SystemDataSources
import androidx.wear.watchface.complications.data.ComplicationType
import androidx.wear.watchface.complications.rendering.CanvasComplicationDrawable
import androidx.wear.watchface.complications.rendering.ComplicationDrawable
import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.UserStyleSchema
import java.time.ZonedDateTime

/**
 * Updates rate in milliseconds for interactive mode. We update once a second to advance the
 * second hand.
 */
private const val INTERACTIVE_UPDATE_RATE_MS = 1000

/**
 * Handler message id for updating the time periodically in interactive mode.
 */
private const val MSG_UPDATE_TIME = 0

private const val HOUR_STROKE_WIDTH = 5f
private const val MINUTE_STROKE_WIDTH = 3f
private const val SECOND_TICK_STROKE_WIDTH = 2f

private const val CENTER_GAP_AND_CIRCLE_RADIUS = 4f

private const val SHADOW_RADIUS = 6f

class MyWatchFace : WatchFaceService() {
    inner class MySharedAssets : Renderer.SharedAssets {
        override fun onDestroy() {
        }
    }

    /**
     * The watch face itself, which includes the renderer for drawing.
     */
    override suspend fun createWatchFace(
        surfaceHolder: SurfaceHolder,
        watchState: WatchState,
        complicationSlotsManager: ComplicationSlotsManager,
        currentUserStyleRepository: CurrentUserStyleRepository
    ): WatchFace {
        return WatchFace(
            WatchFaceType.DIGITAL,
            object : Renderer.CanvasRenderer2<MySharedAssets>(
                surfaceHolder,
                currentUserStyleRepository,
                watchState,
                CanvasType.HARDWARE,
                interactiveDrawModeUpdateDelayMillis = 16,
                clearWithBackgroundTintBeforeRenderingHighlightLayer = true
            ) {
                override suspend fun createSharedAssets(): MySharedAssets {
                    return MySharedAssets()
                }

                override fun renderHighlightLayer(
                    canvas: Canvas,
                    bounds: Rect,
                    zonedDateTime: ZonedDateTime,
                    sharedAssets: MySharedAssets
                ) {
                }

                override fun render(
                    canvas: Canvas,
                    bounds: Rect,
                    zonedDateTime: ZonedDateTime,
                    sharedAssets: MySharedAssets
                ) {
                    for ((_, complication) in complicationSlotsManager.complicationSlots) {
                        if (complication.enabled) {
                            Log.d("Hey", "hey")
                            complication.render(canvas, zonedDateTime, renderParameters)
                        }
                    }

                    var paint = Paint()
                    paint.color = ContextCompat.getColor(baseContext, R.color.watch_face_hands)
                    paint.textSize = 100f
                    paint.isAntiAlias = true
                    paint.style = Paint.Style.STROKE
                    paint.strokeWidth = 2f

                    val hours = zonedDateTime.hour
                    val minutes = zonedDateTime.minute

                    val clockHeight = (paint.fontMetrics.descent + paint.fontMetrics.ascent)

                    val time = String.format("%02d:%02d", hours, minutes)

                    drawText(canvas, time, paint, 0f, 0f)

                    paint = Paint()
                    paint.color = ContextCompat.getColor(baseContext, R.color.watch_date)
                    paint.textSize = 20f
                    paint.isAntiAlias = true
                    paint.typeface = Typeface.SANS_SERIF

                    val dayOfWeek = zonedDateTime.dayOfWeek.name.substring(0, 3)
                    val month = zonedDateTime.month.name
                    val dayOfMonth = zonedDateTime.dayOfMonth

                    val dateHeight = paint.fontMetrics.descent + paint.fontMetrics.ascent;

                    val date = String.format("%s, %s %d", dayOfWeek, month, dayOfMonth)

                    drawText(canvas, date, paint, 0f, clockHeight / 2 + dateHeight - 5f)
                }
            }
        )
    }

    private fun drawText(
        canvas: Canvas,
        text: String,
        paint: Paint,
        offsetX: Float,
        offsetY: Float
    ) {
        val timeWidth = paint.measureText(text)
        val timeHeight = (paint.fontMetrics.descent + paint.fontMetrics.ascent) / 2

        val startX = (canvas.width.toFloat() - timeWidth) / 2 + offsetX
        val startY = canvas.height.toFloat() / 2 - timeHeight + offsetY

        canvas.drawText(text, startX, startY, paint)
    }

    /**
     * The specification of settings the watch face supports.
     * This is similar to a database schema.
     */
    override fun createUserStyleSchema(): UserStyleSchema {
        return super.createUserStyleSchema()
    }

    /**
     * The complication slot configuration for the watchface.
     */
    override fun createComplicationSlotsManager(currentUserStyleRepository: CurrentUserStyleRepository): ComplicationSlotsManager {
        val canvasComplicationFactory = CanvasComplicationFactory { watchState, listener ->
            val complicationDrawable = ComplicationDrawable(this)

            complicationDrawable.activeStyle.backgroundColor = getColor(R.color.watch_date)
            complicationDrawable.activeStyle.textColor = getColor(R.color.watch_date)
            complicationDrawable.activeStyle.iconColor = getColor(R.color.watch_date)
            complicationDrawable.activeStyle.textSize = 20

            CanvasComplicationDrawable(complicationDrawable, watchState, listener)
        }

        return ComplicationSlotsManager(
            listOf(
                ComplicationSlot.createRoundRectComplicationSlotBuilder(
                    0,
                    canvasComplicationFactory,
                    listOf(
                        ComplicationType.RANGED_VALUE,
                        ComplicationType.LONG_TEXT,
                        ComplicationType.SHORT_TEXT,
                        ComplicationType.MONOCHROMATIC_IMAGE,
                        ComplicationType.SMALL_IMAGE
                    ),
                    DefaultComplicationDataSourcePolicy(
                        SystemDataSources.DATA_SOURCE_WORLD_CLOCK,
                        ComplicationType.SHORT_TEXT
                    ),
                    ComplicationSlotBounds(RectF(0.1f, 0.5625f, 0.4f, 0.8125f))
                ).build()
            ),
            currentUserStyleRepository
        )
    }
}

同时希望了解ComplicationSlotBounds的正确设置逻辑,目前使用示例值,推测是0-1的屏幕占比范围。


解决思路

1. 调整绘制顺序,避免Complication被覆盖

当前代码先绘制Complication,再绘制时间和日期,导致Complication被后续元素遮挡。修改render函数,先绘制表盘内容,最后绘制Complication:

override fun render(
    canvas: Canvas,
    bounds: Rect,
    zonedDateTime: ZonedDateTime,
    sharedAssets: MySharedAssets
) {
    // 1. 先绘制时间、日期等表盘内容
    var paint = Paint()
    paint.color = ContextCompat.getColor(baseContext, R.color.watch_face_hands)
    paint.textSize = 100f
    paint.isAntiAlias = true
    paint.style = Paint.Style.STROKE
    paint.strokeWidth = 2f

    val hours = zonedDateTime.hour
    val minutes = zonedDateTime.minute

    val clockHeight = (paint.fontMetrics.descent + paint.fontMetrics.ascent)

    val time = String.format("%02d:%02d", hours, minutes)

    drawText(canvas, time, paint, 0f, 0f)

    paint = Paint()
    paint.color = ContextCompat.getColor(baseContext, R.color.watch_date)
    paint.textSize = 20f
    paint.isAntiAlias = true
    paint.typeface = Typeface.SANS_SERIF

    val dayOfWeek = zonedDateTime.dayOfWeek.name.substring(0, 3)
    val month = zonedDateTime.month.name
    val dayOfMonth = zonedDateTime.dayOfMonth

    val dateHeight = paint.fontMetrics.descent + paint.fontMetrics.ascent;

    val date = String.format("%s, %s %d", dayOfWeek, month, dayOfMonth)

    drawText(canvas, date, paint, 0f, clockHeight / 2 + dateHeight - 5f)

    // 2. 最后绘制Complication,确保不会被遮挡
    for ((_, complication) in complicationSlotsManager.complicationSlots) {
        if (complication.enabled) {
            Log.d("Hey", "hey")
            complication.render(canvas, zonedDateTime, renderParameters)
        }
    }
}

2. 修复ComplicationDrawable样式问题

当前代码中textColor和backgroundColor使用同一颜色,导致文字与背景融合无法看见。修改为区分颜色,并修正textSize的类型(需为浮点型):

val canvasComplicationFactory = CanvasComplicationFactory { watchState, listener ->
    val complicationDrawable = ComplicationDrawable(this)

    // 区分背景与文字/图标颜色,根据表盘背景色调整
    complicationDrawable.activeStyle.backgroundColor = ContextCompat.getColor(this, R.color.complication_background)
    complicationDrawable.activeStyle.textColor = ContextCompat.getColor(this, R.color.complication_text)
    complicationDrawable.activeStyle.iconColor = ContextCompat.getColor(this, R.color.complication_text)
    complicationDrawable.activeStyle.textSize = 20f // 必须为浮点型,避免样式异常

    CanvasComplicationDrawable(complicationDrawable, watchState, listener)
}

3. 验证ComplicationSlotBounds设置逻辑

你的推测正确,ComplicationSlotBounds采用相对坐标系统,取值范围为0到1,对应屏幕宽高的比例:

  • RectF(left, top, right, bottom)四个参数依次代表:
    • left:左边界占屏幕宽度的比例
    • top:上边界占屏幕高度的比例
    • right:右边界占屏幕宽度的比例
    • bottom:下边界占屏幕高度的比例
  • 示例RectF(0.1f, 0.5625f, 0.4f, 0.8125f)表示:左边缘在屏幕宽度10%、上边缘在屏幕高度56.25%、右边缘在屏幕宽度40%、下边缘在屏幕高度81.25%的区域
  • 测试时可调整为更显眼的位置(比如左上角RectF(0.05f, 0.1f, 0.45f, 0.35f)),确认Complication是否显示

4. 其他排查点

  • 更换测试数据源:比如使用SystemDataSources.DATA_SOURCE_STEPS(步数数据源),确认是否是原数据源返回数据异常导致不显示
  • 检查renderParameters:确保传递的渲染参数包含正确的显示状态(交互式模式下可优先忽略低功耗模式相关检查)

内容的提问来源于stack exchange,提问作者Michael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 21:12:36