一、前言
点关注不迷路,持续输出Unity
干货文章。
嗨,大家好,我是新发。之前做了iOS
刘海屏适配,参见我之前这篇文章:https://blog.csdn.net/linxinfa/article/details/87855958
然后最近有同学问我Unity
如何做Android
刘海屏适配,其实网上已经有不少人写了方法,不过帮人帮到底,我就封装一个jar
包,大家直接拿去用c#
调用即可。
二、最终效果演示
Unity
编辑器下的界面效果:
安装到真机上的效果(原谅我手机摄像头太渣了):
刘海在左边的情况:
刘海在右边的情况:
Unity Demo
工程我已上传到CodeChina
,感兴趣的同学可以自行下载学习。
地址:https://codechina.csdn.net/linxinfa/UnityAndroidNotchScreenFit
注,我使用的Unity
版本是:2020.2.7f1c1 (64-bit)
。
想直接下jar
包的戳这里:
https://codechina.csdn.net/linxinfa/UnityAndroidNotchScreenFit/-/raw/master/Assets/Plugins/Android/libs/notchhelper.jar
三、实现原理
为了方便大家理解,我画成图:
1、java接口:判断刘海屏
在java
中封装一个判断是否是刘海屏的接口:
// com.linxinfa.notchscreenfit.NotchScreenHelper
public static boolean hasNotch()
为了方便,我封装成了一个jar
包(源码参见文章末尾):
将其放到Unity
工程中的Plugins/Android/libs
目录中:
2、c#调用java接口
c#
中调用java
接口:
/// <summary>
/// 是否是刘海屏
/// </summary>
private bool hasNotch;
// ...
using (AndroidJavaClass jc = new AndroidJavaClass("com.linxinfa.notchscreenfit.NotchScreenHelper"))
{
hasNotch = jc.CallStatic<bool>("hasNotch");
Debug.Log("是否为刘海屏: " + hasNotch);
}
3、c#适配刘海屏
c#
根据是否是刘海屏,再根据屏幕横屏的方向,调整Panel
的anchor
。
注意:我并没有获取刘海的高度,我觉得刘海高度都差不多,都是40多个像素,所以我就直接写死44像素作为刘海高度,减少不要的代码。
/// <summary>
/// Panel
/// </summary>
public RectTransform panelRectTransform;
private Vector2 originalAnchorMin;
private Vector2 originalAnchorMax;
void Start()
{
// 记录panel原始的anchor
originalAnchorMin = panelRectTransform.anchorMin;
originalAnchorMax = panelRectTransform.anchorMax;
}
/// <summary>
/// 适配刘海屏
/// 通过设置Panel的anchor来调整边距
/// 如果刘海在左边,则是调整anchorMin,如果刘海在右边,则是调整anchorMax
/// </summary>
private void FitNotchScreen()
{
if (!hasNotch) return;
// 缩进44个像素,因为anchor是0到1的值,所以要除以屏幕宽度
var offset = 44f / Screen.width;
if (Screen.orientation == ScreenOrientation.LandscapeLeft)
{
panelRectTransform.anchorMin = new Vector2(originalAnchorMin.x + offset, originalAnchorMin.y);
panelRectTransform.anchorMax = originalAnchorMax;
}
else if (Screen.orientation == ScreenOrientation.LandscapeRight)
{
panelRectTransform.anchorMin = originalAnchorMin;
panelRectTransform.anchorMax = new Vector2(originalAnchorMax.x - offset, originalAnchorMax.y);
}
}
注意,ui
层级结构如下,是以Panel
为父节点的:
子节点设置anchor
以Panel
为参考系,这样子,调整Panel
的anchor
才可以影响到子节点的坐标。
如下,调整Panel
可以影响子节点ui
:
4、打包设置
在Player Settings
中勾选Render outside area
,否则刘海的那一行会是黑边。
为了测试横屏左右旋转,我们屏幕方向选择Auto Rotation
,并勾选Landscape Right
和Landscape Left
。
四、完整代码
1、c#代码
using UnityEngine;
using UnityEngine.UI;
public class Main : MonoBehaviour
{
/// <summary>
/// Panel
/// </summary>
public RectTransform panelRectTransform;
/// <summary>
/// log文本
/// </summary>
public Text logText;
/// <summary>
/// 检测刘海屏按钮
/// </summary>
public Button checkNotchBtn;
/// <summary>
/// 适配刘海屏按钮
/// </summary>
public Button fitNotchBtn;
/// <summary>
/// 是否是刘海屏
/// </summary>
private bool hasNotch;
private Vector2 originalAnchorMin;
private Vector2 originalAnchorMax;
/// <summary>
/// 原始屏幕方向
/// </summary>
private ScreenOrientation curOrientation;
void Start()
{
// 记录panel原始的anchor
originalAnchorMin = panelRectTransform.anchorMin;
originalAnchorMax = panelRectTransform.anchorMax;
curOrientation = Screen.orientation;
logText.text = "";
Application.logMessageReceived += OnUnityLog;
checkNotchBtn.onClick.AddListener(() =>
{
CheckNotchScreen();
});
fitNotchBtn.onClick.AddListener(() =>
{
FitNotchScreen();
});
// 自动执行一次
CheckNotchScreen();
FitNotchScreen();
}
private void Update()
{
// 屏幕方向改变
if (curOrientation != Screen.orientation)
{
// 执行适配
FitNotchScreen();
curOrientation = Screen.orientation;
}
}
/// <summary>
/// 检测是否是刘海屏
/// </summary>
private void CheckNotchScreen()
{
hasNotch = true;
using (AndroidJavaClass jc = new AndroidJavaClass("com.linxinfa.notchscreenfit.NotchScreenHelper"))
{
hasNotch = jc.CallStatic<bool>("hasNotch");
Debug.Log("是否为刘海屏: " + hasNotch);
}
}
/// <summary>
/// 适配刘海屏
/// 通过设置Panel的anchor来调整边距
/// 如果刘海在左边,则是调整anchorMin,如果刘海在右边,则是调整anchorMax
/// </summary>
private void FitNotchScreen()
{
if (!hasNotch) return;
Debug.Log("FitNotchScreen");
// 缩进44个像素,因为anchor是0到1的值,所以要除以屏幕宽度
var offset = 44f / Screen.width;
if (Screen.orientation == ScreenOrientation.LandscapeLeft)
{
panelRectTransform.anchorMin = new Vector2(originalAnchorMin.x + offset, originalAnchorMin.y);
panelRectTransform.anchorMax = originalAnchorMax;
}
else if (Screen.orientation == ScreenOrientation.LandscapeRight)
{
panelRectTransform.anchorMin = originalAnchorMin;
panelRectTransform.anchorMax = new Vector2(originalAnchorMax.x - offset, originalAnchorMax.y);
}
}
private void OnUnityLog(string condition, string stackTrace, LogType type)
{
logText.text += condition + "\n";
}
}
2、java代码
package com.linxinfa.notchscreenfit;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.DisplayCutout;
import android.view.View;
import android.view.WindowInsets;
import java.lang.reflect.Method;
import java.util.List;
import com.unity3d.player.UnityPlayer;
public class NotchScreenHelper
{
private static boolean hasExecuteNotch = false;
private static boolean isNotch = false;
// 是否是刘海或是钻孔屏幕 全局只取一次
@SuppressWarnings("unchecked")
public static boolean hasNotch() {
if (!hasExecuteNotch) {
isNotch = false;
// 通过UnityPlayer.currentActivity获取activity对象
Activity activity = UnityPlayer.currentActivity;
// 手机厂商
String manufacturer = Build.MANUFACTURER.toLowerCase();
switch (manufacturer)
{
case "huawei":
case "honour":
isNotch = hasNotchAtHuawei(activity);
break;
case "xiaomi":
isNotch = hasNotchAtXiaoMi(activity);
break;
case "oppo":
isNotch = hasNotchAtOPPO(activity);
break;
case "vivo":
isNotch = hasNotchAtVivo(activity);
break;
case "smartisan":
isNotch = hasNotchSamsung(activity);
break;
default:
isNotch = isOtherBrandHasNotch(activity);
break;
}
hasExecuteNotch = true;
}
return isNotch;
}
// 小米刘海屏判断
@SuppressWarnings("unchecked")
private static boolean hasNotchAtXiaoMi(Activity activity) {
int result = 0;
if (android.os.Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) {
try {
ClassLoader classLoader = activity.getClassLoader();
@SuppressWarnings("rawtypes")
Class SystemProperties = classLoader.loadClass("android.os.SystemProperties");
//参数类型
@SuppressWarnings("rawtypes")
Class[] paramTypes = new Class[2];
paramTypes[0] = String.class;
paramTypes[1] = int.class;
Method getInt = SystemProperties.getMethod("getInt", paramTypes);
//参数
Object[] params = new Object[2];
params[0] = new String("ro.miui.notch");
params[1] = new Integer(0);
result = (Integer) getInt.invoke(SystemProperties, params);
} catch (Exception e) {
e.printStackTrace();
}
}
return 1 == result;
}
// 华为刘海屏判断
@SuppressWarnings("unchecked")
private static boolean hasNotchAtHuawei(Activity activity) {
boolean ret = false;
try {
ClassLoader classLoader = activity.getClassLoader();
Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
ret = (boolean) get.invoke(HwNotchSizeUtil);
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
// OPPO刘海屏判断
@SuppressWarnings("unchecked")
private static boolean hasNotchAtOPPO(Activity activity) {
return activity.getPackageManager()
.hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
// VIVO刘海屏判断
private static final int VIVO_NOTCH = 0x00000020;//是否有刘海
private static final int VIVO_FILLET = 0x00000008;//是否有圆角
@SuppressWarnings("unchecked")
private static boolean hasNotchAtVivo(Activity activity) {
boolean ret = false;
try {
ClassLoader classLoader = activity.getClassLoader();
Class FtFeature = classLoader.loadClass("android.util.FtFeature");
Method method = FtFeature.getMethod("isFeatureSupport", int.class);
ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
// 判断三星手机是否有刘海屏
@SuppressWarnings("unchecked")
private static boolean hasNotchSamsung(Activity activity) {
if (android.os.Build.MANUFACTURER.equalsIgnoreCase("samsung")) {
try {
final Resources res = activity.getResources();
final int resId = res.getIdentifier("config_mainBuiltInDisplayCutout", "string", "android");
final String spec = resId > 0 ? res.getString(resId) : null;
return spec != null && !TextUtils.isEmpty(spec);
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
// 其他Android手机是否有刘海屏 通过Google Android SDK的方法进行判断
@TargetApi(Build.VERSION_CODES.P)
@SuppressWarnings("unchecked")
private static boolean isOtherBrandHasNotch(Activity activity) {
if (activity == null || activity.getWindow() == null) return false;
if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) return false;
View decorView = activity.getWindow().getDecorView();
WindowInsets windowInsets = decorView.getRootWindowInsets();
if (windowInsets == null) return false;
if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) return false;
DisplayCutout displayCutout = windowInsets.getDisplayCutout();
if (displayCutout == null) return false;
List<Rect> rects = displayCutout.getBoundingRects();
if (rects != null && rects.size() > 0) {
return true;
}
return false;
}
}
五、结束语
完毕。
喜欢Unity
的同学,不要忘记点击关注,如果有什么Unity
相关的技术难题,也欢迎留言或私信~
转载:https://blog.csdn.net/linxinfa/article/details/115346335