Android : context menu item click causes variables to change state - android

I'm currently trying to implement a context menu for my app, there is a list of tasks and when the user long-clicks on one, a context menu should appear giving the user some options depending on the item he selected.
Implementing the menu itself wasn't too hard and my implementation gives the result I expect :
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if(v.getId() == R.id.list){ //check if the click comes from the listview
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
menuChoosenTask = taskAdapter.getItem(info.position); //retrive the choosen task
Log.e("CreateMenu", "Choose item with name " + menuChoosenTask.getTaskName());
menu.setHeaderTitle(menuChoosenTask.getTaskName());
int options = 3;
if(menuChoosenTask.getTypeOfTask() == WeeklyTask.TYPE_TASK_WORK) options = 4;
for(int i = 0; i < options; i++){
menu.add(Menu.NONE, i, i, menuOptions[i]);
}
}
}
But for handling user clicks, the trouble comes and weird things happen :
#Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
final int menuItemIndex = item.getItemId();
menuChoosenTask = taskAdapter.getItem(info.position);
int choosenTaskId = menuChoosenTask.getId();
String selectedOption = menuOptions[menuItemIndex];
if(selectedOption.equals(getString(R.string.cm_option_edit))){
//DO STUFF
}
if(selectedOption.equals(getString(R.string.cm_option_delete_for_everyday))){
//DO STUFF
}
if(selectedOption.equals(getString(R.string.cm_option_delete_for_this_day)){
//DO STUFF
}
return true;
}
The problems happens when I try to retrieve the menuChoosenTask, for some reason the taskAdapter may reduce it's size, that is, from what i've seen in the debugger : When creating the menu taskAdapter has size 4 and info.position is 3 so no problem, but when I click, info.position is still 3 but now taskAdapter has size 2.
So I first tried to work around this by setting menuChoosenTask as a global variable and just set it's state when the menu is created and re-use it when the user selects an option, but for some reasons the variable loses it's state when an option is selected and becomes null.
My question is : How can I retrieve the object and why do these weird things happen?
EDIT :
I managed to narrow down the problem a bit : The 2 functions you see are in a class called DayFragment and one of it's variable is set by the children class when called, so basically what happens is that the first method (menu creation) is called from a child (let's say the second, and from what I've seen: the one on my screen) but the second method (option selection) is called from another one , here is the full code of the class plus one of its subclasses :
DayFragment.java:
public abstract class DayFragment extends Fragment {
protected int DAY_ID = 0;
protected List<WeeklyTask> tasks;
protected TaskAdapter taskAdapter;
protected MainViewModel mainViewModel;
protected AppDatabase mDB;
protected String[] menuOptions = {"Edit", "Delete for this day", "Delete for everyday", "To-Do's"};
protected WeeklyTask menuChoosenTask;
public DayFragment(){}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.tasks_list, container, false);
setDayId();
tasks = new ArrayList<>();
taskAdapter = new TaskAdapter(getActivity(), tasks);
mDB = AppDatabase.getInstance(getContext());
ListView listView = rootView.findViewById(R.id.list);
listView.setAdapter(taskAdapter);
registerForContextMenu(listView);
mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
setupFAB( (FloatingActionButton) rootView.findViewById(R.id.fab));
setupViewModel();
return rootView;
}
public abstract void setupFAB(FloatingActionButton fab);
protected abstract void setDayId();
public void setupViewModel() {
mainViewModel.getTasks().removeObservers(this);
mainViewModel.getTasks().observe(this, new Observer<List<WeeklyTask>>() {
#Override
public void onChanged(#Nullable List<WeeklyTask> weeklyTasks) {
taskAdapter.clear();
int id = 0;
WeeklyTask task;
for(int i = 0; i < weeklyTasks.size(); i++){
task = weeklyTasks.get(i);
if(task.getDays()[DAY_ID]){ // IF should be shown this day
taskAdapter.insert(task, id);
id++;
}
}
taskAdapter.notifyDataSetChanged();
}
});
}
public boolean atLeastOneTrue(boolean[] arr){
boolean out = false;
for(int i = 0; i < arr.length; i++){
out = out | arr[i];
}
return out;
}
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if(v.getId() == R.id.list){ //check if the click comes from the listview
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
menuChoosenTask = taskAdapter.getItem(info.position);
Log.e("CreateMenu", "Choose item with name " + menuChoosenTask.getTaskName() + " for day " + DAY_ID);
menu.setHeaderTitle(menuChoosenTask.getTaskName());
int options = 3;
if(menuChoosenTask.getTypeOfTask() == WeeklyTask.TYPE_TASK_WORK) options = 4;
for(int i = 0; i < options; i++){
menu.add(Menu.NONE, i, i, menuOptions[i]);
}
}
}
#Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
Log.e("menuClicked", "menu for day " + DAY_ID + " clicked on item no " + info.position);
final int menuItemIndex = item.getItemId();
menuChoosenTask = taskAdapter.getItem(info.position);
int choosenTaskId = menuChoosenTask.getId();
String selectedOption = menuOptions[menuItemIndex];
if(selectedOption.equals(getString(R.string.cm_option_edit))){
//DOSTUFF
}
if(selectedOption.equals(getString(R.string.cm_option_delete_for_everyday))){
//DOSTUFF
}
if(selectedOption.equals(getString(R.string.cm_option_delete_for_this_day))){
//DOSTUFF
}
return true;
}
}
One of its children classes (there are 7 of them) :
public class MondayFragment extends DayFragment {
public static final int DAY_ID = 0;
public void setDayId(){
super.DAY_ID = DAY_ID;
}
}
All these fragments are shown in a PagerAdapter (coupled with a view pager) so I 'kinda' get why this happens but I have no idea how to prevent it.

