替换linearLayout权重机制

时间:2012-09-14 15:47:25

标签: android android-layout android-linearlayout android-layout-weight android-percent-library

背景:

  • 由于性能原因,Google建议避免使用嵌套加权linearLayouts。
  • 使用嵌套加权linearLayout很难读,写和维护。
  • 对于放置可用大小百分比的视图,仍然没有其他好的选择。只有解决方案才是权重并使用OpenGL。甚至没有像WPF / Silverlight上显示的“viewBox”那样自动缩放内容。

这就是为什么我决定创建我自己的布局,你可以告诉每个孩子他们的重量(和周围的重量)与它的大小相比应该是什么。

我似乎已经成功了,但出于某种原因,我认为有一些我无法追查的错误。

其中一个错误就是textView,即使我为它提供了大量空间,它也会将文本放在顶部而不是放在中心。另一方面,imageViews工作得很好。另一个错误是,如果我在自定义布局中使用布局(例如frameLayout),则其中的视图将不会显示(但布局本身将会显示)。

请帮我弄清楚它为什么会发生。

如何使用:而不是线性布局的下一个用法(我故意使用长XML,以显示我的解决方案如何缩短事物):

<LinearLayout 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:orientation="vertical">

  <View android:layout_width="wrap_content" android:layout_height="0px"
    android:layout_weight="1" />

  <LinearLayout android:layout_width="match_parent"
    android:layout_height="0px" android:layout_weight="1"
    android:orientation="horizontal">

    <View android:layout_width="0px" android:layout_height="wrap_content"
      android:layout_weight="1" />

    <TextView android:layout_width="0px" android:layout_weight="1"
      android:layout_height="match_parent" android:text="@string/hello_world"
      android:background="#ffff0000" android:gravity="center"
      android:textSize="20dp" android:textColor="#ff000000" />

    <View android:layout_width="0px" android:layout_height="wrap_content"
      android:layout_weight="1" />

  </LinearLayout>
  <View android:layout_width="wrap_content" android:layout_height="0px"
    android:layout_weight="1" />
</LinearLayout>

我所做的只是(x是将视图本身放在权重列表中的位置):

<com.example.weightedlayouttest.WeightedLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res/com.example.weightedlayouttest"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" tools:context=".MainActivity">

  <TextView android:layout_width="0px" android:layout_height="0px"
    app:horizontalWeights="1,1x,1" app:verticalWeights="1,1x,1"
    android:text="@string/hello_world" android:background="#ffff0000"
    android:gravity="center" android:textSize="20dp" android:textColor="#ff000000" />

</com.example.weightedlayouttest.WeightedLayout>

我的特殊布局代码是:

