web-dev-qa-db-fra.com

ListView personnalisé et menu contextuel. Comment l'obtenir?

J'ai deux fichiers de mises en page dans mon application. Aussi j'ai l'activité étend ListActivity. Chaque élément de cette activité ressemble à un fichier de mise en forme item.xml. J'essaie d'obtenir un menu contextuel lorsque j'appuie longuement sur un élément, mais je ne le vois pas.

Dans mon activité, j'essaie de m'inscrire à forFontContextMenu (getListView ()) et de remplacer deux méthodes

  @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>

Tout ça ne marche pas. Peut-être que la raison est dans LinearLayout? Je trouve également un sujet similaire Android: le menu contextuel ne s'affiche pas pour ListView avec les membres définis par LinearLayout? mais j'ai un élément de liste plus compliqué.

Comment obtenir un menu contextuel dans mon cas?

De plus, dans mon activité, la classe interne étend ArrayAdapter. Dans cette classe de la méthode getView, je peux définir OnCreateContextMenuListener sur chaque vue, après l'affichage de ce menu contextuel, mais je ne sais pas comment gérer les clics sur les éléments. Si j'essaie de faire cela dans la méthode onContextItemSelected, l'objet item.getMenuInfo () est toujours null et je ne peux pas en obtenir des informations.

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);
                    }
                });

Merci. J'espère pour votre aide.

16
Georgy Gobozov

J'ai eu la solution, mon ami m'a aidé! J'espère que cette information sera utile à quelqu'un. Il s'agit d'un code de classe complet avec ArrayAdapter et une présentation de liste complexe et un menu contextuel.

   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
        }

    }
}

laissez-moi savoir si quelqu'un trouvera une meilleure approche. Merci!

33
Georgy Gobozov

Les segments de code suivants de la classe imbriquée ComplexObjectAdapter, répertoriés dans la réponse de Georgy Gobozov, ne sont pas vraiment nécessaires:

    @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
}

Cela fonctionne simplement parce que, dans la fonction setOnCreateContextMenuListener () de la classe View, il appelle la fonction 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;
}

Cela signifie que le problème peut être résolu en définissant la propriété Long Clickable pour chaque élément enfant après sa création:

    @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;
    }

vous pouvez également résoudre ce problème en définissant cette propriété dans le fichier de présentation XML des éléments enfants de la vue liste, par exemple:

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

    <!-- Child elements -->

</LinearLayout>
7
David Salgado

En fait, il vous suffit de rendre View long cliquable en appelant

v.setLongClickable(true);

Il n'est pas nécessaire de définir la variable factice setOnCreateContextMenuListener, car c'est exactement ce que vous faites: la définition de l'élément est cliquable.

1
user2663241

Je ne sais pas pourquoi, il est nécessaire de définir un null OnCreateContextMenuListener sur chaque ligne de la liste (en plus de registerForContextMenu (...) et d'implémenter onCreateContextMenu (...) et onContextItemSelected (...)

0
iflorit

Le problème de base est que l'élément est dessiné sur par la deuxième présentation item.xml - de sorte que l'élément racine (LinearLayout) correspond à l'élément sur lequel vous appuyez longuement plutôt qu'à celui fourni par ListView d'origine. En tant que tel, lorsque vous gonflez la présentation item.xml, vous devez appeler setOnCreateContextMenuListener comme vous l'avez fait dans le deuxième exemple. Le problème avec ceci est qu'il n'y a aucun moyen pour la présentation de item.xml (qui est un LinearLayout) de communiquer à l'activité quelle position a été choisie. En effet, LinearLayout ne remplace pas la méthode getContextMenuInfo () qui, dans un ListView, renvoie un AdapterView.AdapterContextMenuInfo (car tout le monde semble contraindre son ContextMenuInfo à). 

Donc, idéalement, vous voulez créer votre propre descendant de LinearLayout qui rend le getContextMenuInfo public, crée un faux s'il n'y en a pas, et quand onCreateContextMenu est appelé dans votre adaptateur personnalisé, il le récupère depuis votre LinearLayout personnalisé et le place/id dedans, que votre activité peut retirer. 

C’est ce que j’ai fait dans ma propre application et cela fonctionne très bien et c’est une solution générique: vous pouvez y insérer tout ce que vous voulez tant qu’elle implémente l’interface ContextMenuInfo (qui n’est qu’une interface marqueur).

0
Richard Vowles

Je ne pense pas que vous souhaitiez associer un menu contextuel aux éléments listView spécifiques. En appelant registerForContextMenu (getListView ()), vous devriez obtenir cette fonctionnalité gratuitement. Je voudrais connecter votre application à un débogueur après avoir supprimé les crochets contextMenu du code de l'adaptateur et défini un point d'arrêt à l'intérieur de onCreateContextMenu (). Je soupçonne que l’appel est appelé, mais la disposition gonflée n’est pas celle que vous attendez. 

0
Greg Giacovelli