Android全局桌面宠物 Unity方案实现_xssdmx的博客-程序员ITS301_unity lottie

技术标签: unity  安卓  Android Unity开发  java  android  移动开发  

Android全局桌面宠物 Unity方案实现

最近接到一个任务是Android设备上实现一个全局的指引动画,开始想着就用普通动画控件或者svga、lottie控件实现,最近正好在学习Unity,所以试着用unity实现。经过三天努力,居然实现了。话不多说,马上开始:

1、准备素材

在爱给网找到一个蝴蝶3D模型,然后通过3Dmax导出为FBX模型,然后倒入到unity里,具体操作相对比较简单,只是说明一下,模型纹理需要跟导出文件放在一起,否者到unity里面没有纹理皮肤。然后就是在unity修改Animation Type为Legacy,以及动画循环设置:Wrap Mode设置为Loop。
模型下载地址:http://www.aigei.com/3d/model/lactation/
在这里插入图片描述

2、导出透明Unity工程

Unity导出透明应用比较麻烦,耗时最多,找了很久只有这个文章有提及:
https://www.jianshu.com/p/a67f77cd2e62
也看了英文原地址:
https://forum.unity.com/threads/unity3d-export-to-android-with-transparent-background.512129/

于是开始尝试,按照文档所说,只需要修改两个地方就能实现:
1、修改Main Camera 的背景颜色为 Solid Color,并且透明度设置为0。
2、导出设置勾选preserveFramebufferAlpha。
我装的Unity 5.5.0f3 (64-bit)和Unity 2019.1.0a8 (64-bit),没有找到文档所说的preserveFramebufferAlpha选项,按文档所说的2018.1版本可以,我又装了Unity 2018.1.1f1 (64-bit),选项是有了,但是按照设置导出还是不透明。
反复排查和对比,发现我的颜色设置透明度不起作用,只显示6位颜色,而没有透明度显示:
在这里插入图片描述

只能尝试下载其他版本。
找到官方操作手册,发现这个版本也有设置选项
https://docs.unity3d.com/cn/2017.4/Manual/class-PlayerSettingsAndroid.html
于是尝试下了Unity 2017.4.2f2 (64-bit)版本,看到了8位的颜色值,心里大喜:

在这里插入图片描述
本来想着直接导出apk运行,但是一直报错,导出失败,怀疑是版本比较旧,尝试更换了旧版本NDKK和jdk版本,以及自定义了Gradle,都不行。最后只好导出工程,然后自己创建Android工程编译。还算顺利很快运行实现了效果。
中间还出现个小问题,就是渲染的图像颜色不对,有点过曝光。后来发现是camera颜色值问题,设置成#00000000后解决。
在这里插入图片描述

3、做全局window窗口

Unity直接导出的工程是activity显示动画,要做全局widow,需要把UnityPlayer放service里生成。我直接拷贝相关方法,放到service里生成,然后放入系统window,结果什么都不显示。
看UnityPlayer生成传入的context居然是activity类型,顿时心的都凉了。

我不会轻易放弃,我通过Application保存了全局的activity和UnityPlayer,然后从window里面获取,验证可行性,多次尝试都是显示空白。

public class MainApp extends Application {

    private static Activity mActivity;
    static UnityPlayer mUnityPlayer;

    public static Application app;
    @Override
    public void onCreate() {
        super.onCreate();
        app = this;
    }

    public static Activity getActivity() {
        return mActivity;
    }

    public static void setActivity(Activity activity) {
        mActivity = activity;
    }

    public static UnityPlayer getUnityPlayer() {
        return mUnityPlayer;
    }

    public static void setUnityPlayer(UnityPlayer unityPlayer) {
        mUnityPlayer = unityPlayer;
    }
}

偶然间发现,UnityPlayer在activity里面生成,并且生命周期全部放activity,只是把UnityPlayer加载到window窗口,居然可以实现。只是activity不能切换后台,切换到后台动画就暂停了。

尝试各参数,最后发现是 mUnityPlayer.windowFocusChanged(true);这句对显示有关键作用。
于是推到重来,new UnityPlayer用了getApplicationContext,成功!
所以改变service全部生成,结果成功。
代码如下:

package com.Company.bgTest;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

import com.unity3d.player.UnityPlayer;

/**
 * @author hardy
 * @name My Application
 * @class name:com.Company.bgTest
 * @class describe:
 * @time 2020/7/10 15:47
 * @change
 * @chang time
 * @class describe
 */
public class MainService extends Service {
    
    //Log用的TAG
    private static final String TAG = "MainService";

    //要引用的布局文件.
    LinearLayout toucherLayout;
    //布局参数.
    WindowManager.LayoutParams params;
    //实例化的WindowManager.
    WindowManager windowManager;

    //状态栏高度.(接下来会用到)
    int statusBarHeight = -1;

    protected UnityPlayer mUnityPlayer;

    @Override
    public IBinder onBind(Intent intent) {
    
        return null;
    }


    @Override
    public void onCreate() {
    
        super.onCreate();
        Log.i(TAG, "MainService Created");
        //OnCreate中来生成悬浮窗.
        createToucher();
    }


