AlertDialog avec vue personnalisée: redimensionner pour envelopper le contenu de la vue

J’ai eu ce problème dans une application que je construis. Veuillez ignorer toutes les lacunes en matière de conception et le manque d’approches des meilleures pratiques, ceci est simplement pour montrer un exemple de ce que je ne peux pas résoudre.

J’ai DialogFragment qui renvoie un AlertDialog base avec un ensemble de View personnalisé utilisant AlertDialog.Builder.setView() . Si cette View nécessite une taille spécifique, comment puis-je obtenir le redimensionnement correct de la Dialog de Dialog pour afficher tout le contenu dans la View personnalisée?

Voici le code exemple que j’ai utilisé:

 package com.test.test; import android.os.Bundle; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Use a button for launching Button b = new Button(this); b.setText("Launch"); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Launch the dialog myDialog d = new myDialog(); d.show(getFragmentManager(), null); } }); setContentView(b); } public static class myDialog extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Create the dialog AlertDialog.Builder db = new AlertDialog.Builder(getActivity()); db.setTitle("Test Alert Dialog:"); db.setView(new myView(getActivity())); return db.create(); } protected class myView extends View { Paint p = null; public myView(Context ct) { super(ct); // Setup paint for the drawing p = new Paint(); p.setColor(Color.MAGENTA); p.setStyle(Style.STROKE); p.setStrokeWidth(10); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(800, 300); } @Override protected void onDraw(Canvas canvas) { // Draw a rectangle showing the bounds of the view canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), p); } } } } 

Un Button est créé, ce qui ouvre le DialogFragment en un clic. La View personnalisée ( myView ) doit avoir une largeur de 800 et une hauteur de 300, ce qui est correctement défini dans une substitution de onMeasure() . Cette View dessine ses limites mesurées en magenta à des fins de débogage.

La largeur 800 est plus large que la taille de Dialog par défaut de mon appareil, mais est coupée plutôt que de s’étirer correctement.

J’ai examiné les solutions suivantes:

  • DialogFragment.getDialog renvoie null
  • Comment contrôler la largeur et la hauteur du dialog d’alertes par défaut dans Android?
  • Boîte de dialog Taille de l’alerte ou Boîte de dialog des alertes personnalisées

J’ai déduit les deux approches de codage suivantes:

  1. Récupère les WindowManager.LayoutParams de la Dialog et les remplace en utilisant myDialog.getDialog().getWindow().get/setAtsortingbutes()
  2. Utilisation de la setLayout(w, h) via myDialog.getDialog().getWindow().setLayout()

Je les ai essayés partout où je peux penser ( onStart() , dans un onShowListener , après que le Dialog soit créé et montré, etc.) et peut généralement obtenir les deux méthodes pour fonctionner correctement si les LayoutParams sont fournis une valeur spécifique . Mais chaque fois que WRAP_CONTENT est fourni, rien ne se passe.

Aucune suggestion?

MODIFIER:

Capture d’écran de la situation: entrer la description de l'image ici

Capture d’écran d’une valeur spécifique (la note 900 est entrée ici, 850 ne couvre pas toute la largeur de la vue, ce qui est logique étant donné que la fenêtre entière est ajustée. Cela fournit donc – si une autre est nécessaire – la raison pour laquelle WRAP_CONTENT est essentiel les valeurs fixes ne sont pas appropriées): entrer la description de l'image ici

J’ai une solution de travail qui, pour être honnête, me semble trop profonde pour obtenir un résultat aussi simple. Mais ici c’est:

Qu’est-ce qui se passe exactement:

En ouvrant la mise en page Dialog avec le visualiseur hiérarchique , j’ai pu examiner l’intégralité de la structure de AlertDialog et ce qui se passait exactement: entrer la description de l'image ici

Le point bleu est toutes les parties de haut niveau ( Window , frameworks pour le style visuel Dialog , etc.) et à partir de la fin du bleu , les composants pour AlertDialog sont ( rouge = titre, jaune = un talon de survol, peut-être pour la liste AlertDialog s, vert = Contenu du Dialog , c.-à-d. vue personnalisée, orange = boutons).

De là, il était clair que le chemin à 7 vues (du début du bleu à la fin du green ) était ce qui ne parvenait pas à WRAP_CONTENT . L’ LayoutParams.width de LayoutParams.width de chaque View révélé que tous les éléments sont atsortingbués à LayoutParams.width = MATCH_PARENT et qu’une taille est définie (en haut, je suppose). Donc, si vous suivez cet arbre, il est clair que votre View personnalisée en bas de l’arborescence ne pourra jamais affecter la taille du Dialog .

Alors, quelles étaient les solutions existantes?

  • Les deux approches de codage mentionnées dans ma question consistaient simplement à obtenir la View du haut et à modifier ses LayoutParams . De toute évidence, avec tous les objects View de l’arborescence correspondant au parent, si le niveau supérieur est défini sur une taille statique, la Dialog entière changera de taille. Mais si le niveau supérieur est défini sur WRAP_CONTENT , tous les autres objects View de l’arborescence recherchent toujours l’arborescence pour “MATCH leur PARENT”, plutôt que de regarder l’arborescence pour “WRAP leur CONTENU”.

Comment résoudre le problème:

Manipulez, modifiez le LayoutParams.width de tous les objects View dans le chemin affectant pour être WRAP_CONTENT .

J’ai trouvé que cela ne pouvait être fait onStart cycle de vie de DialogFragment . Donc, le onStart est implémenté comme:

 @Override public void onStart() { // This MUST be called first! Otherwise the view tweaking will not be present in the displayed Dialog (most likely overriden) super.onStart(); forceWrapContent(myCustomView); } 

Ensuite, la fonction de modifier de manière appropriée la hiérarchie de LayoutParams :

 protected void forceWrapContent(View v) { // Start with the provided view View current = v; // Travel up the tree until fail, modifying the LayoutParams do { // Get the parent ViewParent parent = current.getParent(); // Check if the parent exists if (parent != null) { // Get the view try { current = (View) parent; } catch (ClassCastException e) { // This will happen when at the top view, it cannot be cast to a View break; } // Modify the layout current.getLayoutParams().width = LayoutParams.WRAP_CONTENT; } } while (current.getParent() != null); // Request a layout to be re-done current.requestLayout(); } 

Et voici le résultat: entrer la description de l'image ici

Cela m’embrouille du fait que l’ensemble du Dialog ne voudrait pas être WRAP_CONTENT avec un jeu de WRAP_CONTENT explicite pour gérer tous les cas qui correspondent à la taille par défaut, mais je suis sûr qu’il y a une bonne raison entendez-le).

Après

 dialog.show(); 

juste utiliser

 dialog.getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, yourHeight); 

Solution très simple, mais ça marche pour moi. Je prolonge une boîte de dialog mais suppose que cela fonctionnera également pour DialogFragment.

AlertDialog utilise ces deux atsortingbuts de fenêtre pour définir la plus petite taille possible pour que, sur les tablettes, ils flottent avec une largeur raisonnable au centre de l’écran.

http://developer.android.com/reference/android/R.attr.html#windowMinWidthMajor http://developer.android.com/reference/android/R.attr.html#windowMinWidthMinor

Vous pouvez étendre un style de dialog par défaut de votre choix et modifier les valeurs pour appliquer votre propre logique.

J’ai trouvé un problème avec la réponse de BT . Le dialog est resté alloué (pas au centre de l’écran). Pour résoudre ce problème, j’ai ajouté la gravité changeante des mises en page des parents. Voir la méthode mise à jour de forceWrapContent ().

 protected void forceWrapContent(View v) { // Start with the provided view View current = v; // Travel up the tree until fail, modifying the LayoutParams do { // Get the parent ViewParent parent = current.getParent(); // Check if the parent exists if (parent != null) { // Get the view try { current = (View) parent; ViewGroup.LayoutParams layoutParams = current.getLayoutParams(); if (layoutParams instanceof FrameLayout.LayoutParams) { ((FrameLayout.LayoutParams) layoutParams). gravity = Gravity.CENTER_HORIZONTAL; } else if (layoutParams instanceof WindowManager.LayoutParams) { ((WindowManager.LayoutParams) layoutParams). gravity = Gravity.CENTER_HORIZONTAL; } } catch (ClassCastException e) { // This will happen when at the top view, it cannot be cast to a View break; } // Modify the layout current.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; } } while (current.getParent() != null); // Request a layout to be re-done current.requestLayout(); } 

Cela a bien fonctionné pour moi:

 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); lp.copyFrom(dialog.getWindow().getAtsortingbutes()); lp.width = WindowManager.LayoutParams.WRAP_CONTENT; lp.height = WindowManager.LayoutParams.WRAP_CONTENT; dialog.show(); dialog.getWindow().setAtsortingbutes(lp); 

Utilisez setStyle(STYLE_NORMAL, android.R.style.Theme_Holo_Light_Dialog);

Sa réponse tardive, mais je pense que je devrais partager cela avec les nouveaux venus afin qu’ils sachent quel dialog ils devraient préférer.

Si vous souhaitez créer une boîte de dialog personnalisée avec l’une de vos vues. Ensuite, vous pouvez choisir ci-dessous la manière la plus simple de le faire.

Pour que la question que vous voulez que la boîte de dialog soit un contenu wrap, vous pouvez créer une mise en page parent avec match_parent avec un arrière-plan transparent et avec un centre de gravité. et mettez votre mise en page principale en dessous. il ressemblera donc au dialog positionné au centre.

BONUS: Par cette méthode, vous pouvez utiliser la vue de défilement, la vue du recycleur et tout type de disposition dans la boîte de dialog.

Dans cet exemple, R.layout.dialog_update_name est la disposition exemple. que vous voulez gonfler sur le dialog.

 public void showCustomDialog(final Context context) { this.context = context; dialog = new Dialog(context); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.dialog_update_name, null, false); findByIds(view); /*HERE YOU CAN FIND YOU IDS AND SET TEXTS OR BUTTONS*/ ((Activity) context).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); dialog.setContentView(view); final Window window = dialog.getWindow(); window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); window.setBackgroundDrawableResource(R.color.colorTransparent); window.setGravity(Gravity.CENTER); dialog.show(); } 

il suffit d’utiliser AppCompatDialog

 import android.content.Context; import android.os.Bundle; import android.support.v7.app.AppCompatDialog; import android.view.Window; import android.widget.ProgressBar; public class ProgressDialogCompat extends AppCompatDialog { public ProgressDialogCompat(Context context) { super(context); supportRequestWindowFeature(Window.FEATURE_NO_TITLE); } @Override protected void onCreate(Bundle savedInstanceState) { Context context = getContext(); int padding = context.getResources().getDimensionPixelSize(R.dimen.x_medium); ProgressBar progressBar = new ProgressBar(context); progressBar.setPadding(padding, padding, padding, padding); setContentView(progressBar); setCancelable(false); super.onCreate(savedInstanceState); } }