Obtenir l'élément cliqué et sa position dans RecyclerView


105

Je remplace ma liste ListViewpar RecyclerView, indiquant ok, mais j'aimerais savoir comment obtenir l'élément cliqué et sa position, similaire à la méthode que OnItemClickListener.onItemClick(AdapterView parent, View v, int position, long id)nous utilisons dans ListView.

Merci pour les idées!




moyen le plus simple pour moi: CustomSelectionCallback
ATES

Réponses:


177

Basé sur le lien: Pourquoi RecyclerView n'a-t-il pas onItemClickListener ()? et En quoi RecyclerView est-il différent de Listview? , et aussi l'idée générale de @ Duncan, je donne ma solution ici:

  • Définissez une interface RecyclerViewClickListenerpour transmettre le message de l'adaptateur à Activity/ Fragment:

    public interface RecyclerViewClickListener {
        public void recyclerViewListClicked(View v, int position);
    }
  • Dans Activity/ Fragmentimplémentez l'interface et transmettez également l'écouteur à l'adaptateur:

    @Override
    public void recyclerViewListClicked(View v, int position){... ...}
    
    //set up adapter and pass clicked listener this
    myAdapter = new MyRecyclerViewAdapter(context, this);
  • Dans Adapteret ViewHolder:

    public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ItemViewHolder> {
       ... ... 
       private Context context;
       private static RecyclerViewClickListener itemListener;
    
    
       public MyRecyclerViewAdapter(Context context, RecyclerViewClickListener itemListener) {
           this.context = context;
           this.itemListener = itemListener;
           ... ...
       }
    
    
       //ViewHolder class implement OnClickListener, 
       //set clicklistener to itemView and, 
       //send message back to Activity/Fragment 
       public static class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
           ... ...
           public ItemViewHolder(View convertView) {
               super(convertView);
               ... ...
               convertView.setOnClickListener(this);
           }
    
           @Override
           public void onClick(View v) {
               itemListener.recyclerViewListClicked(v, this.getPosition());     
    
           }
       }
    }

Après le test, cela fonctionne bien.


[ MISE À JOUR ]

Depuis l'API 22, RecyclerView.ViewHoler.getPosition()est obsolète, donc à la place avec getLayoutPosition().


22
utilisez la méthode getLayoutPosition () car getPosition () est désormais obsolète.
Ankur Chaudhary

2
merci pour ce bout de code :) juste un conseil: référencer la statique private static RecyclerViewClickListener itemListener;de manière statique dans le constructeur.
David Artmann

1
@khurramengr J'ai fini par ajouter un auditeur dans chaque élément de la méthode onBindViewHolder. De plus, mon élément de liste n'était pas entièrement cliquable (seulement une partie de l'élément).
Ferran Negre le

1
Si vous ne comptez pas utiliser un WeakReference ici: new MyRecyclerViewAdapter(context, this)? Cela pourrait provoquer des fuites de mémoire
RamwiseMatt

2
public void recyclerViewListClicked(View v, int position);Le modificateur publicest redondant pour les méthodes d'interface
Joel Raju

35
public class MyRvAdapter extends RecyclerView.Adapter<MyRvAdapter.MyViewHolder>{
    public Context context;
    public ArrayList<RvDataItem> dataItems;

    ...
    constructor
    overrides
    ...

    class MyViewHolder extends RecyclerView.ViewHolder{
        public TextView textView;
        public Context context;

        public MyViewHolder(View itemView, Context context) {
            super(itemView);

            this.context = context;

            this.textView = (TextView)itemView.findViewById(R.id.textView);

            // on item click
            itemView.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View v) {
                    // get position
                    int pos = getAdapterPosition();

                    // check if item still exists
                    if(pos != RecyclerView.NO_POSITION){
                        RvDataItem clickedDataItem = dataItems.get(pos);
                        Toast.makeText(v.getContext(), "You clicked " + clickedDataItem.getName(), Toast.LENGTH_SHORT).show();
                    }
                }
            });
        }
    }
}

4
Je vous remercie. getAdapterPosition () est ce dont j'avais besoin.
Ayaz Alifov

1
Merci pour l'homme de code !! J'ai sauvé ma journée. Je ne savais pas où mettre getAdapterPosition () et en suivant votre code, je l'ai fait fonctionner ... Nice and easy :)
Vinay Vissh