    private void createToucher() {
    
        //赋值WindowManager&LayoutParam.
        params = new WindowManager.LayoutParams();
        windowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
        //设置type.系统提示型窗口,一般都在应用程序窗口之上.
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        //设置效果为背景透明.
        params.format = PixelFormat.RGBA_8888;
        //设置flags.不可聚焦及不可使用按钮对悬浮窗进行操控.
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

        //设置窗口初始停靠位置.
        params.gravity = Gravity.LEFT | Gravity.TOP;
        params.x = 0;
        params.y = 0;

        //设置悬浮窗口长宽数据.
        //注意,这里的width和height均使用px而非dp.这里我偷了个懒
        //如果你想完全对应布局设置,需要先获取到机器的dpi
        //px与dp的换算为px = dp * (dpi / 160).
        params.width = 400;
        params.height = 600;

        LayoutInflater inflater = LayoutInflater.from(getApplication());
        //获取浮动窗口视图所在布局.
        toucherLayout = (LinearLayout) inflater.inflate(R.layout.pet_window, null);
        //添加toucherlayout
        windowManager.addView(toucherLayout, params);

        Log.i(TAG, "toucherlayout-->left:" + toucherLayout.getLeft());
        Log.i(TAG, "toucherlayout-->right:" + toucherLayout.getRight());
        Log.i(TAG, "toucherlayout-->top:" + toucherLayout.getTop());
        Log.i(TAG, "toucherlayout-->bottom:" + toucherLayout.getBottom());

        //主动计算出当前View的宽高信息.
        toucherLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);

        //用于检测状态栏高度.
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
    
            statusBarHeight = getResources().getDimensionPixelSize(resourceId);
        }
        Log.i(TAG, "状态栏高度为:" + statusBarHeight);

        mUnityPlayer =  new UnityPlayer(this.getApplicationContext());
//        mUnityPlayer = MainApp.getUnityPlayer();
        ((RelativeLayout) toucherLayout.findViewById(R.id.rl_pet)).addView(mUnityPlayer);
        mUnityPlayer.start();
        mUnityPlayer.resume();

        mUnityPlayer.setOnTouchListener(new View.OnTouchListener() {
    
            @Override
            public boolean onTouch(View v, MotionEvent event) {
    
                //ImageButton我放在了布局中心,布局一共300dp
                params.x = (int) event.getRawX() - 150;
                //这就是状态栏偏移量用的地方
                params.y = (int) event.getRawY() - 150 - statusBarHeight;
                windowManager.updateViewLayout(toucherLayout,params);
                return false;
            }
        });
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    


        mUnityPlayer.windowFocusChanged(true);

        return super.onStartCommand(intent, flags, startId);
    }

    // Quit Unity
    @Override public void onDestroy ()
    {
    
        mUnityPlayer.pause();
        mUnityPlayer.stop();
        mUnityPlayer.quit();
        super.onDestroy();
    }
}


在这里插入图片描述

另外值得一提的是全局悬浮窗需要设置权限,参考 https://www.jianshu.com/p/ac63c57d2555:

  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/>

以及代码判断用户手动开启:

        //当AndroidSDK>=23及Android版本6.0及以上时,需要获取OVERLAY_PERMISSION.
//使用canDrawOverlays用于检查,下面为其源码。其中也提醒了需要在manifest文件中添加权限.
        /**
         * Checks if the specified context can draw on top of other apps. As of API
         * level 23, an app cannot draw on top of other apps unless it declares the
         * {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission in its
         * manifest, <em>and</em> the user specifically grants the app this
         * capability. To prompt the user to grant this approval, the app must send an
         * intent with the action
         * {@link android.provider.Settings#ACTION_MANAGE_OVERLAY_PERMISSION}, which
         * causes the system to display a permission management screen.
         *
         */
        if (Build.VERSION.SDK_INT >= 23) {
            if (Settings.canDrawOverlays(UnityPlayerActivity.this)) {
                Intent intent = new Intent(UnityPlayerActivity.this, MainService.class);

                Toast.makeText(UnityPlayerActivity.this, "已开启Toucher", Toast.LENGTH_SHORT).show();
//                startService(intent);
//                finish();

//                moveTaskToBack(true);
            } else {
                //若没有权限,提示获取.
                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                Toast.makeText(UnityPlayerActivity.this, "需要取得权限以使用悬浮窗", Toast.LENGTH_SHORT).show();
                startActivity(intent);
            }
        } else {
            //SDK在23以下,不用管.
            Intent intent = new Intent(UnityPlayerActivity.this, MainService.class);

            startService(intent);

            moveTaskToBack(true);
//            finish();
        }
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xssdmx/article/details/107315493

智能推荐

convert oracle 字符串_Oracle Convert()函数_weixin_39521009的博客-程序员ITS301

在 Oracle 中,Convert() 函数可以将字符串从一个字符集转换为另一个字符集。本文要为大家带来的就是 Convert() 函数的使用方法。句法Convert() 函数语法CONVERT( string1, char_set_to [, char_set_from] )string1:要转换的字符串。char_set_to:要转换为的字符集。char_set_from:可选的,要从中转换...

