引言

鸿蒙系统覆盖手机(16:9/18:9)、平板(4:3/16:10)、车机(长条形/异形屏)等多尺寸设备,Unity 2D UI若采用固定像素布局,易出现按钮错位、文本溢出或元素重叠等问题。本文将结合鸿蒙屏幕特性与Unity UGUI系统,设计一套​​动态适配方案​​,通过代码实现按钮、文本等核心元素的自动缩放与布局调整。


一、鸿蒙屏幕适配的核心挑战

鸿蒙设备的屏幕差异主要体现在三个方面,需针对性解决:

1. 屏幕比例多样性

手机常见16:9(如1080×1920),平板多为16:10(如2560×1600),车机可能采用更长比例(如21:9或自定义异形屏)。固定宽高比的UI布局无法适配所有设备。

2. 分辨率跨度大

从低分辨率手机(720×1280)到高分辨率平板(3840×2160),直接使用像素(Px)定位会导致元素大小在不同设备上差异显著(如100px在小屏手机可能过大,在大屏平板可能过小)。

3. 文本可读性要求

鸿蒙设备用户可能在不同距离使用(如手机贴近面部,车机远距离观看),文本需根据屏幕尺寸动态调整字体大小,确保清晰可读。


二、动态UI布局设计思路

核心目标是让UI元素​​“基于屏幕尺寸自动调整”​​,而非依赖固定数值。方案包含以下关键步骤:

1. 基于比例的Canvas缩放

使用Canvas Scaler组件,设置UI Scale ModeScale With Screen Size,以目标分辨率(如1920×1080)为基准,通过Reference ResolutionScreen Match Mode(匹配宽度/高度/混合)实现全局缩放。

2. 动态布局区域划分

将屏幕划分为“安全区域”(如中间80%区域),避免元素被屏幕圆角、刘海遮挡;使用Horizontal/Vertical Layout GroupGrid Layout Group管理子元素,结合Content Size Fitter实现自适应排列。

3. 百分比定位与动态计算

通过代码获取屏幕宽高比(Screen.width/Screen.height),动态计算按钮位置、文本字体大小,确保元素在不同比例屏幕上的相对位置一致。

4. 文本自适应优化

使用TextMeshPro组件替代原生Text,支持自动换行、字体大小自适应(通过Best Fit功能或代码动态调整)。


三、代码实现:动态按钮与文本布局

1. 基础Canvas配置(Unity编辑器设置)

首先创建Canvas并配置适配参数:

  • 渲染模式:Screen Space - Overlay(覆盖屏幕,适合2D UI)。
  • 添加Canvas Scaler组件:
    • UI Scale ModeScale With Screen Size
    • Reference Resolution → 设计基准分辨率(如1920×1080)。
    • Screen Match ModeMatch Width or Height(根据设备选择匹配宽度或高度,或通过代码动态调整)。

