onLongClick with Context Action Bar CAB not taking place, only onListItemClick performed, in tablet activity with ListFragment and DetailFragment

StackOverflow https://stackoverflow.com/questions/20304140

Frage

I have a ListFragment that is called from a ListActivity, with a dual fragment layout on a tablet. I have used the standard MasterDetail Fragment setup from Android Studio 0.3.6 API 19.

MyListFragment looks as follows:

import android.app.Activity;
import android.content.ContentUris;
import android.net.Uri;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.support.v4.widget.SimpleCursorAdapter;

import com.supascale.supascale.contentprovider.SupaScaleContentProvider;
import com.supascale.supascale.database.SupascaleDb;

/**
     * A list fragment representing a list of Animals. This fragment
     * also supports tablet devices by allowing list items to be given an
     * 'activated' state upon selection. This helps indicate which item is
     * currently being viewed in a {@link AnimalDetailFragment}.
     * <p>
     * Activities containing this fragment MUST implement the {@link Callbacks}
     * interface.
     *
     * You can create new ones via the ActionBar entry "Insert".
     * You can delete existing ones via a long press on the item.
     */


public class AnimalListFragment extends ListFragment implements
        android.support.v4.app.LoaderManager.LoaderCallbacks<Cursor>{

    private static final int ACTIVITY_CREATE = 0;
    private static final int ACTIVITY_EDIT = 1;
    private static final int DELETE_ID = Menu.FIRST + 1;

    private SimpleCursorAdapter adapter;
    protected Object mActionMode;
    private ActionMode.Callback mActionModeCallback;

    /**
     * The serialization (saved instance state) Bundle key representing the
     * activated item position. Only used on tablets.
     */
    private static final String STATE_ACTIVATED_POSITION = "activated_position";

    /**
     * The fragment's current callback object, which is notified of list item
     * clicks.
     */
    private Callbacks mCallbacks = sDummyCallbacks;


    /**
     * The current activated item position. Only used on tablets.
     */
    private int mActivatedPosition = ListView.INVALID_POSITION;

    /**
     * A callback interface that all activities containing this fragment must
     * implement. This mechanism allows activities to be notified of item
     * selections. NB AJW
     */
    public interface Callbacks {
        /**
         * Callback for when an item has been selected.
         */
        public void onItemSelected(Uri id);  
    }

    /**
     * A dummy implementation of the {@link Callbacks} interface that does
     * nothing. Used only when this fragment is not attached to an activity.
     */
    private static Callbacks sDummyCallbacks = new Callbacks() {
        @Override
        public void onItemSelected(Uri animalUri) {
        }
    };

    /**
     * Mandatory empty constructor for the fragment manager to instantiate the
     * fragment (e.g. upon screen orientation changes).
     */
    public AnimalListFragment() {
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       // super.onCreate(savedInstanceState);

        return inflater.inflate(R.layout.animal_list, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        String[] from = new String[] { SupascaleDb.KEY_Animal_ANIMALIDENTIFICATION };
        // Fields on the UI to which we map
        int[] to = new int[] { R.id.label };

        getLoaderManager().initLoader(0, null, this);
        adapter = new SimpleCursorAdapter(getActivity(), R.layout.animal_row, null, from,
                to, 0);

        setListAdapter(adapter);

        // Restore the previously serialized activated item position.
        if (savedInstanceState != null
                && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
            setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
        }



        mActionModeCallback = new ActionMode.Callback() {

            // Called when the action mode is created; startActionMode() was called
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                // Inflate a menu resource providing context menu items
                MenuInflater inflater = mode.getMenuInflater();
                inflater.inflate(R.menu.listmenu, menu);
                return true;
            }

            // Called each time the action mode is shown. Always called after onCreateActionMode, but
            // may be called multiple times if the mode is invalidated.
            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                return false; // Return false if nothing is done
            }

            // Called when the user selects a contextual menu item
            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                switch (item.getItemId()) {

                    case DELETE_ID:
                        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item
                                .getMenuInfo();
                        Uri uri = Uri.parse(SupaScaleContentProvider.CONTENT_URI_ANIMAL + "/"
                                + info.id);
                        getActivity().getContentResolver().delete(uri, null, null);
                        mode.finish(); // Action picked, so close the CAB
                        return true;
                    default:
                        return false;
                }
            }

            // Called when the user exits the action mode
            @Override
            public void onDestroyActionMode(ActionMode mode) {
                mActionMode = null;
            }
        };

        getView().setOnLongClickListener( new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                if (mActionMode != null) {
                    return false;
                }

                // Start the CAB using the ActionMode.Callback defined above
                mActionMode = view.startActionMode(mActionModeCallback);
                view.setSelected(true);
                return true;
            }
        });

    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Activities containing this fragment must implement its callbacks.
        if (!(activity instanceof Callbacks)) {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }

        mCallbacks = (Callbacks) activity;
    }

    @Override
    public void onDetach() {
        super.onDetach();

        // Reset the active callbacks interface to the dummy implementation.
        mCallbacks = sDummyCallbacks;
    }


    @Override
    public void onListItemClick(ListView listView, View view, int position, long id) {
        super.onListItemClick(listView, view, position, id);

        // Notify the active callbacks interface (the activity, if the
        // fragment is attached to one) that an item has been selected.

        //2013-11-26 AJW  Append the clicked item's row ID with the content provider Uri
        Uri animalUri = ContentUris.withAppendedId(SupaScaleContentProvider.CONTENT_URI_ANIMAL, id);
        // Send the event and Uri to the host activity
        mCallbacks.onItemSelected(animalUri);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mActivatedPosition != ListView.INVALID_POSITION) {
            // Serialize and persist the activated item position.
            outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
        }
    }


    /**
     * Turns on activate-on-click mode. When this mode is on, list items will be
     * given the 'activated' state when touched.
     */
    public void setActivateOnItemClick(boolean activateOnItemClick) {
        // When setting CHOICE_MODE_SINGLE, ListView will automatically
        // give items the 'activated' state when touched.

        getListView().setChoiceMode(activateOnItemClick
                ? ListView.CHOICE_MODE_SINGLE
                : ListView.CHOICE_MODE_NONE);
    }

    private void setActivatedPosition(int position) {
        if (position == ListView.INVALID_POSITION) {
            getListView().setItemChecked(mActivatedPosition, false);
        } else {
            getListView().setItemChecked(position, true);
        }

        mActivatedPosition = position;
    }

    /**
     *  Creates a new loader after the initLoader () call
     */
    //@Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        String[] projection = { SupascaleDb.KEY_ROWID, SupascaleDb.KEY_Animal_ANIMALIDENTIFICATION };
        CursorLoader cursorLoader = new CursorLoader(getActivity(),
                SupaScaleContentProvider.CONTENT_URI_ANIMAL, projection, null, null, null);
        return cursorLoader;
    }

    //@Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        adapter.swapCursor(data);
    }

    //@Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // data is not available anymore, delete reference
        adapter.swapCursor(null);
    }

}

