Android listview change image in row - android

Hello i'm working on an app where i'm basically just making a custom multi select listview i did something like this LeftAL is just an array list and this code below is in my onClick
if(LeftAL.contains(position)){
ImageView imageView = (ImageView) lView.getChildAt(position).findViewById(R.id.checkbox);
imageView.setImageResource(R.drawable.checkboxoff);
int i = position;
Integer intObj = new Integer(i);
LeftAL.remove(intObj);
}else{
ImageView imageView = (ImageView) lView.getChildAt(position).findViewById(R.id.checkbox);
imageView.setImageResource(R.drawable.checkbox);
LeftAL.add(position);
}
and then checkboxoff looks like this
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="#drawable/checkbox_pressed_off" />
<item android:drawable="#drawable/checkbox_off" />
</selector>
and checkbox on is the same thing basically just different images
Now that all works fine as it should and i didn't actually notice this for a little while but when i check say item 3 in my list. It checks and adds to the array list as it should however if i scroll down to the next group of list items i will see that number 3 of the second set is also checked for instance lets just say for example only 3 rows show on my phone.
Cat
Dog
Egg
-------off screen-----
Fox
Gerbil
Hog
I check Dog then i scroll gerbil would also be checked i guess this is just android like recycling or something but it's definately an issue how can i fix this so that only 3 in the list checks not 3 in the next set as well.
Thank you for any help

Android absolutely recycles the Views in a ListView. What you need to do is ensure that you are storing the checked / unchecked state in a separate list and set the state of the check box accordingly each time it's shown.

Related

Creating Rows in ListView with ArrayAdapter in Kotlin

I have an app that's getting information from an API request and then displaying a list of devices. After several hours of combing through documentation, I cannot figure out how to format the View that is created from the ArrayAdapter. Essentially, if the device has an error, I want to display a red circle to the right of the button and display a green button if there is no error.
deviceList is the name of a ListView that I am trying to display my list of buttons inside of. deviceNames is an array of strings that contains the names of the devices.
The TextViews that are created are also clickable, which is what the onItemClickListener is handling. This section works, but I wanted to leave it in because I do need the buttons to start an activity that displays device-specific information.
Ideally I would like to essentially create a template that I can just change the values of the text and the color of the indicator for
Below is my code:
// List of device names
val listView: ListView = findViewById(R.id.deviceList)
val arrayAdapter1: ArrayAdapter<*>
arrayAdapter1 = ArrayAdapter(
this#Homepage,
R.layout.device_button,
deviceNames
)
listView.setAdapter(arrayAdapter1)
listView.onItemClickListener =
AdapterView.OnItemClickListener { parent, view, position, id ->
val pos = position
println(pos)
val device = jsonArray.getJSONObject(pos)
val ID = device.get("id") as String
println(ID)
goToDeviceDetail(ID)
}
Below is the XML file for device_button. I tried to add formatting here and essentially create a template for a button that would allow me to change the text and the color of the indicator, but it got mad that it wasn't just a TextView.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:gravity="center_vertical"
android:textColor="#25383C"
/>
Below is the button that I would like for it to look like. I'm likely going to just make the background a solid color rather than the image that is in the below picture:
I would say the biggest problem is your using a simple API for a more complex problem. It is entirely possible do it with a ListView and ArrayAdapter. But I would highly recommend looking into RecyclerView/RecyclerView.Adapter
The way it works out is...
RecyclerView.Adapter binds your list of data ie Devices to the individual RecyclerView.ViewHolder
The ViewHolder would inflate your xml layout that contains the button. You then have access to all View contained in that layout easily.
You then can put listeners on the button.
The Adapter then can be setup to receive new data, when received it can rebind the data that has changed.
Say the user clicks one of the device buttons it does a task. When it gets back it will say hey Adapter I have a new List for you.(The list now contains the "fixed" device).
ViewModel(contains observable data)->Fragment/Activity(Observers the data)->Adapter(Receives the data)->ViewHolder(Displays the data)->Activity("Fixes the data")->ViewModel->...loops
Here is a very good example.
https://medium.com/#atifmukhtar/recycler-view-with-mvvm-livedata-a1fd062d2280
If you really want to keep using the ListView and ArrayAdapter you are receiving the clicked view here.
OnItemClickListener {
/*Parent of the view*/ parent,
/*The view clicked*/ view,
/*position of data*/position,
/*id of the view clicked*/ id
->{
view.findById(R.id.text_view);
//onClick
}
}
With that you know what has been clicked so you know what has to be changed later when you get back from your other Activity.

