Créer une vue personnalisée en gonflant une mise en page?

107

J'essaie de créer une vue personnalisée qui remplacerait une certaine mise en page que j'utilise à plusieurs endroits, mais j'ai du mal à le faire.

En gros, je veux remplacer ceci:

<RelativeLayout
 android:id="@+id/dolphinLine"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
    android:layout_centerInParent="true"
 android:background="@drawable/background_box_light_blue"
 android:padding="10dip"
 android:layout_margin="10dip">
  <TextView
   android:id="@+id/dolphinTitle"
   android:layout_width="200dip"
   android:layout_height="100dip"
   android:layout_alignParentLeft="true"
   android:layout_marginLeft="10dip"
   android:text="@string/my_title"
   android:textSize="30dip"
   android:textStyle="bold"
   android:textColor="#2E4C71"
   android:gravity="center"/>
  <Button
   android:id="@+id/dolphinMinusButton"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_toRightOf="@+id/dolphinTitle"
   android:layout_marginLeft="30dip"
   android:text="@string/minus_button"
   android:textSize="70dip"
   android:textStyle="bold"
   android:gravity="center"
   android:layout_marginTop="1dip"
   android:background="@drawable/button_blue_square_selector"
   android:textColor="#FFFFFF"
   android:onClick="onClick"/>
  <TextView
   android:id="@+id/dolphinValue"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_marginLeft="15dip"
   android:background="@android:drawable/editbox_background"
   android:layout_toRightOf="@+id/dolphinMinusButton"
   android:text="0"
   android:textColor="#2E4C71"
   android:textSize="50dip"
   android:gravity="center"
   android:textStyle="bold"
   android:inputType="none"/>
  <Button
   android:id="@+id/dolphinPlusButton"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_toRightOf="@+id/dolphinValue"
   android:layout_marginLeft="15dip"
   android:text="@string/plus_button"
   android:textSize="70dip"
   android:textStyle="bold"
   android:gravity="center"
   android:layout_marginTop="1dip"
   android:background="@drawable/button_blue_square_selector"
   android:textColor="#FFFFFF"
   android:onClick="onClick"/>
</RelativeLayout>

Par ça:

<view class="com.example.MyQuantityBox"
    android:id="@+id/dolphinBox"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:myCustomAttribute="@string/my_title"/>

Donc, je ne veux pas de mise en page personnalisée, je veux une vue personnalisée (il ne devrait pas être possible pour cette vue d'avoir un enfant).

La seule chose qui pourrait changer d'une instance d'une MyQuantityBox à une autre est le titre. J'aimerais beaucoup pouvoir le spécifier dans le XML (comme je le fais sur la dernière ligne XML)

Comment puis-je faire ceci? Dois-je mettre RelativeLayout dans un fichier XML dans / res / layout et le gonfler dans ma classe MyBoxQuantity? Si oui, comment faire?

Merci!

nbarraille
la source
Voir "Contrôles composés" dans Android, et ce lien: stackoverflow.com/questions/1476371
...

Réponses:

27

Oui, vous pouvez le faire. RelativeLayout, LinearLayout, etc. sont des vues, donc une mise en page personnalisée est une vue personnalisée. Juste quelque chose à considérer car si vous vouliez créer une mise en page personnalisée, vous le pourriez.

Ce que vous voulez faire est de créer un contrôle composé. Vous allez créer une sous-classe de RelativeLayout, ajouter tous nos composants dans le code (TextView, etc.), et dans votre constructeur, vous pouvez lire les attributs transmis à partir du XML. Vous pouvez ensuite transmettre cet attribut à votre titre TextView.

http://developer.android.com/guide/topics/ui/custom-components.html

chubbsondubs
la source
130

Un peu vieux, mais j'ai pensé partager comment je le ferais, basé sur la réponse de chubbsondubs: j'utilise FrameLayout(voir documentation ), car il est utilisé pour contenir une seule vue, et y gonfler la vue du xml.

Code suivant:

public class MyView extends FrameLayout {
    public MyView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyView(Context context) {
        super(context);
        initView();
    }

    private void initView() {
        inflate(getContext(), R.layout.my_view_layout, this);
    }
}
Renard
la source
13
Puisque View classe a la méthode statique inflate () il n'y a pas besoin de LayoutInflater.from ()
périphérique
1
N'est-ce pas juste la solution de Johannes à partir d'ici: stackoverflow.com/questions/17836695/... Pourtant, cela gonfle une autre mise en page à l'intérieur. Ce n'est donc pas vraiment la meilleure solution que je suppose.
Tobias Reich
3
c'est le cas, mais la solution de Johannes est de 7.24.13, et l'esprit était de 7.1.13 ... De plus, ma solution utilise FrameLayout qui est censé ne contenir qu'une seule vue (comme écrit dans le document référencé dans la solution). Donc en fait, il est destiné à être utilisé comme espace réservé pour une vue. Je ne connais aucune solution qui n'implique pas l'utilisation d'un espace réservé pour la vue gonflée.
Fox
Je ne comprends pas. Cette méthode (inflate) renvoie une vue, qui est ignorée. Il semble que vous deviez l'ajouter à la vue actuelle.
Jeffrey Blattman le
1
@Jeffrey Blattman s'il vous plaît vérifier la méthode View.inflate , nous utilisons celle-ci (en spécifiant la racine comme this, 3ème paramètre)
V1raNi
36

Voici une démo simple pour créer une vue personnalisée (vue composée) en gonflant à partir de xml

attrs.xml

<resources>

    <declare-styleable name="CustomView">
        <attr format="string" name="text"/>
        <attr format="reference" name="image"/>
    </declare-styleable>
</resources>

CustomView.kt

class CustomView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
        ConstraintLayout(context, attrs, defStyleAttr) {

    init {
        init(attrs)
    }

    private fun init(attrs: AttributeSet?) {
        View.inflate(context, R.layout.custom_layout, this)

        val ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
        try {
            val text = ta.getString(R.styleable.CustomView_text)
            val drawableId = ta.getResourceId(R.styleable.CustomView_image, 0)
            if (drawableId != 0) {
                val drawable = AppCompatResources.getDrawable(context, drawableId)
                image_thumb.setImageDrawable(drawable)
            }
            text_title.text = text
        } finally {
            ta.recycle()
        }
    }
}

custom_layout.xml

Nous devrions utiliser mergeici au lieu de ConstraintLayoutparce que

Si nous utilisons ConstraintLayoutici, la hiérarchie de mise en page sera ConstraintLayout-> ConstraintLayout-> ImageView+ TextView=> nous avons 1 redondant ConstraintLayout=> pas très bon pour les performances

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:parentTag="android.support.constraint.ConstraintLayout">

    <ImageView
        android:id="@+id/image_thumb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:ignore="ContentDescription"
        tools:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/text_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="@id/image_thumb"
        app:layout_constraintStart_toStartOf="@id/image_thumb"
        app:layout_constraintTop_toBottomOf="@id/image_thumb"
        tools:text="Text" />

</merge>

Utilisation de activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <your_package.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#f00"
        app:image="@drawable/ic_android"
        app:text="Android" />

    <your_package.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#0f0"
        app:image="@drawable/ic_adb"
        app:text="ADB" />

</LinearLayout>

Résultat

entrez la description de l'image ici

Démo Github

Phan Van Linh
la source
4
Cela devrait être la réponse acceptée ou la plus votée dans ce fil, car elle mentionne une hiérarchie de mise en page inutile.
Farid
15

Utilisez le LayoutInflater comme indiqué ci-dessous.

    public View myView() {
        View v; // Creating an instance for View Object
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = inflater.inflate(R.layout.myview, null);

        TextView text1 = v.findViewById(R.id.dolphinTitle);
        Button btn1 = v.findViewById(R.id.dolphinMinusButton);
        TextView text2 = v.findViewById(R.id.dolphinValue);
        Button btn2 = v.findViewById(R.id.dolphinPlusButton);

        return v;
    }
Tsunaze
la source
J'ai essayé la même chose. son fonctionne bien. mais, lorsque je clique sur btn1, il appellera les services Web et après avoir reçu la réponse du serveur, je veux mettre à jour du texte dans une position particulière text2..any help pls?
harikrishnan
7

En pratique, j'ai trouvé que vous devez être un peu prudent, surtout si vous utilisez un peu de xml à plusieurs reprises. Supposons, par exemple, que vous ayez une table que vous souhaitez créer une ligne de table pour chaque entrée d'une liste. Vous avez configuré du xml:

Dans my_table_row.xml:

<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android" 
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent" android:id="@+id/myTableRow">
    <ImageButton android:src="@android:drawable/ic_menu_delete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/rowButton"/>
    <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="TextView" android:id="@+id/rowText"></TextView>
</TableRow>

Ensuite, vous voulez le créer une fois par ligne avec du code. Il suppose que vous avez défini un TableLayout parent myTable auquel attacher les lignes.

for (int i=0; i<numRows; i++) {
    /*
     * 1. Make the row and attach it to myTable. For some reason this doesn't seem
     * to return the TableRow as you might expect from the xml, so you need to
     * receive the View it returns and then find the TableRow and other items, as
     * per step 2.
     */
    LayoutInflater inflater = (LayoutInflater)getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View v =  inflater.inflate(R.layout.my_table_row, myTable, true);

    // 2. Get all the things that we need to refer to to alter in any way.
    TableRow    tr        = (TableRow)    v.findViewById(R.id.profileTableRow);
    ImageButton rowButton = (ImageButton) v.findViewById(R.id.rowButton);
    TextView    rowText   = (TextView)    v.findViewById(R.id.rowText);

    // 3. Configure them out as you need to
    rowText.setText("Text for this row");
    rowButton.setId(i); // So that when it is clicked we know which one has been clicked!
    rowButton.setOnClickListener(this); // See note below ...           

    /*
     * To ensure that when finding views by id on the next time round this
     * loop (or later) gie lots of spurious, unique, ids.
     */
    rowText.setId(1000+i);
    tr.setId(3000+i);
}

Pour un exemple clair et simple sur la gestion de rowButton.setOnClickListener (this), consultez Onclicklistener pour un bouton créé par programme .

Neil Townsend
la source
Salut Neil, j'ai essayé la même chose. son fonctionne bien. mais, lorsque je clique sur rowButton, il appelle les services Web et après avoir reçu la réponse du serveur, je veux mettre à jour du texte dans une position particulière rowText..any help pls?
harikrishnan
voir ma question aussi. stackoverflow.com/questions/48719970/…
harikrishnan