18

背景:

  • 出于性能考虑,Google 建议避免使用嵌套加权线性布局。
  • 使用嵌套加权线性布局很难读、写和维护。
  • 放置可用大小百分比的视图仍然没有很好的选择。唯一的解决方案是权重和使用 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

4 回答 4

8

我接受了您的挑战并尝试创建您描述的布局以响应我的评论。你说的对。出乎意料地难以完成。除此之外,我确实喜欢拍摄家蝇。所以我加入进来,想出了这个解决方案。

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

  2. 我在布局中添加了四个属性,称为顶部、左侧、宽度和高度。我的意图是通过允许诸如“10%”、“100px”、“100dp”等值来模仿 HTML。此时唯一接受的值是表示父级百分比的整数。“20” = 布局的 20%。

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

这是 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 在此处输入图像描述

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

压缩的项目文件夹。

APK

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

if(pass == childCount){

if(true){  

它开始按预期工作。

我仍然认为只有在 super 完成后才调整布局是有益的。只需要找到一种更好的方法来知道那是什么时候。

编辑

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

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

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

我还添加了一个按钮来显示/隐藏覆盖图像和一个在活动之间切换的按钮。

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

于 2012-11-08T16:40:11.613 回答
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));

}
于 2012-11-08T09:36:28.317 回答
1

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

百分比相对布局

教程可以在这里找到,repo 可以在这里找到。

示例代码:

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

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

于 2015-08-03T16:24:06.820 回答
-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 一起用于行和列

或使用网格布局。

于 2012-11-06T08:24:27.077 回答