Android custom ArrayAdapter | Change icon on ImageView click

I have a list with elements that use a custom layout which consists of a text view and an image view. For this, I follow ViewHolder pattern like explained here. The image views display one of two icons and I want to change the icon of the clicked image view.
So my first approach was to define the on click listener of the ImageViews in the overridden getView function of my adapter class. The problem is that when the icon of the first ImageView changes and I scroll down to the last its icon changed as well. This question here was not helpful.
Here I found that it's not the best way to handle the click in the getView function but it's better to do it in the listView.setOnItemClickListener. I tried it but I am not able to find out whether an ImageView was clicked or not as the parent object holds the list item and the view parameter the LinearLayout in which the ImageView is contained (even when I click directly in the ImageView). Setting android:focusable="false" of the outer LinearLayout as it is suggested here did not help.
I'm sure someone must have had this issue / use case but I'm not able to find a solution. So, what's the best way to handle the click of the ImageView in my custon list item view?
Try adding the following attributes to the clickable ImageView instead of its ViewGroup, your LinearLayout:
android:focusable="false"
android:focusableInTouchMode="false"
... and as far as updating UI in a AdapterView/ListView/RecyclerView goes, you should make good use of an int flag that tracks the position of the list item that was clicked, and then set it as a conditional statement in getView() before invoking notifyDataSetChanged(), since "every other" row will be updated, especially in lists of say, 100 rows.
I actually answered this in a similar question here and here.
If you have two icons, you can simply put a custom checkbox instead of an imageview :
<CheckBox
android:id="#+id/customchechecbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/checkbox_selector"
android:button="#android:color/transparent" />
checkbox_selector :
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="#drawable/checkbox"
android:state_checked="false"/>
<item android:drawable="#drawable/checkboxselected"
android:state_checked="true"/>
<item android:drawable="#drawable/checkbox"/>
</selector>

Android listview item background change