Merci mec, ton code était exactement ce dont j'ai besoin! Happy man
Naveen Nirban Yadav

13

Voici un exemple pour définir un écouteur de clic.

Adapter extends  RecyclerView.Adapter<MessageAdapter.MessageViewHolder> {  ...  }

 public static class MessageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        public TextView     tv_msg;
        public TextView     tv_date;
        public TextView     tv_sendTime;
        public ImageView    sharedFile;
        public ProgressBar sendingProgressBar;

        public MessageViewHolder(View v) {
            super(v);
            tv_msg =  (TextView) v.findViewById(R.id.tv_msg);
            tv_date = (TextView)  v.findViewById(R.id.tv_date);
            tv_sendTime = (TextView)  v.findViewById(R.id.tv_sendTime);
            sendingProgressBar = (ProgressBar) v.findViewById(R.id.sendingProgressBar);
            sharedFile = (ImageView) v.findViewById(R.id.sharedFile);

            sharedFile.setOnClickListener(this);

        }

        @Override
        public void onClick(View view) {
        int position  =   getAdapterPosition();


            switch (view.getId()){
                case R.id.sharedFile:


                    Log.w("", "Selected"+position);


                    break;
            }
        }

    }

Qu'est-ce que getAdapterPostion (); ici?
TheDevMan

1
la position de la vue cliquée. n'est pas une méthode personnalisée.
Gilberto Ibarra

Cela semble être le moyen le plus propre de le faire, d'autant plus que vous êtes presque toujours intéressé par l'obtention de données à l'aide de la position et que vous n'aurez peut-être pas accès à l'objet RecyclerView.
FrostRocket

Merci beaucoup pourint position = getAdapterPosition();
Shylendra Madda

Veuillez noter que l'appel à getAdapterPosition pendant la mise à jour de la liste, par exemple à cause de l'appel à notififyXYZ, retournera -1
David

11

Placez ce code là où vous définissez la vue recycleur en activité.

    rv_list.addOnItemTouchListener(
            new RecyclerItemClickListener(activity, new RecyclerItemClickListener.OnItemClickListener() {
                @Override
                public void onItemClick(View v, int position) {

                    Toast.makeText(activity, "" + position, Toast.LENGTH_SHORT).show();
                }
            })
    );

Ensuite, créez une classe séparée et mettez ce code:

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {

    private OnItemClickListener mListener;
    public interface OnItemClickListener {
        public void onItemClick(View view, int position);
    }
    GestureDetector mGestureDetector;
    public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
        mListener = listener;
        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }
        });
    }
    @Override
    public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
        View childView = view.findChildViewUnder(e.getX(), e.getY());
        if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
            mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

7

créer un fichier java avec le code ci-dessous

public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
private OnItemClickListener mListener;

public interface OnItemClickListener {
    public void onItemClick(View view, int position);
}

GestureDetector mGestureDetector;

public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
    mListener = listener;
    mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
        @Override public boolean onSingleTapUp(MotionEvent e) {
            return true;
        }
    });
}

@Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
    View childView = view.findChildViewUnder(e.getX(), e.getY());
    if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
        mListener.onItemClick(childView, view.getChildLayoutPosition(childView));
        return true;
    }
    return false;
}

@Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

}

et utilisez simplement l'écouteur sur votre objet RecyclerView.

recyclerView.addOnItemTouchListener(  
new RecyclerItemClickListener(context, new RecyclerItemClickListener.OnItemClickListener() {
  @Override public void onItemClick(View view, int position) {
    // TODO Handle item click
  }
}));

RecyclerItemClickListenern'est pas une classe Android par défaut, vous devez fournir la bibliothèque que vous utilisez ou la classe elle-même.
Greg

@Greg merci !! j'ai oublié de poster!
MazRoid

5

Si vous voulez cliquer sur l'événement de recycle-View de l'activité / fragment au lieu de l'adaptateur, vous pouvez également utiliser le raccourci suivant.

recyclerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                final TextView txtStatusChange = (TextView)v.findViewById(R.id.txt_key_status);
                txtStatusChange.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Log.e(TAG, "hello text " + txtStatusChange.getText().toString() + " TAG " + txtStatusChange.getTag().toString());
                        Util.showToast(CampaignLiveActivity.this,"hello");
                    }
                });
                return false;
            }
        });

Vous pouvez également utiliser d'autres méthodes longues comme l'utilisation de l'interface