public class WeightedLayout extends ViewGroup
  {
  @Override
  protected WeightedLayout.LayoutParams generateDefaultLayoutParams()
    {
    return new WeightedLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
    }

  @Override
  public WeightedLayout.LayoutParams generateLayoutParams(final AttributeSet attrs)
    {
    return new WeightedLayout.LayoutParams(getContext(),attrs);
    }

  @Override
  protected ViewGroup.LayoutParams generateLayoutParams(final android.view.ViewGroup.LayoutParams p)
    {
    return new WeightedLayout.LayoutParams(p.width,p.height);
    }

  @Override
  protected boolean checkLayoutParams(final android.view.ViewGroup.LayoutParams p)
    {
    final boolean isCorrectInstance=p instanceof WeightedLayout.LayoutParams;
    return isCorrectInstance;
    }

  public WeightedLayout(final Context context)
    {
    super(context);
    }

  public WeightedLayout(final Context context,final AttributeSet attrs)
    {
    super(context,attrs);
    }

  public WeightedLayout(final Context context,final AttributeSet attrs,final int defStyle)
    {
    super(context,attrs,defStyle);
    }

  @Override
  protected void onLayout(final boolean changed,final int l,final int t,final int r,final int b)
    {
    for(int i=0;i<this.getChildCount();++i)
      {
      final View v=getChildAt(i);
      final WeightedLayout.LayoutParams layoutParams=(WeightedLayout.LayoutParams)v.getLayoutParams();
      //
      final int availableWidth=r-l;
      final int totalHorizontalWeights=layoutParams.getLeftHorizontalWeight()+layoutParams.getViewHorizontalWeight()+layoutParams.getRightHorizontalWeight();
      final int left=l+layoutParams.getLeftHorizontalWeight()*availableWidth/totalHorizontalWeights;
      final int right=r-layoutParams.getRightHorizontalWeight()*availableWidth/totalHorizontalWeights;
      //
      final int availableHeight=b-t;
      final int totalVerticalWeights=layoutParams.getTopVerticalWeight()+layoutParams.getViewVerticalWeight()+layoutParams.getBottomVerticalWeight();
      final int top=t+layoutParams.getTopVerticalWeight()*availableHeight/totalVerticalWeights;
      final int bottom=b-layoutParams.getBottomVerticalWeight()*availableHeight/totalVerticalWeights;
      //
      v.layout(left+getPaddingLeft(),top+getPaddingTop(),right+getPaddingRight(),bottom+getPaddingBottom());
      }
    }

  // ///////////////
  // LayoutParams //
  // ///////////////
  public static class LayoutParams extends ViewGroup.LayoutParams
    {
    int _leftHorizontalWeight =0,_rightHorizontalWeight=0,_viewHorizontalWeight=0;
    int _topVerticalWeight    =0,_bottomVerticalWeight=0,_viewVerticalWeight=0;

    public LayoutParams(final Context context,final AttributeSet attrs)
      {
      super(context,attrs);
      final TypedArray arr=context.obtainStyledAttributes(attrs,R.styleable.WeightedLayout_LayoutParams);
        {
        final String horizontalWeights=arr.getString(R.styleable.WeightedLayout_LayoutParams_horizontalWeights);
        //
        // handle horizontal weight:
        //
        final String[] words=horizontalWeights.split(",");
        boolean foundViewHorizontalWeight=false;
        int weight;
        for(final String word : words)
          {
          final int viewWeightIndex=word.lastIndexOf('x');
          if(viewWeightIndex>=0)
            {
            if(foundViewHorizontalWeight)
              throw new IllegalArgumentException("found more than one weights for the current view");
            weight=Integer.parseInt(word.substring(0,viewWeightIndex));
            setViewHorizontalWeight(weight);
            foundViewHorizontalWeight=true;
            }
          else
            {
            weight=Integer.parseInt(word);
            if(weight<0)
              throw new IllegalArgumentException("found negative weight:"+weight);
            if(foundViewHorizontalWeight)
              _rightHorizontalWeight+=weight;
            else _leftHorizontalWeight+=weight;
            }
          }
        if(!foundViewHorizontalWeight)
          throw new IllegalArgumentException("couldn't find any weight for the current view. mark it with 'x' next to the weight value");
        }
        //
        // handle vertical weight:
        //
        {
        final String verticalWeights=arr.getString(R.styleable.WeightedLayout_LayoutParams_verticalWeights);
        final String[] words=verticalWeights.split(",");
        boolean foundViewVerticalWeight=false;
        int weight;
        for(final String word : words)
          {
          final int viewWeightIndex=word.lastIndexOf('x');
          if(viewWeightIndex>=0)
            {
            if(foundViewVerticalWeight)
              throw new IllegalArgumentException("found more than one weights for the current view");
            weight=Integer.parseInt(word.substring(0,viewWeightIndex));
            setViewVerticalWeight(weight);
            foundViewVerticalWeight=true;
            }
          else
            {
            weight=Integer.parseInt(word);
            if(weight<0)
              throw new IllegalArgumentException("found negative weight:"+weight);
            if(foundViewVerticalWeight)
              _bottomVerticalWeight+=weight;
            else _topVerticalWeight+=weight;
            }
          }
        if(!foundViewVerticalWeight)
          throw new IllegalArgumentException("couldn't find any weight for the current view. mark it with 'x' next to the weight value");
        }
      //
      arr.recycle();
      }

    public LayoutParams(final int width,final int height)
      {
      super(width,height);
      }

    public LayoutParams(final ViewGroup.LayoutParams source)
      {
      super(source);
      }

    public int getLeftHorizontalWeight()
      {
      return _leftHorizontalWeight;
      }

    public void setLeftHorizontalWeight(final int leftHorizontalWeight)
      {
      _leftHorizontalWeight=leftHorizontalWeight;
      }

    public int getRightHorizontalWeight()
      {
      return _rightHorizontalWeight;
      }

    public void setRightHorizontalWeight(final int rightHorizontalWeight)
      {
      if(rightHorizontalWeight<0)
        throw new IllegalArgumentException("negative weight :"+rightHorizontalWeight);
      _rightHorizontalWeight=rightHorizontalWeight;
      }

    public int getViewHorizontalWeight()
      {
      return _viewHorizontalWeight;
      }

    public void setViewHorizontalWeight(final int viewHorizontalWeight)
      {
      if(viewHorizontalWeight<0)
        throw new IllegalArgumentException("negative weight:"+viewHorizontalWeight);
      _viewHorizontalWeight=viewHorizontalWeight;
      }

    public int getTopVerticalWeight()
      {
      return _topVerticalWeight;
      }

    public void setTopVerticalWeight(final int topVerticalWeight)
      {
      if(topVerticalWeight<0)
        throw new IllegalArgumentException("negative weight :"+topVerticalWeight);
      _topVerticalWeight=topVerticalWeight;
      }

    public int getBottomVerticalWeight()
      {
      return _bottomVerticalWeight;
      }

    public void setBottomVerticalWeight(final int bottomVerticalWeight)
      {
      if(bottomVerticalWeight<0)
        throw new IllegalArgumentException("negative weight :"+bottomVerticalWeight);
      _bottomVerticalWeight=bottomVerticalWeight;
      }

    public int getViewVerticalWeight()
      {
      return _viewVerticalWeight;
      }

    public void setViewVerticalWeight(final int viewVerticalWeight)
      {
      if(viewVerticalWeight<0)
        throw new IllegalArgumentException("negative weight :"+viewVerticalWeight);
      _viewVerticalWeight=viewVerticalWeight;
      }
    }
  }