My AnimalListActivity looks as follows:

/**
 * An activity representing a list of Animals. This activity
 * has different presentations for handset and tablet-size devices. On
 * handsets, the activity presents a list of items, which when touched,
 * lead to a {@link AnimalDetailActivity} representing
 * item details. On tablets, the activity presents the list of items and
 * item details side-by-side using two vertical panes.
 * <p>
 * The activity makes heavy use of fragments. The list of items is a
 * {@link AnimalListFragment} and the item details
 * (if present) is a {@link AnimalDetailFragment}.
 * <p>
 * This activity also implements the required
 * {@link AnimalListFragment.Callbacks} interface
 * to listen for item selections.
 */
public class AnimalListActivity extends FragmentActivity
        implements AnimalListFragment.Callbacks {

    /**
     * Whether or not the activity is in two-pane mode, i.e. running on a tablet
     * device.
     */
    private boolean mTwoPane;
    private SimpleCursorAdapter adapter;

    private static final int ACTIVITY_CREATE = 0;
    private static final int ACTIVITY_EDIT = 1;
    private static final int DELETE_ID = Menu.FIRST + 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animal_list);

        if (findViewById(R.id.animal_detail_container) != null) {
            // The detail container view will be present only in the
            // large-screen layouts (res/values-large and
            // res/values-sw600dp). If this view is present, then the
            // activity should be in two-pane mode.
            mTwoPane = true;

            // In two-pane mode, list items should be given the
            // 'activated' state when touched.
            ((AnimalListFragment) getSupportFragmentManager()
                    .findFragmentById(R.id.animal_list))
                    .setActivateOnItemClick(true);
        }



        // TODO: If exposing deep links into your app, handle intents here.
    }

    /**
     * Callback method from {@link AnimalListFragment.Callbacks}
     * indicating that the item with the given ID was selected.
     */
    @Override
    public void onItemSelected(Uri animalUri) {
       //String id = Long.toString(ContentUris.parseId(animalUri));
        if (mTwoPane) {
            // In two-pane mode, show the detail view in this activity by
            // adding or replacing the detail fragment using a
            // fragment transaction.
            Bundle arguments = new Bundle();
            arguments.putString(AnimalDetailFragment.ARG_URI, animalUri.toString()); //Key value pair Adrian "animal_uri = .."

            AnimalDetailFragment fragment = new AnimalDetailFragment();
            fragment.setArguments(arguments);
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.animal_detail_container, fragment)
                    .commit();

        } else {
            // In single-pane mode, simply start the detail activity
            // for the selected item ID.
            Intent detailIntent = new Intent(this, AnimalDetailActivity.class);
            //Uri todoUri = Uri.parse(SupaScaleContentProvider.CONTENT_URI_ANIMAL + "/" + id);
            //detailIntent.putExtra(AnimalDetailFragment.ARG_ITEM_ID, id);
            //startActivity(detailIntent);
            detailIntent.putExtra(SupaScaleContentProvider.CONTENT_ITEM_TYPE, animalUri);

            // Activity returns an result if called with startActivityForResult
            startActivityForResult(detailIntent, ACTIVITY_EDIT);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.listmenu, menu);
        return true;
    }

    // Reaction to the menu selection
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.insert:
                createAnimal();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void createAnimal() {
        if (mTwoPane) {
            // In two-pane mode, show the detail view in this activity by
            // adding or replacing the detail fragment using a
            // fragment transaction.
            Bundle arguments = new Bundle();
            //arguments.putString(AnimalDetailFragment.ARG_URI, animalUri.toString()); //Key value pair Adrian "animal_uri = .."

            AnimalDetailFragment fragment = new AnimalDetailFragment();
            fragment.setArguments(arguments);
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.animal_detail_container, fragment)
                    .commit();

        } else {
            // In single-pane mode, simply start the detail activity
            Intent i = new Intent(this, AnimalDetailActivity.class);
            startActivityForResult(i, ACTIVITY_EDIT);
        }
    }

}