,, Salut, mais que se passe-t-il si nous devons également cliquer sur la position de l'élément. ?
mfaisalhyder

Vous pouvez simplement définir la position de l'élément dans TAG via l'adaptateur, puis obtenir la balise comme mentionné ci-dessus txtStatusChange.getTag (). ToString ()
Amandeep Rohila

@AmandeepRohila Pour que le code fonctionne, je dois d'abord faire défiler jusqu'à la fin d'une vue de recyclage horizontale. Ce n'est qu'alors que le toast fonctionne. N'y a-t-il pas un moyen de le faire fonctionner sans avoir à faire défiler avant et arrière d'abord?
iOSAndroidWindowsMobileAppsDev

4
recyclerViewObject.addOnItemTouchListener(
    new RecyclerItemClickListener(
        getContext(),
        recyclerViewObject,
        new RecyclerItemClickListener.OnItemClickListener() {
            @Override public void onItemClick(View view, int position) {
                // view is the clicked view (the one you wanted
                // position is its position in the adapter
            }
            @Override public void onLongItemClick(View view, int position) {
            }
        }
    )
);

De quelle sorcellerie s'agit-il? Fonctionne parfaitement.
Bolling

2

Utilisez le code ci-dessous: -

public class SergejAdapter extends RecyclerView.Adapter<SergejAdapter.MyViewHolder>{

...

class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{


    @Override
    public void onClick(View v) {
        // here you use position
        int position = getAdapterPosition();
        ...

    }
}
}

1

RecyclerView ne fournit pas une telle méthode.

Pour gérer les événements de clic sur RecyclerView, j'ai fini par implémenter onClickListener dans mon adaptateur, lors de la liaison du ViewHolder: Dans mon ViewHolder, je garde une référence à la vue racine (comme vous pouvez le faire avec vos ImageViews, TextViews, etc ...) et lors de la liaison le viewHolder J'ai mis une balise dessus avec des informations dont j'ai besoin pour gérer le clic (comme la position) et un écouteur de clics


3
oui, nous pouvons définir l'écouteur de clic dans ViewHolder, mais j'ai besoin de renvoyer la vue et la position de l'élément cliqué sur mon fragment pour un processus ultérieur
Xcihnegn

1

Voici le moyen le plus simple et le plus simple de trouver la position de l'élément cliqué:

J'ai également rencontré le même problème.

Je voulais trouver la position de l'élément cliqué / sélectionné du RecyclerView () et effectuer des opérations spécifiques sur cet élément particulier.

La méthode getAdapterPosition () fonctionne comme un charme pour ce genre de choses. J'ai trouvé cette méthode après une journée de longues recherches et après avoir essayé de nombreuses autres méthodes.

int position = getAdapterPosition();
Toast.makeText(this, "Position is: "+position, Toast.LENGTH_SHORT).show();

Vous n'avez pas besoin d'utiliser de méthode supplémentaire. Créez simplement une variable globale nommée 'position' et initialisez-la avec getAdapterPosition () dans l'une des principales méthodes de l'adaptateur (classe ou similaire).

Voici une brève documentation à partir de ce lien .

getAdapterPosition ajouté dans la version 22.1.0 int getAdapterPosition () Renvoie la position de l'adaptateur de l'élément représenté par ce ViewHolder. Notez que cela peut être différent de getLayoutPosition () s'il y a des mises à jour d'adaptateur en attente mais qu'une nouvelle passe de mise en page ne s'est pas encore produite. RecyclerView ne gère aucune mise à jour d'adaptateur jusqu'au prochain parcours de disposition. Cela peut créer des incohérences temporaires entre ce que l'utilisateur voit à l'écran et le contenu de l'adaptateur. Cette incohérence n'est pas importante car elle sera inférieure à 16 ms, mais cela peut poser un problème si vous souhaitez utiliser la position ViewHolder pour accéder à l'adaptateur. Parfois, vous devrez peut-être obtenir la position exacte de l'adaptateur pour effectuer certaines actions en réponse aux événements utilisateur. Dans ce cas, vous devez utiliser cette méthode qui calculera la position de l'adaptateur du ViewHolder.

Heureux d'aider. N'hésitez pas à poser des doutes.


Quelle est la manière la plus simple et la plus simple d'obtenir la valeur de l'élément cliqué de RecyclerView?
अक्षय परूळेकर