i have a android listview. i want to change listview item background when i click one listview item.
and then previous selected item must go back to default background. this means only one item has to be selected.
i have searched it for a long time. i can change background of selected item using onItemClick()
but i can't change previous selected item. for example, if i select second item, it was changed. and then i select third item. oh my god! it is changed too! what can i do for this. how can i get the previous position?
here is my android code.
private class ListViewItemClickListener implements
AdapterView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
TextView title = (TextView) view.findViewById(R.id.title);
title.setBackgroundResource(R.drawable.list_shape);
}
}
You should use the built in methods of selecting items in a listview. Manually changing the background is prone to error as you have found.
Add this attribute to the root view in your listview item xml
android:background="?android:attr/activatedBackgroundIndicator"
then call setItemChecked(x, true) on your ListView where x is the position of the item you want to be selected.
Ensure your listview has a ChoiceMode set that allows selection (such as "SingleChoice")
When I have this in a similar example I have a global field named:
selectedListItem;
This would be updated in your onitemClickListener and the previous item would then have it's background returned to the default.
So to update your code:
private class ListViewItemClickListener implements
AdapterView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
//First update the previously selected item if one has been set
if(selectedListItem!=null){
TextView previousTitle = (TextView) selectedListItem.findViewById(R.id.title);
previousTitle.setBackgroundResource(R.drawable.list_default_background);
}
//Then update the new one
TextView title = (TextView) view.findViewById(R.id.title);
title.setBackgroundResource(R.drawable.list_shape);
selectedListItem = view;
}
}
So simply initalise selectedListItem as a field in your adapter with the onClickListener as an inner class and have your default background drawable instead of list_default_background .
Alternatively you can track the position numbers instead of the actual view.
EDIT:
To use this method for your list you will also have to keep track of an ID or object instance for your specific list item. In my own solution, in my ListAdapter's getView method I make sure only the list item that matches the ID/instance of the correct item is updated. With your code the way it is you will also find that when you scroll down the view at the same position in this list of visible items is also updated. This is because list view's refer to the list in sets of items, where each set corresponds to the items visible on the screen at any one time.
To update a singular, specific item you would be better suited to using a selector background or indicator as mentioned in the other answers.
HTH
You can change the ListView item colour on clicking it like below. Follow these steps.
(Remember, This is for Custom List View)
Create an XML file in Drawable Folder as Below:
<item android:drawable="#color/orange" android:state_focused="true"/>
<item android:drawable="#color/orange" android:state_pressed="true"/>
<item android:drawable="#drawable/listview"></item>
Choose your own resources.
While implementing Custom ListVIew, you'll have additional layout for Custom List Item design. Below is such an Example.
<ImageView
android:id="#+id/imageView1"
android:layout_width="60dp"
android:layout_height="60dp" />
<TextView
android:id="#+id/textView1"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:layout_toRightOf="#+id/imageView1"
android:background="#drawable/listselect_picture"
android:gravity="center"
android:text="TextView"
android:textColor="#drawable/select_txtcolor"
android:textSize="16sp" />
In Above code I have put the set the XML file from Step 1 as "background" attribute. This will work as you want to.
Additionally if you want to change the text colour on ListItem selection as well, use below XML code and set that XML file as "TextColor" attribute.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#android:color/white"/>
<item android:state_focused="true" android:color="#android:color/white"/>
<item android:state_pressed="true" android:color="#android:color/white"/>
<item android:color="#android:color/black"/>
</selector>
The code above will change the text color to while on selection and revert to original when unclicked.

setImageResource() will not update my ImageView size/content directly

I have a row in a listview activity where an image should be changed when the row is selected. The new image is identical but larger (double size) than the original image. It is supposed to be reduced when it is deselected (Selection is implemented using my internal selection, not the android keyboard selection kind) again.
Problem is when (selected) I change the picture (at the time of bind) of my ImageView () using setImageResource() it does not update to the new larger version in my List row on the first click. Second execution of bind works fine. The same applies for the old item being deselected. The change to a smaller icon does not get in effect until a second click. The problem repeats itself each time I select another row than the currently selected and starts working correctly again on the second click.
I have stepped through code and seen that I pass the right id to the ImageView and this is prooved by the fact that another View showing a yellow border on the same row is changed to visible/hidden when selected/deselected.
To me it feels like the setImageResource() does not refresh the view immediately, but using invalidate() on the view or the row has no effect at all.
Is this a bug (how to work around) or do I do something wrong?
Code: (from my holder class using the holder pattern - executed by the adapter bind method)
#Override
public void refreshFromCursor(final Context context, final Cursor cursor) {
...
boolean selected = adapter.getSelectedPosition()==cursor.getPosition();
if (selected){
selectedIndicator.setVisibility(View.VISIBLE); // Show a "selected" yellow border indicator to the left
} else {
selectedIndicator.setVisibility(View.GONE); // Hide a "selected" yellow border indicator to the left
}
...
if (selected) completionResId = R.drawable.folder_selected;
else completionResId = R.drawable.folder;
...
statusIcon.setImageResource(completionResId); // !!!!! This is where I set the image that does not refresh/resize
...
}
Also tested exchanging the
statusIcon.setImageResource(completionResId);
with
statusIcon.setImageDrawable(context.getResources().getDrawable(completionResId));
but nothing changes in the problematic behaviour
In my opinion it is about the way you have implemented,
if you provide us the full code of refreshFromCursor(...) function we can have better understanding of what you did.
I found a hack. just make your drawable bigger
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="500dp"
android:height="500dp"
android:viewportWidth="139"
android:viewportHeight="139">
<path
android:fillColor="#000000"
android:pathData="M117.037,61.441L36.333,14.846c..." />
</vector>
here I set the drawable to be drawn on a 500dp canvas (instead of a 139dp one that was there originally) and it did the trick. no more weird tiny icons that later increase in size.

