如何在我的 dnd 角色 sheet 项目中动态过滤公开的子种族下拉菜单?
How can I dynamically filter the exposed dropdown menu for subraces in my dnd character sheet project?
免责声明
整个夏天,我一直在努力学习编写 android 应用程序代码。我一直在学习 Android Kotlin 基础知识课程,最近我觉得我可以自己分支一点。
上下文
我正在尝试为最近发布的动漫 5e d&d 游戏制作角色 sheet 应用程序。我的目标只是学习如何编写应用程序。目前我有一个 MainActivity class,它通过 navdrawer 管理导航,并且工作正常。所以我开始处理由 MainActivity 托管的第一个片段(称为 AboutFragment)。这是用户输入姓名、角色名称等内容的地方。
问题
目前,我正在使用公开的下拉菜单来允许用户 select 他们角色的种族和亚种族。 selected 种族应确定哪些子种族可供他们使用 select。例如,如果用户 select 是“Elf”种族,他们在选择自己的种族时只能在“None”、“Dark”、“High”或“Wood”之间进行选择亚种。
如以下代码所示,我可以很好地设置比赛下拉菜单。但是在用户有机会实际 select 比赛之前,目前正在设置子比赛菜单。所以子种族菜单永远不会从“请select一场比赛”改变。
我的印象是我必须在 onCreateView 方法中创建子种族下拉菜单,但这让用户无法在创建子种族菜单之前确定他们的种族。我需要能够在 用户 select 参加比赛后 更改子比赛菜单中显示的数组。
提前致谢!!!
关于片段代码
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 & 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布局
<?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>
为 autoCompleteTextView 创建自定义数组适配器
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 && 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()
}
}
更新
事实上,自定义数组适配器是正确的实现方式。我只是创建了一个名为 SubraceAdapter 的新适配器,它可以从我的 sharedViewModel 中接收对 'race' LiveData 的引用。我在 SubraceAdapter 内部创建了一个方法,该方法将清除下拉菜单,检查 LiveData,然后添加正确的 subrace 选项。每次触发 raceAutoCompleteTextView.doAfterTextChanged 时都会在 onViewCreated 中调用此方法。我的解决方案代码如下:
关于片段
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 & 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
}
}
SubraceAdapter
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())
}
}
免责声明
整个夏天,我一直在努力学习编写 android 应用程序代码。我一直在学习 Android Kotlin 基础知识课程,最近我觉得我可以自己分支一点。
上下文
我正在尝试为最近发布的动漫 5e d&d 游戏制作角色 sheet 应用程序。我的目标只是学习如何编写应用程序。目前我有一个 MainActivity class,它通过 navdrawer 管理导航,并且工作正常。所以我开始处理由 MainActivity 托管的第一个片段(称为 AboutFragment)。这是用户输入姓名、角色名称等内容的地方。
问题
目前,我正在使用公开的下拉菜单来允许用户 select 他们角色的种族和亚种族。 selected 种族应确定哪些子种族可供他们使用 select。例如,如果用户 select 是“Elf”种族,他们在选择自己的种族时只能在“None”、“Dark”、“High”或“Wood”之间进行选择亚种。
如以下代码所示,我可以很好地设置比赛下拉菜单。但是在用户有机会实际 select 比赛之前,目前正在设置子比赛菜单。所以子种族菜单永远不会从“请select一场比赛”改变。
我的印象是我必须在 onCreateView 方法中创建子种族下拉菜单,但这让用户无法在创建子种族菜单之前确定他们的种族。我需要能够在 用户 select 参加比赛后 更改子比赛菜单中显示的数组。
提前致谢!!!
关于片段代码
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 & 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布局
<?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>
为 autoCompleteTextView 创建自定义数组适配器
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 && 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()
}
}
更新
事实上,自定义数组适配器是正确的实现方式。我只是创建了一个名为 SubraceAdapter 的新适配器,它可以从我的 sharedViewModel 中接收对 'race' LiveData 的引用。我在 SubraceAdapter 内部创建了一个方法,该方法将清除下拉菜单,检查 LiveData,然后添加正确的 subrace 选项。每次触发 raceAutoCompleteTextView.doAfterTextChanged 时都会在 onViewCreated 中调用此方法。我的解决方案代码如下:
关于片段
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 & 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
}
}
SubraceAdapter
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())
}
}