У меня есть этот класс контейнера, который используется для создания одного из размеров, будь то ширина или высота, отношение другого измерения. Например, мне нужен контейнер макета 16: 9, где ширина "match_parent". Однако при использовании высоты как "match_parent", Android, похоже, не правильно ретранслирует себя. Когда я устанавливаю высоту как отношение ширины, все в порядке! Vise versa и не работает. Что происходит?
public class RatioLayout extends ViewGroup {
private float ratio = 1.6222f; // 4 x 3 default. 1.78 is 16 x 9
public float getRatio() { return ratio; }
public void setRatio(float ratio) {
this.ratio = ratio;
requestLayout();
}
public RatioLayout(Context context) {
this(context, null);
}
public RatioLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RatioLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("Ratio", "/onMeasure");
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// int exactly = MeasureSpec.EXACTLY; // 1073741824
// int atMost = MeasureSpec.AT_MOST; // -2147483648
// int unspec = MeasureSpec.UNSPECIFIED; // 0
width = Math.round(height * ratio);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
int mw = getMeasuredWidth();
int mh = getMeasuredHeight();
Log.i("Ratio", "mw: " + mw + ", mh: " + mh);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i("Ratio", "/onLayout");
Log.i("Ratio", "l: " + l + ", t: " + t + ", r:" + r + ", b:" + b);
Log.i("Ratio", "mw: " + getMeasuredWidth() + ", mh:" + getMeasuredHeight());
Log.i("Ratio", "w: " + getWidth() + ", mw:" + getHeight());
}
}
Чтобы увидеть его в действии, используйте такой макет:
<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"
tools:context=".MainActivity" >
<com.example.RatioLayout
android:id="@+id/image"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginBottom="100dp"
android:layout_marginTop="100dp"
android:background="#FFFF00FF" />
</RelativeLayout>
И это будет моя деятельность:
Выход журнала:
I/Ratio (9445): /onMeasure
I/Ratio (9445): mw: 1087, mh: 670
I/Ratio (9445): /onMeasure
I/Ratio (9445): mw: 655, mh: 404
I/Ratio (9445): /onLayout
I/Ratio (9445): l: 0, t: 133, r:1087, b:537
I/Ratio (9445): mw: 655, mh:404
I/Ratio (9445): w: 1087, mw:404 <--- NO reason this should not be 655
Почему Android не будет оценивать мою последнюю измеренную ширину, но использовать более старую версию, но самую последнюю высоту?
EDIT: Обновлено. Сделайте родительский элемент RatioLayout НЕ relativeLayout, но LinearLayout или FrameLayout дает мне правильное поведение. По какой-то причине RelativeLayout "кэширует" измеренную ширину и не использует самые последние.
EDIT 2: Этот комментарий в RelativeLayout.onLayout, похоже, подтверждает мое "кэширование", которое, я считаю, является ошибкой
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
int count = getChildCount();
// TODO: we need to find another way to implement RelativeLayout
// This implementation cannot handle every case
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ОКОНЧАТЕЛЬНОЕ ИЗОБРАЖЕНИЕ Хорошо. Я сдаюсь. Это законная ошибка в RelativeLayout. Этот код исправляет его, но создает проблемы с свойствами toRightOf. Работа, которую я нашел, заключалась в том, чтобы вложить этот RatioLayout в другую ViewGroup, такую как LinerLayout. Код для любопытных
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("Ratio", "/onMeasure");
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// int exactly = MeasureSpec.EXACTLY; // 1073741824
// int atMost = MeasureSpec.AT_MOST; // -2147483648
// int unspec = MeasureSpec.UNSPECIFIED; // 0
width = Math.round(height * ratio);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
if (getParent() instanceof RelativeLayout) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)getLayoutParams();
Class<?> clazz = RelativeLayout.LayoutParams.class;
try {
Field left = clazz.getDeclaredField("mLeft");
Field right = clazz.getDeclaredField("mRight");
left.setAccessible(true);
right.setAccessible(true);
int l = left.getInt(params);
if (l == -1) l = params.leftMargin; // if the value is uninitialized, set it to 0;
if (l == -1) l = 0; // if the value is uninitialized, set it to 0;
// setting this seems to break the layout_marginLeft properties.
right.setInt(params, l + getMeasuredWidth());
} catch (NoSuchFieldException e) {
Log.e("Ration", "error", e);
} catch (IllegalArgumentException e) {
Log.e("Ration", "error", e);
} catch (IllegalAccessException e) {
Log.e("Ration", "error", e);
}
}
int mw = getMeasuredWidth();
int mh = getMeasuredHeight();
lastWidth = mw;
Log.i("Ratio", "mw: " + mw + ", mh: " + mh);
}