Как я могу динамически отфильтровать раскрывающееся меню «Открытые» для подпространств в моем проекте описания символов dnd?

#android #android-studio #kotlin #filter #drop-down-menu

Вопрос:

Отказ от ответственности
Все лето я пытался научиться программировать приложения для Android. Я изучал Основы Android в курсе Kotlin, и недавно я достиг точки, когда почувствовал, что могу немного развиться самостоятельно.

Контекст
Я пытаюсь создать приложение для описания персонажей для недавно выпущенной игры anime 5e damp;d. Моя цель-просто научиться программировать приложения. В настоящее время у меня есть класс MainActivity, который управляет навигацией с помощью навигатора, и это работает нормально. Итак, я начал работать над первым фрагментом (называемым AboutFragment), размещенным в MainActivity. Именно здесь пользователь будет вводить такие данные, как свое имя, имя своего персонажа и т.д.

Проблема
В настоящее время я использую открытые выпадающие меню, чтобы пользователь мог выбрать расу и подрасу своего персонажа. Выбранная раса должна определить, какие подрасы будут доступны для их выбора. Например, если пользователь выбирает расу «Эльф», он должен иметь возможность выбирать только между «Нет», «Темный», «Высокий» или «Деревянный» при выборе своей подрасы.

Как видно из приведенного ниже кода, я могу просто отлично настроить выпадающее меню гонки. Но меню подрас в настоящее время настраивается до того, как у пользователя появится возможность выбрать гонку. Таким образом, меню подрас никогда не меняется с «Пожалуйста, выберите гонку».

У меня сложилось впечатление, что я должен создать выпадающее меню подрасы в методе onCreateView, но это не оставляет пользователю возможности определить свою расу до создания меню подрасы. Мне нужно иметь возможность изменять, какой массив отображается в меню подрасы после того, как пользователь выбрал гонку.

Заранее спасибо!!!

О фрагментном коде

 package com.example.anime5echaractersheet

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
import com.example.anime5echaractersheet.databinding.FragmentAboutBinding
import com.example.anime5echaractersheet.model.SharedViewModel

/**
 * A simple [Fragment] subclass.
 * Use the [AboutFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class AboutFragment : Fragment() {

    private val sharedViewModel: SharedViewModel by activityViewModels()

    private var _binding: FragmentAboutBinding? = null
    private val binding get() = _binding!!

    /*
    * initialize binding, set up race selection dropdown menu, inflate layout
    * */
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        _binding = FragmentAboutBinding.inflate(inflater, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        //setup race dropdown
        val races = resources.getStringArray(R.array.races)
        val raceAdapter = ArrayAdapter(requireActivity(), R.layout.dropdown_item, races)
        binding.raceAutocompleteTextView.setAdapter(raceAdapter)
        //setup subrace dropdown
        val subraces: Array<String> = createSubraceMenu()
        val subraceAdapter = ArrayAdapter(requireActivity(), R.layout.dropdown_item, subraces)
        binding.subraceAutocompleteTextView.setAdapter(subraceAdapter)

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.apply { viewModel = sharedViewModel }
        //update viewModel race amp; size when user makes changes
        binding.raceAutocompleteTextView.doAfterTextChanged {
            binding.viewModel?.updateRaceAndSize(binding.raceAutocompleteTextView.text.toString())
        }
    }

    /*
    * nullify _binding before destroying fragment
    * */
    override fun onDestroyView() {
        super.onDestroyView()

        _binding = null
    }

    private fun createSubraceMenu(): Array<String> {
        return when (binding.viewModel?.race?.value) {
            "Archfiend" -> resources.getStringArray(R.array.archfiend_subraces)
            "Asrai" -> resources.getStringArray(R.array.asrai_subraces)
            "Blinkbeast" -> resources.getStringArray(R.array.blinkbeast_subraces)
            "Crogoblin" -> resources.getStringArray(R.array.crogoblin_subraces)
            "Demonaga" -> resources.getStringArray(R.array.demonaga_subraces)
            "Dragonborn" -> resources.getStringArray(R.array.dragonborn_subraces)
            "Dwarf" -> resources.getStringArray(R.array.dwarf_subraces)
            "Elf" -> resources.getStringArray(R.array.elf_subraces)
            "Fairy" -> resources.getStringArray(R.array.fairy_subraces)
            "Gnome" -> resources.getStringArray(R.array.gnome_subraces)
            "Grey" -> resources.getStringArray(R.array.grey_subraces)
            "Half-Dragon" -> resources.getStringArray(R.array.half_dragon_subraces)
            "Half-Elf" -> resources.getStringArray(R.array.half_elf_subraces)
            "Half-Orc" -> resources.getStringArray(R.array.half_orc_subraces)
            "Half-Troll" -> resources.getStringArray(R.array.half_troll_subraces)
            "Halfling" -> resources.getStringArray(R.array.halfling_subraces)
            "Haud" -> resources.getStringArray(R.array.haud_subraces)
            "Human" -> resources.getStringArray(R.array.human_subraces)
            "Kodama" -> resources.getStringArray(R.array.kodama_subraces)
            "Loralai" -> resources.getStringArray(R.array.loralai_subraces)
            "Nekojin" -> resources.getStringArray(R.array.nekojin_subraces)
            "Parasite" -> resources.getStringArray(R.array.races)
            "Raceless" -> resources.getStringArray(R.array.raceless_subraces)
            "Satyr" -> resources.getStringArray(R.array.satyr_subraces)
            "Slime" -> resources.getStringArray(R.array.slime_subraces)
            "Tiefling" -> resources.getStringArray(R.array.tiefling_subraces)
            else -> arrayOf("Please select a race")
        }
    }
}
 

