如何在RelativeLayout中以编程方式进行视图布局?

238

我试图以编程方式实现以下内容(而不是通过XML声明方式):

<RelativeLayout...>
   <TextView ...
      android:id="@+id/label1" />
   <TextView ...
      android:id="@+id/label2"
      android:layout_below: "@id/label1" />
</RelativeLayout>

换句话说,我如何使第二个TextView出现在第一个下方,但我想在代码中实现:

RelativeLayout layout = new RelativeLayout(this);
TextView label1 = new TextView(this);
TextView label2 = new TextView(this);
...
layout.addView(label1);
layout.addView(label2);
setContentView(layout);

更新:

感谢TreeUK的回答。我理解了大致方向,但仍然不能正常工作-“B”与“A”重叠。我做错了什么?

RelativeLayout layout = new RelativeLayout(this);
TextView tv1 = new TextView(this);
tv1.setText("A");

TextView tv2 = new TextView(this);
tv2.setText("B");
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
        RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.FILL_PARENT);
lp.addRule(RelativeLayout.RIGHT_OF, tv1.getId());

layout.addView(tv1);        
layout.addView(tv2, lp);

在你的代码示例中,你实际上没有添加RelativeLayout.BELOW规则,tv1.getId()。 - Tristan Warner-Smith
48
你需要为子视图提供 ID:tv1.setId(1); tv2.setId(2)。父视图不会自动为子视图分配 ID,ID 的默认值是 NO_ID。在视图层次结构中,ID 不必唯一 - 因此可以使用 1、2、3 等任何值 - 并且你必须使用它们才能使相对布局(RelativeLayout)中的相对定位起作用。 - sechastain
我建议不要使用随机(1、2、3等)作为小部件ID,因为如果你需要一个真正有效的生成ID,"随机"数字会使有效的小部件ID混乱,并且你需要保存其值的视图(例如:EditText)将无法正常工作。我花了几个小时才弄清楚这个问题! - Trasd
9个回答

201

根据我所了解到的,你需要使用LayoutParams来添加视图。

LinearLayout linearLayout = new LinearLayout(this);

RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(
        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
relativeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);

parentView.addView(linearLayout, relativeParams);

所有荣誉归于sechastain,如果要通过编程方式对你的项目进行相对定位,你需要为它们分配ID。

TextView tv1 = new TextView(this);
tv1.setId(1);
TextView tv2 = new TextView(this);
tv2.setId(2);

然后 addRule(RelativeLayout.RIGHT_OF, tv1.getId());


1
在我的情况下,除了设置子视图的ID、将子视图添加到父视图并在设置其他子视图的规则之前在子视图上调用requestLayout()之外,没有别的方法可以使事情正常工作。希望这能帮助到某些人。 - 2cupsOfTech
1
如果还有人在关注这个问题:当我有一组视图需要逐个添加时,我无法确定其中一个项目设置为“below”的视图是否已经被添加,如何确保它仍然正确地呈现?据我所知,“addView”会导致布局重新渲染,因此,如果我对尚未添加其相对应项的项目执行此操作,它将崩溃,因为具有该ID的项目尚不存在。 - Megakoresh
1
set(id) 应该与 generateViewId() 一起使用 API 级别 17 中添加。 它生成一个适用于 setId(int) 的值。此值不会与 aapt 为 R.id 生成的构建时 ID 值发生冲突。例如:tv[l_idx].setId(View.generateViewId()); - AndrewBloom
这个答案写得非常糟糕。为什么要使用RelationalLayout参数来布置线性布局?为什么要将线性布局添加到父视图中? - pcodex
嘿@Prab,这个答案现在已经有9年了。如果您对如何改进答案以满足今天的需求有一些积极的反馈意见,请随时点击编辑按钮并提出建议。 - Tristan Warner-Smith

61

长话短说: 使用相对布局(RelativeLayout),您可以在布局内定位元素。

  1. 创建一个新的 RelativeLayout.LayoutParams

RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(...)

(无论是填充父级还是包裹内容,必要时使用绝对数字或引用XML资源)

  • 添加规则: 规则针对父元素或层次结构中的其他“兄弟”元素。

    lp.addRule(RelativeLayout.BELOW, someOtherView.getId())
    lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
    
    只需应用布局参数:最“健康”的方法是:
    parentLayout.addView(myView, lp)
    

    注意:不要在布局回调中更改布局。虽然在这个时候视图才能获得实际尺寸,但诱人的做法会导致意外的结果。


  • 我之前不知道你可以在一行代码中为视图设置参数并将其添加到其父视图 (parentLayout.addView(myView, lp))。我总是用两行代码来实现。谢谢! - Matt
    这对我很有帮助,但是有没有一种方法可以将子元素对齐到某个元素下方,然后为该子元素设置上边距?或者我可以为它所在的元素设置下边距,它会自动调整吗? - Jacob Varner
    当然有。您可以使用setMargins(left,top,right,bottom)在LayoutParams中设置边距(不幸的是,编程API不像XML那样舒适)。请注意,内边距是视图的属性,而不是布局参数。 - Meymann

    29

    刚刚花了四个小时解决这个问题。最终意识到你不能将零作为视图ID使用。尽管NO_ID == -1是允许的,但如果你把它赋给你的视图,事情就会变得混乱...


    2
    我看到了一样的内容。我正在使用 API 7,并且查看了 javadoc,我看到 setID(int) 说明应该是正整数的 id。这是应该在代码中执行的事情,而不仅仅是在文档中进行注释。http://developer.android.com/reference/android/view/View.html#setId(int) - OYRM
    @OYRM Android 是一团糟。 - SpaceMonkey
    你可能刚刚帮我节省了很多时间。谢谢! - rjr-apps

    9

    Android 22最小可运行示例

    enter image description here

    来源:

    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.RelativeLayout;
    import android.widget.TextView;
    
    public class Main extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            final RelativeLayout relativeLayout = new RelativeLayout(this);
    
            final TextView tv1;
            tv1 = new TextView(this);
            tv1.setText("tv1");
            // Setting an ID is mandatory.
            tv1.setId(View.generateViewId());
            relativeLayout.addView(tv1);
    
            // tv2.
            final TextView tv2;
            tv2 = new TextView(this);
            tv2.setText("tv2");
            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.FILL_PARENT);
            lp.addRule(RelativeLayout.BELOW, tv1.getId());
            relativeLayout.addView(tv2, lp);
    
            // tv3.
            final TextView tv3;
            tv3 = new TextView(this);
            tv3.setText("tv3");
            RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            );
            lp2.addRule(RelativeLayout.BELOW, tv2.getId());
            relativeLayout.addView(tv3, lp2);
    
            this.setContentView(relativeLayout);
        }
    }
    

    可与默认项目一起使用,该项目由 android create project ...生成。 GitHub 存储库中包含最小的构建代码


    1
    你能把这个例子扩展到3个TextView吗?我尝试过了,第三个TextView没有显示出来。 - Zizheng Wu

    7

    调用

    tv1.setId(1) 
    

    之后

    tv1.setText("A");
    

    如果我可以问,原因是什么?我有一个问题,但我同时使用ImageView和TextView,所以我想知道何时在ImageView上调用.setId()。 - Joshua G
    FYI - 我在添加视图之前调用了 .setId() 方法,它奏效了,但我不知道为什么.. 哈哈 - Joshua G

    4

    尝试:

    EditText edt = (EditText) findViewById(R.id.YourEditText);
    RelativeLayout.LayoutParams lp =
        new RelativeLayout.LayoutParams
        (
            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
        );
    lp.setMargins(25, 0, 0, 0); // move 25 px to right (increase left margin)
    edt.setLayoutParams(lp); // lp.setMargins(left, top, right, bottom);
    

    1
    很好的最有用的例子。谢谢。 - Vyacheslav

    2
    这种方法使用ViewGroup.MarginLayoutParams对我很有效:
    RelativeLayout myLayout = (RelativeLayout) findViewById(R.id.my_layout);
    
    TextView someTextView = ...
    
    int leftMargin = Util.getXPos();
    int topMargin = Util.getYPos();
    
    RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
        new ViewGroup.MarginLayoutParams(
            RelativeLayout.LayoutParams.WRAP_CONTENT,
            RelativeLayout.LayoutParams.WRAP_CONTENT));
    
    lp.setMargins(leftMargin, topMargin, 0, 0);
    
    myLayout.addView(someTextView, lp);
    

    1
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //setContentView(R.layout.activity_main);
    
            final RelativeLayout relativeLayout = new RelativeLayout(this);
            final TextView tv1 = new TextView(this);
            tv1.setText("tv1 is here");
            // Setting an ID is mandatory.
            tv1.setId(View.generateViewId());
            relativeLayout.addView(tv1);
    
    
            final TextView tv2 = new TextView(this);
            tv2.setText("tv2 is here");
    
            // We are defining layout params for tv2 which will be added to its  parent relativelayout.
            // The type of the LayoutParams depends on the parent type.
            RelativeLayout.LayoutParams tv2LayoutParams = new  RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.WRAP_CONTENT,
                RelativeLayout.LayoutParams.WRAP_CONTENT);
    
            //Also, we want tv2 to appear below tv1, so we are adding rule to tv2LayoutParams.
            tv2LayoutParams.addRule(RelativeLayout.BELOW, tv1.getId());
    
            //Now, adding the child view tv2 to relativelayout, and setting tv2LayoutParams to be set on view tv2.
            relativeLayout.addView(tv2);
            tv2.setLayoutParams(tv2LayoutParams);
            //Or we can combined the above two steps in one line of code
            //relativeLayout.addView(tv2, tv2LayoutParams);
    
            this.setContentView(relativeLayout);
        }
    
    }
    

    0

    如果你真的想手动布局,我建议不要使用标准布局。完全自己来,这里有一个 Kotlin 的例子:

    class ProgrammaticalLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ViewGroup(context, attrs, defStyleAttr) { 
        private val firstTextView = TextView(context).apply {
            test = "First Text"
        }
    
        private val secondTextView = TextView(context).apply {
            text = "Second Text"
        }
    
        init {
            addView(firstTextView)
            addView(secondTextView)
        }
    
        override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
            // center the views verticaly and horizontaly
            val firstTextLeft = (measuredWidth - firstTextView.measuredWidth) / 2
            val firstTextTop = (measuredHeight - (firstTextView.measuredHeight + secondTextView.measuredHeight)) / 2
            firstTextView.layout(firstTextLeft,firstTextTop, firstTextLeft + firstTextView.measuredWidth,firstTextTop + firstTextView.measuredHeight)
    
            val secondTextLeft = (measuredWidth - secondTextView.measuredWidth) / 2
            val secondTextTop = firstTextView.bottom
            secondTextView.layout(secondTextLeft,secondTextTop, secondTextLeft + secondTextView.measuredWidth,secondTextTop + secondTextView.measuredHeight)
        }
    
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 
            // just assume we`re getting measured exactly by the parent
            val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
            val measuredHeight = MeasureSpec.getSize(heightMeasureSpec)
    
            firstTextView.measures(MeasureSpec.makeMeasureSpec(meeasuredWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
            secondTextView.measures(MeasureSpec.makeMeasureSpec(meeasuredWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
    
            setMeasuredDimension(measuredWidth, measuredHeight)
        }
    }
    

    这可能会给你一个想法,如何使其工作


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接