1

Ma solution simple

Make a position holder:

    public class PositionHolder {

    private int position;

    public PositionHolder(int position) {
        this.position = position;
    }

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }
}

Positionnez ou mettez simplement les données dont vous avez besoin à partir de l'activité.

Constructeur d'adaptateur:

public ItemsAdapter(Context context, List<Item> items, PositionHolder positionHolder){
        this.context = context;
        this.items = items;
        this.positionHolder = positionHolder;
}

En activité:

 @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        selectedPosition = 0;

        positionHolder = new PositionHolder(selectedPosition);
        initView();
    }

Dans Adapter onClickLictener dans l'élément
dans onBindViewHolder

holder.holderButton.setOnClickListener(v -> {
            positionHolder.setPosition(position);
            notifyDataSetChanged();
        });

Maintenant, chaque fois que vous changez de position dans RecyclerView, il est conservé dans le Holder (ou peut-être devrait-il être appelé Listener)

J'espère que ce sera utile

Mon premier message; P


0
//Create below methods into the Activity which contains RecyclerView.


private void createRecyclerView() {

final RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recyclerView);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    MyAdapter myAdapter=new MyAdapter(dataAray,MianActivity.this);
    recyclerView.setAdapter(myAdapter);
    recyclerView.setItemAnimator(new DefaultItemAnimator());

    setRecyclerViewClickListner(recyclerView);
}

private void setRecyclerViewClickListner(RecyclerView recyclerView){

    final GestureDetector gestureDetector = new GestureDetector(MainActivity.this,new GestureDetector.SimpleOnGestureListener() {
        @Override public boolean onSingleTapUp(MotionEvent e) {
            return true;
        }
    });


    recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
        @Override
        public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {
            View child =recyclerView.findChildViewUnder(motionEvent.getX(),motionEvent.getY());
            if(child!=null && gestureDetector.onTouchEvent(motionEvent)){
               int position=recyclerView.getChildLayoutPosition(child);
                String name=itemArray.get(position).name;

            return true;
        }

        @Override
        public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {

        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean b) {

        }
    });
}

3
Veuillez ajouter une explication, simplement vider le code sans introduction et une explication de la façon dont cela résout le problème n'est pas très utile.
Mark Rotteveel

0

Essayez de cette façon

Classe d'adaptateur:

 public class ContentAdapter extends RecyclerView.Adapter<ContentAdapter.ViewHolder> {

public interface OnItemClickListener {
    void onItemClick(ContentItem item);
}

private final List<ContentItem> items;
private final OnItemClickListener listener;

public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
    this.items = items;
    this.listener = listener;
}

@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false);
    return new ViewHolder(v);
}

@Override public void onBindViewHolder(ViewHolder holder, int position) {
    holder.bind(items.get(position), listener);
}

@Override public int getItemCount() {
    return items.size();
}

static class ViewHolder extends RecyclerView.ViewHolder {

    private TextView name;
    private ImageView image;

    public ViewHolder(View itemView) {
        super(itemView);
        name = (TextView) itemView.findViewById(R.id.name);
        image = (ImageView) itemView.findViewById(R.id.image);
    }

    public void bind(final ContentItem item, final OnItemClickListener listener) {
        name.setText(item.name);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                listener.onItemClick(item);
            }
        });
    }
}

}

Dans votre activité ou fragment:

ContentAdapter adapter = new ContentAdapter(itemList, this);

Remarque: implémentez OnItemClickListener en fonction du contexte que vous avez donné dans les méthodes d'activité ou de fragment et de remplacement.


Belle solution ci-dessus. Pouvez-vous ajouter à ce qui précède pour montrer à quoi devrait ressembler la méthode onItemClick dans l'activité? J'essaie de passer la position de l'élément à une intention (pour ouvrir l'activité suivante) et je continue à avoir la mauvaise position.
AJW

0

Chaque fois que j'utilise une autre approche. Les gens semblent stocker ou obtenir une position sur une vue, plutôt que de stocker une référence à un objet affiché par ViewHolder.

J'utilise cette approche à la place, et je la stocke simplement dans ViewHolder lorsque onBindViewHolder () est appelé, et définit la référence sur null dans onViewRecycled ().

Chaque fois que ViewHolder devient invisible, il est recyclé. Cela n'affecte donc pas la consommation de mémoire importante.

