¿Por qué mi vista personalizada de Android no es cuadrada?

He creado una vista personalizada para mostrar un tablero para un juego que estoy desarrollando. El tablero de juego debe ser un cuadrado en todo momento. Así que con y la altura debe ser la misma. He seguido este tutorial para lograr esto.

He añadido el siguiente código a mi vista personalizada:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight(); int heigthWithoutPadding = height - getPaddingTop() - getPaddingBottom(); int maxWidth = (int) (heigthWithoutPadding * RATIO); int maxHeight = (int) (widthWithoutPadding / RATIO); if (widthWithoutPadding > maxWidth) { width = maxWidth + getPaddingLeft() + getPaddingRight(); } else { height = maxHeight + getPaddingTop() + getPaddingBottom(); } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint p = new Paint(); p.setStyle(Paint.Style.STROKE); p.setStrokeWidth(3); canvas.drawRect(0, 0, getMeasuredWidth() - 1, getMeasuredHeight() - 1, p); } 

El diseño con mi vista personalizada se ve así:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <view android:layout_width="wrap_content" android:layout_height="wrap_content" class="com.peerkesoftware.nanograms.controls.GameBoard" android:id="@+id/view" android:background="@android:color/holo_purple"/> </RelativeLayout> 

El cuadrado que se dibuja en el método onDraw es siempre un cuadrado. Eso funciona bien.

En el diseño agrego la vista personalizada y le doy a la vista un color de fondo. Cuando la exhibo en un dispositivo en modo del retrato todo trabaja muy bien y el color de fondo está llenando para arriba el cuadrado que dibujo en el método del onDraw.

Cuando cambio el dispositivo al modo horizontal, el cuadrado sigue siendo un cuadrado, pero el área del color de fondo es más grande que eso. Tiene la misma altura, pero tiene más ancho. Pero no tengo ni idea de por qué. Los métodos getMeasuredWidth () y getMeasuredHeight () devuelven los valores correctos. ¿Cómo puede ser que la vista sea aún más grande entonces?

Por alguna razón, su vista no funciona en RelativeLayout. Para la orientación horizontal, la medición termina con las siguientes limitaciones (en mi teléfono):

Width = MeasureSpec: EXACTAMENTE 404; Height = MeasureSpec: AT_MOST 388

Debido a este ancho real termina por encima de la anchura medida (en mi teléfono medido = 388, actual = 404). Por eso ve que el fondo se extiende más allá de su rect. Puede comprobarlo usted mismo agregando un registro a onDraw y onMeasure.

Además, respetando el modo de medición no ayuda, la medición continúa con el mismo resultado que anteriormente.

La solución es usar un diseño diferente. LinearLayout y FrameLayout funcionaron para mí. También es una buena idea usar el tamaño real de la vista en onDraw, con getWidth () y getHeight ().

Si realmente necesita RelativeLayout, puede agregar un FrameLayout secundario para mantener su vista de la siguiente manera:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" > <view android:id="@+id/view" android:layout_width="wrap_content" android:layout_height="wrap_content" class="com.peerkesoftware.nanograms.controls.GameBoard" android:background="@android:color/holo_purple" /> </FrameLayout> </RelativeLayout> 

Subclase ImageView y anula onMeasure y pasa a

 super.onMeasure(widthMeasureSpec, widthMeasureSpec); 

toda la clase:

 public class SquaredImageView extends ImageView { public SquaredImageView(Context context) { super(context); } public SquaredImageView(Context context, AttributeSet attrs) { super(context, attrs); } public SquaredImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, widthMeasureSpec); } } 

Edit: He intentado con una vista normal

 public class CustomView extends View { public CustomView(Context context) { super(context); setBackgroundColor(Color.RED); } public CustomView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int heigth = getMeasuredHeight(); int dimen = (width > heigth) ? heigth : width; setMeasuredDimension(dimen, dimen); } } 

Y un TestActivity

 public class TestActivity extends Activity { protected void onCreate(android.os.Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new CustomView(this)); } } 

Dibuja un rectángulo rojo cuadrado en la pantalla de mi móvil

