3

I want to make an app like this

batch contextual actions in an AbsListView

I have followed Google's guide to enable batch contextual actions in an AbsListView. In my case, I implemented the AbsListView in a Fragment because I want users be able to change layout, I have several layouts for the Activity (a ListView, a GridView, and another AbsListView subclass). I also implement the AbsListView.MultiChoiceModeListener in a separate class because I want to reuse it in several Fragments and Activities.

My problem is: when I long-press the list item, the contextual action bar doesn't show up. It responded to a click however (shows that the selector is working).

I have checked and tried-and-error my codes over and over again to make sure I follow everything on the guide correctly, have browsed many trouble-shootings on this topic, but still I can't figure out why the contextual action bar doesn't come up.

Here is one of the Fragments:

public class List extends Bookshelf {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_list, container, false);
        ListView list = (ListView) rootView.findViewById(R.id.book_list);
        list.setAdapter(new BookListAdapter(getActivity()));

        list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
        list.setMultiChoiceModeListener(new MultiChoiceCallback(list));

        return rootView;
    }

    private static class BookListAdapter extends BaseAdapter {
        private LayoutInflater mInflater;

        public BookListAdapter(Context context) {
            mInflater = LayoutInflater.from(context);
        }

        public int getCount() {
            return 100;
        }

        public Object getItem(int position) {
            return position;
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.view_item_book_1text, null);
                holder = new ViewHolder();
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            return convertView;
        }

        static class ViewHolder {
            TextView title;
            ImageView cover;
        }
    }
}

Here is the MultiChoiceModeListener (MultiChoiceCallback.java):

public class MultiChoiceCallback implements MultiChoiceModeListener {
    AbsListView usingView;

    public MultiChoiceCallback(AbsListView view) {
        usingView = view;
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
        case R.id.bookDetails:
            break;
        case R.id.category:
            break;
        case R.id.delete:
            break;
        }
        return false;
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        mode.getMenuInflater().inflate(R.menu.select_book, menu);
        return false;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }

    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
        final int checkedCount = usingView.getCheckedItemCount();
        switch (checkedCount) {
            case 0:
                mode.setTitle(null);
                break;
            case 1:
                mode.setTitle("1 item selected");
                break;
            default:
                mode.setTitle("" + checkedCount + " items selected");
                break;
        }
    }
}

The layout for the Fragment (layout/fragment_list.xml):

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/book_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</ListView>

The View list item that is inflated (layout/view_item_book_1text.xml):

<?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="51dp"
    android:background="@color/blue_when_clicked"
    android:clickable="true"
    android:focusable="true"
    android:layout_marginLeft="3dp"
    android:layout_marginRight="3dp"
    android:gravity="center_vertical"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/cover"
        android:contentDescription="@string/cover"
        android:layout_width="75dp"
        android:layout_height="45dp"
        android:layout_marginTop="3dp"
        android:layout_marginBottom="3dp"
        android:scaleType="fitCenter"
        android:src="@drawable/cover_02" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="3dp"
        android:text="A Book Title"
        android:textAppearance="?android:attr/textAppearanceMedium" />

</LinearLayout>

The color selector for each list item (color/blue_when_clicked.xml):

<selector xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:drawable="@drawable/color_light_blue" android:state_pressed="true"/>  <!-- pressed -->
    <item android:drawable="@drawable/color_light_gray" android:state_focused="true"/>  <!-- focused -->
    <item android:drawable="@drawable/color_light_gray" android:state_activated="true"/> <!-- selected -->
</selector>

Any help would be very appreciated!

Mohamad Sani
  • 63
  • 1
  • 7
  • Why dont you use Action mode, upon long press of a list item all you need to do is call invalidate upon action mode, you will same result as pictures you posted above. – Techfist Jul 21 '14 at 13:18

2 Answers2

3

I haven't tested it but at first look I think your problem is in your onCreateActionMode (ActionMode mode, Menu menu) method.

According to the documentation - you should return true in order to create ActionMode

true if the action mode should be created, false if entering this mode should be aborted.


The mechanism is the same as in onCreateOptionsMenu (Menu menu) method from Activity. The documentation says it in more clear way here:

You must return true for the menu to be displayed; if you return false it will not be shown.


EDIT:

Have you tried to just launch the code from tutoril mentioned by you? I've done a minimal implementation of ActionMode on ListView. There is even no menu resource inflated, just return true there and ActionMode will appear after long pressing.

final ListView listView = new ListView(getActivity());
listView.setAdapter(new BookListAdapter(getActivity()));
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        return false;
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // not even need to inflate anything in order to ActionMode to appear
        return true;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }
});

Code from adapter (modified your adapter):

private static class BookListAdapter extends BaseAdapter {
    private LayoutInflater mInflater;

    public BookListAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
    }

    public int getCount() {
        return 100;
    }

    public Object getItem(int position) {
        return position;
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = mInflater.inflate(android.R.layout.simple_list_item_1, null);
        }

        ((TextView)convertView).setText("TEST");
        return convertView;
    }
}


Screenshot of result - as you can see the ActionMode is present.

enter image description here

Maciej Ciemięga
  • 10,125
  • 1
  • 41
  • 48
  • Have tried, still not working... thanks anyway for your answer I'll change it anyway – Mohamad Sani Jul 21 '14 at 13:35
  • 1
    I've just run this code myself and it works just fine. Please see my updated answer. If this doesn't work for you - the problem is in other parts of your code. – Maciej Ciemięga Jul 21 '14 at 13:53
  • I struggled again with my codes and finally found the bug (see my answer below)! Thanks Maciej for your help trial, and you actually pointed out another bug regarding the return true – Mohamad Sani Jul 21 '14 at 20:14
0

Aha! Found the bug! It is because of the

android:clickable="true"
android:focusable="true"

in the layout/view_item_book_1text.xml

Sorry to bother everyone! Hope this becomes a troubleshooting note for someone else

Mohamad Sani
  • 63
  • 1
  • 7