Refactor GlViewGroup to ViewRenderer

GlViewGroup doesn't work properly as an actual ViewGroup. For example,
it doesn't support addition of child views after instantiation. This
change turns the class into a renderer, which is also more consistent
with other classes in the package.

PiperOrigin-RevId: 275322295
This commit is contained in:
olly 2019-10-17 21:07:56 +01:00 committed by Oliver Woodman
parent 0c0a67bb93
commit 64786c6ce4
3 changed files with 157 additions and 132 deletions

View file

@ -21,8 +21,10 @@ import android.graphics.SurfaceTexture;
import android.opengl.Matrix;
import android.os.Bundle;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import androidx.annotation.BinderThread;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
@ -30,9 +32,9 @@ import androidx.annotation.UiThread;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.spherical.GlViewGroup;
import com.google.android.exoplayer2.ui.spherical.PointerRenderer;
import com.google.android.exoplayer2.ui.spherical.SceneRenderer;
import com.google.android.exoplayer2.ui.spherical.ViewRenderer;
import com.google.android.exoplayer2.util.Assertions;
import com.google.vr.ndk.base.DaydreamApi;
import com.google.vr.sdk.base.AndroidCompat;
@ -71,18 +73,18 @@ public abstract class GvrPlayerActivity extends GvrActivity {
// If a custom theme isn't specified, the Context's theme is used. For VR Activities, this is
// the old Android default theme rather than a modern theme. Override this with a custom theme.
Context theme = new ContextThemeWrapper(this, R.style.ExoVrTheme);
GlViewGroup glViewGroup = new GlViewGroup(theme, R.layout.exo_vr_ui);
View viewGroup = LayoutInflater.from(theme).inflate(R.layout.exo_vr_ui, /* root= */ null);
playerControlView = Assertions.checkNotNull(glViewGroup.findViewById(R.id.controller));
ViewRenderer viewRenderer = new ViewRenderer(/* context= */ this, gvrView, viewGroup);
playerControlView = Assertions.checkNotNull(viewGroup.findViewById(R.id.controller));
playerControlView.setShowVrButton(true);
playerControlView.setVrButtonListener(v -> exit());
sceneRenderer = new SceneRenderer();
PointerRenderer pointerRenderer = new PointerRenderer();
Renderer renderer = new Renderer(sceneRenderer, pointerRenderer, glViewGroup);
Renderer renderer = new Renderer(sceneRenderer, pointerRenderer, viewRenderer);
// Attach glViewGroup to gvrView in order to properly handle UI events.
gvrView.addView(glViewGroup);
// Standard GvrView configuration
gvrView.setEGLConfigChooser(
8, 8, 8, 8, // RGBA bits.
@ -104,7 +106,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
new ControllerManager(/* context= */ this, new ControllerManagerEventListener());
Controller controller = controllerManager.getController();
ControllerEventListener controllerEventListener =
new ControllerEventListener(controller, pointerRenderer, glViewGroup);
new ControllerEventListener(controller, pointerRenderer, viewRenderer);
controller.setEventListener(controllerEventListener);
}
@ -231,14 +233,14 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private final SceneRenderer sceneRenderer;
private final PointerRenderer pointerRenderer;
private final GlViewGroup glViewGroup;
private final ViewRenderer viewRenderer;
private final float[] viewProjectionMatrix;
public Renderer(
SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) {
SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, ViewRenderer viewRenderer) {
this.sceneRenderer = sceneRenderer;
this.pointerRenderer = pointerRenderer;
this.glViewGroup = glViewGroup;
this.viewRenderer = viewRenderer;
viewProjectionMatrix = new float[16];
}
@ -250,8 +252,8 @@ public abstract class GvrPlayerActivity extends GvrActivity {
Matrix.multiplyMM(
viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0);
sceneRenderer.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT);
if (glViewGroup.isVisible()) {
glViewGroup.getRenderer().draw(viewProjectionMatrix);
if (viewRenderer.isVisible()) {
viewRenderer.draw(viewProjectionMatrix);
pointerRenderer.draw(viewProjectionMatrix);
}
}
@ -262,7 +264,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override
public void onSurfaceCreated(EGLConfig config) {
onSurfaceTextureAvailable(sceneRenderer.init());
glViewGroup.getRenderer().init();
viewRenderer.init();
pointerRenderer.init();
}
@ -271,7 +273,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override
public void onRendererShutdown() {
glViewGroup.getRenderer().shutdown();
viewRenderer.shutdown();
pointerRenderer.shutdown();
sceneRenderer.shutdown();
}
@ -281,16 +283,16 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private final Controller controller;
private final PointerRenderer pointerRenderer;
private final GlViewGroup glViewGroup;
private final ViewRenderer viewRenderer;
private final float[] controllerOrientationMatrix;
private boolean clickButtonDown;
private boolean appButtonDown;
public ControllerEventListener(
Controller controller, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) {
Controller controller, PointerRenderer pointerRenderer, ViewRenderer viewRenderer) {
this.controller = controller;
this.pointerRenderer = pointerRenderer;
this.glViewGroup = glViewGroup;
this.viewRenderer = viewRenderer;
controllerOrientationMatrix = new float[16];
}
@ -319,7 +321,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
}
private void dispatchClick(int action, float yaw, float pitch) {
boolean clickedOnView = glViewGroup.simulateClick(action, yaw, pitch);
boolean clickedOnView = viewRenderer.simulateClick(action, yaw, pitch);
if (action == MotionEvent.ACTION_DOWN && !clickedOnView) {
togglePlayerControlVisibility();
}

View file

@ -1,114 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ui.spherical;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.AnyThread;
import androidx.annotation.UiThread;
import com.google.android.exoplayer2.util.Assertions;
/** This View uses standard Android APIs to render its child Views to a texture. */
public final class GlViewGroup extends FrameLayout {
private final CanvasRenderer canvasRenderer;
/**
* @param context The Context the view is running in, through which it can access the current
* theme, resources, etc.
* @param layoutId ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>)
*/
public GlViewGroup(Context context, int layoutId) {
super(context);
this.canvasRenderer = new CanvasRenderer();
LayoutInflater.from(context).inflate(layoutId, this);
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
Assertions.checkState(width > 0 && height > 0);
canvasRenderer.setSize(width, height);
setLayoutParams(new FrameLayout.LayoutParams(width, height));
}
/** Returns whether the view is currently visible. */
@UiThread
public boolean isVisible() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
if (getChildAt(i).getVisibility() == VISIBLE) {
return true;
}
}
return false;
}
@Override
public void dispatchDraw(Canvas notUsed) {
Canvas glCanvas = canvasRenderer.lockCanvas();
if (glCanvas == null) {
// This happens if Android tries to draw this View before GL initialization completes. We need
// to retry until the draw call happens after GL invalidation.
postInvalidate();
return;
}
// Clear the canvas first.
glCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// Have Android render the child views.
super.dispatchDraw(glCanvas);
// Commit the changes.
canvasRenderer.unlockCanvasAndPost(glCanvas);
}
/**
* Simulates a click on the view.
*
* @param action Click action.
* @param yaw Yaw of the click's orientation in radians.
* @param pitch Pitch of the click's orientation in radians.
* @return Whether the click was simulated. If false then the view is not visible or the click was
* outside of its bounds.
*/
@UiThread
public boolean simulateClick(int action, float yaw, float pitch) {
if (!isVisible()) {
return false;
}
PointF point = canvasRenderer.translateClick(yaw, pitch);
if (point == null) {
return false;
}
long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now, action, point.x, point.y, /* metaState= */ 1);
dispatchTouchEvent(event);
return true;
}
@AnyThread
public CanvasRenderer getRenderer() {
return canvasRenderer;
}
}

