ListView sub-element background changes when list item clicked - android

I'm working on a Fragment that has a custom expandable ListView. When the ListView is selected, elements are added to two LinearLayouts within the selected list item and these layouts are made visible. Each of these new elements are selectable and have the following properties:
android:clickable="true"
android:background="?android:attr/selectableItemBackground"
I noticed that the handling of this background selector state is different on my two devices. I've attached two sets of screenshots for these devices to compare the handling of the same code.
Android 4.3.1 | CyanogenMod 10.2.1
Expanded, Sub-Element Clicked, List Item Clicked
Android 4.0.4 | 2.6.38.8-aokp-bravo-ics
Expanded, Sub-Element Clicked, List Item Clicked
As you can see on the 4.0.4 device when the whole list item is selected all the sub-elements get the focus highlight as well which is undesirable. How do I get around this?

I got tired of dealing with the inconsistencies (or maybe I never properly learnt) and did the following, which may or may not be correct or suit your scenario (treat the code below more like pseudo code than Java).
class SomeAdapter extends BaseAdapter {
final int colorItemDefault;
final int colorItemSelected;
final int colorSubItemDefault;
final int colorSubItemSelected;
public SomeAdapter (final Context c) {
final Resources res = c.getResources();
colorItemDefault = res.getColor (R.color.item_default);
colorItemSelected = res.getColor (R.color.item_selected);
colorSubItemDefault = res.getColor (R.color.sub_item_default);
colorSubItemSelected = res.getColor (R.color.sub_item_selected);
}
public View getView (final int position, final View convertView, final ViewGroup parent...) {
final View view = makeNewViewOrRecycleExisting (...);
final Item item = getItem (position);
// bind content, etc...
view.setBackgroundColor (isSelected (item) ? colorItemSelected : colorItemDefault);
// similar logic for the sub items
}
}

The color that you are seeing is because of the list view's listSelector property. The behavior of which effect will be rendered on list items depends upon the Android version and the theme that you have used for the application. To get around the problem either you set the listSelector to transparent or provide your own selector. You can set the listSelector in you xml or programmatically using either of them. Sets transparent color
android:listSelector="#00000000"
yourListView.setSelector(new ColorDrawable(0x0));

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 - dynamically update layout contents (for a "submenu" effect)

An activity in our Android application features a spinner, the selected value of which affects which other views are to be displayed in the activity (these views are inputs for sub-parameters of the spinner parameter, and so are spinner value specific).
The contents of the activity (below the top-most spinner) should change dynamically upon spinner selection and is visualized with this very quick mockup:
Initially, we had the sub-parameters in their own linear layouts in the activity xml, and upon spinner selection change, hid all the irrelevant sub-parameter layouts, but this seems a fairly rotten approach, and also severely undermines the extensibility of the activity (in terms of adding new top spinner box options and sub-parameters).
We've also considered generating the layout completely in code with declarations of the types of inputs needed (with some encapsulated layout generator based on these declarations) for each parameter, but this seemed a bit of an over-complication, and we'd really prefer to define the sub-parameter layouts in xml.
How should we approach this?
Would this be an appropriate scenario for using fragments? (would using fragments involve hiding and showing them just as awfully as using the sub-parameter linear layouts?
Thanks!
For anyone out there seeking a solution:
We ended up having an empty 'container' view (a linear layout) within our activity's xml, which will store the sub-menus...
<LinearLayout
android:id="#+id/algorithm_layout_container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</LinearLayout>
and seperate xml layout files for each of the sub-menus (eg; the empty negative sub-menu...)
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
tools:context="PACKAGE.ACTIVITY"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
installing an onItemSelectedListener (well actually, having our activity implement it) and upon the selected spinner item changing, adjust the displayed sub-menu by clearing the container, and adding the sub-menus corresponding layout (inflated to a view) to the container...
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
// get the current spinner value
String choice = parent.getSelectedItem().toString();
// get and clear our sub-menu container
LinearLayout container = (LinearLayout) findViewById(R.id.algorithm_layout_container);
container.removeAllViews();
// determine which sub-menu layout to set by the spinner option
// (we're exploiting the strings.xml string identifier for our own identification)
int layout;
if (choice.equals(getString(R.string.halftone_algorithm_choice))) {
layout = R.layout.algorithm_halftone;;
}
else if (choice.equals(getString(R.string.negative_algorithm_choice))) {
layout = R.layout.algorithm_negative;
}
else if (choice.equals(getString(R.string.gaussian_algorithm_choice))) {
layout = R.layout.algorithm_gaussian;
}
else if (choice.equals(getString(R.string.dithering_algorithm_choice))) {
layout = R.layout.algorithm_dithering;
} else {
// only reached via a dev bug: you've got an unexpected spinner value selected
// we handle this with an alert, then switch to another (default) spinner value
}
// inflate the determined layout to a view, and add it to our container
container.addView(LayoutInflater.from(this).inflate(layout, null, false));
}