Ok, well I just spent an hour and a half googling stuff and I finally found a solution :
Wrong fragment in ViewPager receives onContextItemSelected call
As I didn't fully understand what went wrong it took me really long to find it. So my Bad and sorry for all the trouble

Related

How do I get details of which item in a RecyclerView was clicked to generate a context menu?

I'm using a FirebaseRecyclerView to show a list of 'Charts' (defined in the app) in a Fragment in my app. The items in the list can be long-clicked to pop up a menu with options.
The difficulty I have is that when the user clicks on an item in the pop up menu, I can't get the id of the item that was clicked. I've included the relevant parts of ChartListFragment below, and marked where I'm having a problem.
public abstract class ChartListFragment extends Fragment {
private FirebaseRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
private RecyclerView mRecycler;
public ChartListFragment() {}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View rootView = inflater.inflate(
R.layout.fragment_charts_list, container, false);
mRecycler = (RecyclerView) rootView.findViewById(R.id.charts_list);
mRecycler.setHasFixedSize(true);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LinearLayoutManager mManager = new LinearLayoutManager(getActivity());
mManager.setReverseLayout(true);
mManager.setStackFromEnd(true);
mRecycler.setLayoutManager(mManager);
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
Query chartsQuery = getQuery(mDatabase);
mAdapter = new FirebaseRecyclerAdapter<Chart, ChartViewHolder>(Chart.class, R.layout.item_chart,
ChartViewHolder.class, chartsQuery) {
#Override
protected void populateViewHolder(final ChartViewHolder viewHolder, final Chart model, final int position) {
final DatabaseReference chartRef = getRef(position);
final String chartKey = chartRef.getKey();
registerForContextMenu(viewHolder.itemView);
viewHolder.bindToPost(model);
}
};
mRecycler.setAdapter(mAdapter);
}
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (v.getId()==R.id.chart_item) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.menu_long_press_chart_name, menu);
}
}
#Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
// AT THIS POINT I THINK I SHOULD BE ABLE TO GET THE POSITION OF THE ITEM
// THAT WAS CLICKED BY DOING SOMETHING LIKE
// int chartPos = info.position;
// Chart chartClicked = mAdapter.getItem(chartPos);
// BUT info IS NULL, SO info.position CAUSES A CRASH
switch(item.getItemId()) {
case R.id.edit:
// Show Edit Activity
return true;
case R.id.delete:
// Show confirmation message
return true;
default:
return super.onContextItemSelected(item);
}
}
}
My solution has a lot of moving parts, but overall the concept is pretty simple: use an Intent to pass the data you want. You can't rely on the ContextMenu.ContextMenuInfo menuInfo parameter, because this is only present when your context menu is attached to a ListView or GridView.
The first step is you need to get all information you will eventually want (this could be position or it could be something more direct) into your ViewHolder. You need this because you can call RecyclerView.getChildViewHolder() in onCreateContextMenu in order to access the data associated with whichever item you clicked on.
The next step is to attach this data to the context menu using an Intent. In your onCreateContextMenu() implementation, after you inflate your menu, you can write something like this:
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
...
MyViewHolder holder = (MyViewHolder) recycler.getChildViewHolder(v);
int position = holder.position; // or any custom data you want, set up in onBindViewHolder()
Intent data = new Intent();
data.putExtra("position", holder.position);
menu.findItem(R.id.your_item_here).setIntent(data);
menu.findItem(R.id.your_other_item_here).setIntent(data);
}
Finally, in your onContextItemSelected() implementation, you can access this Intent and get your data out of it:
#Override
public boolean onContextItemSelected(MenuItem item) {
int position = item.getIntent().getIntExtra("position", -1);
// do something with position
return true;
}

