Multiselect listView with ActionMode - how to keep selected items? - android

I'm trying to create ListView with installed apps. User selects apps inside wizard (basically viewpager).
My plan is to create a list of custom views (icon, name, package) that will allow to select more than one item. Unfortunatelly checkboxes won't work, because I need this place for another functionality. So, I'll change the background of the element.
So, I found a solution on stackoverflow and changed it a bit.
Firstly - main activity with this list.
public class MainActivity extends ListActivity {
private static final String TAG = MainActivity.class.getName();
private ApplicationsAdapter applicationsAdapter;
private void getAppList(){
//get apps asynch
createList(list);
}
private void createList(ArrayList<ApplicationItem> list){
applicationsAdapter = new ApplicationsAdapter(this, R.layout.application_list_item, list);
setListAdapter(applicationsAdapter);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
getListView().setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
private int nr = 0;
#Override
public boolean onCreateActionMode(android.view.ActionMode mode, Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.cabselection_menu, menu);
return true;
}
#Override
public boolean onPrepareActionMode(android.view.ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(android.view.ActionMode mode, MenuItem item) {
return false;
}
#Override
public void onDestroyActionMode(android.view.ActionMode mode) {
nr = 0;
applicationsAdapter.clearSelection();
}
#Override
public void onItemCheckedStateChanged(android.view.ActionMode mode, int position, long id, boolean checked) {
if (checked) {
nr++;
applicationsAdapter.setNewSelection(position, checked);
L.d(TAG, applicationsAdapter.getItem(position).getAppName());
} else {
nr--;
applicationsAdapter.removeSelection(position);
}
mode.setTitle(nr + " rows selected!");
}
});
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getAppList();
}
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
ApplicationItem item = (ApplicationItem)l.getAdapter().getItem(position);
L.d(TAG + "onListItemClick", applicationsAdapter.getItem(position).getAppName());
l.setItemChecked(position, !applicationsAdapter.isPositionChecked(position));
}
}
In my case, normally this whole thing is inside a fragment, inside the viewpager. For the sake of clarity I changed this into typical activity.
Now, the adapter:
public class ApplicationsAdapter extends ArrayAdapter<ApplicationItem> {
// private HashMap<ApplicationItem, Boolean> objects;
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
public ApplicationsAdapter(Context context, int textViewResourceId, ArrayList<ApplicationItem> objects) {
super(context, textViewResourceId, objects);
//this.objects = 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();
}
public ApplicationItem getItem(int position){
return super.getItem(position);
}
public View getView(int position, View convertView, ViewGroup parent){
View v = convertView;
if (v == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.application_list_item, null);
}
ApplicationItem item = super.getItem(position);
if(item != null){
TextView appName = (TextView) v.findViewById(R.id.appName);
TextView appPackage = (TextView) v.findViewById(R.id.appPackage);
ImageView appIcon = (ImageView) v.findViewById(R.id.appIcon);
if (appName != null){
appName.setText(item.getAppName());
}
if (appPackage != null){
appPackage.setText(item.getPackageName());
}
if (appIcon != null){
appIcon.setImageDrawable(item.getIcon());
}
}
v.setBackgroundColor(Color.parseColor("#00FFFFFF")); //default color
if (mSelection.get(position) != null) {
v.setBackgroundColor(Color.BLUE);// this is a selected position so make it red
}
return v;
}
}
THE PROBLEM:
ActionMode is nice, however I'm not sure how to keep selected elements after it's destroy.
Normally inside onDestroyActionMode I'm clearing the selection. Great, so I'll just delete that. Now after clicking the "tick" symbol all apps are still selected. However, getting back to them is now problematic, because ActionMode will only "fire up" when clicking on unselected element.
So - how should I handle that?

Ha, the answer was quite simple. I should have thought about it yesterday, but apparently I was a bit too exhausted.
First of all, I changed the mode of listview into
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
And deleted setMultiChoiceModeListener() and I'm only using
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
ApplicationItem item = (ApplicationItem)l.getAdapter().getItem(position);
L.d(TAG + "onListItemClick", applicationsAdapter.getItem(position).getAppName());
boolean checked = item.isSelected();
if (!checked) {
item.setSelected(true);
applicationsAdapter.setNewSelection(position, checked);
L.d(TAG, applicationsAdapter.getItem(position).getAppName());
} else {
item.setSelected(false);
applicationsAdapter.removeSelection(position);
}
}
That simple.
Apparently, ActionMode may be fun, but it wasn't the right tool for this job.
And also - adapter from first post can be used as a example of multiselect listView that changes backgrounds instead of checkboxes. Apparently I couldn't find anything simple enough like that.