Android Gridview and getChildAt with NullPointerException

I have a UI that consists of a Gridview loaded with a custom adapter. A ButtonAdapter, in this case. So the grid loads fine, the button clicks function like I want them to, but now I have to indicate on the button that it the "active" selection.
I thought I'd do this by just keeping track and changing the background. As it turns out, and based on a couple posts here on SO, the buttons don't actually exist when they are off screen...and even immediately after a scroll. I'll often get a NullPointerException when trying to change a button background after scrolling.
I've tried changing the views in the adapter to RadioButtons and ToggleButtons, but they all offer similar limitations.
The problem seems mostly to do with the getChildAt() that I use on the grid to "unselect" one button, or whatever, when another one is selected.
Is there a workaround for this, or perhaps another suggestion of similar functionality. A vertically scrollable, grid-like format, etc...
Thanks for the help.
EDIT:
Thanks Craigy...I did forget to put a platform on there o.0...i'll add android.
Have you considered using a Selector? In ignorance of how your buttonAdapter works or what you're pulling it from, you can set the background drawable of any View to change according to its state using selectors.
If your selector is defined like so (assuming the presence of appropriate drawables, of course):
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_focused="true"
android:drawable="#drawable/list_item_pressed" />
<item android:state_pressed="true"
android:drawable="#drawable/list_item_pressed" />
<item android:state_selected="true"
android:state_activated="true"
android:drawable="#drawable/list_item_selected" />
<item android:state_activated="true"
android:drawable="#drawable/list_item_selected" />
<item android:state_selected="true"
android:drawable="#android:color/black" />
<item android:drawable="#android:color/transparent" />
</selector>
And then set your GridView's choice mode to single Choice:
<GridView
...
android:choiceMode="singleChoice" />
This lets the OS take care of everything for you in terms of remembering which position is selected in the list and then clearing it out for you when you've clicked another one
Set a tag on each of the views that the adapter creates in getView(). Later search for the view with that tag by gridView.findViewByTag() or get the view's tag by view.getTag().
Well, this may not perfectly answer your question but I had done something sort of similar. Basically, I added items to a table and needed to have a remove button associated with the item. When the remove button was clicked, it needed to remove only that item from the table. This could be adapted to your needs so rather than removing the item clicked, it finds the previous item, unclicks it, and then highlights then newly clicked one.
So what I did was give a tag to the buttons themselves (obviously they need to be unique). When a button is clicked, save its tag in something like a sharedPreference for later reference. Then when a new button is clicked, simply find the button with the previously clicked tag and unmark the row that it is in and then mark the row for the newly clicked button. Here is the code I used (sorry the variable names are terrible, I actually had this working in a test app that was never released so I didn't bother giving them better names):
//Previous button clicked
String id = <get this from wherever you choose to store it>
// create a new TableRow
TableRow row = new TableRow(getApplicationContext());
TextView t = new TextView(getApplicationContext());
t.setTextColor(Color.BLACK);
t.setText(unique);
Button b = new Button(getApplicationContext());
b.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v)
{
for(int i = 0; i < table.getChildCount(); i++)
{
TableRow row = (TableRow) table.getChildAt(i);
Button bt = (Button) row.getChildAt(1);
TextView view = (TextView)row.getChildAt(0);
if( id.equals(v.getTag())) //they match, so this is the button that was previously clicked
{
//Put your code here to unclick the previous button and mark the new one as clicked.
}
}
}
});
b.setText(R.string.removeButtonText);
b.setTag(t.getText().toString());
/***BE SURE TO SAVE THE NEW BUTTON TAG (t.getText().toString()) SOMEWHERE LIKE A SHARED PREFERENCE****/
//saving the tag
//add the row to the table
row.addView(t);
row.addView(b);
// add the TableRow to the TableLayout
table.addView(row,new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Again, I don't suspect this is the exact answer you are looking for, but maybe it will give you an idea to try out. Hopefully this makes some sense, if not please feel free to ask for clarification. Sorry if it is way off base as well.

Categories

Resources