fragment_about layout

 <?xml version="1.0" encoding="utf-8"?>

<layout 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">

    <data>

        <variable
            name="viewModel"
            type="com.example.anime5echaractersheet.model.SharedViewModel" />
    </data>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            tools:context=".AboutFragment">

            <com.google.android.material.textfield.TextInputLayout
                style="@style/Widget.Anime5eCharacterSheet.TextInputLayout.FilledBox"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:helperText="@string/player_name_helper_text">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@ id/player_name_edit_text"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:autoSizeTextType="uniform"
                    android:hint="@string/player_name"
                    android:inputType="textCapWords" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                style="@style/Widget.Anime5eCharacterSheet.TextInputLayout.FilledBox"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:helperText="@string/character_name_helper_text">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@ id/character_name_edit_text"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:autoSizeTextType="uniform"
                    android:hint="@string/character_name"
                    android:inputType="textCapWords" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_input_layout_margin"
                app:helperText="@string/race_helper_text">

                <com.google.android.material.textfield.MaterialAutoCompleteTextView
                    android:id="@ id/race_autocomplete_text_view"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/race"
                    android:inputType="none" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_input_layout_margin"
                app:helperText="Select a subrace from the dropdown menu">

                <com.google.android.material.textfield.MaterialAutoCompleteTextView
                    android:id="@ id/subrace_autocomplete_text_view"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/subrace"
                    android:inputType="none" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                style="@style/Widget.Anime5eCharacterSheet.TextInputLayout.FilledBox"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:helperText="@string/size_helper_text">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@ id/size"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:focusable="false"
                    android:hint="@string/size"
                    android:inputType="none"
                    android:longClickable="false"
                    android:text="@{viewModel.size}"
                    android:textIsSelectable="true" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                style="@style/Widget.Anime5eCharacterSheet.TextInputLayout.FilledBox"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:helperText="@string/description_helper_text">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@ id/description"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:autoSizeTextType="uniform"
                    android:hint="@string/description"
                    android:inputType="textMultiLine" />
            </com.google.android.material.textfield.TextInputLayout>

        </LinearLayout>
    </ScrollView>
</layout>
 

