#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())
}
}