Related

Multiple items selection in grid view getting repeated

This is the first grid where I selected
the problem is:
when I scroll down, here random images are getting selected
I used glide library for grid view. Here's the Java Code for Multiple selection:
mgridview.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
#Override
public void onItemCheckedStateChanged(ActionMode actionMode, int i, long l, boolean b) {
if (sel_tab2_images.contains(tab2_images.get(i))) {
sel_count = sel_count - 1;
actionMode.setTitle(sel_count + " images selected");
sel_tab2_images.remove(tab2_images.get(i));
viewprev = mgridview.getChildAt(i - mgridview.getFirstVisiblePosition());
viewprev.setBackgroundColor(Color.WHITE);
viewprev.setAlpha(1f);
if(Build.VERSION.SDK_INT>=23) {
viewprev.setForeground(null);
}
} else {
sel_count = sel_count + 1;
actionMode.setTitle(sel_count + " images selected");
sel_tab2_images.add(tab2_images.get(i));
viewprev = mgridview.getChildAt(i - mgridview.getFirstVisiblePosition());
if (viewprev != null) {
viewprev.setAlpha(0.5f);
//ImageView imageView=viewprev.findViewById(R.id.tick);
//Resource(R.drawable.ic_save_black_24dp);
if(Build.VERSION.SDK_INT>=23) {
viewprev.setForeground(getResources().getDrawable(R.drawable.ic_check_white_24dp));
}
}
else
Toast.makeText(getContext(), "Error Occured", Toast.LENGTH_SHORT).show();
}
}
#Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.context_action_bar, menu);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
int del_count = 0;
switch (menuItem.getItemId()) {
case R.id.delete_action_bar:
for (String sel : sel_tab2_images) {
file = new File(sel);
if (file.exists()) {
file.delete();
tab2_images.remove(sel);
if (tab1_fragment.tab1_images.contains(sel)) {
tab1_fragment.tab1_images.remove(sel);
}
if (tab3_fragment.spam_list.contains(sel)) {
tab3_fragment.spam_list.remove(sel);
}
UpdateGallery(sel);
/*
int pos_1=tab2_images.indexOf(sel);
tab2_images.remove(sel);
adapter.notifyDataSetChanged();
viewprev=mgridview.getChildAt(pos_1);
viewprev.setBackgroundColor(Color.WHITE);
*/
del_count++;
}
}
adapter = new ImageAdapterGridView(getContext());
mgridview.setAdapter(adapter);
Toast.makeText(getContext(), del_count + " images deleted", Toast.LENGTH_SHORT).show();
sel_count = 0;
sel_tab2_images.clear();
actionMode.finish();
break;
}
return true;
}
#Override
public void onDestroyActionMode(ActionMode actionMode) {
}
});
return view;
}
Below is the java code for Grid View. I also tried #overriding getItem and getItemId methods but it's not working. This is my first app and I'm still learning. Please help me to fix this!
public class ImageAdapterGridView extends BaseAdapter {
private Context mcontext;
public ImageAdapterGridView(Context c) {
mcontext = c;
}
public int getCount() {
return tab2_images.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertview, ViewGroup parent) {
ImageView imageView;
if(convertview==null){
imageView= new ImageView(mcontext);
imageView.setLayoutParams(new GridView.LayoutParams(280,320));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(16,16,16,16);
}
else{
imageView=(ImageView)convertview;
}
Glide.with(tab2_fragment.this).load("file://" + tab2_images.get(position))
.diskCacheStrategy(DiskCacheStrategy.RESULT)
.skipMemoryCache(true)
.into(imageView);
return imageView;
}
}
#Override
public void onResume() {
super.onResume();
adapter = new ImageAdapterGridView(getContext());
mgridview.setAdapter(adapter);
}
}
You're relying on view in a list. In list views are being reused as they get off screen. So to get this done you have to declare a boolean isSelected per each item (so the best place to do this is in your item model which holds all each grid properties) and make it true as the user select your grid item and always update your view based on that boolean not the view.
Currently your model is holding just the url of your images. If you need to save the isSelected boolean there you should build your model object like:
public class ImageModel {
public String url;
public boolean isSelected;
}
And then use the field objects in your grid list:
public void onItemCheckedStateChanged(ActionMode actionMode, int i, long l, boolean b) {
ImageModel image = tab2_images.get(i);
if (image.isSelected) {
// Image is selected you need to deselect it
} else {
// Image is not selected you need to select it
}
}
In your adapter:
public View getView(int position, View convertview, ViewGroup parent) {
// Your view construction code
ImageModel image = tab2_images.get(i);
if (image.isSelected) {
// Show your check image view
} else {
// Image is not selected yet, hide your check image view
}
}
I hope this helps but grid view is considered deprecated, try to consider using RecyclerView which is more efficient and fast.

