Seeking some insight understanding how a simpleCursorAdapter works - java

Hey guys I was just hoping someone could shed some light on how this code is working and more specifically the simpleCursorAdapter. The full program is an app that is a to-do list, it's a very simple tutorial the user can input data or "notes" and save to a sqlite data base using cursors and loaders.
So my problem is that there is a specific method that I'm having trouble grasping how it works and as a result I cannot manipulate the way the data is displayed. I think the problem lies in the fact that I just don't understand how the adapter is taking in a different layout than what is displayed and showing it all in a list view.
private void fillData() {
// Fields from the database (projection)
// Must include the _id column for the adapter to work
String[] from = new String[] { TodoTable.COLUMN_SUMMARY };
// Fields on the UI to which we map
int[] to = new int[] { R.id.label }; //I don't quite understand but I know it's just a value for the adapter
getLoaderManager().initLoader(0, null, this);
adapter = new SimpleCursorAdapter(this, R.layout.todo_row, null, from,
to, 0); /*This line specifically I don't understand how it is working.
R.layout.todo_row is a near blank xml, used when there are no "todos"
with no listviews. R.layout.todo_list has the listview's but when
assigned in the adapter it doesn't work.
setListAdapter(adapter);
}
Overall I'm trying to make 3 listviews side by side to read data from the DB and just play around. If anyone could help me out it would be very much so appreciated, thank you.
R.layout.todo_row
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/icon"
android:layout_width="30dp"
android:layout_height="24dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:src="#drawable/reminder" >
</ImageView>
<TextView
android:id="#+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:lines="1"
android:text="#+id/TextView01"
android:textSize="24dp"
>
</TextView>
</LinearLayout>
and R.layout.todo_list
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="#android:id/list"
android:layout_width="110dp"
android:layout_height="200dp" >
</ListView>
<ListView
android:id="#+id/listMiddle"
android:layout_width="110dp"
android:layout_height="200dp"
android:layout_toRightOf="#android:id/list" >
</ListView>
<ListView
android:id="#+id/listRight"
android:layout_width="110dp"
android:layout_height="200dp"
android:layout_toRightOf="#id/listMiddle" >
</ListView>
<TextView
android:id="#android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/no_todos" />
</RelativeLayout>
The entire class is below
package de.vogella.android.todos;
import android.app.ListActivity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import de.vogella.android.todos.contentprovider.MyTodoContentProvider;
import de.vogella.android.todos.database.TodoTable;
/*
* TodosOverviewActivity displays the existing todo items
* in a list
*
* You can create new ones via the ActionBar entry "Insert"
* You can delete existing ones via a long press on the item
*/
public class TodosOverviewActivity extends ListActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
private static final int ACTIVITY_CREATE = 0;
private static final int ACTIVITY_EDIT = 1;
private static final int DELETE_ID = Menu.FIRST + 1;
// private Cursor cursor;
private SimpleCursorAdapter adapter;
private SimpleCursorAdapter middleAdapter;
private SimpleCursorAdapter rightAdapter;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.todo_list);
this.getListView().setDividerHeight(2);
fillData();
registerForContextMenu(getListView());
}
// Create the menu based on the XML defintion
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.listmenu, menu);
return true;
}
// Reaction to the menu selection
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.insert:
createTodo();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case DELETE_ID:
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
.getMenuInfo();
Uri uri = Uri.parse(MyTodoContentProvider.CONTENT_URI + "/"
+ info.id);
getContentResolver().delete(uri, null, null);
fillData();
return true;
}
return super.onContextItemSelected(item);
}
private void createTodo() {
Intent i = new Intent(this, TodoDetailActivity.class);
startActivity(i);
}
// Opens the second activity if an entry is clicked
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, TodoDetailActivity.class);
Uri todoUri = Uri.parse(MyTodoContentProvider.CONTENT_URI + "/" + id);
i.putExtra(MyTodoContentProvider.CONTENT_ITEM_TYPE, todoUri);
startActivity(i);
}
private void fillData() {
// Fields from the database (projection)
// Must include the _id column for the adapter to work
String[] from = new String[] { TodoTable.COLUMN_SUMMARY };
String[] middleId = new String[] { TodoTable.COLUMN_ID };
// Fields on the UI to which we map
int[] to = new int[] { R.id.label };
int[] two = new int[] { R.id.label };
getLoaderManager().initLoader(0, null, this);
adapter = new SimpleCursorAdapter(this, R.layout.todo_row, null, from,
to, 0);
middleAdapter = new SimpleCursorAdapter(this, R.layout.todo_row, null, middleId,
two, 0);
setListAdapter(adapter);
// setListAdapter(middleAdapter);
}
#Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, DELETE_ID, 0, R.string.menu_delete);
}
// Creates a new loader after the initLoader () call
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = { TodoTable.COLUMN_ID, TodoTable.COLUMN_SUMMARY };
CursorLoader cursorLoader = new CursorLoader(this,
MyTodoContentProvider.CONTENT_URI, projection, null, null, null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
// data is not available anymore, delete reference
adapter.swapCursor(null);
}
}

