使用Libgdx的都知道,Libgdx使用的是GLSurfaceView,GLSurfaceView从Android 1.5(API level 3)开始加入,作为SurfaceView的补充。它可以看做是SurfaceView的1种典型使用模式。在SurfaceView的基础上,它加入了EGL的管理,并自带了渲染线程。
所以,GLSurfaceView还是和SurfaceView一样,有一个致命的点,就是SurfaveView是独立于Activity的view的,所以,GLSurfaceView只能在当前Activity的View的最上层或者最下层。
Activity包括的多个View会组成View hierachy的树形结构,只有最顶层的DecorView,也就是根结点视图,才是对WMS可见的。这个DecorView在WMS中有1个对应的WindowState,在SF中有对应的Layer。而SurfaceView自带1个Surface,这个Surface在WMS中有自己对应的WindowState,在SF中也会有自己的Layer。。简单理解如下图(图来源于互联网):
所以,一般的Libgdx用法有三种:
整个游戏都是Libgdx,只有一个GLSurfaceView
这种方法是官方推荐使用的方法了,就是纯游戏引擎的。不多说。
和原生结合,在部分页面嵌入Libgdx
部分页面使用和正常使用非常相似,不多说。
和原生结合,在部分页面的部分View嵌入Libgdx
这种方法因为上诉所说的原因,只能把GLSurfaceView放到最上层或者最下层,而且想要透明背景的话,只能放最上层
放置最上层的方法,初始化view完毕之后,转换为SurfaceView,然后设置setZOrderOnTop:
val view = initializeForView(application, cfg)
if (view is SurfaceView) {
//设置透明
view.holder.setFormat(PixelFormat.TRANSLUCENT)
//置于顶部
view.setZOrderOnTop(true)
}
放置最下层的方法,设置setZOrderMediaOverlay,此方法不能透明。
val view = initializeForView(application, cfg)
if (view is SurfaceView) {
view.setZOrderMediaOverlay(true)
}
根据一的分析之后,如果我们想和原生的view进行随意透明层叠,好像几乎是不可能!!!
但是,有解决方案。TextureView在4.0(API level 14)中引入,与SurfaceView一样继承View, 它可以将内容流直接投影到View中,它可以将内容流直接投影到View中,可以用于实现Live preview等功能。和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。 但是它既然有优点,也有缺点:
所以,上面所说的内容,俗话说就是:
SurfaceView 出现最早,解决类似视频播放的问题(可以用单独一个线程来渲染UI)。后来发现用起来不方便, 渲染线程需要单独编写,一大堆都可以独立成模板。所以后来就出现了GLSurfaceView, 概括一句话就是使用了模板的SurfaceView。再后来发现GLSurfaceView不能根据屏幕的变化而变化,这是由于SurfaceView同应用的Surface不是在同一层导致的问题。人们就想到把这个GLSurfaceView弄到应用的Surface中, 所以就产生了TextureView.
其实Android官方在Android4.0以后推出TextureView,本意就是想代替GLSurfaceView。可是TextureView在里面并没有像GLSurfaceView那样封装好一个绘制线程以及OpenGL的初始化。所以,在网友的努力下,GLTextureView出来了。
确定了方案之后,我们就把Libgdx的GLSurfaceview替换为GLTextureView来实现和原生view进行层叠的效果,这里用到的GLTextureview如下:
点我查看GLTextureview(貌似需要科学上网)
去github下载libgdx的源码,把gdx-backend-android的源码作为module导入到as,因为GLSurfaceview的创建就是这个包里面
可以看到,Surfaceview在AndroidGraphics
的139行的createGLSurfaceView
方法进行初始化
Surfaceview同级目录,创建textureview,把下载的GLTextureView放进去。然后在这个目录创建GLTextureView20,然后参照GLSurfaceView20编写,如下:
/**
* 针对Libgdx的GLTextureView
* @date 2019.03.13
* @author Skyhand
*/
public class GLTextureView20 extends GLTextureView {
static String TAG = "GLTextureView20";
final ResolutionStrategy resolutionStrategy;
static int targetGLESVersion;
public GLTextureView20 (Context context, ResolutionStrategy resolutionStrategy, int targetGLESVersion) {
super(context);
GLTextureView20.targetGLESVersion = targetGLESVersion;
this.resolutionStrategy = resolutionStrategy;
init(false, 16, 0);
}
public GLTextureView20 (Context context, ResolutionStrategy resolutionStrategy) {
this(context, resolutionStrategy, 2);
}
public GLTextureView20 (Context context, boolean translucent, int depth, int stencil, ResolutionStrategy resolutionStrategy) {
super(context);
this.resolutionStrategy = resolutionStrategy;
init(translucent, depth, stencil);
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
ResolutionStrategy.MeasuredDimension measures = resolutionStrategy.calcMeasures(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measures.width, measures.height);
}
@Override
public InputConnection onCreateInputConnection (EditorInfo outAttrs) {
// add this line, the IME can show the selectable words when use chinese input method editor.
if (outAttrs != null) {
outAttrs.imeOptions = outAttrs.imeOptions | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
}
BaseInputConnection connection = new BaseInputConnection(this, false) {
@Override
public boolean deleteSurroundingText (int beforeLength, int afterLength) {
int sdkVersion = android.os.Build.VERSION.SDK_INT;
if (sdkVersion >= 16) {
/*
* In Jelly Bean, they don't send key events for delete. Instead, they send beforeLength = 1, afterLength = 0. So,
* we'll just simulate what it used to do.
*/
if (beforeLength == 1 && afterLength == 0) {
sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_DEL);
return true;
}
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
@TargetApi(16)
private void sendDownUpKeyEventForBackwardCompatibility (final int code) {
final long eventTime = SystemClock.uptimeMillis();
super.sendKeyEvent(new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, code, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
super.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, KeyEvent.ACTION_UP, code, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
}
};
return connection;
}
private void init (boolean translucent, int depth, int stencil) {
this.setEGLContextFactory(new GLTextureView20.ContextFactory());
this.setEGLConfigChooser(translucent ? new GLTextureView20.ConfigChooser(8, 8, 8, 8, depth, stencil) : new GLTextureView20.ConfigChooser(5, 6, 5, 0, depth, stencil));
this.setSurfaceTextureListener(this);
if(translucent){
setOpaque(false);
}
}
static class ContextFactory implements GLTextureView.EGLContextFactory {
private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
@Override
public EGLContext createContext (EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
Log.w(TAG, "creating OpenGL ES " + GLTextureView20.targetGLESVersion + ".0 context");
checkEglError("Before eglCreateContext "+targetGLESVersion, egl);
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, GLTextureView20.targetGLESVersion, EGL10.EGL_NONE};
EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
boolean success = checkEglError("After eglCreateContext "+targetGLESVersion, egl);
if ((!success || context == null) && GLTextureView20.targetGLESVersion > 2) {
Log.w(TAG, "Falling back to GLES 2");
GLTextureView20.targetGLESVersion = 2;
return createContext(egl, display, eglConfig);
}
Log.w(TAG, "Returning a GLES "+targetGLESVersion+" context");
return context;
}
@Override
public void destroyContext (EGL10 egl, EGLDisplay display, EGLContext context) {
egl.eglDestroyContext(display, context);
}
}
static boolean checkEglError (String prompt, EGL10 egl) {
int error;
boolean result = true;
while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
result = false;
Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
}
return result;
}
private static class ConfigChooser implements GLTextureView.EGLConfigChooser {
public ConfigChooser (int r, int g, int b, int a, int depth, int stencil) {
mRedSize = r;
mGreenSize = g;
mBlueSize = b;
mAlphaSize = a;
mDepthSize = depth;
mStencilSize = stencil;
}
private static int EGL_OPENGL_ES2_BIT = 4;
private static int[] s_configAttribs2 = {EGL10.EGL_RED_SIZE, 4, EGL10.EGL_GREEN_SIZE, 4, EGL10.EGL_BLUE_SIZE, 4,
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE};
@Override
public EGLConfig chooseConfig (EGL10 egl, EGLDisplay display) {
int[] num_config = new int[1];
egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
int numConfigs = num_config[0];
if (numConfigs <= 0) {
throw new IllegalArgumentException("No configs match configSpec");
}
EGLConfig[] configs = new EGLConfig[numConfigs];
egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
return chooseConfig(egl, display, configs);
}
public EGLConfig chooseConfig (EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
for (EGLConfig config : configs) {
int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
if (d < mDepthSize || s < mStencilSize) {
continue;
}
int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize) {
return config;
}
}
return null;
}
private int findConfigAttrib (EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) {
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
return mValue[0];
}
return defaultValue;
}
private void printConfigs (EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
int numConfigs = configs.length;
Log.w(TAG, String.format("%d configurations", numConfigs));
for (int i = 0; i < numConfigs; i++) {
Log.w(TAG, String.format("Configuration %d:\n", i));
printConfig(egl, display, configs[i]);
}
}
private void printConfig (EGL10 egl, EGLDisplay display, EGLConfig config) {
int[] attributes = {EGL10.EGL_BUFFER_SIZE, EGL10.EGL_ALPHA_SIZE, EGL10.EGL_BLUE_SIZE, EGL10.EGL_GREEN_SIZE,
EGL10.EGL_RED_SIZE, EGL10.EGL_DEPTH_SIZE, EGL10.EGL_STENCIL_SIZE, EGL10.EGL_CONFIG_CAVEAT, EGL10.EGL_CONFIG_ID,
EGL10.EGL_LEVEL, EGL10.EGL_MAX_PBUFFER_HEIGHT, EGL10.EGL_MAX_PBUFFER_PIXELS, EGL10.EGL_MAX_PBUFFER_WIDTH,
EGL10.EGL_NATIVE_RENDERABLE, EGL10.EGL_NATIVE_VISUAL_ID, EGL10.EGL_NATIVE_VISUAL_TYPE,
0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
EGL10.EGL_SAMPLES, EGL10.EGL_SAMPLE_BUFFERS, EGL10.EGL_SURFACE_TYPE, EGL10.EGL_TRANSPARENT_TYPE,
EGL10.EGL_TRANSPARENT_RED_VALUE, EGL10.EGL_TRANSPARENT_GREEN_VALUE, EGL10.EGL_TRANSPARENT_BLUE_VALUE, 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
EGL10.EGL_LUMINANCE_SIZE, EGL10.EGL_ALPHA_MASK_SIZE, EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RENDERABLE_TYPE, 0x3042 // EGL10.EGL_CONFORMANT
};
String[] names = {"EGL_BUFFER_SIZE", "EGL_ALPHA_SIZE", "EGL_BLUE_SIZE", "EGL_GREEN_SIZE", "EGL_RED_SIZE",
"EGL_DEPTH_SIZE", "EGL_STENCIL_SIZE", "EGL_CONFIG_CAVEAT", "EGL_CONFIG_ID", "EGL_LEVEL", "EGL_MAX_PBUFFER_HEIGHT",
"EGL_MAX_PBUFFER_PIXELS", "EGL_MAX_PBUFFER_WIDTH", "EGL_NATIVE_RENDERABLE", "EGL_NATIVE_VISUAL_ID",
"EGL_NATIVE_VISUAL_TYPE", "EGL_PRESERVED_RESOURCES", "EGL_SAMPLES", "EGL_SAMPLE_BUFFERS", "EGL_SURFACE_TYPE",
"EGL_TRANSPARENT_TYPE", "EGL_TRANSPARENT_RED_VALUE", "EGL_TRANSPARENT_GREEN_VALUE", "EGL_TRANSPARENT_BLUE_VALUE",
"EGL_BIND_TO_TEXTURE_RGB", "EGL_BIND_TO_TEXTURE_RGBA", "EGL_MIN_SWAP_INTERVAL", "EGL_MAX_SWAP_INTERVAL",
"EGL_LUMINANCE_SIZE", "EGL_ALPHA_MASK_SIZE", "EGL_COLOR_BUFFER_TYPE", "EGL_RENDERABLE_TYPE", "EGL_CONFORMANT"};
int[] value = new int[1];
for (int i = 0; i < attributes.length; i++) {
int attribute = attributes[i];
String name = names[i];
if (egl.eglGetConfigAttrib(display, config, attribute, value)) {
Log.w(TAG, String.format(" %s: %d\n", name, value[0]));
} else {
// Log.w(TAG, String.format(" %s: failed\n", name));
while (egl.eglGetError() != EGL10.EGL_SUCCESS)
;
}
}
}
protected int mRedSize;
protected int mGreenSize;
protected int mBlueSize;
protected int mAlphaSize;
protected int mDepthSize;
protected int mStencilSize;
private int[] mValue = new int[1];
}
}
然后还得新建一个GdxEglConfigChooser
,参照surefaceview
里面的GdxEglConfigChooser
进行编写,这里不贴出来了,只是改了接口,其他一模一样的。 接下来会有用到这个类
此时,目录是这样子的:
Surfaceview
还是TextureView
,加一个配置,在AndroidApplicationConfiguration
添加是否使用textureview
的配置public boolean useTextureView = true;
protected GLTextureView.EGLConfigChooser getTextureEglConfigChooser () {
return new com.badlogic.gdx.backends.android.textureview.GdxEglConfigChooser(config.r, config.g, config.b, config.a, config.depth, config.stencil, config.numSamples);
}
SurfaceView
的操作,看到createGLSurfaceView
方法,添加TextureView的判断创建 protected View createGLSurfaceView (AndroidApplicationBase application, final ResolutionStrategy resolutionStrategy) {
if (!checkGL20()) throw new GdxRuntimeException("Libgdx requires OpenGL ES 2.0");
if(config.useTextureView){ //利用GLTextureView的方式创建
GLTextureView.EGLConfigChooser configChooser = this.getTextureEglConfigChooser();
GLTextureView view = new GLTextureView20(application.getContext(), resolutionStrategy, this.config.useGL30 ? 3 : 2);
if (configChooser != null) {
view.setEGLConfigChooser(configChooser);
} else {
view.setEGLConfigChooser(this.config.r, this.config.g, this.config.b, this.config.a, this.config.depth, this.config.stencil);
}
view.setOpaque(false);
view.setRenderer(this);
return view;
}else{
....旧版的GLSurfaceView创建
}
}
preserveEGLContextOnPause
方法,增加GLTextureView20
的判断 protected void preserveEGLContextOnPause () {
int sdkVersion = android.os.Build.VERSION.SDK_INT;
if (view instanceof GLTextureView20 || (sdkVersion >= 11 && view instanceof GLSurfaceView20) || view instanceof GLSurfaceView20API18) {
try {
view.getClass().getMethod("setPreserveEGLContextOnPause", boolean.class).invoke(view, true);
} catch (Exception e) {
Gdx.app.log(LOG_TAG, "Method GLSurfaceView.setPreserveEGLContextOnPause not found");
}
}
}
onPauseGLSurfaceView
方法,增加GLTextureView20
的判断 public void onPauseGLSurfaceView () {
if (view != null) {
if (view instanceof GLTextureView20) ((GLTextureView20)view).onPause();
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).onPause();
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).onPause();
}
}
onResumeGLSurfaceView
方法,增加GLTextureView20
的判断 public void onResumeGLSurfaceView () {
if (view != null) {
if (view instanceof GLTextureView20) ((GLTextureView20)view).onResume();
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).onResume();
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).onResume();
}
}
setContinuousRendering
方法,增加GLTextureView20
的判断 @Override
public void setContinuousRendering (boolean isContinuous) {
if (view != null) {
// ignore setContinuousRendering(false) while pausing
this.isContinuous = enforceContinuousRendering || isContinuous;
int renderMode = this.isContinuous ? GLSurfaceView.RENDERMODE_CONTINUOUSLY : GLSurfaceView.RENDERMODE_WHEN_DIRTY;
if (view instanceof GLTextureView20) ((GLTextureView20)view).setRenderMode(renderMode);
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).setRenderMode(renderMode);
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).setRenderMode(renderMode);
mean.clear();
}
}
setContinuousRendering
方法,增加GLTextureView20
的判断 @Override
public void setContinuousRendering (boolean isContinuous) {
if (view != null) {
// ignore setContinuousRendering(false) while pausing
this.isContinuous = enforceContinuousRendering || isContinuous;
int renderMode = this.isContinuous ? GLSurfaceView.RENDERMODE_CONTINUOUSLY : GLSurfaceView.RENDERMODE_WHEN_DIRTY;
if (view instanceof GLTextureView20) ((GLTextureView20)view).setRenderMode(renderMode);
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).setRenderMode(renderMode);
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).setRenderMode(renderMode);
mean.clear();
}
}
requestRendering
方法,增加GLTextureView20
的判断 @Override
public void requestRendering () {
if (view != null) {
if (view instanceof GLTextureView20) ((GLTextureView20)view).requestRender();
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).requestRender();
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).requestRender();
}
}
修改createGLSurfaceView即可,参考上面的修改AndroidGraphics
完成上面的步骤就已经完成了,然后Demo和例子已经开源到Github,欢迎Star。Github点我
原版SurfaceView置于底部效果:
原版SurfaceView透明置于顶部效果:
修改之后的TextureView与原生View进行透明层叠效果: