238

I'm trying to achieve the following programmatically (rather than declaratively via XML):

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

In other words, how do I make the second TextView appear below the first one, but I want to do it in code:

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

Update:

Thanks, TreeUK. I understand the general direction, but it still doesn't work - "B" overlaps "A". What am I doing wrong?

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);
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Dan
  • 2,389
  • 2
  • 14
  • 4
  • In your code sample, you're not actually adding the rule of RelativeLayout.BELOW, tv1.getId(); – Tristan Warner-Smith Feb 06 '11 at 16:51
  • 48
    you need to provide id's to your child views: tv1.setId(1); tv2.setId(2); Parent views do not automatically assign child views an Id, and the default value for an Id is NO_ID. Id's don't have to be unique in a view hiearchy - so 1, 2, 3, etc are all fine values to use - and you must use them for relative anchoring to work in RelativeLayout. – sechastain Feb 06 '11 at 16:51
  • I recommend NOT using random (1, 2, 3, etc.) as widget IDs because if you need a real, valid, generated ID, the "random" numbers will throw off the valid widget IDs and the view you need to save its value (eg: EditText) will not. Took me hours to figure this one out! – Trasd Jun 14 '23 at 20:52

9 Answers9

201

From what I've been able to piece together, you have to add the view using 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);

All credit to sechastain, to relatively position your items programmatically you have to assign ids to them.

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

Then addRule(RelativeLayout.RIGHT_OF, tv1.getId());

Kirill Ryabin
  • 193
  • 1
  • 2
  • 15
Tristan Warner-Smith
  • 9,631
  • 6
  • 46
  • 75
  • 1
    in my case apart from setting the ids of a childview, adding childview to the parentview and calling requestLayout() on childview before setting the rules of the other childview made things work. Hope this helps someone – 2cupsOfTech Aug 26 '15 at 16:46
  • 1
    In case anyone still follows this: when i have a collection of views I am adding one by one and I can not be certain that one of the views that an item is set to be `below` has already been added, how to make sure it still renders correctly? From my understanding `addView` causes the layout to re-render, so if I do it for an item who's relative has not been added yet, it'd gonna crash since item with that ID doesn't yet exist. – Megakoresh Jul 19 '16 at 12:46
  • 1
    set(id) should be used with generateViewId () Added in API level 17. It Generates a value suitable for use in setId(int). This value will not collide with ID values generated at build time by aapt for R.id. Example: tv[l_idx].setId(View.generateViewId()); – AndrewBloom May 03 '17 at 13:28
  • This answer is very badly written. What's the rationale behind using RelationalLayout parameters for a Linear layout? And why are you adding the Linear Layout to a Parent View? – pcodex Sep 28 '19 at 10:59
  • Hey @Prab, this answer is now 9 years old. If you have some positive feedback on how the answer can be improved for today's needs, please feel free to hit the edit button and suggest it – Tristan Warner-Smith Sep 29 '19 at 13:45
61

Cut the long story short: With relative layout you position elements inside the layout.

  1. create a new RelativeLayout.LayoutParams

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

    (whatever... fill parent or wrap content, absolute numbers if you must, or reference to an XML resource)

  2. Add rules: Rules refer to the parent or to other "brothers" in the hierarchy.

    lp.addRule(RelativeLayout.BELOW, someOtherView.getId())
    lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
    
  3. Just apply the layout params: The most 'healthy' way to do that is:

    parentLayout.addView(myView, lp)
    

Watch out: Don't change layout from the layout callbacks. It is tempting to do so because this is when views get their actual sizes. However, in that case, unexpected results are expected.

sjngm
  • 12,423
  • 14
  • 84
  • 114
Meymann
  • 2,520
  • 2
  • 28
  • 22
  • I had no idea you could set params to a View and add a View to it's parent View all in one line of code (`parentLayout.addView(myView, lp`). I always did that in two lines of code. Thank you! – Matt Dec 06 '13 at 14:54
  • This worked for me and helped a lot, but is there a way to align the child below something and then set the margin above for that child? Or can I set the margin below for the thing that it is below and will it adjust correctly? – Jacob Varner Aug 12 '14 at 06:19
  • Sure there is. You can set the margins (unfortunately, the programmatic API is not as comfortable as the XML) with setMargins (left, top, right, bottom). It's in the LayoutParams. Note that padding is a property of the view, not the layout params. – Meymann Aug 13 '14 at 09:53
29

Just spent 4 hours with this problem. Finally realized that you must not use zero as view id. You would think that it is allowed as NO_ID == -1, but things tend to go haywire if you give it to your view...

fastfox
  • 443
  • 4
  • 6
  • 2
    I see the same. I'm using API 7, and viewing the javadoc I see that setID(int) does state that the id should be a positive integer. It's something that should be enforced in the code, not simply commented in the docs. http://developer.android.com/reference/android/view/View.html#setId(int) – OYRM Apr 03 '13 at 13:53
  • @OYRM Android is a mess – SpaceMonkey Aug 15 '15 at 23:55
  • You quite possibly just saved me a TON of time. Thanks! – rjr-apps Jun 26 '16 at 07:20
9

Android 22 minimal runnable example

enter image description here

Source:

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

Works with the default project generated by android create project .... GitHub repository with minimal build code.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
7

call

tv1.setId(1) 

after

tv1.setText("A");
Peter Ajtai
  • 56,972
  • 13
  • 121
  • 140
user299870
  • 71
  • 1
  • If I may ask, what is the reason? I am having an issue but I am using ImageView as well as TextView so I am wondering when to call .setId() on the ImageView. – Joshua G Oct 17 '13 at 21:18
  • FYI - I called .setId() right before I add the view and it worked, no idea why.. lol – Joshua G Oct 17 '13 at 21:23
4

Try:

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);
Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
2

This approach with ViewGroup.MarginLayoutParams worked for me:

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);
Gene Bo
  • 11,284
  • 8
  • 90
  • 137
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);
    }

}
Richa
  • 700
  • 8
  • 12
0

If you really want to layout manually, i'd suggest not to use a standard layout at all. Do it all on your own, here a kotlin example:

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

This might give you an idea how this could work

martyglaubitz
  • 992
  • 10
  • 21