Custom ListView and context menu. How to get it?

2019-01-16 13:07发布

I have a two layouts files in my app. Also I have Activity extends ListActivity. Every item of this activity looks consider item.xml layout file. I am trying to get context menu when make long presss on item, but I don't see it.

In my activity I trying to registerForContextMenu(getListView()) and override two methods

  @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = this.getIntent().getExtras();
        registerForContextMenu(getListView());
        new PopulateAdapterTask().execute(ACTION_SELECT);   
     }

    @Override
        public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
            MenuInflater inflater = getMenuInflater();
            inflater.inflate(R.menu.context_menu, menu);
        }


        @Override
        public boolean onContextItemSelected(MenuItem item) {
            AdapterView.AdapterContextMenuInfo info;
            try {
                info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
            } catch (ClassCastException e) {
                return false;
            }
            long id = getListAdapter().getItemId(info.position);
            Log.d(TAG, "id = " + id);
            return true;
        }

Main.xml

    <?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@android:id/tabhost"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent">
    <LinearLayout
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:padding="5dp">
        <TabWidget
                android:id="@android:id/tabs"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"/>
        <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:padding="5dp">
            <ListView
                    android:id="@+id/list"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    />

        </FrameLayout>

    </LinearLayout>

</TabHost>

Item.xml

<?xml version="1.0" encoding="utf-8"?>
     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
        >
    <ImageView
            android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
    <TextView
            android:id="@+id/info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:textSize="15sp"
            android:singleLine="true"
            android:ellipsize="marquee"
            android:scrollHorizontally = "true"
            android:maxWidth="200dp"
            />


    <LinearLayout
             android:layout_width="fill_parent"
             android:layout_height="fill_parent"
             android:gravity="right"
            >
        <ImageButton
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:background="@null"
                android:paddingRight="10dp"                
                android:paddingLeft="10dp"


                />
    </LinearLayout>

</LinearLayout>

All this doesn't work. Maybe the reason is in LinearLayout? I also find similar topic Android: Context menu doesn't show up for ListView with members defined by LinearLayout? but I have more complicated list item.

How to get context menu in my case?

Also in my activity I have inner class extends ArrayAdapter. In this class in getView method I can set OnCreateContextMenuListener on every View, after that context menu is appears, but I don't know how to handle items clicks. If I am trying to do this in method onContextItemSelected, item.getMenuInfo() object always is null and i can't to get some information from it.

private class ChannelAdapter extends ArrayAdapter<Channel> {

        private List<Channel> channels;

        public ChannelAdapter(Context context, int textViewResourceId, List<Channel> objects) {
            super(context, textViewResourceId, objects);
            this.channels = objects;
        }


        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = vi.inflate(R.layout.station_item, null);
            }


                v.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
                    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
                        MenuInflater inflater = getMenuInflater();
                        inflater.inflate(R.menu.context_menu, menu);
                    }
                });

Thanks. Hope for your help.

6条回答
我只想做你的唯一
2楼-- · 2019-01-16 13:37

Actually, you just need to make View long clickable by calling

v.setLongClickable(true);

It's not needed to set dummy setOnCreateContextMenuListener, because it's does just that - set's the item long clickable.

查看更多
我命由我不由天
3楼-- · 2019-01-16 13:42

I got solution, my friend helped me! Hope this information will helpful to someone. This is complete class code with ArrayAdapter and complex list layout and context menu.

   public class ComplexListActivity extends ListActivity {
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setListAdapter(new ComplexObjectAdapter(this, R.layout.item, getComplexObjects()));
        registerForContextMenu(getListView());
    }

    private List getComplexObjects() {
        List<ComplexObject> list = new ArrayList<ComplexObject>();
        list.add(new ComplexObject("1", "1", getResources().getDrawable(R.drawable.icon)));
        list.add(new ComplexObject("2", "2", getResources().getDrawable(R.drawable.icon)));
        list.add(new ComplexObject("3", "3", getResources().getDrawable(R.drawable.icon)));
        list.add(new ComplexObject("4", "4", getResources().getDrawable(R.drawable.icon)));
        list.add(new ComplexObject("5", "5", getResources().getDrawable(R.drawable.icon)));
        list.add(new ComplexObject("6", "6", getResources().getDrawable(R.drawable.icon)));
        list.add(new ComplexObject("7", "7", getResources().getDrawable(R.drawable.icon)));
        list.add(new ComplexObject("8", "8", getResources().getDrawable(R.drawable.icon)));
        list.add(new ComplexObject("9", "9", getResources().getDrawable(R.drawable.icon)));
        return list;
    }


    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.context_menu, menu);
    }


    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterView.AdapterContextMenuInfo info;
        try {
            info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
        } catch (ClassCastException e) {
            Log.e("", "bad menuInfo", e);
            return false;
        }
        long id = getListAdapter().getItemId(info.position);
        Log.d("", "id = " + id);
        Toast.makeText(this, "id = " + id, Toast.LENGTH_SHORT).show();
        return true;
    }

    private class ComplexObjectAdapter extends ArrayAdapter<ComplexObject> implements View.OnCreateContextMenuListener {

        private List<ComplexObject> objects;

        public ComplexObjectAdapter(Context context, int textViewResourceId, List<ComplexObject> objects) {
            super(context, textViewResourceId, objects);
            this.objects = objects;
        }


        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = vi.inflate(R.layout.item, null);
            }
            final ComplexObject o = objects.get(position);
            if (o != null) {

                TextView textlInfo = (TextView) v.findViewById(R.id.info);
                textlInfo.setText(o.getName());

                ImageView channelIcon = (ImageView) v.findViewById(R.id.icon);
                channelIcon.setAdjustViewBounds(true);
                channelIcon.setMaxHeight(30);
                channelIcon.setMaxWidth(30);
                channelIcon.setImageDrawable(o.getLogo());


                ImageButton button = (ImageButton) v.findViewById(R.id.button);
                button.setImageResource(R.drawable.icon);
                v.setOnCreateContextMenuListener(this);

            }
            return v;
        }

         public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
          // empty implementation
        }

    }
}

