Pregunta

He visto a algunas personas preguntar cómo hacer zoom en un ViewGroup completo (como un RelativeLayout) de una vez. Por el momento, esto es algo que necesito lograr. El enfoque más obvio, para mí, sería mantener el factor de escala de zoom como una sola variable en algún lugar; y en cada uno de los métodos onDraw () de Views secundarios, ese factor de escala se aplicaría al Canvas antes de dibujar los gráficos.

Sin embargo, antes de hacer eso, he intentado ser inteligente (ja, generalmente una mala idea) y extender RelativeLayout, a una clase llamada ZoomableRelativeLayout. Mi idea es que cualquier transformación de escala podría aplicarse solo una vez al lienzo en la función dispatchDraw () anulada, de modo que no habría absolutamente ninguna necesidad de aplicar el zoom por separado en ninguna de las vistas secundarias.

Esto es lo que hice en mi ZoomableRelativeLayout. Es solo una extensión simple de RelativeLayout, con dispatchDraw () anulado:

protected void dispatchDraw(Canvas canvas){         
    canvas.save(Canvas.MATRIX_SAVE_FLAG);       
    canvas.scale(mScaleFactor, mScaleFactor);
    super.dispatchDraw(canvas);     
    canvas.restore();       
}

El mScaleFactor es manipulado por un ScaleListener en la misma clase.

Realmente funciona. Puedo pellizcar para hacer zoom en ZoomableRelativeLayout, y todas las vistas contenidas en él se reescalan correctamente.

Excepto que hay un problema. Algunas de esas vistas secundarias están animadas y, por lo tanto, llamo periódicamente a invalidate () en ellas. Cuando la escala es 1, esas vistas secundarias se vuelven a dibujar periódicamente perfectamente. Cuando la escala es distinta de 1, esas vistas secundarias animadas solo se actualizan en una parte de su área de visualización, o no se actualizan, según la escala de zoom.

Mi pensamiento inicial fue que cuando se llama a invalidate () de una vista secundaria individual, es posible que el sistema vuelva a dibujarla individualmente, en lugar de pasar un Canvas del dispatchDraw () del RelativeLayout principal, lo que significa que la vista secundaria termina actualizándose sin aplicar la escala de zoom. Pero, curiosamente, los elementos de las vistas secundarias que se vuelven a dibujar en la pantalla tienen la escala de zoom correcta. Es casi como si el área que el sistema decide actualizar realmente en el mapa de bits de respaldo permanece sin escalar, si eso tiene sentido. Para decirlo de otra manera, si tengo una sola vista infantil animada y gradualmente acerco más y más lejos de una escala inicial de 1, y si colocamos un cuadro imaginario en el área donde está esa vista infantil cuando la escala de zoom es 1 , entonces las llamadas a invalidate () solo causan una actualización de los gráficos en ese cuadro imaginario. Pero los gráficos que se ven para actualizar están hechos a la escala correcta. Si amplía tanto que la vista secundaria ahora se ha alejado completamente de donde estaba con una escala de 1, entonces no se verá ninguna parte de ella para actualizarse. Daré otro ejemplo: imagina que la vista de mi niño es una bola que se anima al cambiar entre amarillo y rojo. Si me acerco un poco de manera que la bola se mueva hacia la derecha y hacia abajo, en cierto punto solo verá el cuarto superior izquierdo de la bola con colores animados.

Si acerco y alejo la imagen continuamente, veo que las vistas de los niños se animan completa y correctamente. Esto se debe a que se está redibujando todo el ViewGroup.

Espero que esto tenga sentido; Intenté explicarlo lo mejor que pude. ¿Estoy un poco perdedor con mi estrategia ViewGroup ampliable? ¿Existe otra forma?

Gracias,

Trev

¿Fue útil?

Solución

Si está aplicando un factor de escala al dibujo de sus hijos, también debe aplicar el factor de escala apropiado a todas las demás interacciones con ellos: enviar eventos táctiles, invalidaciones, etc.

Entonces, además de dispatchDraw (), necesitará anular y ajustar apropiadamente el comportamiento de al menos estos otros métodos. Para ocuparse de las invalidaciones, deberá anular este método para ajustar las coordenadas secundarias de manera adecuada:

http:// developer.android.com/reference/android/view/ViewGroup.html#invalidateChildInParent (int [], android.graphics.Rect)

Si desea que el usuario pueda interactuar con las vistas secundarias, también deberá anular esto para ajustar las coordenadas táctiles de manera adecuada antes de que se envíen a los niños:

http://developer.android.com/reference/android /view/ViewGroup.html#dispatchTouchEvent(android.view.MotionEvent )

También le recomiendo encarecidamente que implemente todo esto dentro de una subclase de ViewGroup simple que administra una sola vista secundaria. Esto eliminará cualquier complejidad de comportamiento que RelativeLayout esté introduciendo en su propio ViewGroup, simplificando lo que necesita para tratar y depurar en su propio código. Coloque RelativeLayout como un elemento secundario de su ViewGroup de zoom especial.

Por último, una mejora para su código: en dispatchDraw () desea guardar el estado del lienzo después de aplicar el factor de escala. Esto asegura que el niño no pueda modificar la transformación que ha establecido.

Otros consejos

La excelente respuesta de hackbod me ha recordado que necesito publicar la solución a la que finalmente llegué. Tenga en cuenta que esta solución, que funcionó para mí para la aplicación que estaba haciendo en ese momento, podría mejorarse aún más con las sugerencias de Hackbod. En particular, no necesitaba manejar eventos táctiles, y hasta que leí la publicación de Hackbod no se me ocurrió que si lo hacía, también tendría que escalarlos.

