2013-03-13 32 views
10

Tôi có lớp vùng chứa này được sử dụng để tạo một trong các thứ nguyên, chiều rộng hoặc chiều cao, tỷ lệ của thứ nguyên khác. Ví dụ, tôi muốn một thùng chứa bố trí 16: 9 nơi chiều rộng là "match_parent". Tuy nhiên, khi sử dụng chiều cao là "match_parent", Android dường như không chuyển tiếp chính xác. Khi tôi thiết lập chiều cao là một tỷ lệ của chiều rộng tất cả mọi thứ là tốt! Ngược lại và nó không hoạt động. Chuyện gì vậy?RelativeLayout không cập nhật đúng chiều rộng của chế độ xem tùy chỉnh

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

} 

Để nhìn thấy nó trong hành động, sử dụng một cách bố trí như thế này:

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

Và đây sẽ là Hoạt động của tôi:

đầu ra Logging:

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 

Tại sao Android không tôn trọng đo lường mới nhất của tôiWidth nhưng sử dụng một phiên bản cũ hơn nhưng chiều cao mới nhất?

EDIT: Đã cập nhật. Đặt phụ huynh của RatioLayout NOT một RelativeLayout nhưng LinearLayout hoặc FrameLayout cho tôi hành vi đúng. Đối với một số lý do RelativeLayout là "bộ nhớ đệm" đo lườngWidth và không sử dụng mới nhất.

EDIT 2: bình luận trong RelativeLayout.onLayout Điều này dường như xác nhận của tôi "đó là bộ nhớ đệm" mà tôi tin là một lỗi

@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) { 

CUỐI CÙNG EDIT Ok. Tôi từ bỏ. Đây là lỗi hợp pháp trong RelativeLayout. Loại mã này sửa chữa nó, nhưng nó tạo ra các vấn đề với các thuộc tính toRightOf. Công việc xung quanh mà tôi tìm thấy là làm tổ RatioLayout này vào một ViewGroup khác như LinerLayout. Mã cho những người tò mò

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

Trả lời

1

Tôi đã thực sự cố gắng để thực hiện chính xác những gì bạn đang cố gắng để làm trước, đó là, làm cho một View thể là lớn càng tốt trong khi vẫn giữ một số tỉ lệ (tức là hình vuông hoặc 4: 3 hoặc một cái gì đó như thế).

Vấn đề của tôi là khi Chế độ xem của tôi ở mức ScrollView kích thước được tính không chính xác. Tôi đã không thể tìm ra điều này, nhưng tôi sẽ đăng mã của tôi dưới đây chỉ trong trường hợp nó giúp. Nó thực sự tương tự như mã của bạn, nhưng tôi kế thừa từ FrameLayout và tôi sẽ gọi số super.onMeasure().

Tôi đã kiểm tra kỹ dự án mà tôi đã sử dụng và tôi thực sự có nó dưới dạng con trực tiếp của 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> 
Các vấn đề liên quan