Android limit the no of items displayed in a spinner's dropdown list

I have a spinner item bound to an array adapter which might have 0 or more items at any time. I want the spinner dropdown list to only show three items at a time, the rest of the items being scrollable.
I have tried wrapping the spinner within a layout with a fixed width but the spinner drop down list still takes up the entire screen(if there are that many items in the array adapter) to display the list.
I was looking at the Spinner source code and it seems like you can't do that with a spinner.
The Spinner has its own private interface called SpinnerPopup which defines how dropdown items can be shown. This is currently based on the spinnerMode allowing for a dropdown or dialog list.
Both options are also implemented inside the Spinner class as private classes: DialogPopup and DropdownPopup. Since you can't access them, it seems to me your only options at this point are:
Implement your own custom spinner based on other widgets such as in this example.
Copy the code from the Spinner class which seems pretty self-contained and implement your version of a spinner with it, modifying whatever you like in it.
I'm sorry I couldn't be of more help.
Good luck!
EDIT:
If you choose option 2, I think all you need to do is add your mode implementing the SpinnerPopup interface. Then inside the constructor Spinner(Context context, AttributeSet attrs, int defStyle, int mode) add another case to the switch checking for the modes to instantiate your own popup. Doesn't seem hard.
This answer doesn't work anymore, please look elsewhere.
Here's the ultimately simple solution.. just copy this line in Spinner
tag...
android:dropDownHeight="100dp" <!--change 100dp to your requirement-->
you can also modify width...
android:dropDownWidth="100dp" <!--change 100dp to your requirement-->
and you know what it works on AutoCompleteTextView too and with
atleast api 16...
The recommended solutions are not really interesting because they hard code the height of drop down this is bad because font size are actually different in different phones, I handled it dynamic and reliable like this :
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.drop_down_text_view,
new String[]{"A", "B", "C", "D", "E"}) {
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
TextView dropDownTextView = (TextView) super.getView(position, convertView, parent);
dropDownTextView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
myDropDownMenuOrSpinnerOrAutoCompleteTextView.setDropDownHeight(dropDownTextView.getHeight() * 3);
dropDownTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
return dropDownTextView;
}
};

ListView items don't become visually "highlighted" when selected during contextual action mode

