小言_互联网的博客

Unity Android刘海屏适配,帮你封装了jar,你不用写java了,直接用c#调用即可

497人阅读  评论(0)

一、前言

点关注不迷路,持续输出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#根据是否是刘海屏,再根据屏幕横屏的方向,调整Panelanchor

注意:我并没有获取刘海的高度,我觉得刘海高度都差不多,都是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为父节点的:

子节点设置anchorPanel为参考系,这样子,调整Panelanchor才可以影响到子节点的坐标。

如下,调整Panel可以影响子节点ui

4、打包设置

Player Settings中勾选Render outside area,否则刘海的那一行会是黑边。

为了测试横屏左右旋转,我们屏幕方向选择Auto Rotation,并勾选Landscape RightLandscape 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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场