Comment disposer les vues dans RelativeLayout par programme?

234

J'essaie d'atteindre les programmes suivants (plutôt que de manière déclarative via XML):

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

En d'autres termes, comment faire apparaître le second en TextViewdessous du premier, mais je veux le faire en code:

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

Mettre à jour:

Merci, TreeUK. Je comprends la direction générale, mais cela ne fonctionne toujours pas - "B" chevauche "A". Qu'est-ce que je fais mal?

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);
Dan
la source
Dans votre exemple de code, vous n'ajoutez pas réellement la règle de RelativeLayout.BELOW, tv1.getId ();
Tristan Warner-Smith
48
vous devez fournir des identifiants à vos vues enfant: tv1.setId (1); tv2.setId (2); Les vues parent n'attribuent pas automatiquement un identifiant aux vues enfant et la valeur par défaut d'un identifiant est NO_ID. Les identifiants n'ont pas à être uniques dans une vue de recherche - donc 1, 2, 3, etc. sont tous de bonnes valeurs à utiliser - et vous devez les utiliser pour que l'ancrage relatif fonctionne dans RelativeLayout.
sechastain

Réponses:

196

D'après ce que j'ai pu reconstituer, vous devez ajouter la vue à l'aide de 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);

Tout crédit à sechastain, pour positionner relativement vos articles par programme, vous devez leur attribuer des identifiants.

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

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

Tristan Warner-Smith
la source
1
dans mon cas, outre la définition des identifiants d'une vue enfant, l'ajout de la vue enfant à la vue parent et l'appel de requestLayout () sur la vue enfant avant de définir les règles de l'autre vue enfant ont fait fonctionner les choses. J'espère que cela aide quelqu'un
2cupsOfTech
Au cas où quelqu'un suivrait toujours ceci: lorsque j'ai une collection de vues, j'ajoute une par une et je ne peux pas être certain qu'une des vues qu'un élément est défini pour être belowa déjà été ajoutée, comment s'assurer qu'elle s'affiche toujours correctement? D'après ma compréhension addView, la mise en page est rendue à nouveau, donc si je le fais pour un élément dont le parent n'a pas encore été ajouté, il se bloquera car l'élément avec cet ID n'existe pas encore.
Megakoresh
1
set (id) doit être utilisé avec generateViewId () Ajouté au niveau 17 de l'API. Il génère une valeur appropriée pour une utilisation dans setId (int). Cette valeur n'entrera pas en collision avec les valeurs d'ID générées au moment de la construction par aapt pour R.id. Exemple: tv [l_idx] .setId (View.generateViewId ());
AndrewBloom
Cette réponse est très mal écrite. Quelle est la raison d'être de l'utilisation des paramètres RelationalLayout pour une disposition linéaire? Et pourquoi ajoutez-vous la disposition linéaire à une vue parent?
pcodex
Hé @Prab, cette réponse a maintenant 9 ans. Si vous avez des commentaires positifs sur la façon dont la réponse peut être améliorée pour les besoins d'aujourd'hui, n'hésitez pas à appuyer sur le bouton modifier et à le suggérer 👍
Tristan Warner-Smith
60

Pour faire court: avec la mise en page relative, vous positionnez les éléments à l'intérieur de la mise en page.

  1. créer un nouveau RelativeLayout.LayoutParams

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

    (peu importe ... remplir le contenu parent ou d'habillage, des nombres absolus si vous le devez, ou une référence à une ressource XML)

  2. Ajouter des règles: les règles se réfèrent au parent ou à d'autres "frères" de la hiérarchie.

    lp.addRule(RelativeLayout.BELOW, someOtherView.getId())
    lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
  3. Il suffit d'appliquer les paramètres de mise en page: La façon la plus «saine» de le faire est:

    parentLayout.addView(myView, lp)

Attention : ne modifiez pas la mise en page à partir des rappels de mise en page. Il est tentant de le faire car c'est à ce moment que les vues obtiennent leur taille réelle. Cependant, dans ce cas, des résultats inattendus sont attendus.

Meymann
la source
Je ne savais pas que vous pouviez définir des paramètres sur une vue et ajouter une vue à son parent Voir tout sur une seule ligne de code ( parentLayout.addView(myView, lp). J'ai toujours fait ça en deux lignes de code. Je vous remercie!
Matt
Cela a fonctionné pour moi et m'a beaucoup aidé, mais existe-t-il un moyen d'aligner l'enfant en dessous de quelque chose, puis de définir la marge au-dessus pour cet enfant? Ou puis-je définir la marge ci-dessous pour la chose qu'elle est inférieure et va-t-elle s'ajuster correctement?
Jacob Varner
Bien sûr que oui. Vous pouvez définir les marges (malheureusement, l'API programmatique n'est pas aussi confortable que le XML) avec setMargins (gauche, haut, droite, bas). C'est dans les LayoutParams. Notez que le remplissage est une propriété de la vue, pas les paramètres de mise en page.
Meymann
29

Je viens de passer 4 heures avec ce problème. Enfin réalisé que vous ne devez pas utiliser zéro comme id de vue . On pourrait penser que c'est autorisé comme NO_ID == -1, mais les choses ont tendance à se détraquer si vous le donnez à votre vue ...

fastfox
la source
2
Je vois la même chose. J'utilise l'API 7, et en visualisant le javadoc, je vois que setID (int) indique que l'id doit être un entier positif. C'est quelque chose qui devrait être appliqué dans le code, pas simplement commenté dans les documents. developer.android.com/reference/android/view/…
OYRM
@OYRM Android est un gâchis
SpaceMonkey
Vous m'avez peut-être sauvé une tonne de temps. Merci!
rjr-apps
9

Exemple exécutable minimal d'Android 22

entrez la description de l'image ici

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

Fonctionne avec le projet par défaut généré par android create project .... Dépôt GitHub avec un code de construction minimal .

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
1
Pourriez-vous étendre cet exemple à 3 TextView? J'ai essayé et le troisième TextViewmanque.
Zizheng Wu
7

appel

tv1.setId(1) 

après

tv1.setText("A");
user299870
la source
Si je peux demander, quelle en est la raison? Je rencontre un problème mais j'utilise ImageView ainsi que TextView, donc je me demande quand appeler .setId () sur ImageView.
Joshua G
Pour info - j'ai appelé .setId () juste avant d'ajouter la vue et cela a fonctionné, aucune idée pourquoi .. lol
Joshua G
4

Essayer:

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
la source
1
Nice l'exemple le plus utile. Merci.
Vyacheslav
2

Cette approche avec ViewGroup.MarginLayoutParams a fonctionné pour moi:

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
la source
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
la source
0

Si vous voulez vraiment mettre en page manuellement, je vous suggère de ne pas utiliser du tout une mise en page standard. Faites tout par vous-même, voici un exemple de 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)
    }
}

Cela pourrait vous donner une idée de la façon dont cela pourrait fonctionner

martyglaubitz
la source