I followed the official Android site's tutorial on creating contextual action menus. Using the code below, when I long press on one of my ListView items, it does become selected, but it does not visually indicate that its been selected. I am using the Holo Light theme, and I expect the background color of every selected item in my ListView to change to a shade of blue.
Is this normal behavior?
I have tried testing listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); and not even a single row will highlight.
Using listView.setSelector(android.R.color.holo_blue_light); does appear to highlight the row which was last selected, but it does not highlight the other rows which are selected.
Have I done something wrong, or do I need to make the background change manually? If so, how?
I have also tried listView.setSelector(android.R.drawable.list_selector_background); which is a real selector that contains items for different states. Unfortunately, it still only applies to the most recently selected ListView item.
public class MyActivity extends ListActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The list is generated here
ListView listView = getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
// implements empty methods
}
}
}
Thanks!
In my opinion it should be possible the way do describe, but unfortunately it does not.
What I have found so far is that you have to use a XML resource which describes the way the background should look like when the 'state' of the item is 'activated'.
Create a file called 'list_item_background_activated.xml' in the 'res/drawable/' directory.
In it you define a root element of type selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:state_activated="true"
android:drawable="#android:color/darker_gray" />
</selector>
Now you should modify the related resource (the resource which defines how you ListItem looks like) to reference this drawable:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/list_item_background_activated" >
...
</RelativeLayout>
I just implemented Contextual Action Mode and had the same problem that you have/had. I too figured that the default behavior would be that each selected row would be highlighted in some way, but no.
The easiest way for me to get the desired effect was to change the list item resource I was using for my ListActivity.
I'm using an ArrayAdapter so the choice for me is made in the constructor of the adapter:
new ArrayAdapter<Exercise>(context, android.R.layout.simple_list_item_1);
The last parameter is the resource id for a layout file the adapter will use for instanciating new views (List items) in the listview.
I had chosen the one I figured to be the most basic one. By changing it to:
new ArrayAdapter<Exercise>(context, android.R.layout.simple_list_item_activated_1);
I got the desired effect, the rows selected in Contextual Action Mode are now staying highlighted.
There's also a couple of other resource id's defined in android.R.layout that you can chose from to get a similar but different result: http://developer.android.com/reference/android/R.layout.html , simple_list_item_activated_1 just did it for me.
Not sure if
// The list is generated here
hides an adapter and if so an array adapter but if it does, this should help.
Otherwise I figure you could use the predefined resources in another place or take the slightly longer way and define selectors as Brabbeldas suggests
In ActionMode, the ListView keeps track of the item checked state automatically when the user clicks on an item.
When you are using an adapter for your ListView, you set the background of an item based on the checked state:
#Override
public void bindView(final View view, final Context context, final Cursor cursor)
{
int pos = cursor.getPosition();
boolean selected = ((SessionsActivity)context).listView.isItemChecked(pos);
if(!selected)
view.setBackgroundResource(R.drawable.list_selector);
else
view.setBackgroundResource(R.drawable.list_selector_active);
...
AND, you also need to invalidate the ListView, after each item click:
private AbsListView.MultiChoiceModeListener multiChoiceModeListener = new AbsListView.MultiChoiceModeListener()
{
#Override
public void onItemCheckedStateChanged(ActionMode mode, int position,
long id, boolean checked)
{
// Here you can do something when items are selected/de-selected,
// such as update the title in the CAB
listView.invalidateViews();
}

How to highlight ListView item on touch?

I have a simple ListView and I want each of it items to be highlighted on user's touch. I thought this should happen by default but it isn't. Can you advice?
ListView xml:
<ListView
android:id="#+id/list_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
android:divider="#206600"
android:dividerHeight="2dp"
android:smoothScrollbar="true"
android:background="#ffffff"
>
</ListView>
And code of my Adapter:
private class MyAdapter extends ArrayAdapter<Task> {
private LayoutInflater mInflater;
public MyAdapter(Context context, int resource, List<Task> list) {
super(context, resource, list);
mInflater = LayoutInflater.from(context);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
v = mInflater.inflate(R.layout.list_item, null);
}
Task task = taskList.get(position);
/* Setup views from your layout using data in Object here */
return v;
}
You may want to post your actual row layout code, but I suspect the problem will be that you set a background color on your list row. By default, the selectors are drawn behind the list items (which looks nicer, since the highlight color is behind the text). Either don't set a background color on your list items, or set it to draw the selector on top instead with ListView's drawSelectorOnTop XML attribute.
EDIT: If you really must have an opaque background for the default state and don't want to use drawSelectorOnTop, you can also try this: Set a background on your list rows, but use a StateListDrawable to use #android:drawable/list_selector_background for all but the default state (you can define an xml file in your drawables folder for this; see the StateList documentation).
You could also nest a layout inside your outer backgrounded row layout with its background set to #android:drawable/list_selector_background; that way the background would draw on top of your background, but below the content.
ListViews do not retain a visual indication of focus (or selection) while in touch mode. You will only see this when you use the hardware keyboard or controls to navigate your UI.
See the Google Touch Mode Android Blog article for more details.
So, if you are only using touch mode, you will never see focus or selection on ListViews.
I believe this has to do with the "Enabled" attribute of the items in the ListAdapter.
If your Adapter contains the code:
#Override
public boolean areAllItemsEnabled() {
return true;
}
Then each item should be clickable (and therefore should highlight on being touched).
Could you post details (and possibly code) of what kind of Adapter you're using for this list?
I struggled with this for a few days. In the end, I have to create a widget that supports Checkable interface and return that widget/view in my adapter's getiew() function. And the listview needs to be in ListView.CHOICE_MODE_SINGLE (or possibly any other mode specified by android:choiceMode) for it to keep track of the choice made on the UI.
So in essence, all the following needs to be in place for the listview item to stay highlighted:
ListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
ListAdapter.getView() return a view that implements Checkable interface
ListView.OnItemClickListener should call setItemChecked(position, true) to mark the item to be checked (and thus highlight it in the listview)
Hope this can help someone who is also struggling with this.

Categories

Resources