string.xml

 <resources>
    <string name="app_name">Anime 5e Character Sheet</string>
    <!--fragment labels-->
    <string name="about">About</string>
    <string name="stats">Stats</string>
    <string name="start">Start</string>
    <!--textfield labels-->
    <string name="player_name">Player Name</string>
    <string name="character_name">Character Name</string>
    <string name="race">Race</string>
    <string name="description">Description</string>
    <string name="size">Size (READ ONLY)</string>
    <string name="subrace">Subrace</string>
    <!--textfield helpertext-->
    <string name="player_name_helper_text">Enter your name</string>
    <string name="character_name_helper_text">Enter your character's name</string>
    <string name="race_helper_text">Select a race from the dropdown menu</string>
    <string name="size_helper_text">Your character's size (READ ONLY)</string>
    <string name="description_helper_text">Enter a physical description of your character</string>
    <!--races for race dropdown-->
    <string-array name="races">
        <item>Archfiend</item>
        <item>Asrai</item>
        <item>Blinkbeast</item>
        <item>Crogoblin</item>
        <item>Demonaga</item>
        <item>Dragonborn</item>
        <item>Dwarf</item>
        <item>Elf</item>
        <item>Fairy</item>
        <item>Gnome</item>
        <item>Grey</item>
        <item>Half-Dragon</item>
        <item>Half-Elf</item>
        <item>Half-Orc</item>
        <item>Half-Troll</item>
        <item>Halfling</item>
        <item>Haud</item>
        <item>Human</item>
        <item>Kodama</item>
        <item>Loralai</item>
        <item>Nekojin</item>
        <item>Parasite</item>
        <item>Satyr</item>
        <item>Slime</item>
        <item>Tiefling</item>
    </string-array>
    <!--subraces for subrace dropdown-->
    <string-array name="archfiend_subraces">
        <item>Aerial</item>
        <item>None</item>
    </string-array>
    <string-array name="asrai_subraces">
        <item>Blessed</item>
        <item>None</item>
    </string-array>
    <string-array name="blinkbeast_subraces">
        <item>Multi</item>
        <item>None</item>
    </string-array>
    <string-array name="crogoblin_subraces">
        <item>None</item>
        <item>Steel</item>
    </string-array>
    <string-array name="demonaga_subraces">
        <item>Aqua</item>
        <item>Fire</item>
    </string-array>
    <string-array name="dragonborn_subraces">
        <item>Black</item>
        <item>Blue</item>
        <item>Brass</item>
        <item>Bronze</item>
        <item>Copper</item>
        <item>Gold</item>
        <item>Green</item>
        <item>Red</item>
        <item>Silver</item>
        <item>White</item>
    </string-array>
    <string-array name="dwarf_subraces">
        <item>Hill</item>
        <item>Mountain</item>
    </string-array>
    <string-array name="elf_subraces">
        <item>None</item>
        <item>Dark</item>
        <item>High</item>
        <item>Wood</item>
    </string-array>
    <string-array name="fairy_subraces">
        <item>Rock</item>
        <item>Woodland</item>
    </string-array>
    <string-array name="gnome_subraces">
        <item>None</item>
        <item>Forest</item>
        <item>Rock</item>
    </string-array>
    <string-array name="grey_subraces">
        <item>Elite</item>
        <item>None</item>
    </string-array>
    <string-array name="half_dragon_subraces">
        <item>Bronze</item>
        <item>Copper</item>
        <item>Gold</item>
        <item>Silver</item>
    </string-array>
    <string-array name="half_elf_subraces">
        <item>None</item>
    </string-array>
    <string-array name="half_orc_subraces">
        <item>None</item>
    </string-array>
    <string-array name="half_troll_subraces">
        <item>None</item>
        <item>Water</item>
    </string-array>
    <string-array name="halfling_subraces">
        <item>None</item>
        <item>Lightfoot</item>
        <item>Stout</item>
    </string-array>
    <string-array name="haud_subraces">
        <item>Leaping</item>
        <item>None</item>
    </string-array>
    <string-array name="human_subraces">
        <item>Standard</item>
        <item>Variant</item>
    </string-array>
    <string-array name="kodama_subraces">
        <item>None</item>
        <item>Void</item>
    </string-array>
    <string-array name="loralai_subraces">
        <item>None</item>
        <item>Paired</item>
    </string-array>
    <string-array name="nekojin_subraces">
        <item>None</item>
        <item>Panthera</item>
    </string-array>
    <string-array name="raceless_subraces">
        <item>None</item>
    </string-array>
    <string-array name="satyr_subraces">
        <item>Forest</item>
        <item>Mountain</item>
        <item>Under</item>
    </string-array>
    <string-array name="slime_subraces">
        <item>Blue</item>
        <item>Other</item>
    </string-array>
    <string-array name="tiefling_subraces">
        <item>Bloodline of Asmodeus</item>
        <item>None</item>
    </string-array>