AnimalListActivity.xml

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/animal_list"
    android:name="com.supascale.supascale.AnimalListFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp"
    tools:context=".AnimalListActivity"
    tools:layout="@android:layout/list_content" />

The List.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@android:id/list" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="No Animal Data"
        android:id="@android:id/empty" />

</LinearLayout>

The row.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="wrap_content" >

    <ImageView
            android:id="@+id/icon"
            android:layout_width="30dp"
            android:layout_height="24dp"
            android:layout_marginLeft="4dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="8dp"
            android:src="@drawable/reminder" >
    </ImageView>

    <TextView
            android:id="@+id/label"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="6dp"
            android:lines="1"
            android:text="@+id/textViewNormalTotal"
            android:textSize="24sp"
            >
    </TextView>

</LinearLayout>

The fragment list scrolls correctly, and when clicked the detail fragment is correctly populated. When I long click, nothing happens, and when I release the longclick, the click event is fired.

I need for the long click to result in the CAB (Context Action Bar) to display, so that I can delete a record, or share a record etc.

mActionMode = view.startActionMode(mActionModeCallback); just never appears to be called.

Hope I have been clear enough. Regards

Adrian

I edited and added the imports for the ListFragment, as it may have to do with support.v4?

War es hilfreich?

Lösung

Ok, so I did not understand the CAB help so nicely.

http://developer.android.com/guide/topics/ui/menus.html#CAB

enter image description here

For a ListView or ListFragment, and not another kind of view, you must use the setOnItemLongClickListener, and not the setOnLongClickListener, as in the help from developer.android.com.

The setOnLongClickListener, is never activated by the listview, and thus never can procede to create the CAB.

I removed the following code:

getView().setOnLongClickListener( new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                if (mActionMode != null) {
                    return false;
                }

                // Start the CAB using the ActionMode.Callback defined above
                mActionMode = view.startActionMode(mActionModeCallback);
                view.setSelected(true);
                return true;
            }
        });

and replaced with the following:

getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {

            @Override
            public boolean onItemLongClick(AdapterView<?> arg0, View view,
                                           int position, long id) {
                if (mActionMode != null) {
                    return false;
                }

                Animal_id = id;
                // Start the CAB using the ActionMode.Callback defined above
                mActionMode = getActivity().startActionMode(mActionModeCallback);
                view.setSelected(true);
                return true;
            }
        });

Now the long click event fires, and I can get the CAB to display properly.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top