Почему мой пользовательский вид android не квадратный?

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

я добавил следующий код в мой пользовательский вид:

@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 () возвращают правильные значения. Как может быть, что вид еще больше?

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>

подкласс 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));
    }

}

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


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

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

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

что происходит в этом примере 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 mode (для регистрации проблемы, по крайней мере).


код onMeasure() код не является однозначным. Измените свой код следующим образом и установите match_parent как width & height в 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);
}