So my problem is that there is a specific method that I'm having trouble grasping how it works and as a result I cannot manipulate the way the data is displayed.
The method:
adapter = new SimpleCursorAdapter(this, R.layout.todo_row, null, from, to, 0);
Well, let's break this constructor down by each parameter:
this, a Context. The Adapter needs a Context to inflate each row's layout.
R.layout.todo_row, the row's layout. Every record in your Cursor will be displayed in this layout. (Exactly how the Cursor is displayed depends on from and to.)
null, a Cursor. This holds all of the data that will be shown in your ListView.
from, an array of the essential Views in the rows layout.
to, an array of the essential columns from your Cursor.
0, flags for when and why the data should be refreshed.
The trick behind every thing is this: the ids in the fourth (from) must each match a View in the second parameter (R.layout.todo_row). The Strings in fifth parameter must each match a column name in your Cursor. The fourth (from) and fifth parameters (to) must match one-to-one, because each column is displayed in one View. That's it really.
As you may have realized by now, this note:
R.layout.todo_row is a near blank xml, used when there are no "todos" with no listviews.
is wrong, sorry. If you want to display a note when the Cursor is empty add:
<TextView android:id="#android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="No data"/>
to todo_list.xml as described in ListActivity's documenation. By using this "magic id" in your TextView, the note should automatically be shown or hidden when appropriate.
All of this only interacts view the first ListView (with the id: `android:id="#android:id/list"), you need to create new Cursors and Adapters to use the other ListViews. Hope that helps!

I haven't looked at the source code for SimpleCursorAdapter. However, it appears that it is mostly doing two things:
The query for your data, based on the params you provide in fillData.
Looping through the results and populating the list using your template.
In my debuggin, I did notice that it's pretty efficient about filling the list - it only allocates as many rows as are needed to display. As you scroll, it recycles them rather than free reallocate them.
It looks like your fillData code is good. You don't say what isn't working so perhaps it's elsewhere. I've never used onCreateLoader (but probably should), so can't comment on that.
I saw one minor problem: in your R.layout.todo_row, you forgot the orientation attribute.

I'm assuming that the code compiles and runs fine and you just want to know what's going on. Well, there are a few things you need to be aware of. The first one is that the ListView doesn't take a layout parameter, your activity does in setContentView. Your R.layout.todo_list is only used by the TodosOverviewActivity to create the "screen" or "look" of the activity, that is, 3 ListView views side by side. Since the activity is a ListActivity it will automatically look for an entry of type ListView with an id of #android:id/list to automatically hook up the list listeners (just saves you a bit of typing), so your other lists will pretty much just sit there until you hook them up yourself (don't use the same id for items on the same layout). If you need to access these other lists you'll need to use the findViewById method in your activity and search for the id of the list you want. For example, we can access the middle list using this:
ListView middleList = (ListView)this.findById(R.id.listMiddle);
Now that we have the list, we need something to show. The lists are completely empty and you need to bring in data from somewhere. In your case, the data comes from a Cursor object you get from a ContentProvider. The cursor contains only one column that matters to us, the TodoTable.COLUMN_SUMMARY column that has the text we want to display in the list. The problem is that a list doesn't have a clue of what to do with a Cursor since the only thing it does is put a view on the screen and scroll it up and down. The Cursor, on the other hand, has all the data you want to show but doesn't have a clue of what a View is, much less how to put all the data it contains inside one for the list to show. Now you have the SimpleCursorAdapter which is, like the name says, an adapter. It is used to make incompatible things work together. On one side you have a list that needs a view, on the other side you have a cursor with the data you want to show, so now you need an adapter that will map each piece of data to part of a view. The SimpleCursorAdapter will ask you for 4 things in particular. The first is the layout of the view to show on the list, that is, what should a list item look like. This is the R.layout.todo_row that tells the adapter what views should be created. In this case we only have an icon and a TextView with the id R.id.label. Second, it will ask you for the cursor that contains the data, which is being set inside the onLoadFinished method (it is null when the adapter is created). Third, it wants to know what columns on the cursor matter. This is the String[] from array that says it should look for the data in the TodoTable.COLUMN_SUMMARY. Last, it needs to know where in the view to put this data, and this is the int[] to that contains the id of the TextView you'll be using to display the text, R.id.label.
In summary, the adapter is like a map between the data from the cursor and the layout for the view. Now, when the list needs a view to show on screen it will ask the adapter to give it one. The adapter then either recycles or creates a view form the layout you provided, fetches the data for each piece of the layout from the cursor and gives it all ready to go to the list to put it on the screen.

Related

Spinner updating only on user actions

I have IconText that has image and text views inside it, both class and xml.
I then populate the Spinner inside of the MainActivity with these IconTexts, using extended BaseAdapter (IconTextAdapter) as adapter.
Now, IconText works fine (shows as it should).
Spinner however doesn't.
When I start the app, it shows the first IconText as it should.
When I open the choose dialog, everything is showing as it should.
When I select another item, the choose dialog collapses and spinner displays no IconText (ie. only "arrow down" for opening choose dialog).
I can still open the choose dialog and choose another.
I've noticed that if I exit from app (return button, not really quiting the app) and enter again that the proper IconText is shown.
I guess that the fault lies with the adapter?
I will try avoiding posting a lot of code [:
IconText.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView android:id="#+id/it_image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="#drawable/home"/>
<TextView
android:id="#+id/it_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="20sp"
android:text="ABC"
android:textColor="#color/black_overlay"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
IconText.java
public class IconText {
static LayoutInflater inflator;
public ImageView icon;
public TextView text;
public View view;
public IconText(String title, int icon_id){
Log.d("IconText", "Create");
view = inflator.inflate(R.layout.icon_text, null);
text = (TextView) view.findViewById(R.id.it_text);
icon = (ImageView) view.findViewById(R.id.it_image);
text.setText(title);
icon.setImageResource(icon_id);
}
public static void initInflator(Context context){
if(inflator != null) return;
Log.d("IconText", "Init inflator");
inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
}
IconTextAdapter.java
public class IconTextAdapter extends BaseAdapter implements SpinnerAdapter{
public ArrayList<IconText> items;
public IconTextAdapter(){
super();
items = new ArrayList<>();
}
#Override
public int getCount() {
return items.size();
}
#Override
public Object getItem(int arg0) {
return items.get(arg0);
}
#Override
public long getItemId(int position) {
return items.get(position).view.getId();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
return items.get(position).view;
}
}
MainActivity, relevant code
void initFilter(){
Log.d("MainAction", "Find Filter");
filter = (Spinner) findViewById(R.id.filter);
Log.d("MainAction", "Create Adapter");
IconTextAdapter adapter = new IconTextAdapter();
Log.d("MainAction", "Create items");
for(int i=0; i<ETypes.names.length; i++){
IconText it = new IconText(ETypes.names[i], ETypes.icons[i]);
adapter.items.add(it);
}
Log.d("MainAction", "Setting adapter");
filter.setAdapter(adapter);
}
ETypes names[string] and icons[id] are static.
Inflator is initialized succesfully.
I haven't worked much in Android. If it were Java/Swing, I guess I would just call a redraw or something.
I know there are some bad practices here (all public variables, and so on). This code is still in early prototype stage, it will be fixed soon. I'm not looking for optimization, just for the solution to my problem.
Update 1:
So I saw I didn't implement getDropDownView so I did, the same code as getView (no need to post it?).
Also I made an experiment: At the end of IconText contrusctor I added
view = text
And it works just fine (showing only text).
I guess this pinpoints that the problem originates from custom view?
Update 2:
Did another experiment with IconText, setting view = icon; and it doesn't behave as it should, ie. it behaves like it's a custom view.
Doesn't really solve this specific bug, but it solves my problem.
Custom Image and Text View in Spinner Solution.
To update the list you must call
adapter.notifyDataSetChanged()
after adding new data to your adapter
Please refer to the following
https://developer.android.com/reference/android/widget/BaseAdapter.html#notifyDataSetChanged()

How to change style of specific rows in android ListView

I have a listview in my app, which is declared in XML, then the content is passed to it in code based on a string array which I have declared in XML. I'm new to android programming, but I was wondering how I could make the text in certain listview elements bold and darken the listview background behind it.
I have my set names as an array in my strings.xml:
<string-array name="setsArray">
<item>set1</item>
<item>set2</item>
<item>set3</item>
<item>set4</item>
<item>set5</item>
<item>set6</item>
</string-array>
I have my listview declared in the activity's layout.xml
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/listView"
android:layout_below="#+id/app_bar"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
And in the code of the activity I have this:
final ListView setList = (ListView) findViewById(R.id.listView);
setList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.setsArray)));
setList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int itemPosition = position;
String itemValue = (String) setList.getItemAtPosition(itemPosition);
if (!itemValue.equals("SPANISH")){
Intent intent = new Intent(view.getContext(), SetViewActivity.class);
intent.putExtra("value", itemValue);
startActivity(intent);
}
}
});
Note that this code takes the name of the set that was clicked on, and passes it onto the next activity with the intent so it knows what to display. If possible I need to keep this intact or replace it with another system which sends something which I can use to distinguish the sets in the next activity.
because you don't use a custom adapter (I guess), try this please:
<string-array name="setsArray">
<item>set1</item>
<item>set2</item>
<item>set3</item>
<item>set4</item>
<item> <![CDATA[ <b>set5</b> ]]> </item>
<item>set6</item>
</string-array>
I hope android using Html.fromHtml() by default :)
Android:Make characters bold in xml
Please watch The World of ListView. It explains the important aspects of ListViews and how to make an adapters for your ListView. Pay particular attention to the part about using getItemViewType() and getViewTypeCount().
I think you'll have to implement custom ListAdapter. In getView() method you'll be able to change style of item based on index.
As a side note, I don't see a reason why you wouldn't use RecyclerView. It's a more flexible and up to date choice for creating lists.
With RecyclerView it could be done like this:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.mTextView.setText(mDataset[position]);
if(position == 5) holder.mTextView.setTypeface(null, Typeface.BOLD);
}

Custom Array Adapter Returning Blank Screen?

I'm still very new to application development, so this is probably a very stupid question but I can't seem to find the right answer (or at least one that I can understand with my very limited knowledge of java).
I'm using a custom ArrayAdapter called ListRow. It works fine with a regular Activity, but not with the ListActivity that I need it to be in for my app to work.
Below is a sample of the code that I'm using. Any help would be greatly appreciated and you'd be helping a ton!
ListView mListview;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(new ListRow(this, THEME_NAMES, THEME_ICONS));
getListView().setTextFilterEnabled(true);
}
public class ListRow extends BaseAdapter {
private Context mContext;
private String[] mThemeNames = THEME_NAMES;
private int[] mThemeIcons = THEME_ICONS;
public ListRow(Context c, String[] t, int[] i) {
mContext = c;
mThemeNames = t;
mThemeIcons = i;
mListview=(ListView)findViewById(R.id.list);
}
#Override
public int getCount() {
return mThemeNames.length;
}
#Override
public Object getItem(int arg0) {
return null;
}
#Override
public long getItemId(int arg0) {
return 0;
}
#Override
public View getView(int position, View converView, ViewGroup parent) {
View List;
if(converView==null){
List=new View(mContext);
LayoutInflater mLayoutinflater=getLayoutInflater();
List=mLayoutinflater.inflate(R.layout.list_view, parent, false);
} else {
List = (View)converView;
}
ImageView imageView = (ImageView)List.findViewById(R.id.image);
TextView textView = (TextView)List.findViewById(R.id.text);
imageView.setImageResource(mThemeIcons[position]);
textView.setText(mThemeNames[position]);
return List;
}
}
And here's the layout I've defined for each list item
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center"
android:id="#+id/image"
android:layout_alignParentLeft="true"
android:contentDescription="#string/preview" />
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/image" />
</RelativeLayout>
If you can, please use small words with me lol, java has turned out to be hard to understand for me, and also try to explain as much as you can. Thanks in advance!
FIGURED IT OUT!
So I just put you all through a bit of hell. The layout that contains my list items is called list_item, not list_view. However I have learned a lot here so THANK YOU ALL VERY MUCH! I wish there were a way I could help you guys out...
Moral of this question? CHECK YOUR LAYOUT NAMES!!
You need to set The Adapter in this way
setListAdapter(new ListRow(this, your_theme_names_array, your_theme_icon_array));
You dont need to use ArrayAdapter for this, that is just for Creating a Adapter for an array of String
EDITED
The Layout XML does not have the problem i think.
Check the List given below one by one
Check List
Check Whether R.layout.list_view point to the layout you given in the Question.
Try this for setting adapter setListAdapter(new ListRow(this, String[] { }, int[] { })); it will show you blank screen (If you get the Blank Screen that means either THEME_NAMES or THEME_ICONS is null or their values is null)
Remove the Line imageView.setImageResource(mThemeIcons[position]); and
textView.setText(mThemeNames[position]); this will also give u blank screen (If you get blank screen then R.layout.list_view does not contain R.id.image or R.id.text.
You have to add your mListView in your ArrayAdapter in setListAdapter.Only then the contents of your listview will be display in the pattern you have mentioned in customadapter. I cannot see where you have added elements in listview.

Formatting Specific Parts of a Listview Item

So in my application I am using a ListView to display data from an ArrayList which holds objects. The data is displayed using the same method as the tutorial on the android developer website:
// automatically adds a ListView to fill the entire screen of this activity
setListAdapter(new ArrayAdapter<Part>(this, R.layout.list_item, Main.parts));
ListView lv = getListView();
// allows the user to start typing to filter the list
lv.setTextFilterEnabled(true);
// set the click listener for each list item
lv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Toast.makeText(getApplicationContext(), ((TextView) view).getText(), Toast.LENGTH_SHORT).show();
}
});
The objects are currently displayed in each list_item:
<!-- Defines the layout for each item being placed in the ListView. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
android:textSize="16sp" >
</TextView>
So I return each object as such:
public String toString() {
return "Item Number: " + itemNmbr + "\nPrice: " + price + "\nDescription: " + desc;
}
An example of how the list currently looks:
http://imageshack.us/photo/my-images/689/devicet.png/
The problem is, I need to format the title separately from the data. (because it needs to be bold, and possibly spaced out a little further.)
Any ideas? I'm currently testing on how to get two textviews to work together.
Thank you!
I would recommend using a custom adapter extending BaseAdapter.
http://developer.android.com/reference/android/widget/BaseAdapter.html
See this link for an example.
http://www.softwarepassion.com/android-series-custom-listview-items-and-adapters/

How do I clear ListView selection?

TL;DR: You choose an option from (a) my listview. Then, you change your mind and type something in (b) my edit text. How do I clear your listview selection and only show your edittext? (and vice versa)
I have an application with a listview of options as well as an edittext to create an own option. I need the user to either choose or create an option, but not both. Here's a drawing of my layout:
Whenever the user selects an option from the listview, I set it as "selected" by making it green, like so:
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_selected="true"
android:drawable="#color/colorPrimary"/>
<item
android:state_selected="false"
android:drawable="#color/windowBackground" />
</selector>
(this is set as the background of my listview)
Problem: I want to unselect the listview option if the user decides to type in their own option since they can only have one option.
User selects an option from the listview
User decides they want to create their own option using the edittext
The listview option is unselected when they start typing their own
I've tried doing the following, but nothing unselects.
e.setOnEditorActionListener(new TextView.OnEditorActionListener()
{
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
for(int i=0; i<=5; i++){
listView.setItemChecked(i, false);
}
listView.clearChoices();
listView.requestLayout()
adapter.notifyDataSetChanged()
}
}
A very puzzling predicament, any help is appreciated!
Edit: here is the layout of the edittext:
<EditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBaseline="#+id/textView4"
android:layout_alignBottom="#+id/textView4"
android:layout_toEndOf="#+id/textView4"
android:layout_toRightOf="#+id/textView4"
android:color="#color/colorPrimary"
android:imeOptions="actionDone"
android:inputType="text"
android:textColor="#color/textColorPrimary"
android:textColorHint="#color/colorPrimary" />
Edit: here is the layout of the listview:
<ListView
android:id="#+id/listview"
android:layout_width="wrap_content"
android:layout_height="250dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="#+id/toolbar"
android:background="#drawable/bg_key"
android:choiceMode="singleChoice"
android:listSelector="#color/colorPrimary">
</ListView>
Long Story Short
ListView selector (android:listSelector) is designed to indicate a click event, but not selected items.
If a ListView selector is drawn (after first click) it won't dissapear without drastic changes in the ListView
Hence use only drawables with transparent background if no state is applied to it as a ListView selector. Don't use a plain color resource for it, don't confuse yourself.
Use ListView choice mode (android:choiceMode) to indicate selected items.
ListView tells which row is selected by setting android:state_activated on the row's root view. Provide your adapter with corresponding layout/views to represent selected items correctly.
TL/DR Solutions
You can hide/remove selector with one of the following:
Making the selector transparent getSelector().setAlpha(0)
Resetting the current adapter with setAdapter(myAdapter) (adapter might be the same)
Solutions that might or might not work, depending on the OS version:
Making the list view to refresh layout completely via requestLayout(), invalidate() or forceLayout() methods;
Making the list view to refresh layout via notifyDataSetChanged()
Theory
Well, the built-in selection in ListView is utterly tricky at a first glance. However there are two main distinctions you should keep in mind to avoid confusing like this - list view selector and choice mode.
ListView selector
ListView selector is a drawable resource that is assumed to indicate an event of clicking a list item. You can specify it either by XML-property android:listSelector or using method setSelector(). I couldn't find it in docs, but my understanding is that this resource should not be a plain color, because after it's being drawn, it won't vanish without drastic changes in the view (like setting an adapter, that in turn may cause some glitches to appear), hence such drawable should be visible only while particular state (e.g. android:state_pressed) is applied. Here is a simple example of the drawable that can be used as a List View selector
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="#android:color/darker_gray" />
<item
android:drawable="#android:color/transparent" />
</selector>
For whatever reason you cannot use a Color State List as List View selector, but still can use plain colors (that are mostly inappropriate) and State List drawables. It makes things somewhat confusing.
After the first click on a List View happens, you will not be able to remove List View selector from the List View easily.
The main idea here is that List View selector is not designed to indicate selected item.
ListView choice mode
ListView choice mode is assumed to indicate selected items. As you might know, primarily there are two choice modes we can use in ListView - Single Choice and Multiple Choice. They allow to track a single or multiple rows selected respectively. You can set them via android:choiceMode XML-property or setChoiceMode() method.
The ListView itself keeps selected rows in it and let them know which one is selected at any given moment by setting android:state_activated property of the row root view. In order to make your rows reflect this state, their root view must have a corresponding drawable set, e.g. as a background. Here is an example of such drawable:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_activated="true"
android:drawable="#android:color/holo_green_light" />
<item
android:drawable="#android:color/transparent" />
</selector>
You can make rows selected/deselected programmatically using the setItemChecked() method. If you want a ListView to clear all selected items, you can use the clearChoices() method. You also can check selected items using the family of the methods: getCheckedItemCount(), getCheckedItemIds(), getCheckedItemPosition() (for single choice mode), getCheckedItemPositions() (for multiple choice mode)
Conclusion
If you want to keep things simple, do not use the List View selector to indicate selected items.
Solving the issue
Option 1. Dirty fix - hide selector
Instead of actually removing selector, changing layouts and implementing a robust approach, we can hide the selector drawable when it's needed and show it later when clicking a ListView item:
public void hideListViewSelector() {
mListView.getSelector().setAlpha(0);
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mListView.getSelector().getAlpha() == 0) {
mListView.getSelector().setAlpha(255);
}
}
Option 2. Thoughtful way
Let's go through your code and make it comply the rules i described step by step.
Fix ListView layout
In your ListView layout the selector is set to a plain color, and therefore your items are colored by it when they are clicked. The drawable you use as the ListView background have no impact, because ListView state doesn't change when its rows are clicked, hence your ListView always has just #color/windowBackground background.
To solve your problem you need at first remove the selector from the ListView layout:
<ListView
android:id="#+id/listview"
android:layout_width="wrap_content"
android:layout_height="250dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="#+id/toolbar"
android:listSelector="#color/colorPrimary"
android:background="#color/windowBackground"
android:choiceMode="singleChoice"/>
Make your rows reflect activated state
In the comments you give your adapter as follows:
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, text1, listOfThings);
You also asked me if it's possible to keep using standard adapter to achieve desired behavior. We can for sure, but anyway a few changes are required. I can see 3 options for this case:
1. Using standard android checked layout
You can just specify a corresponding standard layout - either any of the layouts that use CheckedTextView without changed background drawable as the root component or of those that use activatedBackgroundIndicator as their background drawable. For your case the most appropriate option should be the simple_list_item_activated_1. Just set it as in your ArrayAdapter constructor like this:
final ArrayAdapter<String> adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_activated_1, android.R.id.text1, listOfThings);
This option is the closest to what i understand by 'standard' adapter.
2. Customize your adapter
You can use standard layout and mostly standard adapter with a small exception of getting a view for your items. Just introduce an anonymous class and override the method getView(), providing row views with corresponding background drawable:
final ArrayAdapter<String> adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1, listOfThings) {
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
final View view = super.getView(position, convertView, parent);
if (convertView == null) {
view.setBackgroundResource(R.drawable.list_item_bg);
}
return view;
}
};
3. Customize your layout
The most common way of addressing this issue is of course introducing your own layout for the items view. Here is my simple example:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#drawable/list_item_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="16dp">
<TextView
android:id="#android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
I saved it in a file /res/layout/list_view_item.xml Do not forget setting this layout in your adapter:
final ArrayAdapter<String> adapter = new ArrayAdapter(this, R.layout.list_view_item, android.R.id.text1, listOfThings);
Clearing selection
After that your rows will reflect selected state when they are clicked, and you can easily clear the selected state of your ListView by calling clearChoices() and consequence requestLayout() to ask the ListView to redraw itself.
One little comment here that if you want unselect the item when user start typing, but not when he actually clicks the return (done) button, you need to use a TextWatcher callback instead:
mEditText.addTextChangedListener(new TextWatcher(){
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mListView.getCheckedItemCount() > 0) {
mListView.clearChoices();
mListView.requestLayout();
}
}
#Override
public void afterTextChanged(Editable s) {}
});
Hopefully, it helped.
I have a good solution to do that. Add EditText to your layout which contains on your ListView as this layout:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="#+id/list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:choiceMode="singleChoice"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Comment"
android:layout_below="#id/list_view"
android:id="#+id/editText"
android:nextFocusUp="#id/editText"
android:nextFocusLeft="#id/editText"/>
</RelativeLayout>
Then initialize Boolean variable to check whether editText if focused or not for example use this : boolean canBeSelected = true;
Then after setting adapter use this code:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (canBeSelected) {
listView.setSelector(R.drawable.background);
listView.setSelected(true);
listView.setSelection(i);
} else {
if (!editText.isFocused()){
canBeSelected = true;
listView.setSelector(R.drawable.background);
listView.setSelected(true);
listView.setSelection(i);
}
}
}
});
editText.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
canBeSelected = false;
Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
listView.setSelector(transparentDrawable);
listView.clearChoices();
listView.setSelected(false);
return false;
}
});
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (editText.isFocused()){
Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
listView.setSelector(transparentDrawable);
listView.clearChoices();
listView.setSelected(false);
canBeSelected = false;
}
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
listView.setSelector(transparentDrawable);
listView.clearChoices();
listView.setSelected(false);
canBeSelected = false;
}
#Override
public void afterTextChanged(Editable editable) {
if (editText.isFocused()) {
Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
listView.setSelector(transparentDrawable);
listView.clearChoices();
listView.setSelected(false);
canBeSelected = false;
}
}
});
}
Hope it works with you :)
Re-setting the adapter in the edittext listener worked for me:
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
listview.clearChoices();
listview.setAdapter(adapter);
Toast.makeText(getApplicationContext(),"Typing" + listview.getSelectedItemPosition(), Toast.LENGTH_SHORT).show();
}
#Override
public void afterTextChanged(Editable editable) {
}
});
I put the selected index in a toast to check if the item was correctly deselected.
Hope this works!!
Just call clear when you make the request for the second data set:
arrayAdapter!!.clear()
You load your first data set
The user select one elements,
This action highlight your item
For any reason you launch the reload of your data set (because edittext's value changed),
at this moment call, clear() on your adapter.
Then you retrieved your dataset, you send it to the arrayAdapter and
No one is selected .
This is because when you clear, it also clear the selected flag

Categories

Resources