linux 安装actrive mq,ActiveMQ学习之jdbc消息持久化_孤独的根号 三的博客-程序员ITS301

1、将mysql的驱动包复制到activemq的lib目录下将mysql驱动包(驱动名称:mysql-connector-java-5.1.46.jar)复制到activemq的lib文件夹内2、配置persistenceAdapter的jdbc进入到安装目录根目录下conf文件夹中,打开activemq.xml,修改成jdbc的持久化修改之前: 修改后: 3、数据库连接池配置将下面配置到acti...

systemd设置nginx开机自启动_运维猫(运维开发)的博客-程序员ITS301

1、简介服务器每次重启,都需要手动启动一些服务,这不是一个程序员可以忍受的,难怪大家都喜欢写脚本。CentOS7之后已不再使用chkconfig管理启动项,而是使用systemd。Lin...

单目摄像头与激光雷达的信息融合_Mr.Naruto的博客-程序员ITS301_单目与雷达融合

本科毕设,我做的就是这个摄像头和雷达信息的融合。正当我愁怎么下手呢,恰好我看到一篇论文,讲的就是这个方面已取得的成绩,下面我主要分享看这篇论文的感受。本人英文水平,学术能力有限,如有错误,欢迎留言。                     LIMO:Lidar-Monocular Visual Odometry首先分析了摄像头的缺点。摄像头:在进行特征点的捕捉时,可能会有误差,如...

php和java做web的区别_PHP与JAVA做WEB开发的一些区别_美自的博客-程序员ITS301

一.SESSION使用方法的区别PHP中的SESSION的使用方法:1.每次使用SESSION必须使用SESSION_START();2.注册SESSION使用SESSION_REGISTER();3.SESSION初始化使用$_SESSION["SESSION名"];4.SESSION使用完后须使用UNSET()或SESSION_UNREGISTER()方法关闭SESSION.(其中第2,4点在...

随便推点

PHP的cURL选项CURLOPT_SSL_VERIFYPEER详解_焚膏油以继晷,恒兀兀以穷年的博客-程序员ITS301_ssl_verifypeer

在开发微信支付的过程中,遇到了关于cURL加密传输的问题,做下记录方便今后查阅。提交数据到https时,需要pem证书来加密。我们使用浏览器访问https的时候,浏览器会自动加载网站的安全证书进行加密。但是你用curl请求https时,没有通过浏览器,就只有自己手动增加一个安全证书进行加密。代码示例:01private funct_1671465600

ora2mysql 工具下载_Oracle 转 PostgreSQL 工具(ora2pg)_鄂奎阿的博客-程序员ITS301

Ora2pg可以用来将Oracle数据库转换成PostgreSQL。ora2pg 11.2 修复很多主要的问题,特别是直接导入数据到 PG 和 Windows 下的移植版本,这两个地方都会导致程序崩溃。ora2pg 11.0 支持多处理器,可使用并行模式全速进行数据导入导出,数据导入性能提升 10 倍以上。多处理器支持让 Ora2Pg 的速度接近 ETL 工具的速度;同时该版本增加新的导出类型来生...

c0000005 Access Violation_Vinx911的博客-程序员ITS301_c0000005 access_violation

出现这种错误原因总结:1. 指针异常。引用指针前判断指针是否为空2. 数据越界。一个典型例子,char ValueName[256];strncpy(ValueName,&amp;amp;value-&amp;gt;Name,value-&amp;gt;NameLength);value-&amp;gt;NameLength 大于了256,致使ValueName访问越界,崩溃顺便记录崩溃调试的方法...

第2章 - 程序的基本结构_2112222222222的博客-程序员ITS301

2.1 - 2.4 启动第一个flask服务器from flask import Flaskapp = Flask(__name__)#申请一个处理客户端请求的对象@app.route('/')#路由:处理url请求和python函数的映射关系,客户端请求的url('/')会相应的在这部分处理def index(): #视图函数 ,返回值是响应,也就是客户端收到的内容...

Plato Farm通过LaaS协议Elephant Swap,为社区用户带来全新体验_大个子音乐家的博客-程序员ITS301

将成为刚需,因为大家都需要ePLATO来进一步解锁ePLATO.而在ElephantSwap。资产,我们需要在ElephantSwap中进行交易行为来解锁ePLATO.ePLATO。投资回报,据悉在PLATO代coin上线ElephantSwap后,将能够为投资。在PLATO上线ElephantSwap后,任何持有PLATO。而在PLATO上线ElephantSwap后,ePLATO。比如PLATO,需要将其在ElephantSwap中进行。...

ctrip-apollo多环境部署-史上最简单_cf的博客-程序员ITS301

一套Portal可以管理多个环境,但是每个环境都需要独立部署一套Config Service、Admin Service和ApolloConfigDB,apollo 0.10.2版本默认支持的环境为:LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS(ps:环境枚举类, com.ctrip.framework.apollo.core.enums.Env, 添加自...

推荐文章

热门文章

相关标签