I'm trying to implement multiple list item selection and user can perform actions based on menu item appears on actionbar.
I have tried a way ListView.CHOICE_MODE_MULTIPLE_MODAL but this option works only for API 11 or above.
Is there a way we can make use of the same technique for API 11 below i.e below code works only API 11 onwards.
list.setMultiChoiceModeListener(new MultiChoiceModeListener() {
#Override
public void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked) {
// Capture total checked items
final int checkedCount = list.getCheckedItemCount();
// Set the CAB title according to total checked items
mode.setTitle(checkedCount + " Selected");
// Calls toggleSelection method from ListViewAdapter Class
listviewadapter.toggleSelection(position);
}
Using ActionBarSherlock the MultiChoiceModeListener if you want to support API level < 11.
A workaround is to use the onItemClickListener.
List setup:
listView = (ListView) timeline.findViewById(android.R.id.list);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
listView.setItemsCanFocus(false);
listView.setAdapter(new ListAdapter(getActivity(), R.layout.cleaning_list_item, items));
Listener of ListFragment or ListActivity:
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
SparseBooleanArray checked = listView.getCheckedItemPositions();
boolean hasCheckedElement = false;
for (int i = 0; i < checked.size() && !hasCheckedElement; i++) {
hasCheckedElement = checked.valueAt(i);
}
if (hasCheckedElement) {
if (mMode == null) {
mMode = ((SherlockFragmentActivity) getActivity()).startActionMode(new MyActionMode());
mMode.invalidate();
} else {
mMode.invalidate();
}
} else {
if (mMode != null) {
mMode.finish();
}
}
}
Where MyActionMode is an implementation of ActionMode.Callback:
private final class MyActionMode implements ActionMode.Callback { /* ... */ }
Related
My code :
ArrayList<Integer> selectedItemIds = new ArrayList<>();
boolean isItemSelected = false;
main_listview.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Object object = parent.getItemAtPosition(position);
if(object instanceof Listview_Item_Details)
{
TextView itemName = (TextView) view.findViewById(R.id.listview_itemName);
TextView itemDetails = (TextView) findViewById(R.id.listview_itemDetails);
if(!selectedItemIds.isEmpty())
{
for(int i = 0; i < selectedItemIds.size();i++)
{
if(selectedItemIds.get(i) == position)
{
isItemSelected = true;
if(selectedItemIds.size() == 1)
{
selectedItemIds.clear();
}
else
{
selectedItemIds.remove(i);
}
}
}
if(isItemSelected)
{
view.findViewById(R.id.custom_listview_item).setBackgroundResource(R.drawable.listview_item_background);
itemName.setTextColor(getResources().getColor(R.color.colorBlack));
itemDetails.setTextColor(getResources().getColor(R.color.colorGray_Dark));
isItemSelected = false;
}
else
{
selectedItemIds.add(position);
view.findViewById(R.id.custom_listview_item).setBackgroundResource(R.drawable.listview_selected_item_background);
itemName.setTextColor(getResources().getColor(R.color.colorWhite));
itemDetails.setTextColor(getResources().getColor(R.color.colorPrimaryDark));
}
}
else
{
selectedItemIds.add(position);
view.findViewById(R.id.custom_listview_item).setBackgroundResource(R.drawable.listview_selected_item_background);
itemName.setTextColor(getResources().getColor(R.color.colorWhite));
itemDetails.setTextColor(getResources().getColor(R.color.colorPrimaryDark));
}
}
return true;
}
});
NOTE : "custom_listview_item" is RelativeLayout of custom layout for item, in which both textviews are.
Now everythings works but like i said in question, if i DONT scroll the listview and selected/unselect multiple items, it works but i scroll it randomly changes background of multiple items and even changes background of selected items too.
What is the problem here?
Use Recyclerview instead of Listview to handle this kind of situations and customizing the view items.
I'm working on an Android app that involves ListView and SharedPreference. Now, the problem is that I'm not able to remove the item from the ListView dynamically. I tried List_view_name.remove(1) but it only removes the 1st index from the array but when I try to remove the second item from the list, it removes the 1st item.
The second thing is that I tried removing ArrayList places from the SharedPreference so that it doesn't show up when the app reloads but it removes all the items from the places ArrayList. How to remove a particular item from ListView and SharedPreference in onContextItemSelected.
Below I'm posting the code.
public class MainActivity extends AppCompatActivity {
static ArrayList<String> places = new ArrayList<String>();
static ArrayList<LatLng> locations = new ArrayList<>(); //to save lat and long
static ArrayAdapter arrayAdapter;
ListView listView;
SharedPreferences sharedPreferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
sharedPreferences = this.getSharedPreferences("com.starprojects.memorableplaces", Context.MODE_PRIVATE);
//tricker locations
ArrayList<String> latitudes = new ArrayList<>();
ArrayList<String> longitudes = new ArrayList<>();
//initially set
places.clear();
latitudes.clear();
longitudes.clear();
locations.clear();
//to restore
try {
places = (ArrayList<String>) ObjectSerializer.deserialize(sharedPreferences.getString("places", ObjectSerializer.serialize(new ArrayList<>())));
latitudes = (ArrayList<String>) ObjectSerializer.deserialize(sharedPreferences.getString("latitudes", ObjectSerializer.serialize(new ArrayList<>())));
longitudes = (ArrayList<String>) ObjectSerializer.deserialize(sharedPreferences.getString("longitudes", ObjectSerializer.serialize(new ArrayList<>())));
} catch (IOException e) {
e.printStackTrace();
}
//check to see if we have anything from data storage
if(places.size() > 0 && latitudes.size() > 0 && longitudes.size() > 0) {
//something is gone wrong and we dont have same number of places,latitudes and logitudes, app will give error so check to see
if(places.size() == latitudes.size() && latitudes.size() == longitudes.size()) {
//loop through both latitude nad lonitude
for(int i = 0;i<latitudes.size();i++) {
locations.add(new LatLng(Double.parseDouble((latitudes.get(i))), Double.parseDouble(longitudes.get(i))));
}
}
} else {
places.add("Add a new place....");
locations.add(new LatLng(0,0));
}
arrayAdapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,places);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
// Toast.makeText(MainActivity.this, i, Toast.LENGTH_SHORT).show();
Intent intent = new Intent(getApplication(),MapsActivity.class);
intent.putExtra("placeNumber",i);
startActivity(intent);
}
});
registerForContextMenu(listView);
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
if(listView.getItemAtPosition(i) == listView.getItemAtPosition(0)) {
Toast.makeText(MainActivity.this, "No", Toast.LENGTH_SHORT).show();
} else {
listView.showContextMenu();
}
return true;
}
});
}
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
// super.onCreateContextMenu(menu, v, menuInfo);
if(v.getId()==R.id.listView) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
menu.setHeaderTitle("Menu");
String[] menuItems = getResources().getStringArray(R.array.menu);
for (int i = 0; i<menuItems.length; i++) {
menu.add(Menu.NONE, i, i, menuItems[i]);
}
}
}
#Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo(); //returns null
if((item.getTitle()).equals("Delete")) {
int position = (int) info.id;
Log.i("Psotion",String.valueOf(position));
places.remove(position);
return true;
}
return super.onContextItemSelected(item);
}
}
You can remove value from ArrayList used in Adapter and then call notifydatasetchanged() .
For this you can create a class MyCustomAdapter and extends BaseAdapter class. You can get more info on google.
You need to remove item from list not listview. In your case you can remove items like: places.remove(2); and locations.remove(2) after that notify your adapter about list items change like this: arrayAdapter.notifyDataSetChanged();
try like this:
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
int index = info.position;
places.remove(index); // delete item from corresponding list
arrayAdapter.notifyDataSetChanged();
My problem is when I select an item and I release the item isn't selected and the first time that I do click there isn't checked elements.
Code:
public class SongsFragment extends SherlockListFragment implements
LoaderManager.LoaderCallbacks<Cursor>, OnItemLongClickListener {
...
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
mMode = null;
mListView = getListView();
mListView.setItemsCanFocus(false);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mListView.setOnItemLongClickListener(this);
}
...
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Log.d(getClass().getSimpleName(), "longClick");
SparseBooleanArray checked = mListView.getCheckedItemPositions();
if(checked.size() == 0)
Log.d(getClass().getSimpleName(), "checked size 0");
boolean hasCheckedElement = false;
for (int i = 0; i < checked.size() && !hasCheckedElement; i++) {
Log.d(getClass().getSimpleName(), "For: " + Integer.toString(i));
hasCheckedElement = checked.valueAt(i);
}
if (hasCheckedElement) {
Log.d(getClass().getSimpleName(),"hasCheckedElement");
if (mMode == null) {
mMode = getSherlockActivity().startActionMode(
mActionModeCallback);
}
} else {
Log.d(getClass().getSimpleName(),"!!!hasCheckedElement");
if (mMode != null) {
mMode.finish();
}
}
return false;
}
...
}
For example: do click on a item, release, do click on the same item. This is the output:
SongsFragment(16560): longClick
SongsFragment(16560): checked size 0
SongsFragment(16560): !!!hasCheckedElement
SongsFragment(16560): longClick
SongsFragment(16560): For: 0
SongsFragment(16560): hasCheckedElement
Why the first time isn't checked?
Why the item isn't selected when I release?
Thanks.
I found this tutorial (in spanish): http://androcode.es/2012/03/seleccion-individualmultiple-de-elementos-en-un-listview/
Now all it's working.
I am developing a simple Android app which creates a ListView with a checkbox as follows
listAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_multiple_choice,results);
this.setListAdapter(listAdapter);
ListView lv = getListView();
lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
lv.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
CheckedTextView check = (CheckedTextView)view;
//The below code needed for running on AVD version 4.2
if(check.isChecked()) {
// Checkbox checked do something
}
else {
// Not checked. Do something
}
/* Exact opposite need on Samsung phone running 2.3.6
if(!check.isChecked()) {
// Checkbox checked do something
}
else {
// Not checked. DO something
} */
This is truly weird. How will the app ever be compatible with both version when they require completely opposite checks?
Please let me know how I can handle this. Any help will be appreciated.
i know the problem, on newer frameworks the onItemClick call before the checkd attribute of the checkbox changed!
i check the checked state of my checkboxes in after item clicking (e.g. on activity close or save button click).
look at list.getCheckedItemPositions()... it will return a SparseBooleanArray you can use!
EDIT: example
public class MyMultiChoiceActivity extends Activity implements View.OnClickListener {
private ListView list = null;
private boolean savedSelectionInvalid = true;
private List<Integer> selectedIds = new ArrayList<Integer>();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.entry_data_multi_choice_filter);
//initialize the list
list = (ListView) findViewById(R.id.list);
listAdapter = new MyListAdapterClass(this, R.layout.list_item_multiple_choice);
list.setAdapter(listAdapter);
//onclick on a item, set the selectaion as invalid
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
savedSelectionInvalid = true;
}
});
list.setAdapter(listAdapter);
//...
}
//...
private void validateChecked() {
if(savedSelectionInvalid == true) {
SparseBooleanArray checkedItemlist = list.getCheckedItemPositions();
selectedIds.clear();
for (int i=0; i < checkedItemlist.size(); i++){
if (checkedItemlist.valueAt(i)) {
selectedIds.add(a.keyAt(i));
}
}
this.savedSelectionInvalid = false;
}
}
//...
//this method se
private void saveAndClose() {
validateChecked();
//now you have the selectedIds filled with every checked item key!
this.finish();
}
}
The issue can be solved by checking the version of the device.
The documentation recommends you check Build.VERSION.SDK_INT against the values in Build.VERSION_CODES.
Sample usage
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
// only for android older than gingerbread
}
http://developer.android.com/reference/android/os/Build.VERSION.html
(More like a hack than a solution though)
You can also try to use something like this:
mFilterListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
...
SparseBooleanArray checkedItemlist = mFilterListView.getCheckedItemPositions();
boolean checked = false;
for (int i = 0; i < checkedItemlist.size(); i++) {
if (checkedItemlist.keyAt(i) == position) {
checked = checkedItemlist.valueAt(i);
}
...
}
}
As a result you have a flag checked, which indicates what is the state of item after the click.
After reading and try'n'error for days, I´m giving up and ask for help.
< edit >
I am using ActionBarSherlock.
< /edit >
What I want to achieve:
A ListView with a custom layout for each row, where the user can select multiple list items.
A selected list item should have a different background color. When there is at least one item selected, a contextual action bar (CAB) should be shown.
It should look more or less like the multiple selection of emails in the GMail app. The only difference is that in the gmail app the selection is done by clicking the checkbox of a row, whereas I don´t want to have a checkbox, but a row should be selected no matter, where the user clicks.
What I tried:
Following this tutorial, using a Checkable row layout with some logic to change the background color when the check state was toggled, I got everything working except that I could not register a click listener like OnItemClickListener on the ListView to show the CAB. Neither providing a click listener for each row View helped because this prevented to change the background color of the selected items.
I also tried adding a MultiChoiceModeListener to the ListView like that
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { //.. });
With the same result, no background color change.
What I am looking for: A hint or a tutorial or sample code how to do this. If you need some code snippets to help, let me know.
See if the code helps you(it's basically a ListActivity with a custom adapter to hold the status of checked items(+ different background)):
public class CABSelection extends ListActivity {
private ArrayList<String> mItems = new ArrayList<String>();
private SelectionAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for (int i = 0; i < 24; i++) {
mItems.add("Name" + i);
}
// R.layout.adapters_cabselection_row is a LinearLayout(with green
// background(#99cc00)) that wraps an ImageView and a TextView
mAdapter = new SelectionAdapter(this,
R.layout.adapters_cabselection_row, R.id.the_text, mItems);
setListAdapter(mAdapter);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() {
private int nr = 0;
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.cabselection_menu, menu);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
StringBuilder sb = new StringBuilder();
Set<Integer> positions = mAdapter.getCurrentCheckedPosition();
for (Integer pos : positions) {
sb.append(" " + pos + ",");
}
switch (item.getItemId()) {
case R.id.edit_entry:
Toast.makeText(CABSelection.this, "Edited entries: " + sb.toString(),
Toast.LENGTH_SHORT).show();
break;
case R.id.delete_entry:
Toast.makeText(CABSelection.this, "Deleted entries : " + sb.toString(),
Toast.LENGTH_SHORT).show();
break;
case R.id.finish_it:
nr = 0;
mAdapter.clearSelection();
Toast.makeText(CABSelection.this, "Finish the CAB!",
Toast.LENGTH_SHORT).show();
mode.finish();
}
return false;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
nr = 0;
mAdapter.clearSelection();
}
#Override
public void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked) {
if (checked) {
nr++;
mAdapter.setNewSelection(position, checked);
} else {
nr--;
mAdapter.removeSelection(position);
}
mode.setTitle(nr + " rows selected!");
}
});
}
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
l.setItemChecked(position, !mAdapter.isPositionChecked(position));
}
private class SelectionAdapter extends ArrayAdapter<String> {
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
public SelectionAdapter(Context context, int resource,
int textViewResourceId, List<String> objects) {
super(context, resource, textViewResourceId, objects);
}
public void setNewSelection(int position, boolean value) {
mSelection.put(position, value);
notifyDataSetChanged();
}
public boolean isPositionChecked(int position) {
Boolean result = mSelection.get(position);
return result == null ? false : result;
}
public Set<Integer> getCurrentCheckedPosition() {
return mSelection.keySet();
}
public void removeSelection(int position) {
mSelection.remove(position);
notifyDataSetChanged();
}
public void clearSelection() {
mSelection = new HashMap<Integer, Boolean>();
notifyDataSetChanged();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);//let the adapter handle setting up the row views
v.setBackgroundColor(Color.parseColor("#99cc00")); //default color
if (mSelection.get(position) != null) {
v.setBackgroundColor(Color.RED);// this is a selected position so make it red
}
return v;
}
}
}
The R.layout.adapters_cabselection_row is a custom layout for the row(a very simple one) with a green background:
<?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="match_parent"
android:background="#99cc00" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_launcher" />
<TextView
android:id="#+id/the_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ffffff"
android:textSize="17sp"
android:textStyle="bold" />
</LinearLayout>
R.menu.cabselection_menu is a menu file with 3 options(edit, delete, finish the CAB) which don't do anything except pop a Toast with a message regarding the rows selected:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/edit_entry"
android:icon="#android:drawable/ic_menu_edit"
android:title="Edit!"/>
<item
android:id="#+id/delete_entry"
android:icon="#android:drawable/ic_menu_delete"
android:title="Delete!"/>
<item
android:id="#+id/finish_it"
android:icon="#android:drawable/ic_menu_crop"
android:title="Get me out!"/>
</menu>
I think the easiest way is to apply
android:background="android:attr/activatedBackgroundIndicator"
To which ever layout is the one you will be clicking.
This highlights the layout when selected using
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
worked for me anyway
Using ActionBarSherlock the MultiChoiceModeListener used in Luksprog´s answer is not yet available if you want to support API level < 11.
A workaround is to use the onItemClickListener.
List setup:
listView = (ListView) timeline.findViewById(android.R.id.list);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
listView.setItemsCanFocus(false);
listView.setAdapter(new ListAdapter(getActivity(), R.layout.cleaning_list_item, items));
Listener of ListFragment or ListActivity:
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
SparseBooleanArray checked = listView.getCheckedItemPositions();
boolean hasCheckedElement = false;
for (int i = 0; i < checked.size() && !hasCheckedElement; i++) {
hasCheckedElement = checked.valueAt(i);
}
if (hasCheckedElement) {
if (mMode == null) {
mMode = ((SherlockFragmentActivity) getActivity()).startActionMode(new MyActionMode());
mMode.invalidate();
} else {
mMode.invalidate();
}
} else {
if (mMode != null) {
mMode.finish();
}
}
}
Where MyActionMode is an implementation of ActionMode.Callback:
private final class MyActionMode implements ActionMode.Callback { /* ... */ }