Escalar un SurfaceView fijo para llenar verticalmente y mantener la relación de aspecto

He estado tratando de buscar una respuesta y parece que no puedo encontrar una (es un enfoque bastante poco convencional para programar un juego de Android, por lo que es de esperar, al menos eso creo).

Estoy tratando de crear un juego de rol estilo GBA en Android (por la nostalgia). OpenGL no era lo suficientemente bueno para mí porque no era lo suficientemente personalizable en el ciclo del juego (solo quiero que se llame a draw cuando haya algo que deba dibujarse, ya que quiero que este juego sea súper eficiente en la forma en que usa la batería, lo que significa hacer que actúe más como una aplicación de encuestas que como un juego). Creé una vista de superficie personalizada con su propio hilo que se llama cada vez que se necesita hacer un dibujo (lo modelé después del juego de Android LunarLander de muestra en el SDK).

La cuestión es que quiero que el juego en sí sea de un tamaño fijo (208x160) y luego se amplíe al tamaño de la pantalla. El problema que tengo es que no parece haber ninguna forma de hacerlo desde los parámetros normales de SurfaceView extendido en XML. El juego en su estado actual se ilustra a continuación. Estoy tratando de hacer que se estire a la altura de la pantalla y mantener la proporción en el ancho, sin agregar al juego visible (manteniéndolo fijo, independientemente del tamaño de la pantalla).

https://i.stack.imgur.com/EWIdE.png (Iba a publicar la imagen real en línea. La prevención de spam puede morderme >.<)

Actualmente obtengo el lienzo de SurfaceView y dibujo directamente en él, y proporciono mi tamaño como una dimensión de píxeles codificada (208 px, 160 px).

Así es como se ve el hilo actualmente al obtener el lienzo y dibujarlo. ¿Cuál sería una mejor manera de dibujar en un lienzo sin cambiar el tamaño que quiero que ocupe virtualmente el juego?

@Override
    public void run()
    {
        Canvas c = null;
        try
        {
            c = surfaceHolder.lockCanvas(null);
            synchronized (surfaceHolder)
            {
                draw(c);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (c != null)
                surfaceHolder.unlockCanvasAndPost(c);
        }
    }

El método de dibujo es este (StringBuilder es mi propio objeto codificado en el motor):

public void draw(Canvas canvas)
{       
    canvas.drawColor(Color.GRAY);
    StringBuilder stringBuilder = new StringBuilder(canvas, Color.BLACK, letters);
    stringBuilder.drawString("Oh, hello there!");
    stringBuilder.setLocation(10, 20);
    stringBuilder.drawString("Why am I so small?");
}

¿Alguna idea?

preguntado el 22 de mayo de 12 a las 19:05

¿Ha considerado escalar su lienzo para que se ajuste a la SurfaceView? -

@K-ballo El lienzo proviene de SurfaceView en sí ... llamar a run () (que es el método de ejecución del hilo) crea un lienzo y luego establece el lienzo en SurfaceHolder, que luego bloquea el lienzo, luego dibujo a y luego desbloquear y publicar en la vista cuando haya terminado. A menos que haya leído mal el código original de la muestra LunarLander... -

Entonces... ? ¿No puedes simplemente llamar? scale ¿en eso? -

Intenté llamar a escala, pero eso solo funciona si no me importa el tamaño del lienzo. En este caso, lo hago, y la vista establece el tamaño del lienzo. Si hago match_parent y luego escalo, se vuelve más grande, seguro, pero el tamaño del lienzo no es el tamaño en el que quiero que permanezca. Básicamente, solo quiero dibujar en un lienzo de tamaño fijo, y luego, tal vez, si hay una manera de dibujar eso en otro lienzo, pero escalado, eso podría funcionar para esto. -

En realidad, deberías dibujar en un lienzo. como si dibujar en un lienzo de tamaño fijo, pero inicialmente llamar scale para que los contenidos escalen adecuadamente manteniendo la relación de aspecto. -

2 Respuestas

Asume tu draw la función se renombra a drawBase en lugar de. Así es como lo haces:

public void draw(Canvas canvas)
{
    final float scaleFactor = Math.min( getWidth() / 208.f, getHeight() / 160.f );
    final float finalWidth = 208.f * scaleFactor;
    final float finalHeight = 160.f * scaleFactor;
    final float leftPadding = ( getWidth() - finalWidth ) / 2;
    final float topPadding =  ( getHeight() - finalHeight ) / 2;

    final int savedState = canvas.save();
    try {
        canvas.clipRect(leftPadding, topPadding, leftPadding + finalWidth, topPadding + finalHeight);

        canvas.translate(leftPadding, topPadding);
        canvas.scale(scaleFactor, scaleFactor);

        drawBase(canvas);
    } finally {
        canvas.restoreToCount(savedState);
    }
}

contestado el 22 de mayo de 12 a las 20:05

El problema es que obtengo un lienzo de SurfaceView y SurfaceView ya tiene un tamaño predeterminado del diseño. Aunque me gusta este código, todavía no crea una vista de juego que tenga una proporción fija de 1.3 con cajas de pilares como relleno en los lados. Todavía hay juego extra en el lado derecho y será una distracción para los jugadores. - el agresor

@TheAssailant: Allí, ajustó el código para relleno y recorte. - K-Ballo

¡Gracias! Esto funciona (casi) a la perfección. Para lo que necesito, simplemente configuro topMargin en 0 y utilicé la altura original suministrada para el recorte. Trabajado como un encanto. Pero sí, esto es perfecto, ¡muchas gracias! - el agresor

después de escalar, qué se puede hacer para desplazar la parte de escala de la vista de superficie... gracias por su preocupación... - kamal_tech_view

(La respuesta aceptada de K-ballo está bien, pero en los 2.5 años desde que se escribió, las pantallas se han vuelto mucho más densas, lo que significa que el renderizado de software es cada vez más costoso. Es importante obtener el hardware para ayudar a hacer el trabajo).

Para renderizar a una resolución específica, puede usar SurfaceHolder#setFixedSize(). Esto establece el tamaño de la superficie para que tenga las dimensiones exactas que especifique. La superficie se ampliará para que coincida con el tamaño de la Vista mediante el escalador de hardware, que será más eficiente que el escalado de software. La función se describe en este blog, y puedes verlo en acción en de grafika Actividad "Ejercitador escalador de hardware" (vídeo de demostración).

Puede mantener la relación de aspecto adecuada para pantallas de diferentes tamaños ajustando el tamaño de la superficie (que es lo que hace Grafika para esa demostración), o ajustando la vista para que el diseño sea tipo letter-box o pilar. Puede encontrar un ejemplo de esto último en la clase AspectFrameLayout de Grafika, que se usa para varias demostraciones de cámara y video.

Con respecto a esto:

OpenGL no era lo suficientemente bueno para mí porque no era lo suficientemente personalizable en el ciclo del juego (solo quiero que se llame a draw cuando haya algo que deba dibujarse...

Tienes dos opciones. Primero, puedes usar GLSurfaceView#setRenderMode() para deshabilitar el renderizado continuo. En segundo lugar, no necesita usar GLSurfaceView para usar OpenGL. La mayor parte del código GLES en Grafika opera en SurfaceView o TextureView.

Respondido el 05 de enero de 15 a las 17:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.