4 个答案:

答案 0 :(得分:8)

我接受了您的挑战,并尝试根据我的评论创建您描述的布局。你是对的。实现起来非常困难。除此之外,我喜欢拍摄苍蝇。所以我跳上船,想出了这个解决方案。

  1. 扩展现有的布局类,而不是从头开始创建自己的布局类。我使用RelativeLayout开始,但所有人都可以使用相同的方法。这使您能够在不想操作的子视图上使用该布局的默认行为。

  2. 我在布局中添加了四个属性:top,left,width和height。我的意图是通过允许诸如“10%”,“100px”,“100dp”等的值来模仿HTML。此时,接受的唯一值是表示父项%的整数。 “20”=布局的20%。

  3. 为了获得更好的性能,我允许super.onLayout()执行所有迭代,并仅在最后一次传递时使用自定义属性操作视图。由于这些视图将独立于兄弟姐妹进行定位和缩放,因此我们可以在其他所有事情都解决后移动它们。

  4. 这是atts.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="HtmlStyleLayout">
            <attr name="top" format="integer"/>
            <attr name="left" format="integer"/>
            <attr name="height" format="integer"/>
            <attr name="width" format="integer"/>
    
        </declare-styleable>
    </resources>
    

    这是我的布局类。

    package com.example.helpso;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.RelativeLayout;
    
    
    public class HtmlStyleLayout extends RelativeLayout{
    
        private int pass =0;
        @Override
          protected HtmlStyleLayout.LayoutParams generateDefaultLayoutParams()
            {
            return new HtmlStyleLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT);
            }
    
          @Override
          public HtmlStyleLayout.LayoutParams generateLayoutParams(final AttributeSet attrs)
            {
            return new HtmlStyleLayout.LayoutParams(getContext(),attrs);
            }
    
          @Override
          protected RelativeLayout.LayoutParams generateLayoutParams(final android.view.ViewGroup.LayoutParams p)
            {
            return new HtmlStyleLayout.LayoutParams(p.width,p.height);
            }
    
          @Override
          protected boolean checkLayoutParams(final android.view.ViewGroup.LayoutParams p)
            {
            final boolean isCorrectInstance=p instanceof HtmlStyleLayout.LayoutParams;
            return isCorrectInstance;
            }
    
        public HtmlStyleLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
    
        }
    
        public void setScaleType(View v){
            try{
                ((ImageView) v).setScaleType (ImageView.ScaleType.FIT_XY);
            }catch (Exception e){
                // The view is not an ImageView 
            }
        }
    
    
        @Override
          protected void onLayout(final boolean changed,final int l,final int t,final int r,final int b)
            {
            super.onLayout(changed, l, t, r, b);           //Let the parent layout do it's thing
    
    
            pass++;                                        // After the last pass of
            final int childCount = this.getChildCount();   // the parent layout
            if(true){                        // we do our thing
    
    
                for(int i=0;i<childCount;++i)
                  {
                  final View v=getChildAt(i);
                  final HtmlStyleLayout.LayoutParams params = (HtmlStyleLayout.LayoutParams)v.getLayoutParams();
    
                  int newTop = v.getTop();                 // set the default value
                  int newLeft = v.getLeft();               // of these to the value
                  int newBottom = v.getBottom();           // set by super.onLayout() 
                  int newRight= v.getRight();             
                  boolean viewChanged = false;
    
                  if(params.getTop() >= 0){
                      newTop = ( (int) ((b-t) * (params.getTop() * .01))  );
                      viewChanged = true;
                  }
    
                  if(params.getLeft() >= 0){
                      newLeft = ( (int) ((r-l) * (params.getLeft() * .01))  );
                      viewChanged = true;
                  }
    
                  if(params.getHeight() > 0){
                      newBottom = ( (int) ((int) newTop + ((b-t) * (params.getHeight() * .01)))  );
                      setScaleType(v);                        // set the scale type to fitxy
                      viewChanged = true;
                  }else{
                      newBottom = (newTop + (v.getBottom() - v.getTop()));
                      Log.i("heightElse","v.getBottom()=" +
                              Integer.toString(v.getBottom())
                              + " v.getTop=" +
                              Integer.toString(v.getTop()));
                  }
    
                  if(params.getWidth() > 0){
                      newRight = ( (int) ((int) newLeft + ((r-l) * (params.getWidth() * .01)))  );
                      setScaleType(v);
                      viewChanged = true;
                  }else{
                      newRight = (newLeft + (v.getRight() - v.getLeft()));
                  }
    
                    // only call layout() if we changed something
                    if(viewChanged)
                        Log.i("SizeLocation",
                                Integer.toString(i) + ": "
                                + Integer.toString(newLeft) + ", "
                                + Integer.toString(newTop) + ", "
                                + Integer.toString(newRight) + ", "
                                + Integer.toString(newBottom));
                    v.layout(newLeft, newTop, newRight, newBottom);
                  }
    
    
    
    
    
                pass = 0;                                 // reset the parent pass counter
            }
            }
    
    
         public  class LayoutParams extends RelativeLayout.LayoutParams
            {
    
            private int top, left, width, height;
            public LayoutParams(final Context context, final AttributeSet atts) {
                super(context, atts);
                TypedArray a = context.obtainStyledAttributes(atts, R.styleable.HtmlStyleLayout);
                top =  a.getInt(R.styleable.HtmlStyleLayout_top , -1);
                left = a.getInt(R.styleable.HtmlStyleLayout_left, -1);
                width = a.getInt(R.styleable.HtmlStyleLayout_width, -1);
                height = a.getInt(R.styleable.HtmlStyleLayout_height, -1);
                a.recycle();
    
    
            }
            public LayoutParams(int w, int h) {
                super(w,h);
                Log.d("lp","2");
            }
            public LayoutParams(ViewGroup.LayoutParams source) {
                super(source);
                Log.d("lp","3");
            }
            public LayoutParams(ViewGroup.MarginLayoutParams source) {
                super(source);
                Log.d("lp","4");
            }
            public int getTop(){
                return top;
            }
            public int getLeft(){
                return left;
            }
            public int getWidth(){
                return width;
            }
            public int getHeight(){
                return height;
            }
            }
    }
    

    以下是一个示例活动xml

    <com.example.helpso.HtmlStyleLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:html="http://schemas.android.com/apk/res/com.example.helpso"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_alignParentRight="true"
            android:layout_alignParentTop="true"
            android:scaleType="fitXY"
            android:src="@drawable/bg" />
    
        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/overlay"
            html:height="10"
            html:left="13"
            html:top="18"
            html:width="23" />
    
    </com.example.helpso.HtmlStyleLayout>
    

    以下是我用于测试的图像。

    bg enter image description here

    如果您没有为特定属性设置值,则会使用默认值。因此,如果您设置宽度而不是高度,则图像将按宽度缩放,而wrap_content则为高度。

    Zipped project folder.

    apk

    我找到了bug的来源。问题是我使用布局的子计数作为指示它将对onLayout进行多少次调用的指示器。这在旧版本的android中似乎不适用。我注意到2.1 onLayout只被调用一次。所以我改变了

    if(pass == childCount){
    

    if(true){  
    

    它开始按预期工作。

    我仍然认为仅在超级完成后才调整布局是有益的。只需要找到一种更好的方法来了解它是什么时候。

    修改

    我没有意识到你的目的是用逐像素精度拼凑图像。我通过使用双浮点精度变量而不是整数来实现您正在寻找的精度。但是,在允许图像缩放的同时,您将无法实现此目的。当放大图像时,在现有像素之间以某个间隔添加像素。新像素的颜色是周围像素的一些加权平均值。当您彼此独立地缩放图像时,它们不会共享任何信息。结果是在接缝处总会有一些神器。再加上舍入的结果,因为你不能有一个部分像素,你总是有+/- 1像素容差。

    要验证这一点,您可以在高级照片编辑软件中尝试相同的任务。我使用PhotoShop。使用与我的apk中相同的图像,我将它们放在单独的文件中。我将它们垂直放大168%,水平放大127%。然后我将它们放在一起放在一个文件中并尝试对齐它们。结果与我的apk中的结果完全相同。

    为了演示布局的准确性,我在apk中添加了第二个活动。在此活动中,我没有缩放背景图像。其他一切都完全一样。结果是无缝的。

    我还添加了一个按钮来显示/隐藏叠加图像,还有一个用于在活动之间切换。

    我更新了google驱动器上的apk和压缩项目文件夹。你可以通过上面的链接获得它们。

答案 1 :(得分:4)

在尝试完代码后,我找到了您提到的问题的原因,这是因为在您的自定义布局中,您只能布局正确的孩子,但是您忘了衡量你的孩子正确,这将直接影响绘图层次结构,所以只需添加以下代码,它就适合我。

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSize = MeasureSpec.getSize(widthMeasureSpec)-this.getPaddingRight()-this.getPaddingRight();
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);

    int heightSize = MeasureSpec.getSize(heightMeasureSpec)-this.getPaddingTop()-this.getPaddingBottom();
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    if(heightMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.UNSPECIFIED)
        throw new IllegalArgumentException("the layout must have a exact size");

    for (int i = 0; i < this.getChildCount(); ++i) {
        View child = this.getChildAt(i);
        LayoutParams lp = (LayoutParams)child.getLayoutParams();
        int width = lp._viewHorizontalWeight * widthSize/(lp._leftHorizontalWeight+lp._rightHorizontalWeight+lp._viewHorizontalWeight);
        int height =  lp._viewVerticalWeight * heightSize/(lp._topVerticalWeight+lp._bottomVerticalWeight+lp._viewVerticalWeight);
        child.measure(width | MeasureSpec.EXACTLY,  height | MeasureSpec.EXACTLY);
    }

    this.setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));

}