Highlighting the list rows

I'm having an issue with a long press on a row to get highlighted.
I looked over how to handle the single click to lead to another activity and a long press to get the contextual action bar comes up. I decided to switch the listview choice by ListView.CHOICE_MODE_MULTIPLE_MODAL and ListView.CHOICE_MODE_NONE to let the built in android methods to do their work on selected rows. The single click is working as it is intended.
The multiple modal is working and the contextual action bar is showing the number of the notes selected yet the rows aren't highlighted. I have checked the theme and it doesn't work either for Theme.AppCompat and Theme.AppCompat.Light.
Here are the code -
Classes
MainActivity extends AppCompatActivity {}
ObjectListFragment extends ListFragment implements
AdapterView.OnItemLongClickListener, AbsListView.MultiChoiceModeListener {}
NoteListAdapter extends ArrayAdapter<NoteTO> {}
initialization of listeners and other objects in ListFragment
//get the data
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
LogUtils.LOGD(getClass().getSimpleName(), "onViewCreated");
ListView listView = getListView();
listView.setOnItemLongClickListener(this);
listView.setMultiChoiceModeListener(this);
int choiceMode = (savedInstanceState == null ? ListView.CHOICE_MODE_NONE : savedInstanceState.getInt(STATE_CHOICE_MODE));
listView.setChoiceMode(choiceMode);
}
Action mode methods, long press method and private methods
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
l.setItemChecked(position, true);
NoteTO note = (NoteTO) l.getItemAtPosition(position);
Intent i = new Intent(getActivity(), NoteActivity.class);
NotePreferences.setNote(getActivity(), note);
AppPreferences.setActionFlag(getActivity(), AppConstants.ACTION_UPDATE);
startActivityForResult(i, 1);
}
#Override
public void onItemCheckedStateChanged(ActionMode actionMode, int i, long l, boolean b) {
if (mActiveMode != null) {
updateSubtitle(actionMode);
}
}
#Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.contextual_menu, menu);
this.mActiveMode = actionMode;
updateSubtitle(mActiveMode);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
boolean result = performActions(menuItem);
updateSubtitle(mActiveMode);
return(result);
}
#Override
public void onDestroyActionMode(ActionMode actionMode) {
if (mActiveMode != null) {
mActiveMode = null;
getListView().setChoiceMode(ListView.CHOICE_MODE_NONE);
getListView().setAdapter(getListView().getAdapter());
}
}
public boolean performActions(MenuItem item) {
List<NoteTO> list = listAdapter.getList();
Set<Integer> positionSet = listAdapter.getCurrentCheckedPosition();
Integer[] positions = listAdapter.getCurrentCheckedPosition().toArray(new Integer[positionSet.size()]);
if(item.getItemId() == R.id.item_delete) {
List<NoteTO> notesToBeDeleted = new ArrayList<NoteTO>();
String text = "";
for(int i = 0; i < positions.length; i++) {
NoteTO note = (NoteTO) list.get(positions[i]);
notesToBeDeleted.add(note);
}
task = new ObjectListFragment.deleteNotesTask().execute(notesToBeDeleted);
return true;
}
else if(item.getItemId() == R.id.item_share) {
String text = "";
for(int i = 0; i < positions.length; i++) {
NoteTO note = (NoteTO) list.get(positions[i]);
text = text + note.getBody() + "\r\n"+ "\r\n";
}
shareNotes(text);
return true;
}
return false;
}
private void updateSubtitle(ActionMode mode) {
mode.setSubtitle("(" + getListView().getCheckedItemCount() + ")");
}
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
getListView().setItemChecked(position, true);
return(true);
}
Adapter - there isn't any code that overwrite up the selectors of the rows.
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
View row = super.getView(position, convertView, parent);
NoteHolder holder = (NoteHolder) row.getTag();
if(holder == null) {
holder = new NoteHolder(row);
row.setTag(holder);
}
NoteTO note = getItem(position);
if(mSettings.getListSingleLine() == 0) {
holder.textviewNote.setEllipsize(null);
holder.textviewNote.setSingleLine(false);
}
else {
holder.textviewNote.setEllipsize(TextUtils.TruncateAt.END);
holder.textviewNote.setSingleLine();
}
holder.textviewNote.setText(note.getBody());
holder.textviewNote.setTextSize(getmSettings().getListViewTextSize());
holder.textviewNote.setTypeface(getmSettings().getGeneralFontStyleTypeFace());
//holder.textviewNote.setBackgroundColor(ContextCompat.getColor(context, android.R.color.background_dark));
//holder.textviewNote.setBackground(selector);
//arraylist has containers to get the boolean of the position
if (mSelection.get(position) != null) {
//holder.textviewNote.setSelected(true);
//holder.textviewNote.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_blue_light));
//holder.textviewNote.setTextColor(mSettings.getListViewTextColor());
//holder.textviewNote.setBackground(selector);
}
return row;
}
I found the solution, I overlooked the styles where will get the highlighted rows -
<item name="android:background">?android:attr/activatedBackgroundIndicator</item>
Thanks to
Highlight custom listview item when long click
and
Commonsware - The Busy Coder's Guide to Android Development