2. 动态布局管理器(C#脚本)

创建UILayoutManager单例,负责监听屏幕尺寸变化并调整UI元素:

// UILayoutManager.cs
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class UILayoutManager : MonoBehaviour {
    public static UILayoutManager Instance { get; private set; }

    [Header("基准配置")]
    [Tooltip("设计基准的屏幕宽高比(如1920x1080对应16:9)")]
    public Vector2 designAspectRatio = new Vector2(1920f, 1080f);
    [Tooltip("安全区域边距(屏幕边缘预留空间)")]
    public float safeAreaMargin = 0.05f; // 5%边距

    [Header("按钮配置")]
    public Button[] dynamicButtons; // 需要动态调整的按钮数组
    public float buttonMinWidth = 200f; // 按钮最小宽度(基准分辨率下的像素)
    public float buttonHeight = 60f;    // 按钮固定高度(可根据需求调整)

    [Header("文本配置")]
    public TextMeshProUGUI[] dynamicTexts; // 需要动态调整的文本数组
    public float minFontSize = 14f;       // 最小字体大小
    public float maxFontSize = 24f;       // 最大字体大小

    private void Awake() {
        if (Instance == null) Instance = this;
        else Destroy(gameObject);
    }

    private void Start() {
        AdjustLayout();
        // 监听屏幕尺寸变化(鸿蒙设备旋转或分辨率切换时触发)
        Screen.onResolutionChanged += OnResolutionChanged;
    }

    private void OnDestroy() {
        Screen.onResolutionChanged -= OnResolutionChanged;
    }

    // 屏幕尺寸变化回调
    private void OnResolutionChanged(Vector2Int resolution) {
        AdjustLayout();
    }

    // 核心布局调整逻辑
    public void AdjustLayout() {
        // 1. 计算当前屏幕宽高比与基准宽高比的匹配度
        float currentAspectRatio = (float)Screen.width / Screen.height;
        float aspectRatioDiff = Mathf.Abs(currentAspectRatio - designAspectRatio.x / designAspectRatio.y);

        // 2. 调整Canvas Scaler的匹配模式(可选:根据宽高比自动切换匹配宽度/高度)
        CanvasScaler canvasScaler = FindObjectOfType<CanvasScaler>();
        if (canvasScaler != null) {
            canvasScaler.matchWidthOrHeight = (currentAspectRatio > designAspectRatio.x / designAspectRatio.y) 
                ? 1f // 宽度主导(适合横屏设备)
                : 0f; // 高度主导(适合竖屏设备)
        }

        // 3. 调整按钮布局(位置+大小)
        AdjustButtons();

        // 4. 调整文本布局(字体大小+位置)
        AdjustTexts();
    }

    // 动态调整按钮位置与大小
    private void AdjustButtons() {
        float safeWidth = Screen.width * (1 - 2 * safeAreaMargin);
        float safeHeight = Screen.height * (1 - 2 * safeAreaMargin);

        foreach (Button btn in dynamicButtons) {
            // 获取按钮原始大小(基准分辨率下的设计值)
            RectTransform rectTrans = btn.GetComponent<RectTransform>();
            Vector2 originalSize = rectTrans.sizeDelta;

            // 动态计算宽度(最小为buttonMinWidth,最大不超过安全区域的80%)
            float targetWidth = Mathf.Clamp(originalSize.x * (Screen.width / designAspectRatio.x), 
                buttonMinWidth, safeWidth * 0.8f);
            // 高度保持固定比例(或根据设计需求调整)
            float targetHeight = originalSize.y * (targetWidth / originalSize.x);

            // 更新按钮大小
            rectTrans.sizeDelta = new Vector2(targetWidth, targetHeight);

            // 水平居中放置在安全区域底部(Y坐标为安全区域高度 - 按钮高度 - 边距)
            float xPos = (Screen.width - targetWidth) / 2;
            float yPos = safeHeight - targetHeight - (Screen.height * safeAreaMargin);
            rectTrans.anchoredPosition = new Vector2(xPos, yPos);
        }
    }

    // 动态调整文本字体大小与位置
    private void AdjustTexts() {
        float safeWidth = Screen.width * (1 - 2 * safeAreaMargin);
        float safeHeight = Screen.height * (1 - 2 * safeAreaMargin);

        foreach (TextMeshProUGUI text in dynamicTexts) {
            RectTransform rectTrans = text.GetComponent<RectTransform>();
            // 文本宽度自适应(不超过安全区域的90%)
            float maxWidth = safeWidth * 0.9f;
            text.maxWidth = maxWidth;

            // 自动调整字体大小(使用TextMeshPro的Best Fit功能)
            text.autoSizeTextContainer = true;
            text.alignment = TextAlignmentOptions.Center;

            // 若需手动控制字体大小(替代Best Fit):
            /*
            float textWidth = text.preferredWidth;
            float textHeight = text.preferredHeight;
            float scale = Mathf.Min(maxWidth / textWidth, safeHeight / textHeight);
            text.fontSize = Mathf.RoundToInt(Mathf.Clamp(text.fontSize * scale, minFontSize, maxFontSize));
            */
        }
    }
}

3. 按钮与文本的Unity编辑器配置

在Unity中创建UI元素并绑定脚本:

  1. 新建Canvas,添加UILayoutManager脚本到空物体(如命名为UILayoutRoot)。
  2. 创建按钮(Button)并添加到dynamicButtons数组:
    • 设置按钮的RectTransform锚点为Middle Center(中间居中)。
    • 添加Horizontal Layout Group(可选,用于多按钮横向排列)。
  3. 创建文本(TextMeshPro - Text (UI))并添加到dynamicTexts数组:
    • 启用Auto Size(在Inspector中勾选Auto Size Text)。
    • 设置AnchorStretch(拉伸填充父容器)。

4. 鸿蒙设备特殊处理(刘海屏/异形屏)

鸿蒙部分设备(如折叠屏、带刘海的手机)存在屏幕安全区域限制,需通过Screen.safeArea获取安全区域坐标,避免UI被遮挡:

// 在AdjustLayout方法中添加安全区域适配
Rect safeArea = Screen.safeArea;
Vector2Int safeSize = new Vector2Int(
    (int)(safeArea.width * Screen.dpi),
    (int)(safeArea.height * Screen.dpi)
);

// 使用safeSize替代Screen.width/height计算布局
// 示例:将按钮放置在安全区域内
float safeLeft = safeArea.x * (Screen.width / Screen.currentResolution.width);
float safeBottom = safeArea.y * (Screen.height / Screen.currentResolution.height);
rectTrans.anchoredPosition = new Vector2(safeLeft + (safeWidth - targetWidth)/2, safeBottom + safeHeight - targetHeight - margin);

四、测试与优化建议

1. 多分辨率测试

在Unity中通过Game视图的分辨率下拉菜单模拟鸿蒙设备(如1080×1920、2560×1600、2160×1080),验证按钮是否居中、文本是否换行或缩放。

2. 真机调试

通过DevEco Studio连接鸿蒙手机/平板,使用adb logcat查看是否有布局错误日志(如文本溢出),调整buttonMinWidthmaxFontSize参数。

3. 性能优化

  • 避免在AdjustLayout中频繁调用GetComponent,可将RectTransform组件缓存。
  • 对于复杂UI(如列表),使用Recycle ListView替代多个独立按钮,减少Draw Call。

4. 动态加载布局配置

可通过JSON文件存储不同设备的布局参数(如按钮大小、文本字体),运行时根据设备型号加载对应配置,提升适配灵活性。


总结

通过Canvas Scaler全局缩放、动态计算元素位置/大小,结合代码对屏幕宽高比和分辨率的实时响应,可有效解决鸿蒙多尺寸设备的UI适配问题。核心原则是​​“相对布局优于绝对定位”​​,通过百分比、安全区域和动态计算确保UI在不同设备上的一致性与可用性。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