2013-03-13 2 views
10

У меня есть этот класс контейнера, который используется для создания одного из размеров, будь то ширина или высота, отношение другого измерения. Например, мне нужен контейнер макета 16: 9, где ширина «match_parent». Однако при использовании высоты как «match_parent», Android, похоже, не правильно ретранслирует себя. Когда я устанавливаю высоту как отношение ширины, все в порядке! Vise versa и не работает. Что происходит?RelativeLayout не правильно обновляет ширину пользовательского вида

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 NOT a 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) { 

FINAL EDIT Ok. Я сдаюсь. Это законная ошибка в 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); 
} 

ответ

1

Я на самом деле пытался сделать именно то, что вы пытаетесь сделать раньше, то есть, сделать View быть столь же большим, насколько это возможно, сохраняя при этом некоторое соотношение сторон (т.е. квадрат или 4: 3 или что-то в этом роде).

Моя проблема заключалась в том, что когда мой вид был в ScrollView, размер вычислялся неправильно. Мне не удалось это понять, но я напишу свой код ниже, если это поможет. Это действительно похоже на ваш код, но я наследую от FrameLayout, и в итоге я вызываю super.onMeasure().

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

Java:

/** 
* A FrameLayout which tries to be as big as possible while maintaining a given ratio between its width and height. 
* Formula: Height = Width/ratio; 
* Usage: 
* 1) Set layout_width and layout_height to "match_parent" 
* 2) For 4:3 for example, set ratio to 4/3 = 1.333333 etc. Or don't specify, and it will be square by default. 
*/ 
public class RatioFrameLayout extends FrameLayout 
{ 
    private final static float DEFAULT_RATIO = 1f; 

    private float ratio; 
    private boolean measured = false; 

    public RatioFrameLayout(Context context) 
    { 
     super(context); 
    } 

    public RatioFrameLayout(Context context, AttributeSet attrs) 
    { 
     super(context, attrs); 
     readAttributes(context, attrs); 
    } 

    public RatioFrameLayout(Context context, AttributeSet attrs, int defStyle) 
    { 
     super(context, attrs, defStyle); 
     readAttributes(context, attrs); 
    } 

    private void readAttributes(Context context, AttributeSet attrs) 
    { 
     TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RatioFrameLayout); 

     String value = a.getString(R.styleable.RatioFrameLayout_ratio); 
     try 
     { 
      ratio = Float.parseFloat(value); 
     } 
     catch (Exception e) 
     { 
      ratio = DEFAULT_RATIO; 
     } 

     a.recycle(); 
    } 

    public float getRatio() 
    { 
     return ratio; 
    } 

    public void setRatio(float ratio) 
    { 
     this.ratio = ratio; 
    } 

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

     int targetWidth = getMeasuredWidth(); 
     int targetHeight = Math.round((float)getMeasuredWidth()/ratio); 

     if (targetHeight > getMeasuredHeight() && getMeasuredHeight() != 0) 
     { 
      targetWidth = Math.round(getMeasuredHeight() * ratio); 
      targetHeight = getMeasuredHeight(); 
     } 

     super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(targetHeight, MeasureSpec.EXACTLY)); 
    } 

    private void printMeasureSpec(String description, int value) 
    { 
     int mode = MeasureSpec.getMode(value); 
     String modeName = mode == MeasureSpec.AT_MOST ? "AT_MOST" 
       : mode == MeasureSpec.EXACTLY ? "EXACTLY" : "UNSPECIFIED"; 
     DLog.d(String.format("Measure spec for %s, mode = %s, size = %d", description, modeName, MeasureSpec.getSize(value))); 
    } 
} 

attrs.xml:

<resources> 
    <declare-styleable name="RatioFrameLayout"> 
     <attr name="ratio" format="string|reference" /> 
    </declare-styleable> 
</resources> 
Смежные вопросы