Android: Change spinner items without closing the spinner

I am trying to implement a "More" button for a spinner. Basically I want to show only a few items at first and when the user clicks the last item ("More...") the spinner will change and show all items.
So the feature I need here, is a way to dynamically change spinner items without closing the spinner. I've managed to do everything but the last part. Every time I change the items the spinner automatically closes (without losing focus).
The only workaround I thought was to use mSpinner.performClick() to immediately open the spinner after it closes. Of course, that's not good enough because I get this quick close-reopen effect. Not cool.
I created a custom spinner class that manages the logic:
public class ReservationStatusSpinner extends Spinner {
// --------------------------------------------------
// State
// --------------------------------------------------
private final String mMoreStatus;
private OnItemSelectedListener mUserListener;
private ArrayAdapter<String> mAdapter;
private boolean mOpenInitiated = false;
// --------------------------------------------------
// Interfaces
// --------------------------------------------------
private interface OnSpinnerEventsListener {
// Not needed, but may be needed in the future -> void onSpinnerOpened();
void onSpinnerClosed();
}
private OnSpinnerEventsListener mOnSpinnerEventsListener;
public interface OnStatusSelectedListener {
void onStatusSelected(String status);
}
private OnStatusSelectedListener mOnStatusSelectedListener;
// --------------------------------------------------
// Construction/Initialization
// --------------------------------------------------
public ReservationStatusSpinner(Context context) {
super(context);
mMoreStatus = getContext().getResources().getString(R.string.status_more);
init();
}
public ReservationStatusSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
mMoreStatus = getContext().getResources().getString(R.string.status_more);
init();
}
private void init() {
// Add listener
super.setOnItemSelectedListener(new OnItemSelected());
mOnSpinnerEventsListener = new OnSpinnerEventsListener() {
#Override
public void onSpinnerClosed() {
filterAndSelect();
}
};
}
// --------------------------------------------------
// Overridden methods
// --------------------------------------------------
#Override
public boolean performClick() {
// register that the Spinner was opened so we have a status
// indicator for the activity(which may lose focus for some other
// reasons)
mOpenInitiated = true;
return super.performClick();
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// mSpin is our custom Spinner
if (mOpenInitiated && hasFocus) {
performClosedEvent();
}
}
#Override
public void setOnItemSelectedListener(OnItemSelectedListener l) {
mUserListener = l;
}
// --------------------------------------------------
// Private methods
// --------------------------------------------------
private static ArrayList<String> getAllStatuses(Context context) {
ArrayList<String> items = new ArrayList<>();
CharSequence[] statusesCSArray = context.getResources().getTextArray(R.array.reservation_status);
for (CharSequence cs : statusesCSArray)
items.add(cs.toString());
return items;
}
private void performClosedEvent() {
mOpenInitiated = false;
if (mOnSpinnerEventsListener != null) {
mOnSpinnerEventsListener.onSpinnerClosed();
}
}
private void filterAndSelect() {
List<String> items = filterStatuses((String)getSelectedItem(), mMoreStatus);
setItems(items);
setSelection(0);
}
// --------------------------------------------------
// Public methods
// --------------------------------------------------
public void setStatus(String status) {
// Find status in adapter
int pos = -1;
for (int i = 0; i < mAdapter.getCount(); ++i) {
if (mAdapter.getItem(i).equals(status)) {
pos = i;
break;
}
}
if (pos != -1)
setSelection(pos);
}
public void setAdapter(ArrayAdapter<String> adapter) {
super.setAdapter(adapter);
mAdapter = adapter;
}
public void setOnStatusSelectedListener(OnStatusSelectedListener l) {
mOnStatusSelectedListener = l;
}
public void setItems(List<String> items) {
mAdapter.clear();
mAdapter.addAll(items);
mAdapter.notifyDataSetChanged();
}
// --------------------------------------------------
// Utilities
// --------------------------------------------------
public static ArrayList<String> filterStatuses(String selectedStatus, String moreStatus) {
ArrayList<String> list = new ArrayList<>(DataUtilities.filterStatuses(selectedStatus));
// Add selected status at start
list.add(0, selectedStatus);
// Append "More"
list.add(moreStatus);
return list;
}
// --------------------------------------------------
// Custom ItemSelectedListener for ReservationStatusSpinner
// --------------------------------------------------
private class OnItemSelected implements OnItemSelectedListener {
private String mPreviousStatus;
private boolean mMoreClicked = false;
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String more = getContext().getResources().getString(R.string.status_more);
String status = getSelectedItem().toString();
ArrayList<String> items = new ArrayList<>();
if (status.equals(more)) {
items.addAll(getAllStatuses(getContext()));
items.remove(mMoreStatus);
setItems(items);
//setStatus(mPreviousStatus);
mMoreClicked = true;
// Reopen spinner (it closes after changing data) (TODO: Fix this)
ReservationStatusSpinner.this.performClick();
} else if (!mMoreClicked) {
filterAndSelect();
}
if (!status.equals(more)) {
if (mUserListener != null)
mUserListener.onItemSelected(parent, view, position, id);
if (mOnStatusSelectedListener != null)
mOnStatusSelectedListener.onStatusSelected(status);
}
mPreviousStatus = status;
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
if (mUserListener != null)
mUserListener.onNothingSelected(parent);
}
}
}
and a custom adapter:
public class ImageSpinnerAdapter extends ArrayAdapter<String> {
private LayoutInflater mInflater;
public ImageSpinnerAdapter(Context context, int textViewResourceId, List<String> titles) {
super(context, textViewResourceId, titles);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getDropDownView(int position, View convertView, #NonNull ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.row_image_spinner_dropdown, parent, false);
} else {
view = convertView;
}
ImageView icon = (ImageView)view.findViewById(R.id.spinner_icon);
setIcon(icon, getItem(position));
TextView text = (TextView)view.findViewById(R.id.spinner_status_text);
text.setText(DataUtilities.addWhitespacesToStatus(getItem(position)));
view.setPadding(0, 0, 0, 0);
return view;
}
#NonNull
#Override
public View getView(int position, View convertView, #NonNull ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.row_image_spinner_view, parent, false);
} else {
view = convertView;
}
// Set icon
ImageView icon = (ImageView)view.findViewById(R.id.spinner_icon);
setIcon(icon, getItem(position));
view.setPadding(0, 0, 0, 0);
return view;
}
private void setIcon(ImageView icon, String status) {
// Make sure there are no whitespaces in status
status = DataUtilities.removeWhitespaceFromStatus(status);
// Get the correct image for each status
icon.setImageResource(DataUtilities.statusToIconResource(status));
}
}
In my Spinner, most of the work is done in the private class OnItemSelected at the end of the snippet.
At first I thought the problem was the convert view at my adapter (I wasn't using the convert view pattern at first) but as you can see I'm using it now.
The problem occurs on 2 different devices and my emulator so it's safe to assume that it is not a device specific problem.
Anyone have any ideas or any pointers?
You have to create custom dialog with MultiSelectListview and More button.On click of more button you have to add all elements to Listview and call notifyDataSetChanged() method.
Default Spinner can't simple way to load "More" items. But "More" button has no sense. If you have 30-50 items just load all to Spinner. For 50-150 items use own ListBox/RecyclerView based Spinner. If more 150 items user too hard search necessary one item. In last case useful to add "Search" functionality.
See MultiSelect Spinner for ideas.

Select multiple items in GridView

I am developing a gallery-like Activity. Everything runs fine but there is a key functionality which is missing and I couldn't find a decent answer or explanation. What I need is to give the user the ability to select multiple items by long clicking on them.
Here is the desired result:
You can clearly see the selected pics and the options in the ActionBar.
My setup is this:
1.I have a GridView in my XML:
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:columnWidth="90dp"
android:numColumns="auto_fit"
android:verticalSpacing="5dp"
android:horizontalSpacing="5dp"
android:stretchMode="columnWidth"
android:gravity="center"/>
2.It is attached to a class, which extends BaseAdapter and loads images using Picasso:
public class GalleryAdapter extends BaseAdapter {
Context mContext;
List<String> mDataset;
public GalleryAdapter(Context context, List<String> dataset) {
mContext = context;
mDataset = dataset;
}
#Override
public int getCount() {
return mDataset.size();
}
#Override
public Object getItem(int position) {
return mDataset.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if(convertView == null){
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(200,200));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8,8,8,8);
} else {
imageView = (ImageView) convertView;
}
//TODO: REMOVE INTEGER.VALUEOFF. IT'S MADE FOR MOCK
Picasso.with(mContext).load(Integer.valueOf(mDataset.get(position))).fit().into(imageView);
return imageView;
}
}
3.It is attached to the Activity:
//Get images paths
List<String> data = getImagesPath(this);
List<String> sortedData = new ArrayList<>();
for(String file : data){
sortedData.add(0, file);
}
GalleryAdapter adapter = new GalleryAdapter(this, sortedData);
mGallery.setAdapter(adapter);
From now on the struggle begins:
I tried making my item in the GridView to implement Checkable:
public class CheckableItem extends LinearLayout implements Checkable{
private boolean mIsChecked = false;
private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
public CheckableItem(Context context) {
super(context);
}
#Override
protected int[] onCreateDrawableState(int extraSpace) {
int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked())
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
return drawableState;
}
#Override
public void setChecked(boolean checked) {
mIsChecked = checked;
refreshDrawableState();
}
#Override
public boolean isChecked() {
return mIsChecked;
}
#Override
public void toggle() {
setChecked(!isChecked());
refreshDrawableState();
}
Then I load it in my XML and added .setChoiceMode(GridView.CHOICE_MODE_MULTIPLE) to my GridView in the Activity. Nothing Happened.
Added MultiChoiceModeListener:
class MultiChoiceListener implements GridView.MultiChoiceModeListener{
#Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
int selectCount = mGallery.getCheckedItemCount();
switch (selectCount) {
case 1:
mode.setSubtitle("One item selected");
break;
default:
mode.setSubtitle("" + selectCount + " items selected");
break;
}
}
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.setTitle("Select Items");
mode.setSubtitle("One item selected");
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
}
}
Then added it to my gridView .setMultiChoiceModeListener(newMultiChoiceListener()); but still no results.
Can someone propose a way to achieve this item selecting behavior in GridView? I'd like to use the native Android API, no 3rd party libraries.
I think Checkable and the rest that I see in your code is a bit of an overkill...When I want to implement something like that I just add another field in the class of the object that is shown in the GridView/ListView which stores the checked state of the object. example:
class Image {
Bitmap bm;
boolean isChecked=false;
public Image(Bitmap bm){
this.bm=bm;
}
public boolean isChecked(){
return isChecked;
}
public void toggleChecked(){
isChecked = !isChecked;
}
}
and feed an ArrayList to the adapter. an in the getView method of the adapter I do something like this;
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(images.get(position).isChecked()){
//show the overlay view that suggests the item is selected
}
else{
//hide the overlay view
}
}
finally in the onItemClickListener of the ListView
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
images.get(position).toggleChecked();
listView.getAdapter().notifyDataSetChanged();
}
});