答案 2 :(得分:1)

现在有一个比我所做的自定义布局更好的解决方案:

PercentRelativeLayout

可以找到教程here并找到回复here

示例代码:

<android.support.percent.PercentRelativeLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
     <ImageView
         app:layout_widthPercent="50%"
         app:layout_heightPercent="50%"
         app:layout_marginTopPercent="25%"
         app:layout_marginLeftPercent="25%"/>
 </android.support.percent.PercentFrameLayout/>

或:

 <android.support.percent.PercentFrameLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
     <ImageView
         app:layout_widthPercent="50%"
         app:layout_heightPercent="50%"
         app:layout_marginTopPercent="25%"
         app:layout_marginLeftPercent="25%"/>
 </android.support.percent.PercentFrameLayout/>

我想知道它是否可以处理我在这里展示的问题。

答案 3 :(得分:-1)

我建议使用以下优化:

<FrameLayout 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:gravity="center">


    <TextView android:layout_width="wrap_content"
      android:layout_height="wrap_content" android:text="@string/hello_world"
      android:background="#ffff0000" android:gravity="center"
      android:textSize="20dp" android:textColor="#ff000000" />

</FrameLayout>

或使用http://developer.android.com/reference/android/widget/LinearLayout.html#attr_android:weightSum

或使用TableLayout和layout_weight表示行和列

或使用GridLayout。