2013-06-21 2 views
8

Я создал пользовательский вид для отображения игрового поля для игры, которую я разрабатываю. Игровая площадка должна быть квадратной во все времена. Таким образом, и высота, и высота должны быть одинаковыми. Я добился этого this tutorial.Почему мой андроид не является квадратным?

Я добавил следующий код в мой настраиваемое представление:

@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); 
} 

Компоновка с моей настраиваемое представление выглядит следующим образом:

<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> 

квадрат, который рисуется в методе OnDraw всегда является квадрат. Это прекрасно работает.

В макете я добавляю пользовательский вид и придаю виду цвет фона. Когда я показываю его на устройстве в портретном режиме, все работает отлично, а цвет фона заполняет квадрат, который я рисую методом onDraw.

Когда я переключаю устройство в альбомный режим, квадрат по-прежнему является квадратом, но область цвета фона больше. Он имеет ту же высоту, но имеет большую ширину. Но я понятия не имею, почему. Методы getMeasuredWidth() и getMeasuredHeight() возвращают правильные значения. Как может быть, что вид еще больше, чем это?

+2

Как вы определяете пользовательский вид в макете? – bplayer

+0

С какой площадью это ширина больше в ландшафте? Наверное, не так, но вы используете разные макеты для портрета и пейзажа? – Luksprog

ответ

5

По какой-то причине ваш взгляд не работает в RelativeLayout. Для измерения ориентации на местности заканчиваются следующие ограничения (на моем телефоне):

width = MeasureSpec: ТОЧНО 404; height = MeasureSpec: AT_MOST 388

Из-за этой фактической ширины заканчивается отличная от измеренной ширины (на моем телефоне измерено = 388, фактическое = 404). Вот почему вы видите фон, выходящий за пределы вашего прямоугольника. Вы можете проверить это самостоятельно, добавив некоторые записи в onDraw и onMeasure.

Кроме того, соблюдение режима измерения не помогает, измерение по-прежнему заканчивается тем же результатом, что и выше.

Решение заключается в использовании другого макета. LinearLayout и FrameLayout работали для меня. Также полезно использовать фактический размер в onDraw, с getWidth() и getHeight().

Если вам действительно нужно RelativeLayout, вы можете добавить дочерний FrameLayout, чтобы держать ваш взгляд, как это:

<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> 
+0

Ваше решение работает, но только если я задаю ширину и высоту представления match_parent. Единственная проблема, с которой я до сих пор сталкиваюсь, заключается в том, что очень сложно разместить другие представления рядом с моим пользовательским представлением. С RelativeLayout это было бы легко. –

+0

Не обращайте внимания на мое последнее заявление. Я могу установить его очень легко, заменив FrameLayout на LinearLayout. –

0

Это мой код, я использую этот код, и он работает нормально. Разве это не то, что вы ищете, но может дать вам идею. На основании моего требования высота изображения должна быть такой же длины, как и ширина.

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); 
    } 
} 

Определено в XML как:

<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"/> 

И, наконец, в фрагменте это что-то вроде:

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

Я еще не тестировал это, но думаю, что в ландшафтном режиме это будет иметь ту же проблему. –

1

Подкласс ImageView и переопределить onMeasure и перейти к

super.onMeasure(widthMeasureSpec, widthMeasureSpec); 

в весь класс:

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: Я попытался с нормальным видом

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); 
    } 
} 

и TestActivity

public class TestActivity extends Activity { 

    protected void onCreate(android.os.Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(new CustomView(this)); 
    } 

} 

рисует квадрат красный прямоугольник на экране моего мобильного устройства

+0

Я не вижу смысла в расширении ImageView вместо обычного представления. Я попробовал это для своего класса, но проблема осталась прежней. Высота в ширину не одинакова (не квадрат) для вида в ландшафтном режиме. –

+0

Шахта была всего лишь примером. Какой класс вы расширили? – Blackbelt

+0

Я продлил обычный вид. –

0

Я не конечно, какое решение будет удовлетворять потребность, но код из учебника имеет фундаментальную проблему проектирования - он не интерпретирует MeasureSpec параметров onMeasure() правильно. Ответ в onMeasure custom view explanation объясняет правильное пересечение этих параметров.

Что происходит в этом примере, onMeasure() вызывается дважды для каждого вхождения чертежа для пользовательского представления.

Для первого вызова оба параметра MeasureSpecs задают режим AT_MOST и дают начальные значения без учета полей и отступов.Таким образом, код из учебника законно делает ширину и высоту соответствовать.

Для второго вызова, однако, содержащий RelativeLayout принимает поля и отступы во внимание, и передает MeasureSpec для Width с режимом EXACT, в то время как Height передается в режиме AT_MOST.

Для вертикального экрана это работает нормально, потому что Width уменьшен по краям, а значение для высоты по-прежнему равно высоте экрана (также уменьшено по краям). Таким образом, во время второго кода звонка законно уменьшает высоту, чтобы соответствовать теперь - меньшая ширина, и все работает правильно.

Для горизонтальной ориентации экрана значение «Ширина» установлено на значение, определенное во время первого вызова, тогда как Height уменьшается по краям. Когда код пытается установить Width в соответствие с Height, эта настройка не работает должным образом, потому что Width режим: EXACT. В результате свойства вида Width и MeasuredWidth заканчиваются разными значениями.

Есть несколько простых способов сделать код в вопросе работы: вы можете просто удалить вертикальные поля:

<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" > 

Или, если поля очень важны, вы можете изменить, содержащий расположение к абсолютному:

<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" > 

В обоих случаях представляется целесообразным изменить реализацию onMeasure(), чтобы соблюдать режим EXACT (для регистрации проблемы как минимум).

+0

Я предлагаю НЕ использовать AbsoluteLayout. Он устарел после Android API уровня 3! –

+1

@LJoosse True, AbsoluteLayout устарел. Но вопрос был «почему ширина не соответствует высоте в альбомной ориентации», и я пытался проиллюстрировать основную причину проблемы. Не зная реальных требований к компоновке экрана, сложно предложить конкретное решение. – amoiseyev

0

Ваш код onMeasure() не прост. Измените код следующим образом & Set match_parent как ширина & высота в XML (на самом деле оно не имеет значения)

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); 
}