android get listview item by contextmenu

In my application I'm register ContextMenu on Listview and I want to get clicked Listview item by context menu. For example if I have two row in list view with this structure:
public class StructReceiveSms{
public int userID;
public String username;
}
my adapter can be show username in listview. now i'm in below code can define conext menu on list view:
public class FragmentSmsReceiveMaster extends Fragment {
private static final Boolean DEBUG = true;
public ArrayAdapter adapter;
private ArrayList<StructReceiveSms> receiveSmsArray;
.
.
.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
.
.
.
smsView = (ListView) view.findViewById(R.id.listView);
smsView.setAdapter(adapter);
registerForContextMenu(smsView);
.
.
.
}
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
String[] menuItems = getResources().getStringArray(R.array.SmsMasterContextMenu);
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();
int menuItemIndex = item.getItemId();
String listItemName = adapter.getItem(info.position) + "";
/* GET CLICKED LISTVIEW ITEM AFTER CHOOSE CONTEXTMENU MENU ITEMS */
Toast.makeText(G.currentActivity, listItemName, Toast.LENGTH_SHORT).show();
return true;
}
}
Now after click on context menu items I can get which clicked by user by menuItemIndex but I can not get which Listview's items in onContextItemSelected function. for example ater opening context menu on first item I can get userID and username and show it. How to do this, thanks
Since your Adapter's data list consists of StructReceiveSms objects, the adapter.getItem(info.position) call in onContextItemSelected() will return the list item the context menu was opened for, but it will need to be cast to the StructReceiveSms type. From this, you can get the userID and username you want.
public boolean onContextItemSelected(MenuItem item)
{
...
StructReceiveSms listItem = (StructReceiveSms) adapter.getItem(info.position);
String selectedName = listItem.username;
int selectedId = listItem.userID;
...
}
This is assuming you've not overridden the getItem() method of the Adapter to return something else, but I imagine you would've shown that if you had.

Weird bug in my databases causing changes when none should happen