</resources>
 

Комментарии:

1. Пожалуйста, отредактируйте вопрос, чтобы ограничить его конкретной проблемой с достаточной детализацией для определения адекватного ответа.

Ответ №1:

создайте пользовательский адаптер массива для Autocompletextview

 class ShopAddressProvinceAdapter(
context: Context,
resource: Int,
) :
ArrayAdapter<ShopAddressProvince>(context, resource) {

lateinit var binding: ItemDropdownBinding
var list: MutableList<ShopAddressProvince> = mutableListOf()

private var onItemClicked: ((id: Int, name: String) -> Unit) =
    { id, name -> }

fun setOnClickListener(listener: (id: Int, name: String) -> Unit) {
    onItemClicked = listener
}

private class ProvinceVH constructor(private val binding: ItemDropdownBinding) :
    RecyclerView.ViewHolder(binding.root) {

    fun bind(item: ShopAddressProvince) {
        binding.apply {
            textView.text = item.text
        }
    }

}

@SuppressLint("ViewHolder")
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {

    val holder: ProvinceVH

    if (convertView == null) {
        binding = DataBindingUtil.inflate(
            LayoutInflater.from(context), R.layout.item_dropdown, parent, false
        )
        holder = ProvinceVH(binding)
        holder.itemView.tag = holder
    } else {
        holder = convertView.tag as ProvinceVH
    }

    getItem(position)?.let {
        holder.bind(it)
        holder.itemView.setOnClickListener { view ->
            onItemClicked.invoke(it.id, it.text)
        }
    }

    return holder.itemView
}

override fun getFilter(): Filter {
    return filter
}



override fun getItem(position: Int): ShopAddressProvince? {
    return list[position]
}

override fun getCount(): Int {
    return list.size
}

private val filter = object : Filter() {
    override fun performFiltering(constraint: CharSequence?): FilterResults {
        val result = FilterResults()
        val suggestions: MutableList<ShopAddressProvince> = mutableListOf()

        if (constraint != null) {
            suggestions.clear()
            val filterPattern = constraint.toString().lowercase()
            for (item in list) {
                if (item.text.lowercase().contains(filterPattern)) {
                    suggestions.add(item)
                }
            }
            result.values = suggestions
            result.count = suggestions.size
        }

        return result
    }

    override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
        clear()
        if (results != null amp;amp; results.count > 0) {
            addAll(results.values as MutableList<ShopAddressProvince>)
        } else {
            addAll(list)
        }
        notifyDataSetChanged()
    }

    override fun convertResultToString(resultValue: Any?): CharSequence {
        return (resultValue as ShopAddressProvince).text
    }

}

fun addList(data: MutableList<ShopAddressProvince>) {
    this.list = data
    notifyDataSetChanged()
}
 

}

Ответ №2:

Обновить

Пользовательский адаптер массива был, по сути, правильным решением для реализации. Я просто создал новый адаптер под названием SubraceAdapter, который мог бы использовать ссылку на данные о «расе» из моей модели sharedViewModel. Я создал метод внутри подрасы, который очистит выпадающее меню, проверит данные о жизни, а затем добавит правильные параметры подрасы. Этот метод вызывается в onViewCreated каждый раз, когда запускается raceAutoCompleteTextView.doAfterTextChanged. Мой код решения приведен ниже:

О фрагментации

 package com.example.anime5echaractersheet

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.example.anime5echaractersheet.adapter.SubraceAdapter
import com.example.anime5echaractersheet.databinding.FragmentAboutBinding
import com.example.anime5echaractersheet.model.SharedViewModel