En resumen, para mi aplicación, lo que necesitaba lograr era tener un diagrama grande (específicamente, el diseño del piso de un edificio) con otros pequeños símbolos de "marcadores" superpuestos. El diagrama de fondo y los símbolos de primer plano se dibujan usando gráficos vectoriales (es decir, objetos Path () y Paint () aplicados a Canvas en el método onDraw ()). La razón para querer crear todos los gráficos de esta manera, en lugar de simplemente usar recursos de mapa de bits, es porque los gráficos se convierten en tiempo de ejecución usando mi convertidor de imágenes SVG.

El requisito era que el diagrama y los símbolos de marcador asociados fueran todos elementos secundarios de un ViewGroup y se pudieran acercar todos juntos.

Mucho del código se ve desordenado (fue un trabajo urgente para una demostración) así que en lugar de simplemente copiarlo todo, intentaré explicar cómo lo hice con los fragmentos de código relevantes citados.

En primer lugar, tengo un ZoomableRelativeLayout.

public class ZoomableRelativeLayout extends RelativeLayout { ...

Esta clase incluye clases de escucha que amplían ScaleGestureDetector y SimpleGestureListener para que el diseño se pueda desplazar y ampliar. De particular interés aquí es el detector de gestos de escala, que establece una variable de factor de escala y luego llama a invalidate () y requestLayout (). No estoy estrictamente seguro en este momento si invalidate () es necesario, pero de todos modos, aquí está:

private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

@Override
public boolean onScale(ScaleGestureDetector detector){
    mScaleFactor *= detector.getScaleFactor();
    // Apply limits to the zoom scale factor:
    mScaleFactor = Math.max(0.6f, Math.min(mScaleFactor, 1.5f);
    invalidate();
    requestLayout();
    return true;
    }
}

Lo siguiente que tuve que hacer en mi ZoomableRelativeLayout fue anular onLayout (). Para hacer esto, encontré útil mirar los intentos de otras personas de un diseño ampliable, y también encontré muy útil mirar el código fuente original de Android para RelativeLayout. Mi método anulado copia mucho de lo que está en onLayout () de RelativeLayout pero con algunas modificaciones.

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
    int count = getChildCount();
    for(int i=0;i<count;i++){
        View child = getChildAt(i); 
        if(child.getVisibility()!=GONE){
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)child.getLayoutParams();
            child.layout(
                (int)(params.leftMargin * mScaleFactor), 
                (int)(params.topMargin * mScaleFactor), 
                (int)((params.leftMargin + child.getMeasuredWidth()) * mScaleFactor), 
                (int)((params.topMargin + child.getMeasuredHeight()) * mScaleFactor) 
                );
        }
    }
}

Lo que es significativo aquí es que cuando llamo a 'layout ()' en todos los hijos, estoy aplicando el factor de escala a los parámetros de diseño también para esos hijos. Este es un paso hacia la solución del problema de recorte, y también es importante que establezca correctamente la posición x, y de los niños entre sí para diferentes factores de escala.

Otra cosa clave es que ya no intento escalar el Canvas en dispatchDraw (). En su lugar, cada Vista secundaria escala su Canvas después de obtener el factor de escala del ZoomableRelativeLayout principal a través de un método getter.

A continuación, pasaré a lo que tenía que hacer dentro de las Vistas secundarias de mi ZoomableRelativeLayout. Solo hay un tipo de Vista que contengo como niños en mi ZoomableRelativeLayout; es una Vista para dibujar gráficos SVG que llamo SVGView. Por supuesto, el material SVG no es relevante aquí. Aquí está su método onMeasure ():

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    float parentScale = ((FloorPlanLayout)getParent()).getScaleFactor();
    int chosenWidth, chosenHeight;
    if( parentScale > 1.0f){
        chosenWidth =  (int) ( parentScale * (float)svgImage.getDocumentWidth() );
        chosenHeight = (int) ( parentScale * (float)svgImage.getDocumentHeight() );
    }
    else{
        chosenWidth =  (int) (  (float)svgImage.getDocumentWidth() );
        chosenHeight = (int) (  (float)svgImage.getDocumentHeight() );          
    }

    setMeasuredDimension(chosenWidth, chosenHeight);
}

Y onDraw ():

@Override
protected void onDraw(Canvas canvas){   
    canvas.save(Canvas.MATRIX_SAVE_FLAG);       

    canvas.scale(((FloorPlanLayout)getParent()).getScaleFactor(), 
            ((FloorPlanLayout)getParent()).getScaleFactor());       


    if( null==bm  ||  bm.isRecycled() ){
        bm = Bitmap.createBitmap(
                getMeasuredWidth(),
                getMeasuredHeight(),
                Bitmap.Config.ARGB_8888);


        ... Canvas draw operations go here ...
    }       

    Paint drawPaint = new Paint();
    drawPaint.setAntiAlias(true);
    drawPaint.setFilterBitmap(true);

    // Check again that bm isn't null, because sometimes we seem to get
    // android.graphics.Canvas.throwIfRecycled exception sometimes even though bitmap should
    // have been drawn above. I'm guessing at the moment that this *might* happen when zooming into
    // the house layout quite far and the system decides not to draw anything to the bitmap because
    // a particular child View is out of viewing / clipping bounds - not sure. 
    if( bm != null){
        canvas.drawBitmap(bm, 0f, 0f, drawPaint );
    }

    canvas.restore();

}

Una vez más, como descargo de responsabilidad, probablemente haya algunas verrugas en lo que publiqué allí y todavía tengo que revisar cuidadosamente las sugerencias de Hackbod e incorporarlas. Tengo la intención de volver y editar esto más. Mientras tanto, espero que pueda comenzar a proporcionar consejos útiles a otros sobre cómo implementar un ViewGroup ampliable.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top