这是一个手动的手游异形屏适配解决方案,即交给玩家一个slider,玩家通过调整slider来改变UI的适配,类似"明日方舟"的效果

实现这种方案的关键在于将除了背景以外的UI元素放入一个"SafeRect"中,这个SafeRect是一个撑满的空节点

实现效果如图所示

代码如下
csharp
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class SafeRect : MonoBehaviour
{
/// <summary>
/// 需要适配的安全区
/// </summary>
public RectTransform safeRect;
/// <summary>
/// 滑动条
/// </summary>
public Slider safeRectSlider;
/// <summary>
/// 滑动量
/// </summary>
public TextMeshProUGUI safeRectText;
/// <summary>
/// 偏移系数
/// </summary>
private float safeRectOffsetValue;
private List<Action<float>> callbacks = new List<Action<float>>();
private float screenWidth;
private float screenHeight;
private Rect safeArea;
private float SafeRectOffsetValue
{
get
{
return safeRectOffsetValue;
}
set
{
if (value != safeRectOffsetValue)
{
safeRectOffsetValue = value;
foreach (var callback in callbacks)
{
callback?.Invoke(value);
}
}
}
}
private void Start()
{
safeRectSlider.onValueChanged.AddListener(SliderChange);
screenWidth = Screen.width;
screenHeight = Screen.height;
safeArea = Screen.safeArea;
callbacks.Add(Fix);
}
/// <summary>
/// 调整SafeRect宽度
/// </summary>
/// <param name="width"></param>
private void SafeRectChange(float width)
{
if (safeRect != null)
{
safeRect.offsetMin = new Vector2(width, 0);
safeRect.offsetMax = new Vector2(-width, 0);
}
}
/// <summary>
/// 获得SafeRect左右两边的最大调整量
/// </summary>
/// <returns></returns>
private float GetSafeRectMaxOffset()
{
float width = Screen.width;
float height = Screen.height;
float result = width - 16/9f * height; //刚好是16:9的屏幕,这里会是0,但又因为目前市面上没有刚好16:9还异形屏的手机,所以能用
if (result < 0) //即当前屏幕小于16:9的情况下(目前好像没有小于16:9又是异形屏的手机,折叠屏除外,所以这里不适合折叠屏)
{
return 0;
}
result = result * 0.5f / (height / 1080f);
return result;
}
/// <summary>
/// 偏移值改变的回调
/// </summary>
/// <param name="value"></param>
private void Fix(float value)
{
if (safeArea.width == screenWidth && safeArea.height == screenHeight) //如果不是异形屏,那么一直不变
{
return;
}
var safeRectWidth = GetSafeRectMaxOffset() * value;
var s = screenWidth - safeArea.width;
float diff = float.MaxValue;
if (s > 0) //是异形屏
{
diff = safeArea.xMin;
}
if (safeRectWidth >= diff)
{
safeRectWidth = diff;
}
SafeRectChange(safeRectWidth);
}
/// <summary>
/// 滑动条回调
/// </summary>
/// <param name="input"></param>
private void SliderChange(float input)
{
SafeRectOffsetValue = input; //设置偏移值
safeRectText.text = ((int)(input * 100)).ToString();
}
}
这里的核心设计点在于,通过滑动条获得一个值,这个值就是SafeRect可以变化的一个"偏移系数 "

然后将这个值给到SafeRectOffsetValue,SafeRectOffsetValue本身是一个全局的可以被监听的数据类型,事先将需要监听的事件注册进来



Fix就是核心方法,这里会先判断一下当前屏幕是否是异形屏,如果不是异形屏,那么就直接return,否则就继续获取一个变化宽度,即GetSafeRectMaxOffset() * value

这里第一次获取的result就是在16:9的参考宽高比之下,当前屏幕分辨率的宽和以当前高为基准的理想16:9宽的差值,如果这个值小于0,即代表当前屏幕的宽高比小于16:9,而目前市面上又没有小于16:9的异形屏(折叠屏除外),所以这里直接返回0,即不管怎么拉都不会变;最后,将result / 2再除以当前屏幕高和1080的比值(因为参考分辨率是1920 x 1080,即16:9)得到最终"SafeRect"的左右两边可以调整的最大值 (这里比例因子的设计可能存在问题,但是先这么放着)
比如有屏幕分辨率2340 x1080
result = 2340 - 16/9 * 1080 = 420
scaleFactor = 1080 / 1080 = 1
最终左右两边偏移量 = 420 / 2 / 1 = 210
即"SafeRect"的左右两边最多可以改变210
这个最大值还需要乘滑动条传过来的偏移值,从0 - 1,即偏移值越大,两边收缩的越多
然后获得当前屏幕的"SafeArea",获取安全区的宽度,判断是否是异形屏,如果是异形屏,那么得到在当前屏幕下最大的调整值,即"SafeArea"的起点

最终调用SafeRectChange,这个方法通过改变"SafeRect"的offsetMin和offsetMax来改变其对于左右两边的距离,因为offsetMin是UI左下锚点到UI左下角的向量,offsetMax是UI右上锚点到UI右上角的向量