/**
 * A simple [Fragment] subclass.
 * Use the [AboutFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class AboutFragment : Fragment() {

    private lateinit var subraceAdapter: SubraceAdapter

    private val sharedViewModel: SharedViewModel by activityViewModels()

    private var _binding: FragmentAboutBinding? = null
    private val binding get() = _binding!!

    /*
    * initialize binding, set up race selection dropdown menu, inflate layout
    * */
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        _binding = FragmentAboutBinding.inflate(inflater, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        //setup race dropdown
        val races = resources.getStringArray(R.array.races)
        val raceAdapter = ArrayAdapter(requireActivity(), R.layout.dropdown_item, races)
        binding.raceAutocompleteTextView.setAdapter(raceAdapter)
        //setup subrace dropdown
        subraceAdapter = SubraceAdapter(requireActivity(), R.layout.dropdown_item, sharedViewModel.race)
        binding.subraceAutocompleteTextView.setAdapter(subraceAdapter)

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.apply { viewModel = sharedViewModel }
        //update viewModel race amp; size when user makes changes
        binding.raceAutocompleteTextView.doAfterTextChanged {
            binding.viewModel?.updateRaceAndSize(binding.raceAutocompleteTextView.text.toString())
            subraceAdapter.updateList()
        }
    }

    /*
    * nullify _binding before destroying fragment
    * */
    override fun onDestroyView() {
        super.onDestroyView()

        _binding = null
    }
}
 

Подадаптер

 package com.example.anime5echaractersheet.adapter

import android.content.Context
import android.widget.ArrayAdapter
import androidx.lifecycle.LiveData
import com.example.anime5echaractersheet.R.array.*

class SubraceAdapter(context: Context, resource: Int, private val race: LiveData<String>) :
    ArrayAdapter<String?>(context, resource) {

    fun updateList() {
        clear()
        val newSubraceList: Array<String?> = when (race.value) {
            context.resources.getStringArray(races)[0] -> {
                context.resources.getStringArray(archfiend_subraces)
            }
            context.resources.getStringArray(races)[1] -> {
                context.resources.getStringArray(asrai_subraces)
            }
            context.resources.getStringArray(races)[2] -> {
                context.resources.getStringArray(blinkbeast_subraces)
            }
            context.resources.getStringArray(races)[3] -> {
                context.resources.getStringArray(crogoblin_subraces)
            }
            context.resources.getStringArray(races)[4] -> {
                context.resources.getStringArray(demonaga_subraces)
            }
            context.resources.getStringArray(races)[5] -> {
                context.resources.getStringArray(dragonborn_subraces)
            }
            context.resources.getStringArray(races)[6] -> {
                context.resources.getStringArray(dwarf_subraces)
            }
            context.resources.getStringArray(races)[7] -> {
                context.resources.getStringArray(elf_subraces)
            }
            context.resources.getStringArray(races)[8] -> {
                context.resources.getStringArray(fairy_subraces)
            }
            context.resources.getStringArray(races)[9] -> {
                context.resources.getStringArray(gnome_subraces)
            }
            context.resources.getStringArray(races)[10] -> {
                context.resources.getStringArray(grey_subraces)
            }
            context.resources.getStringArray(races)[11] -> {
                context.resources.getStringArray(half_dragon_subraces)
            }
            context.resources.getStringArray(races)[12] -> {
                context.resources.getStringArray(half_elf_subraces)
            }
            context.resources.getStringArray(races)[13] -> {
                context.resources.getStringArray(half_orc_subraces)
            }
            context.resources.getStringArray(races)[14] -> {
                context.resources.getStringArray(half_troll_subraces)
            }
            context.resources.getStringArray(races)[15] -> {
                context.resources.getStringArray(halfling_subraces)
            }
            context.resources.getStringArray(races)[16] -> {
                context.resources.getStringArray(haud_subraces)
            }
            context.resources.getStringArray(races)[17] -> {
                context.resources.getStringArray(human_subraces)
            }
            context.resources.getStringArray(races)[18] -> {
                context.resources.getStringArray(kodama_subraces)
            }
            context.resources.getStringArray(races)[19] -> {
                context.resources.getStringArray(loralai_subraces)
            }
            context.resources.getStringArray(races)[20] -> {
                context.resources.getStringArray(nekojin_subraces)
            }
            context.resources.getStringArray(races)[21] -> {
                context.resources.getStringArray(races)
            }
            context.resources.getStringArray(races)[22] -> {
                context.resources.getStringArray(raceless_subraces)
            }
            context.resources.getStringArray(races)[23] -> {
                context.resources.getStringArray(satyr_subraces)
            }
            context.resources.getStringArray(races)[24] -> {
                context.resources.getStringArray(slime_subraces)
            }
            context.resources.getStringArray(races)[25] -> {
                context.resources.getStringArray(tiefling_subraces)
            }
            else -> arrayOf("Please select a race")
        }
        addAll(newSubraceList.toList())
    }
}