I am stuck trying to figure out how to make a popup menu appear when an Item in a list view is selected. I have attempted to put together various code segments together and a few different methods but this is what I have at the moment.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test)
val textView = findViewById<TextView>(R.id.textView)
var tl = arrayOf("a","a","a","a","a")
val listview = findViewById<ListView>(R.id.tlist)
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, tl)
listview.adapter = adapter
listview.onItemClickListener = object : OnItemClickListener{
override fun onItemClick(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
)
{
val listPopupmenu = View(tl+[position])
val showPopUp = PopupMenu(
this,
listPopupmenu
)
showPopUp.setOnMenuItemClickListener { menuItem ->
val id = menuItem.itemId
if (id == 0) {
textView.text = "Item 0"
} else if (id == 1) {
textView.text = "Item 1"
} else if (id == 2) {
textView.text = "Item 2"
} else if (id == 3) {
textView.text = "Item 3"
}
false
}
showPopUp.menu.add(Menu.NONE, 0, 0, "Item 0")
showPopUp.menu.add(Menu.NONE, 1, 1, "Item 1")
showPopUp.menu.add(Menu.NONE, 2, 2, "Item 2")
showPopUp.menu.add(Menu.NONE, 3, 3, "Item 3")
listPopupmenu.setOnClickListener {
showPopUp.show()
}
Toast.makeText(applicationContext, "${textView} was clicked", Toast.LENGTH_SHORT).show()
}
}
}
}
I am trying to make it so that when you select an item on the list, it has a drop down window with options. Then when you select an option it pops up at the bottom letting you know that option has been selected.
I can do the list bit and make it so that when you click a item on the list it appears at the bottom saying that item was clicked, I can also do the drop down menu when you click a button. When I put it together however it doesn't work and just crashes. Can someone help me out? Or at least let me know if my code is heading in the right direction? It would be very much appreciated!
Related
I use Jetpack Compose UI to build a simple TODO app. The idea is to have a list of tasks that could be checked or unchecked, and checked tasks should go to the end of the list.
Everything is working fine except when I check the first visible item on the screen it moves down along with the scroll position.
I believe that has something to do with LazyListState, there is such function:
/**
* When the user provided custom keys for the items we can try to detect when there were
* items added or removed before our current first visible item and keep this item
* as the first visible one even given that its index has been changed.
*/
internal fun updateScrollPositionIfTheFirstItemWasMoved(itemsProvider: LazyListItemsProvider) {
scrollPosition.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
}
So I would like to disable this kind of behavior but I didn't find a way.
Below is a simple code to reproduce the problem and a screencast. The problem in this screencast appears when I try to check "Item 0", "Item 1" and "Item 4", but it works as I expect when checking "Item 7" and "Item 8".
It also behaves the same if you check or uncheck any item that is currently the first visible item, not only if the item is first in the whole list.
class MainActivity : ComponentActivity() {
#OptIn(ExperimentalFoundationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
val checkItems by remember { mutableStateOf(generate(20)) }
LazyColumn() {
items(
items = checkItems.sortedBy { it.checked.value },
key = { item -> item.id }
) { entry ->
Row(
modifier = Modifier.animateItemPlacement(),
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = entry.checked.value,
onCheckedChange = {
checkItems.find { it == entry }
?.apply { this.checked.value = !this.checked.value }
}
)
Text(text = entry.text)
}
}
}
}
}
}
}
}
data class CheckItem(val id: String, var checked: MutableState<Boolean> = mutableStateOf(false), var text: String = "")
fun generate(count: Int): List<CheckItem> =
(0..count).map { CheckItem(it.toString(), mutableStateOf(it % 2 == 0), "Item $it") }
Since animateItemPlacement requires a unique ID/key for the Lazy item to get animated, maybe sacrificing the first item, setting its key using its index position (no animation) will prevent the issue
itemsIndexed(
items = checkItems.sortedBy { it.checked.value },
key = { index, item -> if (index == 0) index else item.id }
) { index, entry ->
...
}
I have a few views on my item layout which is used to display items in a recyclerView in Kotlin. Some views are to be shown/hidden based on the conditions. Every item in the recyclerview shows the views (button, editText) correctly except the last item. Even though the last item doesn't meet the criteria to show the views, that is already hidden in the XML, it shows those views. I have checked my code but I couldn't make out the reason why it's happening.
Following code is within the onBindViewHolder of my ItemListAdapter.kt class.
if (model.category == "Food") {
Log.d("CheckTag", "item is ${model.item_title} and the category is ${model.category}")
holder.binding.tvDeliveryType.visibility = View.VISIBLE
holder.binding.tvDeliveryType.text = "Same Day Delivery"
mFireStore.collection(Constants.CART_ITEMS)
.whereEqualTo(Constants.USER_ID, FirestoreClass().getCurrentUserID())
.whereEqualTo(Constants.PRODUCT_ID, model.product_id).get()
.addOnSuccessListener { document ->
if (document.documents.size > 0) {
holder.binding.llGoToCart.visibility = View.VISIBLE
} else {
holder.binding.llAddToCart.visibility = View.VISIBLE
}
}.addOnFailureListener { e ->
}
holder.binding.btnGoToCart.setOnClickListener {
context.startActivity(Intent(context, CartListActivity::class.java))
}
holder.binding.btnAddToCart.setOnClickListener {
if (holder.binding.etQuantity.text.toString().isNotEmpty()) {
if (holder.binding.etQuantity.text.toString().toInt() > 0) {
holder.binding.spnUom.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
uom = parent?.getItemAtPosition(position).toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
val addToCart = Cart(
FirestoreClass().getCurrentUserID(),
model.user_id,
model.product_id,
model.title,
model.price,
model.image,
holder.binding.etQuantity.text.toString(),
uom = uom
)
FirestoreClass().addToCart(context, addToCart)
Toast.makeText(
context, "Item added to the cart", Toast.LENGTH_SHORT
).show()
holder.binding.llAddToCart.visibility = View.GONE
holder.binding.llGoToCart.visibility = View.VISIBLE
} else {
Toast.makeText(
context, "Please enter a valid quantity", Toast.LENGTH_SHORT
).show()
}
} else {
Toast.makeText(
context, "Please enter quantity required", Toast.LENGTH_SHORT
).show()
}
}
} else {
holder.binding.tvDeliveryType.visibility = View.GONE
}
It was due to the else part which was missing in the code.
Long story short. I want my spinner to always show same first item "Add". When item selected from the list it should be gone from the list and the specific action happen. The text "Add" should still appear on spinner. So my question is how to make that spinner always show first item on his dataList? PS. I made that first item in the list don't show when drop down is open.
My layout:
<Spinner
android:id="#+id/spinner_add"
style="#style/Widget.AppCompat.Spinner.Underlined"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:backgroundTint="#color/colorButton" />
Method to set spinner adapter with data list:
private fun fillAddSpinner() {
val spinner: Spinner = findViewById(R.id.spinner_add)
val titles: MutableList<String> = ArrayList()
titles.add(resources.getString(R.string.add_advanced_filter))
for (filter in tableAdvancedFilters) {
titles.add(filter.title)
}
val dataAdapter = object : ArrayAdapter<String?>(this, R.layout.spinner_item,
titles as List<String?>
) {
override fun getDropDownView(
position: Int,
convertView: View?,
parent: ViewGroup
): View {
var v: View? = null
// If this is the initial dummy entry, make it hidden
if (position == 0) {
val tv = TextView(context)
tv.height = 0
tv.visibility = View.GONE
v = tv
} else { // Pass convertView as null to prevent reuse of special case views
v = super.getDropDownView(position, null, parent)
}
// Hide scroll bar because it appears sometimes unnecessarily, this does not prevent scrolling
parent.isVerticalScrollBarEnabled = false
return v!!
}
}
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = dataAdapter
}
I want it stay the same like it is now:
After user selects one item from spinner I will render specific filter choices.
If you want to programmatically set spinner selected item use the following: spinnerObject.setSelection(INDEX_OF_ITEM)
I have RecyclerView inside another RecyclerView. When item of inner RecyclerView is clicked, I'm showing PopupWindow like below.
When user would click button inside popup, item of outer RecyclerView should update data. The problem is that, after update outer item notifyItemChanged(position), PopupWindow displays in left, top corner of fragment, instead of below item of inner RecyclerView.
override fun onBindViewHolder(holder : MessageItemHolder, messagePosition : Int)
{
val item = messages[messagePosition]
//Display some data
if(item.isRateViewOpened)
showRatePopup(holder, messagePosition, item)
holder.tvMessage.setOnLongClickListener {
showRatePopup(holder, messagePosition, item)
true
}
}
private fun showRatePopup(holder : MessageItemHolder, messagePosition : Int, message : Message)
{
messages[messagePosition].isRateViewOpened = true
val rateView = layoutInflater.inflate(R.layout.popup_rate, null)
setRateIcons(message.status, rateView)
rateView.ibLike.setOnClickListener { displayRateStatus(RateStatus.LIKE, message, messagePosition, rateView) }
rateView.ibDislike.setOnClickListener { displayRateStatus(RateStatus.DISLIKE, message, messagePosition, rateView) }
val popupWindow = PopupWindow(rateView, FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT, true)
popupWindow.setBackgroundDrawable(ColorDrawable())
popupWindow.isOutsideTouchable = true
val location = IntArray(2)
holder.tvMessage.getLocationOnScreen(location)
popupWindow.showAsDropDown(holder.tvMessage, 0, 0, Gravity.NO_GRAVITY)
popupWindow.setOnDismissListener {
messages[messagePosition].isRateViewOpened = false
}
}
I know there have been several questions that dealt with the problem how to add the "Select one..." hint for the Spinner before the first selection is made. But that's not my case.
What I need is to display the hint only when the SpinnerAdapter is empty. By default in such case, nothing happens on click (but that is not the major problem), and most of all, the spinner doesn't display any text, so it looks like this, which obviously doesn't feel right:
Any idea how to simply handle this problem? I've come up with 2 possible solutions, but I don't like any of them very much:
If the SpinnerAdapter is empty, hide the Spinner from the layout and display a TextView with the same background as the Spinner instead.
Implement a custom SpinnerAdapter whose getCount() returns 1 instead of 0 if the internal list is empty, and at the same time, have its getView() return a TextView with the required "Empty" message, possibly grey-coloured. But that would require specific checking if the selected item is not the "Empty" one.
You can use this SpinnerWithHintAdapter class below
class SpinnerWithHintAdapter(context: Context, resource: Int = android.R.layout.simple_spinner_dropdown_item) :
ArrayAdapter<Any>(context, resource) {
override fun isEnabled(position: Int): Boolean {
return position != 0
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return (super.getDropDownView(position, convertView, parent) as TextView).apply {
if (position == 0) {
// Set the hint text color gray
setTextColor(Color.GRAY)
} else {
setTextColor(Color.BLACK)
}
}
}
fun attachTo(spinner: Spinner, itemSelectedCallback: ((Any?) -> Unit)? = null) {
spinner.apply {
adapter = this#SpinnerWithHintAdapter
itemSelectedCallback?.let {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selectedItem = parent?.getItemAtPosition(position)
// If user change the default selection
// First item is disable and it is used for hint
if (position > 0) {
it(selectedItem)
}
}
}
}
}
}
}
How to use? Let's assume I have data class called City
data class City(
val id: Int,
val cityName: String,
val provinceId: Int
) {
/**
* By overriding toString function, you will show the dropdown text correctly
*/
override fun toString(): String {
return cityName
}
}
In the activity, initiate the adapter, add hint(first item), add main items, and finally attach it to your spinner.
SpinnerWithHintAdapter(this#MyActivity)
.apply {
// add hint
add("City")
// add your main items
for (city in cityList) add(city)
// attach this adapter to your spinner
attachTo(yourSpinner) { selectedItem -> // optional item selected listener
selectedItem?.apply {
if (selectedItem is City) {
// do what you want with the selected item
}
}
}
}