@Override
public void onBindViewHolder(final ItemViewHolder holder, int position) {
    ...
    holder.displayedItem = adapterItemsList.get(i);
    ...
}

@Override
public void onViewRecycled(ItemViewHolder holder) {
    ...
    holder.displayedItem = null;
    ...
}

class ItemViewHolder extends RecyclerView.ViewHolder {
    ...
    MySuperItemObject displayedItem = null;
    ...
}

0

L'extension courte pour la méthode Kotlin
renvoie la position absolue de tous les éléments (pas la position des seuls éléments visibles).

fun RecyclerView.getChildPositionAt(x: Float, y: Float): Int {
    return getChildAdapterPosition(findChildViewUnder(x, y))
}

Et utilisation

val position = recyclerView.getChildPositionAt(event.x, event.y)

0

Utilisez getLayoutPosition () dans votre méthode java d'interface personnalisée. Cela renverra la position sélectionnée d'un élément, vérifiez tous les détails sur

https://becody.com/get-clicked-item-and-its-position-in-recyclerview/


1
Bienvenue dans Stack Overflow! Bien que les liens soient un excellent moyen de partager des connaissances, ils ne répondront pas vraiment à la question s'ils sont rompus à l'avenir. Ajoutez à votre réponse le contenu essentiel du lien qui répond à la question. Au cas où le contenu serait trop complexe ou trop volumineux pour tenir ici, décrivez l'idée générale de la solution proposée. N'oubliez pas de toujours conserver un lien de référence vers le site Web de la solution d'origine. Voir: Comment rédiger une bonne réponse?
sɐunıɔ ןɐ qɐp

0

Depuis le concepteur, vous pouvez définir la onClickpropriété de listItem sur une méthode définie avec un seul paramètre. J'ai un exemple de méthode défini ci-dessous. La méthode getAdapterPosition vous donnera l'index du listItem sélectionné.

public void exampleOnClickMethod(View view){
    myRecyclerView.getChildViewHolder(view).getAdapterPosition());
}

Pour plus d'informations sur la configuration d'un RecyclerView, consultez la documentation ici: https://developer.android.com/guide/topics/ui/layout/recyclerview


0
//simply check if the adapter position you get not less than zero

holder.btnDelItem.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(holder.getAdapterPosition()>=0){
            list.remove(holder.getAdapterPosition());
            notifyDataSetChanged();
        }
    }
});

3
Lorsque vous répondez à une ancienne question, votre réponse serait beaucoup plus utile aux autres utilisateurs de StackOverflow si vous incluiez un contexte pour expliquer comment votre réponse aide, en particulier pour une question qui a déjà une réponse acceptée. Voir: Comment écrire une bonne réponse .
David Buck

0

Après de nombreux essais et erreurs, j'ai trouvé qu'il existe un moyen très simple de le faire, c'est-à-dire en créant une interface dans la classe d'adaptateur et en l'implémentant en fragment, voici maintenant la torsion, j'ai instansi le modèle de vue à l'intérieur de ma fonction de remplacement présente dans le fragment maintenant, vous pouvez envoyer les données de cette fonction à viemodel et de là n'importe où.

Je ne sais pas si cette méthode est une bonne méthode de codage, mais veuillez me le faire savoir en commentaire et si quelqu'un veut voir le faites-le moi savoir dans la section des commentaires, j'ouvre régulièrement le flux de stackover.


0
recyclerView.addOnItemTouchListener(object : AdapterView.OnItemClickListener,
                RecyclerView.OnItemTouchListener {
                override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
                    TODO("Not yet implemented")
                }

                override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
                    TODO("Not yet implemented")
                }

                override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
                    TODO("Not yet implemented")
                }

                override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
                    TODO("Not yet implemented")
                }

            })

Si vous utilisez Kotlin


Bien que ce code puisse résoudre la question, inclure une explication sur comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre message et entraînerait probablement plus de votes à la hausse. N'oubliez pas que vous répondez à la question des lecteurs à l'avenir, pas seulement à la personne qui la pose maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limites et des hypothèses applicables.
Богдан Опир

-4

Dans onViewHolder, définissez onClickListiner sur n'importe quelle vue et cliquez sur le côté pour utiliser ce code:

Toast.makeText (Drawer_bar.this, "position" + position, Toast.LENGTH_SHORT) .show ();

Remplacez Drawer_Barpar le nom de votre activité.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.