let me know if someone will find better approach. Thanks!

查看更多
男人必须洒脱
4楼-- · 2019-01-16 13:42

I don't think you want to attach a context menu to the specific listView items. By calling registerForContextMenu(getListView()) you should get that functionality for free. I would hook your application up to a debugger after you remove the contextMenu hooks from the adapter code and set a breakpoint inside of onCreateContextMenu(). My suspicion is that it is getting called but the layout that is being inflated is not what you expect.

查看更多
对你真心纯属浪费
5楼-- · 2019-01-16 13:50

Following segments of code from nested class ComplexObjectAdapter, listed into Georgy Gobozov's answer, are not really needed:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        if (v == null) {
            LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.item, null);
        }
        final ComplexObject o = objects.get(position);
        if (o != null) {

            TextView textlInfo = (TextView) v.findViewById(R.id.info);
            textlInfo.setText(o.getName());

            ImageView channelIcon = (ImageView) v.findViewById(R.id.icon);
            channelIcon.setAdjustViewBounds(true);
            channelIcon.setMaxHeight(30);
            channelIcon.setMaxWidth(30);
            channelIcon.setImageDrawable(o.getLogo());


            ImageButton button = (ImageButton) v.findViewById(R.id.button);
            button.setImageResource(R.drawable.icon);
            // NOT NEEDED
            v.setOnCreateContextMenuListener(this);

        }
        return v;
    }

// NOT NEEDED
public void onCreateContextMenu(ContextMenu contextMenu, View view,  ContextMenu.ContextMenuInfo contextMenuInfo) {
            // empty implementation
}

It just works because inside the function setOnCreateContextMenuListener() from class View, it calls the function setLongClickable(true):

/**
 * Register a callback to be invoked when the context menu for this view is
 * being built. If this view is not long clickable, it becomes long clickable.
 *
 * @param l The callback that will run
 *
 */
public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
    if (!isLongClickable()) {
        setLongClickable(true);
    }
    mOnCreateContextMenuListener = l;
}

It means the problem can be solved setting the Long Clickable property for each child item, after it is created:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        if (v == null) {
            LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.item, null);
            // SET LONG CLICKABLE PROPERTY
            v.setLongClickable(true);
        }
        final ComplexObject o = objects.get(position);
        if (o != null) {

            TextView textlInfo = (TextView) v.findViewById(R.id.info);
            textlInfo.setText(o.getName());

            ImageView channelIcon = (ImageView) v.findViewById(R.id.icon);
            channelIcon.setAdjustViewBounds(true);
            channelIcon.setMaxHeight(30);
            channelIcon.setMaxWidth(30);
            channelIcon.setImageDrawable(o.getLogo());


            ImageButton button = (ImageButton) v.findViewById(R.id.button);
            button.setImageResource(R.drawable.icon);
            // NOT NEEDED
            // v.setOnCreateContextMenuListener(this);

        }
        return v;
    }

or it also can be solved setting this property in the XML layout file of the list view child elements, for example:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:longClickable="true">

    <!-- Child elements -->

</LinearLayout>
查看更多
贪生不怕死
6楼-- · 2019-01-16 13:53

The basic problem is that the item is being drawn on by the second item.xml layout - so the root element (LinearLayout) is what is being long pressed rather than what the original ListView provided. As such, when you inflate the item.xml layout, you need to call the setOnCreateContextMenuListener as you have in fact done in the second example. The problem with this is that there is no way for the layout from item.xml (which is a LinearLayout) to communicate back to the Activity which position was chosen. This is because the LinearLayout does not override the getContextMenuInfo() method which in a ListView returns a AdapterView.AdapterContextMenuInfo (as everyone seems to coerce their ContextMenuInfo to).

So ideally, you want to create your own LinearLayout descendant that makes the getContextMenuInfo public, creates a fake one if there isn't one there, and when onCreateContextMenu is called in your custom adapter, it grabs it from your custom LinearLayout and puts the position/id in there, which your activity can pull out.

This is what I have done in my own application and it works very nicely and is a generic solution - you can in fact put anything you like in there as long as it implements the ContextMenuInfo interface (which is just a marker interface).

查看更多
做个烂人
7楼-- · 2019-01-16 14:02

I don't now why, it is necessary to set a null OnCreateContextMenuListener on every list row (in addition to registerForContextMenu(...) and implement onCreateContextMenu(...) and onContextItemSelected(...)

查看更多
登录 后发表回答