View file

@ -0,0 +1,137 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ui.spherical;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.google.android.exoplayer2.util.Assertions;
/** Renders a {@link View} on a quad and supports simulated clicks on the view. */
public final class ViewRenderer {
private final CanvasRenderer canvasRenderer;
private final View view;
private final InternalFrameLayout frameLayout;
/**
* @param context A context.
* @param parentView The parent view.
* @param view The view to render.
*/
public ViewRenderer(Context context, ViewGroup parentView, View view) {
this.canvasRenderer = new CanvasRenderer();
this.view = view;
// Wrap the view in an internal view that redirects rendering.
frameLayout = new InternalFrameLayout(context, view, canvasRenderer);
canvasRenderer.setSize(frameLayout.getMeasuredWidth(), frameLayout.getMeasuredHeight());
// The internal view must be added to the parent to ensure proper delivery of UI events.
parentView.addView(frameLayout);
}
/** Finishes constructing this object on the GL Thread. */
public void init() {
canvasRenderer.init();
}
/**
* Renders the view as a quad.
*
* @param viewProjectionMatrix Array of floats containing the quad's 4x4 perspective matrix in the
* {@link android.opengl.Matrix} format.
*/
public void draw(float[] viewProjectionMatrix) {
canvasRenderer.draw(viewProjectionMatrix);
}
/** Frees GL resources. */
public void shutdown() {
canvasRenderer.shutdown();
}
/** Returns whether the view is currently visible. */
@UiThread
public boolean isVisible() {
return view.getVisibility() == View.VISIBLE;
}
/**
* Simulates a click on the view.
*
* @param action Click action.
* @param yaw Yaw of the click's orientation in radians.
* @param pitch Pitch of the click's orientation in radians.
* @return Whether the click was simulated. If false then the view is not visible or the click was
* outside of its bounds.
*/
@UiThread
public boolean simulateClick(int action, float yaw, float pitch) {
if (!isVisible()) {
return false;
}
@Nullable PointF point = canvasRenderer.translateClick(yaw, pitch);
if (point == null) {
return false;
}
long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now, action, point.x, point.y, /* metaState= */ 1);
frameLayout.dispatchTouchEvent(event);
return true;
}
private static final class InternalFrameLayout extends FrameLayout {
private final CanvasRenderer canvasRenderer;
public InternalFrameLayout(Context context, View wrappedView, CanvasRenderer canvasRenderer) {
super(context);
this.canvasRenderer = canvasRenderer;
addView(wrappedView);
measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
Assertions.checkState(width > 0 && height > 0);
setLayoutParams(new FrameLayout.LayoutParams(width, height));
}
@Override
public void dispatchDraw(Canvas notUsed) {
@Nullable Canvas glCanvas = canvasRenderer.lockCanvas();
if (glCanvas == null) {
// This happens if Android tries to draw this View before GL initialization completes. We
// need to retry until the draw call happens after GL invalidation.
postInvalidate();
return;
}
// Clear the canvas first.
glCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// Have Android render the child views.
super.dispatchDraw(glCanvas);
// Commit the changes.
canvasRenderer.unlockCanvasAndPost(glCanvas);
}
}
}