I think the easier way to describe my bug is by showing it in a video. But the general idea is that I use a database to keep track of which items in a list of checkboxes is checked off. Whenever I click on an item and activate that list's action mode, it immediately forget those changes, in spite of them having already made their way to the database. What makes this worse is when multiple items become afflicted with this bug, they end up forming a strange bit of circular logic, wherein one item in the database will become checked and the other will become unchecked, and the two will continue this in a cycle as actions are done on the list due to the checkboxes constantly cycling between a checked state and an unchecked state and thus triggering the checkbox's onCheckedChangedListener. There is no code in onListItemClick() that should be causing these changes to happen on either a list level or database level, and no code in onPrepareActionMode or onCreateActionMode that would reload the CursorLoader or manipulate the selected item. Can anyone help me figure out what's going on to cause this? (My implementation can be found in the video, but I also included it below.)
public class TaskCursorAdapter extends ResourceCursorAdapter{
public View newView(Context context, Cursor cursor, ViewGroup parent){
final LayoutInflater inflater = LayoutInflater.from(context);
final long assignedID = cursor.getInt(cursor.getColumnIndex(NagTasksDatabaseHelper.ID));
View v = inflater.inflate(layout, parent, false);
CheckBox taskCheckBox = (CheckBox)v.findViewById(R.id.taskCheckBox);
TextView taskText = (TextView)v.findViewById(R.id.taskTitle);
TextView taskNote = (TextView)v.findViewById(R.id.taskNote);
if (cursor.getInt(cursor.getColumnIndex(NagTasksDatabaseHelper.CHECKED))==1){
taskText.setPaintFlags(taskText.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
taskNote.setPaintFlags(taskNote.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
taskCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//Current method:
AlertDialog.Builder builder = new AlertDialog.Builder(buttonView.getContext());
builder.setCancelable(true);
Log.i("TaskCursorAdapter", "CheckBox assignedID="+assignedID);
if (assignedID!=-1) {
NagTasksDatabaseHelper helper = new NagTasksDatabaseHelper(buttonView.getContext());
if (isChecked) {
helper.checkOffTask(assignedID);
//builder.setMessage("Item "+assignedID+" checked");
} else {
helper.uncheckTask(assignedID);
//builder.setMessage("Item "+assignedID+" unchecked");
}
helper.close();
} else {
builder.setMessage("No ID found. Try something else.");
builder.create().show();
}
}
});
return v;
}
public void bindView(View v, Context context, Cursor cursor){
CheckBox taskCheckBox = (CheckBox)v.findViewById(R.id.taskCheckBox);
TextView taskText = (TextView)v.findViewById(R.id.taskTitle);
TextView taskNote = (TextView)v.findViewById(R.id.taskNote);
taskCheckBox.setChecked(cursor.getInt(cursor.getColumnIndex(NagTasksDatabaseHelper.CHECKED))==1);
//final long assignedID = v.getId();
//v.setClickable(true);
//LinearLayout holder = (LinearLayout)v.findViewById(R.id.container);
//holder.setClickable(true);
//IdFromDb = c.getInt(c.getColumnIndex(NagTasksDatabaseHelper.ID));
taskText.setText(cursor.getString(cursor.getColumnIndex(NagTasksDatabaseHelper.TASK)));
taskNote.setText(cursor.getString(cursor.getColumnIndex(NagTasksDatabaseHelper.NOTE)));
//isCheckedFromDb = c.getInt(c.getColumnIndex(NagTasksDatabaseHelper.CHECKED));
//cursor = c;
}
public class TaskListFragment extends SherlockListFragment implements ActionMode.Callback {
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context_menu_single_choice, menu);
mode.setTitle(R.string.activeActionMode);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
public void onListItemClick(ListView l, View v, int position, long id){
//super.onListItemClick(l, v, position, id);
Log.i("NagTaskListFragment", "User clicked on "+id);
if (mActionMode == null) {
idForActionMode=id;
mActionMode = getSherlockActivity().startActionMode(this);
}
if (l.getSelectedView() == v) {
v.setSelected(false);
mActionMode.finish();
mActionMode = null;
idForActionMode = -1;
} else if (l.getSelectedView() != null) {
l.getSelectedView().setSelected(false);
v.setSelected(true);
idForActionMode = id;
//onPrepareActionMode(mActionMode, mActionMode.getMenu());
//Log.i("NagTaskListFragment", "selectedItemID = "+l.getSelectedItemId());
} else {
v.setSelected(true);
idForActionMode = id;
//Log.i("NagTaskListFragment", "selectedItemID = "+l.getSelectedItemId());
}
}
So, I moved the setChecked() method to newView and that seemed to have fixed the issue. I'm not sure if that's the correct fix, but it works for now.

Multiple selection in custom ListView with CAB

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 { /* ... */ }

How to stop long clicks being handled as short click when no context menu shown

I have a list view which has been registered for a context menu. For some items within the list a context menu is not applicable. In these cases I just don't inflate a menu in the onCreateContextMenu method.
Unfortunately this means that when items that don't display a context menu are long-clicked Android then handles this as a short-click (presumably because the context menu would normally return true to say that the long-click event has been handled).
This results in an inconsistent behaviour in the listview - some items show a context menu when you long click them - others don't and then perform the default click behaviour. How can I ensure that even items that don't display a context menu consume the long click so that the onItemClick method isn't called?
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
Playable playable = (Playable) info.targetView.getTag(R.id.playable);
if (playable != null && !(playable instanceof AutoRadioStation) && !(playable.getId().equals(Playlist.AUTOMATIC_PLAYLIST))) {
v.setTag(R.id.playable, playable); // This copies the tag so that it is contained within the view used for the menu.
Drawable stationImage = (Drawable) ((ImageView) info.targetView.findViewById(R.id.artwork)).getDrawable().getConstantState().newDrawable();
menu.setHeaderTitle(playable.getName());
menu.setHeaderIcon(stationImage);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.saved_context_menu, menu);
}
}
I had a similar issue and I ended up using a Dialog instead of a context menu.
My activity implements OnItemLongClickListener, and I show it from onLongItemClick() if the condition is satisfied.
I finally got around to implementing a version of NathanZ solution. There didn't seem to be much stuff out there about turning a contextMenu into a DialogFragment so I'll paste most of my solution here.
Implementing an onLongItemClick listener also meant that I was able to have a long-click event that didn't require a menu within the listview. Unfortunately because you can't pass menus around to a dialog I had to reuse an existing ListViewElement type to store an id and a text string for each "menu" item in my listview.
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int item, long position) {
Playable playable = (Playable) view.getTag(R.id.playable);
//Switch haptic feedback off by default so if we don't handle the long click we don't vibrate
parent.setHapticFeedbackEnabled(false);
if (playable == null) {
// This must be a message bar so the only option is to update all saved content
updateAll();
parent.setHapticFeedbackEnabled(true);
} else {
if (!(playable instanceof AutoRadioStation) && !(playable.getId().equals(Playlist.AUTOMATIC_PLAYLIST))) {
Drawable drawable = (Drawable) ((ImageView) view.findViewById(R.id.artwork)).getDrawable().getConstantState().newDrawable();
showContextDialog(playable, drawable);
parent.setHapticFeedbackEnabled(true);
}
}
return true;
}
private void showContextDialog(Playable playable, Drawable drawable) {
FragmentManager fm = getActivity().getSupportFragmentManager();
final List<ListViewElement> array = new ArrayList<ListViewElement>();
array.add(new ListViewElement(R.id.menu_share, null, getString(R.string.share), true));
array.add(new ListViewElement(R.id.menu_delete, null, getString(R.string.delete), true));
ContextMenuDialog dialog = new ContextMenuDialog(drawable, playable.getName(), array, playable);
dialog.setOnItemClickListener(this);
dialog.show(fm, "Context Menu");
}
//Callback from the ContextMenuDialog class
#Override
public void onItemClickDialogFragment(int option, Playable playable) {
switch (option) {
case R.id.menu_delete :
// Perform delete actions
break;
case R.id.menu_share :
// Perform share actions
break;
}
}
public class ContextMenuDialog extends DialogFragment implements OnItemClickListener {
private Drawable drawableIcon;
private String title;
private List<ListViewElement> values;
private Playable playable;
private DialogFragmentOnItemClickListener listener;
public interface DialogFragmentOnItemClickListener {
void onItemClickDialogFragment(int option, Playable playable);
}
public void setOnItemClickListener(DialogFragmentOnItemClickListener listener) {
this.listener = listener;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Create the dialog without a title since the layout includes a customized title
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.MyDialogStyle);
}
public ContextMenuDialog(Drawable drawableIcon, String title, List<ListViewElement> values, Playable playable) {
this.drawableIcon = drawableIcon;
this.title = title;
this.values = values;
this.playable = playable;
}
public ContextMenuDialog(int drawableResource, String title, List<ListViewElement> values, Playable playable) {
this.drawableIcon = getResources().getDrawable(drawableResource);
this.title = title;
this.values = values;
this.playable = playable;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.context_menu, container, true);
TextView titleView = (TextView) view.findViewById(R.id.context_menu_title);
titleView.setText(title);
ImageView icon = (ImageView) view.findViewById(R.id.context_menu_artwork);
icon.setImageDrawable(drawableIcon);
ListView listView = (ListView) view.findViewById(R.id.context_menu_listview);
ContextMenuAdapter adapter = new ContextMenuAdapter(getActivity(), R.layout.context_menu_list_item, values);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
return view;
}

Categories

Resources