Android對(duì)接Unity3D念赶,原生View與Unity3D視圖的層級(jí)關(guān)系
場(chǎng)景
- 1.很多人在對(duì)接unity的時(shí)候础钠,難免產(chǎn)品會(huì)讓你添加一些原生的view到unity視圖上,但是你會(huì)發(fā)現(xiàn)所添加的原生view并不能被看見(jiàn)叉谜,你甚至開(kāi)始懷疑自己的代碼旗吁,但是確實(shí)代碼沒(méi)有錯(cuò),只是原生的view被unity的UnityPlayer遮擋了停局,至于這個(gè)UnityPlayer后面會(huì)說(shuō)到很钓。
- 2.假如原生的activity只是單純(有些產(chǎn)品可能要求其他騷操作)的加載一個(gè)unity場(chǎng)景,但是這個(gè)場(chǎng)景的模型又非常大董栽,并且unity端沒(méi)有給你一個(gè)加載場(chǎng)景的動(dòng)畫(huà)码倦,那么很殘忍的結(jié)果就是這個(gè)加載unity場(chǎng)景的過(guò)程中會(huì)出現(xiàn)界面黑屏、白屏甚至花屏(華為出現(xiàn)過(guò))锭碳。這個(gè)時(shí)候產(chǎn)品發(fā)現(xiàn)不對(duì)袁稽,認(rèn)為應(yīng)該叫unity端加一個(gè)動(dòng)畫(huà),但是擒抛,很殘忍的是就算加了動(dòng)畫(huà)推汽,仍然會(huì)出現(xiàn)短暫的界面黑屏、白屏甚至花屏歧沪。那么聰明的原生就說(shuō)歹撒,我加一個(gè)原生的加載動(dòng)畫(huà)不就ojbk了?并且通過(guò)unity端的回調(diào)來(lái)取消這個(gè)動(dòng)畫(huà)不就非常優(yōu)雅了诊胞。然而暖夭,然而發(fā)現(xiàn)新加的原生動(dòng)畫(huà)看不到,看到的是黑屏、白屏甚至花屏鳞尔。好氣嬉橙!
分析
1、按照官方的文檔集成寥假,在原生中顯現(xiàn)Unity3D場(chǎng)景首先是創(chuàng)建一個(gè)Activity市框,然后在這個(gè)Activity中實(shí)例化一個(gè)UnityPlayer對(duì)象就可以了(詳細(xì)查看官方文檔),注意這里有一個(gè)細(xì)節(jié)就是在Activity中放置UnityPlayer這個(gè)對(duì)象最好是幀布局糕韧,相對(duì)布局會(huì)卡頓枫振,因?yàn)槲覀兌贾溃谠袖秩舅俣龋篎rameLayout>LinearLayout>RelativeLayout萤彩,親測(cè)粪滤,當(dāng)模型過(guò)大的時(shí)候,RelativeLayout的容器基本上用不了雀扶。以下是放置unity的界面代碼(不是小白直接跳過(guò)):
open abstract class UnityPlayerActivity : AppCompatActivity() {
protected lateinit var mUnityPlayer: BaseUnityPlayer // don't change the name of this variable; referenced from native code
//提示框
private var loading: LoadingDialog? = null
//白屏菊花
private var progress: ProgressDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFormat(PixelFormat.RGBX_8888) // <--- This makes xperia play happy
mUnityPlayer = BaseUnityPlayer(this)
mUnityPlayer.requestFocus()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
setIntent(intent)
}
override fun onResume() {
super.onResume()
mUnityPlayer.resume()
mUnityPlayer.windowFocusChanged(true)//unity主動(dòng)聚焦
}
override fun onPause() {
super.onPause()
mUnityPlayer.pause()
}
override fun onDestroy() {
mUnityPlayer.quit()
super.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mUnityPlayer.lowMemory()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
mUnityPlayer.lowMemory()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mUnityPlayer.configurationChanged(newConfig)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
mUnityPlayer.windowFocusChanged(hasFocus)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
return if (event.action == KeyEvent.ACTION_MULTIPLE) mUnityPlayer.injectEvent(event) else super.dispatchKeyEvent(event)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
return mUnityPlayer.injectEvent(event)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return mUnityPlayer.injectEvent(event)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return mUnityPlayer.injectEvent(event)
}
/*API12*/
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
return mUnityPlayer.injectEvent(event)
}
/**
* 用于網(wǎng)絡(luò)加載提示的彈窗
*/
protected fun showLoading() {
loading = loading ?: LoadingDialog()
loading?.setCallBack(object : LoadingDialog.OnDisMissCallBack {
override fun disMiss() {
cancelCurrent()
}
})
if (loading?.isAdded == false) {
loading?.show(supportFragmentManager, "Loading")
} else {
loading?.dialog?.show()
}
}
protected fun disMisLoading() {
loading?.dismiss()
}
/**
* U3D白屏界面上的靜態(tài)菊花
*/
protected fun showProgress() {
progress = progress ?: ProgressDialog.show(this, false, null)
if (!progress!!.isShowing) {
progress!!.show()
}
}
protected fun disMissProgress() {
progress!!.dismiss()
}
abstract fun cancelCurrent()
}
xml中(筆者放置UnityPlayer的界面布局):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:InnerView="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:CircleProgressBar="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.cninct.rmm.mvp.view.activity.MainUnityActivity">
<!--unity的宿主控件杖小,頂層和該層都是用幀布局,提高繪制效率-->
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<!--底部菜單欄-->
<include
android:id="@+id/layout_tab"
layout="@layout/view_main_tab"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"/>
......
Activity中:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_unity_main)
initView()
......
}
//首頁(yè)相關(guān)控件的初始化
private fun initView() {
fl_content.addView(mUnityPlayer)
......
}
2愚墓、在正常顯示之后就可以發(fā)現(xiàn)予权,其實(shí)真正顯示Unity的就是一個(gè)布局容器fl_content,而fl_content是通過(guò)addView的方式將UnityPlayer添加進(jìn)容器中浪册,所以UnityPlayer也是一個(gè)View扫腺,繼續(xù)查看UnityPlayer,一下是部分代碼:
public class UnityPlayer extends FrameLayout implements d {
public static Activity currentActivity = null;
private static boolean n;
c a = new c(this, (byte) 0);
i b = null;
private boolean c = false;
private boolean d = true;
private l e = new l();
private final ConcurrentLinkedQueue f = new ConcurrentLinkedQueue();
private BroadcastReceiver g = null;
private boolean h = false;
private a i = new a(this, (byte) 0);
private TelephonyManager j;
private j k;
private ContextWrapper l;
private SurfaceView m;
private boolean o;
private Bundle p = new Bundle();
private n q;
private boolean r = false;
private ProgressBar s = null;
private Runnable t = new Runnable() {
public final void run() {
int p = UnityPlayer.this.nativeActivityIndicatorStyle();
if (p >= 0) {
if (UnityPlayer.this.s == null) {
UnityPlayer.this.s = new ProgressBar(UnityPlayer.this.l, null, new int[]{16842874, 16843401, 16842873, 16843400}[p]);
UnityPlayer.this.s.setIndeterminate(true);
UnityPlayer.this.s.setLayoutParams(new LayoutParams(-2, -2, 51));
UnityPlayer.this.addView(UnityPlayer.this.s);
}
UnityPlayer.this.s.setVisibility(0);
UnityPlayer.this.bringChildToFront(UnityPlayer.this.s);
}
}
};
...
以上可以發(fā)現(xiàn)村象,其實(shí)UnityPlayer就是繼承的FrameLayout笆环,并且可以猜測(cè)這個(gè)FrameLayout中有至少一個(gè)SurfaceView顯示真正的模型場(chǎng)景等,因?yàn)槠胀╒iew是沒(méi)有這個(gè)能力去渲染的厚者,只有通過(guò)SurfaceView開(kāi)啟線程去渲染(證據(jù):在與Unity通信的時(shí)候躁劣,通過(guò)Unity的消息回調(diào)去更新UI會(huì)提示不能在非主線程中更新界面的提示)。所以原生View和Unity3D場(chǎng)景問(wèn)題其實(shí)就是在這個(gè)UnityPlayer中的SurfaceView與我們的原生View發(fā)生了Z-Index上的沖突库菲,或者說(shuō)是遮擋习绢。那么怎樣把我們的原生View放在UnityPlayer上面呢?其實(shí)我們首先需要把SurfaceView的Z-Index降低蝙昙。就像開(kāi)發(fā)地圖和多媒體播放一樣,存在地圖梧却、多媒體奇颠、普通View的顯示層級(jí),我們需要將這些層級(jí)處理一下放航,通過(guò)SurfaceView的setZOrderOnTop方法和setZOrderMediaOverlay方法烈拒。當(dāng)然如果沒(méi)有多媒體不需要設(shè)置setZOrderMediaOverlay方法。
解決問(wèn)題
直接貼代碼了:
class BaseUnityPlayer(contextWrapper: ContextWrapper) : UnityPlayer(contextWrapper) {
override fun addView(child: View) {
if (child is SurfaceView) {
child.setZOrderOnTop(false)
}
super.addView(child)
}
}