Este es mi código, estoy usando este código y está funcionando bien. No es exacly a lo que usted está buscando pero puede darle una idea. Sobre la base de mi requisito altura de la vista de la imagen debe ser la misma longitud que el ancho.

 public class SquareImageView extends ImageView { private static final String TAG = "SquareImageView"; public SquareImageView(Context context) { super(context); } public SquareImageView(Context context, AttributeSet attrs) { super(context, attrs); } public SquareImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ super.onMeasure(widthMeasureSpec, heightMeasureSpec); //int h = this.getMeasuredHeight(); int w = this.getMeasuredWidth(); setMeasuredDimension(w, w); } } 

Definido en xml como:

 <com.something.widget.SquareImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/ivProfilePicture" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_alignParentTop="true" android:adjustViewBounds="true" android:scaleType="fitXY" android:background="@drawable/bg_curve_white_2" android:contentDescription="@string/general_content_description" android:src="@drawable/ic_default_logo" android:layout_marginTop="@dimen/side_margin"/> 

Y finalmente en Fragment es algo así como:

 SquareImageView ivProfilePicture = (SquareImageView) view.findViewById(R.id.ivProfilePicture); 

No estoy seguro de qué solución servirá la necesidad, pero el código del tutorial tiene un problema de diseño fundamental – no interpreta los parámetros onMeasure() de onMeasure() correctamente. Respuesta en la explicación de vista personalizada de onMeasure explica la correcta interpretación de estos parámetros.

Lo que sucede en este ejemplo, onMeasure() se invoca dos veces para cada aparición de dibujo para la vista personalizada.

Para la primera llamada, ambos MeasureSpecs especifican el modo AT_MOST y dan valores iniciales sin tener en cuenta los márgenes y el relleno. Por lo tanto, el código del tutorial legítimamente hace que el ancho y la altura coincidan.

Para la segunda llamada, sin embargo, contiene RelativeLayout toma los márgenes y el relleno en cuenta, y pasa MeasureSpec para el Width con el modo EXACT , mientras que la Height se pasa en el modo AT_MOST .

Para la pantalla vertical, esto funciona bien porque el Width se redujo por los márgenes, y el valor de la altura sigue siendo la altura de la pantalla (también reducido por los márgenes). Por lo tanto, durante el segundo código de llamada, legítimamente reduce la altura para que coincida ahora con un ancho menor, y todo funciona correctamente.

Sin embargo, para la orientación horizontal de la pantalla, "Ancho" se ajusta al valor definido durante la primera llamada, mientras que Height se reduce por los márgenes. Cuando el código intenta establecer el Width para que coincida con la Height , esta operación de configuración no funciona correctamente porque el modo Width es EXACT . Como resultado, las propiedades de la vista Width y MeasuredWidth terminan con valores diferentes.

Hay un par de formas sencillas de hacer que el código en cuestión funcione: simplemente puede eliminar márgenes verticales:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/zero" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/zero" tools:context=".MainActivity" > 

O, si los márgenes son importantes, puede cambiar el diseño que contiene a absoluto:

 <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > 

En ambos casos parece tener sentido cambiar la implementación de onMeasure() para respetar el modo EXACT (para registrar un problema al menos).

Su código onMeasure() no es sencillo. Cambiar el código de la siguiente manera y establecer match_parent como el ancho y la altura en xml (En realidad no importa)

 void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight(); int heigthWithoutPadding = height - getPaddingTop() - getPaddingBottom(); int maxWidth = (int) (heigthWithoutPadding * RATIO); int maxHeight = (int) (widthWithoutPadding / RATIO); if (widthWithoutPadding > maxWidth) { width = maxWidth + getPaddingLeft() + getPaddingRight(); } else { height = maxHeight + getPaddingTop() + getPaddingBottom(); } setMeasuredDimension(width, height); } 
  • Crear un AlertDialog en Android con la vista xml personalizada
  • MonoDroid: Error al llamar al constructor de la vista personalizada - TwoDScrollView
  • RecyclerView y Data Binding no funcionan
  • El constructor de vista personalizada en Android 4.4 se bloquea en Kotlin, ¿cómo arreglarlo?
  • Cómo iniciar correctamente la actividad desde la vista personalizada (Java Class)?
  • Cómo agregar desplazamiento para una vista personalizada en Android
  • Vista personalizada como bloqueo / desbloqueo de la pantalla de Android
  • OnDraw eficiente de ArrayList
  • Vista de calibre simple como speedmeter en android?
  • Comportamiento extraño de View.getHitRect ()
  • Los niños de Vista personalizada se null después de inflar la vista en Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.