Android全局桌面寵物 Unity方案實現


轉載:https://blog.csdn.net/xssdmx/article/details/107315493

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();
}






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM