web-dev-qa-db-fra.com

Présentation Kotlin synthétique et personnalisée dans DialogFragment

Disons que j'ai cette mise en page:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:orientation="vertical">

<ImageButton
    Android:id="@+id/add_dep_btn"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:layout_alignParentEnd="true"
    Android:layout_alignParentRight="true"
    Android:layout_marginEnd="5dp"
    Android:layout_marginRight="5dp"
    Android:src="@Android:drawable/ic_input_add" />

<EditText
    Android:id="@+id/add_dep_text"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:layout_alignBottom="@id/add_dep_btn"
    Android:layout_alignParentLeft="true"
    Android:layout_alignParentStart="true"
    Android:layout_alignTop="@id/add_dep_btn"
    Android:layout_marginLeft="5dp"
    Android:layout_marginStart="5dp"
    Android:layout_toLeftOf="@id/add_dep_btn"
    Android:layout_toStartOf="@id/add_dep_btn" />

<Android.support.v7.widget.RecyclerView
    Android:id="@+id/dep_list"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:layout_below="@id/add_dep_btn" />

<TextView
    Android:id="@+id/empty_text"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:layout_below="@id/add_dep_text"
    Android:layout_margin="20dp"
    Android:gravity="center"
    Android:text="@string/no_dep"
    Android:textSize="22sp" />
</RelativeLayout>

Et je l'utilise dans un DialogFragment:

class DepartmentChoiceDialog : DialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val builder = AlertDialog.Builder(activity)
        builder.setTitle(R.string.choose_or_create_dep)
            .setView(R.layout.department_chooser_dialog)
            .setNegativeButton(Android.R.string.cancel, { d, i ->
                d.cancel()
            })
        return builder.create()
    }
}

si je me réfère au widget en utilisant synthétique:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    dep_list.layoutManager = LinearLayoutManager(activity)
    dep_list.itemAnimator = DefaultItemAnimator()
    dep_list.setHasFixedSize(true)
}

J'ai eu cette erreur au moment de l'exécution:

Java.lang.NullPointerException: tentative d'appeler la méthode virtuelle 'Android.view.View Android.view.View.findViewById (int)' sur une référence d'objet null. sur MyDialog ._ $ _ findCachedViewById (DepartmentChoiceDialog.kt: 0)

Je ne comprends pas comment utiliser synthétique dans le cas DialogFragment. Cela fonctionne très bien dans Fragment and Activity.

18
Geob-o-matic

J'ai trouvé un moyen qui fonctionne pour les dialogues personnalisés.

class ServerPickerDialogFragment: AppCompatDialogFragment() 
{
  // Save your custom view at the class level
  lateinit var customView: View;
  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                            savedInstanceState: Bundle?): View? 
  {
       // Simply return the already inflated custom view
       return customView
  }

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
      // Inflate your view here
      customView = context!!.layoutInflater.inflate(R.layout.dialog_server_picker, null) 
      // Create Alert Dialog with your custom view
      return AlertDialog.Builder(context!!)
             .setTitle(R.string.server_picker_dialog_title)
             .setView(customView)
             .setNegativeButton(Android.R.string.cancel, null)
             .create()
  }

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
  {
    super.onViewCreated(view, savedInstanceState)
    // Perform remaining operations here. No null issues.
    rbgSelectType.setOnCheckedChangeListener({ _, checkedId ->
      if(checkedId == R.id.rbSelectFromList) {
             // XYZ
      } else {
             // ABC
      }
    })
  }
}
9
Sabaat Ahmad

Il semble que cela ne soit pas encore supporté par défaut, mais j'ai trouvé le moyen le plus simple de le faire. Dans une classe de dialogue de base:

protected abstract val containerView: View

override fun getView() = containerView

Dans une sous-classe:

override val containerView by unsafeLazy {
    View.inflate(context, R.layout.dialog_team_details, null) as ViewGroup
}

Vous pouvez ensuite utiliser les vues synthétiques comme vous le feriez normalement et utiliser la variable containerView comme vue de votre boîte de dialogue.

5
SUPERCILEX

Passer à onCreateView implementation

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.department_chooser_dialog, container, false)
}

et utiliser un titre personnalisé (TextView) et annuler (Button) dans le department_chooser_dialog

onActivityCreated sera exécuté après onCreateView et ira très bien.

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    dep_list.layoutManager = LinearLayoutManager(activity)
    dep_list.itemAnimator = DefaultItemAnimator()
    dep_list.setHasFixedSize(true)
}
2
Lucas Montano

Donc, je ne suis pas sûr si cela a été résolu ... Je viens de découvrir cela. Si vous avez une vue de dialogue personnalisée, créez une classe qui étend DialogFragment et utilisez l'objet "dialogue" pour importer des vues dans la présentation. J'utilise Android Studio 3.1.3 et Kotlin version 1.2.41 au moment de la rédaction.

import kotlinx.Android.synthetic.main.your_custom_layout.*

class SelectCountryBottomSheet : BottomSheetDialogFragment() {

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
      val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
      dialog.setContentView(R.layout.your_custom_layout)
      dialog.some_custom_close_button.setOnClickListener { dismiss() }
      return dialog
  }
}
2
Aceofspadez44

Parce que la valeur par défaut de la vue est fragment (la méthode kotlin generate _ $ _ findCachedViewById), mais si nous créons View à partir du dialogue, lead en fragment view est null, nous ne pouvons donc pas utiliser directement default xxx, mais nous pouvons utiliser dialog.xxx xxx

1
act262

Déplacez votre code de onActivityCreated à onViewCreated, méthode .

import kotlinx.Android.synthetic.main.department_chooser_dialog.dep_list

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    dep_list.apply {
        layoutManager = LinearLayoutManager(activity)
        itemAnimator = DefaultItemAnimator()
        setHasFixedSize(true)
    }
}

En fait, je n'ai pas approfondi le code généré et il y a peut-être un bug.

1
Ferenc Boldog

La réponse précédente ne fonctionnera pas, car onViewCreated n'est pas appelé lorsque vous utilisez onCreateDialog. Vous devez d'abord importer kotlinx ... department_chooser_dialog. view . Dep_list, puis l'utiliser comme suit:

import kotlinx.Android.synthetic.main.department_chooser_dialog.view.dep_list
...
class DepartmentChoiceDialog : DialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val builder = AlertDialog.Builder(activity)
        val dialog = inflater.inflate(R.layout.department_chooser_dialog, null)
        dialog.dep_list.layoutManager = LinearLayoutManager(activity)
        dialog.dep_list.itemAnimator = DefaultItemAnimator()
        dialog.dep_list.setHasFixedSize(true)
        builder.setTitle(R.string.choose_or_create_dep)
               .setView(dialog)
                    ...
1
Andrey Koretskyy

Le code kotlin dans un fragment comme celui-ci: 

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.your_layout, container, false).apply {
        mContentView = this
        button1.setOnClickListener { 
            //do something
        }
    }
}

Après avoir décompilé le bytecode, vous pouvez voir l’implémentation des propriétés synthétiques: ((Button)this._$_findCachedViewById(id.button1)) Et de la méthode _$_findCachedViewById:

public View _$_findCachedViewById(int var1) {
  if (this._$_findViewCache == null) {
     this._$_findViewCache = new HashMap();
  }

  View var2 = (View)this._$_findViewCache.get(var1);
  if (var2 == null) {
     View var10000 = this.getView();
     if (var10000 == null) {
        return null;
     }

     var2 = var10000.findViewById(var1);
     this._$_findViewCache.put(var1, var2);
  }

  return var2;

}

donc la magie est juste la this.getView(). La propriété Fragment.mView est attribuée après Fragment.onCreateView(inflater, container, savedInstanceState), si vous utilisez Kotlin Synthetic Properties dans la méthode onCreateView (), il y aura un NPE. Code de FragmentManager.moveToState()

case Fragment.CREATED:
    ...
    f.mView = f.performCreateView(f.performGetLayoutInflater(
                                f.mSavedFragmentState), container, 
    f.mSavedFragmentState);
    ...

Pour réparer le NPE, assurez-vous que la méthode getView renvoie une vue non NULL.

private var mContentView: View? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.your_layout, container, false).apply {
        mContentView = this
    }
}
override fun getView(): View? {
    return mContentView
}

et au rappel de cycle de vie onDestroyView(), définissez mContentView sur null. 

override fun onDestroyView() {
    super.onDestroyView()
    mView = null
}
0
ggaier