Delete an item from Array List contact to Custom List View always delteing the latest one

I have a custom list view, contains delete button and spinner (the spinner contain A-E characters).
And I have an issue with deleting the true row from my custom list view.
Custom list view code:
public class customListView extends BaseAdapter
{
public Activity context;
ArrayList<MyActivity.UserProperties> userPropertieses;
public String[] spinnerValues;
public LayoutInflater inflater;
public customListView(Activity context, ArrayList<MyActivity.UserProperties> userPropertieses, String[] spinnerArray)
{
super();
this.context = context;
this.userPropertieses = userPropertieses;
spinnerValues = spinnerArray;
this.inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() { return userPropertieses.size(); }
#Override
public Object getItem(int i) { return null; }
#Override
public long getItemId(int i) { return 0; }
class ViewHolder
{
Button btnRemove;
Spinner spinner;
}
#Override
public View getView(final int i, View view, ViewGroup viewGroup)
{
final ViewHolder holder;
if (view == null)
{
holder = new ViewHolder();
view = inflater.inflate(R.layout.custom_layout, null);
holder.spinner = (Spinner) view.findViewById(R.id.spinner);
holder.btnRemove = (Button) view.findViewById(R.id.bu_Remove);
// populate spinner
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>
(view.getContext(), android.R.layout.simple_spinner_item, spinnerValues);
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.spinner.setFocusable(true);
holder.spinner.requestFocus();
holder.spinner.setAdapter(dataAdapter);
view.setTag(holder);
// remove user implementation
holder.btnRemove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.i("custom list view debug", "i = " + i); // debug. verify i value is correct
((MyActivity) context).deleteUser(i);
}
});
}
else
holder = (ViewHolder) view.getTag();
return view;
}
}
And my main activity code looks like this:
public class MyActivity extends Activity
{
ListView listView;
ArrayList<UserProperties> userProperties = new ArrayList<UserProperties>();
customListView adapter;
SensorManager sensorManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
for (int i = 0; i<5; i++) {
userProperties.add(new UserProperties());
}
listView = (ListView) findViewById(R.id.listView);
String[] spinnerValues = new String[] {"A", "B", "C", "D", "E"};
adapter = new customListView(MyActivity.this, userProperties, spinnerValues);
listView.setAdapter(adapter);
}
public void deleteUser (int index)
{
Log.i("debug", "Removing item " + index); // the index is really true and the true node deleting from the ArrayList but somehow the latest delete from the UI
userProperties.remove(index);
adapter.notifyDataSetChanged();
}
}
When I click on the Remove button deleteUser method called with the right index. but although the right node deleting from userProperties ArrayList somehow after notiftDataSetChanged is still alive
and the latest node delete.
So, How can I delete the right node/row (from the ArrayList and UI...)
Thank you!
EDIT:
Just to be clear, i variable contain true index. The true node deleted from the ArrayList. but something append after I called notify method.
I prefer to stay with BaseAdapter and not implement ArrayAdapter. Thank you!
EDIT 2:
After more debugging I found out my question was wrong. the true row really deleted just spinner values somehow update their values. I cannot close the question because it already answered. Thanks.
((MyActivity) context).deleteUser(i);
This line will always delete the first value from the ListView
You can use CAB (contextual action bar)
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;
}
}
}
Another way
adapter = new MyListAdapter(this);
lv = (ListView) findViewById(android.R.id.list);
lv.setAdapter(adapter);
lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
AlertDialog.Builder adb=new AlertDialog.Builder(MyActivity.this);
adb.setTitle("Delete?");
adb.setMessage("Are you sure you want to delete " + position);
final int positionToRemove = position;
adb.setNegativeButton("Cancel", null);
adb.setPositiveButton("Ok", new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
MyDataObject.remove(positionToRemove);
adapter.notifyDataSetChanged();
}});
adb.show();
}
});
getView(final int i,
Do not make i final. You did that to use i in onClick(). But that is not possible. So remove the final. Add:
holder.btnRemove.setTag(i);
And in onClick:
int position = v.getTag();
..deleteUser(position);
Maybe you have to cast something somewhere..
Remark: You have to set the tag always. So do it just before return view;.
Please do not use an i for position.

Categories

Resources