RecyclerView OnClickListener using interface

后端 未结 11 741
清歌不尽
清歌不尽 2020-12-01 09:49

I use RecyclerView adapter to display data inside an activity, I want to implement onClickListener inside the activity, currently, I am setting

相关标签:
11条回答
  • 2020-12-01 09:58

    Registering clickListener inside onCreateView instead of onBindViewHolder is more performant since you only add listener when a view is created not ever time recyclerView is scrolled.

    And i use ListAdapter with DiffUtil callback instead of RecyclerViewAdapter

    abstract class BaseListAdapter<ItemType>(
        callBack: DiffUtil.ItemCallback<ItemType> = DefaultItemDiffCallback(),
        private inline val onItemClicked: ((ItemType, Int) -> Unit)? = null
    ) : ListAdapter<ItemType, BaseItemViewHolder>(
        AsyncDifferConfig.Builder<ItemType>(callBack)
            .setBackgroundThreadExecutor(Executors.newSingleThreadExecutor())
            .build()
    ) {
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseItemViewHolder {
    
            return BaseItemViewHolder(
    
                DataBindingUtil.inflate(
                    LayoutInflater.from(parent.context),
                    getLayoutRes(viewType),
                    parent, false
                )
            ).apply {
                onViewHolderCreated(this, viewType, binding)
            }
    
        }
    
        fun createCustomViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    
            return BaseItemViewHolder(
    
                DataBindingUtil.inflate(
                    LayoutInflater.from(parent.context),
                    getLayoutRes(viewType),
                    parent, false
                )
            )
        }
    
        override fun onBindViewHolder(
            holder: BaseItemViewHolder,
            position: Int,
            payloads: MutableList<Any>
        ) {
            val item: ItemType? = currentList.getOrNull(position)
    
            item?.let {
                holder.binding.setVariable(BR.item, item)
                onViewHolderBound(holder.binding, item, position, payloads)
                holder.binding.executePendingBindings()
            }
    
        }
    
        override fun onBindViewHolder(holder: BaseItemViewHolder, position: Int) {
    
        }
    
    
        /**
         * get layout res based on view type
         */
        protected abstract fun getLayoutRes(viewType: Int): Int
    
        /**
         * Called when a ViewHolder is created. ViewHolder is either created first time or
         * when data is refreshed.
         *
         * This method is not called when RecyclerView is being scrolled
         */
        open fun onViewHolderCreated(
            viewHolder: RecyclerView.ViewHolder,
            viewType: Int,
            binding: ViewDataBinding
        ) {
    
            binding.root.setOnClickListener {
                onItemClicked?.invoke(getItem(viewHolder.bindingAdapterPosition), viewHolder.bindingAdapterPosition)
            }
        }
    
        /**
         * bind view while RecyclerView is being scrolled and new items are bound
         */
        open fun onViewHolderBound(
            binding: ViewDataBinding,
            item: ItemType,
            position: Int,
            payloads: MutableList<Any>
        ) {
    
        }
    
    
    }
    
    open class BaseItemViewHolder(
        val binding: ViewDataBinding
    ) : RecyclerView.ViewHolder(binding.root)
    
    
    class DefaultItemDiffCallback<ItemType> : DiffUtil.ItemCallback<ItemType>() {
    
        override fun areItemsTheSame(
            oldItem: ItemType,
            newItem: ItemType
        ): Boolean {
            return oldItem === newItem
        }
    
        override fun areContentsTheSame(
            oldItem: ItemType,
            newItem: ItemType
        ): Boolean {
            return oldItem.hashCode() == newItem.hashCode()
        }
    }
    

    Another better user experience is using onBindViewHolder with payLoad which lets you only update some part of the rows instead of whole row. For instance you have image, title and body in rows, and only body changes frequently, without payload image flashes and provides bad user experience. But with payload you can decide which part of the row should be updated allowing you not to reload parts that were not updated.

    0 讨论(0)
  • 2020-12-01 09:58

    If I understood correctly you want to set the on click logic in the Activity.

    You can do this by setting the OnClickListener in the Activity and passing it in the Adapter constructor.

    MyAdapter myAdapter = new MyAdapter(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(activity, "clicked on " +position, Toast.LENGTH_SHORT).show();
            }
        }));
    

    And your MyAdapter Constructor would be:

    final private OnClickListener onClickListener;
    
    public MyAdapter(OnClickListener onClickListener) {
        this.OnClickListener = OnClickListener;
    }
    

    So your new code would be something like this

    public void onBindViewHolder(MyHolder holder, final int position) {
        final Listdata data = listdata.get(position);
        holder.vname.setText(data.getName());
    
        holder.vname.setOnClickListener(onClickListener);
    

    }

    0 讨论(0)
  • 2020-12-01 09:59

    I found super duper easy method! I recommend this one

    Example Code:

    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);
            Picasso.with(itemView.getContext()).load(item.imageUrl).into(image);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override public void onClick(View v) {
                    listener.onItemClick(item);
                }
            });
        }
    }
    }
    

    And Use RecyclerView Adapter using below code:

    recycler.setAdapter(new ContentAdapter(items, new ContentAdapter.OnItemClickListener() {
    @Override public void onItemClick(ContentItem item) {
        Toast.makeText(getContext(), "Item Clicked", Toast.LENGTH_LONG).show();
    }
    }));
    

    i found this from here

    Hope it helped you.

    0 讨论(0)
  • 2020-12-01 09:59

    [BEST SOLUTION]

    In my way, I just created a single instance of ClickListener, And it dispatches click event to both RecyclerView and Activity or Fragment:

    class LeagueAdapter(onLeagueSelected: (League, Int, View) -> Unit) : 
    RecyclerView.Adapter<LeagueHolder>() {
    
        val dataSet = arrayListOf<League>()
    
        private val clickListener = View.OnClickListener {
            it?.let  {
                val adapterPosition = it.tag as Int
                onLeagueSelected(dataSet[adapterPosition], adapterPosition, it)
    
                // perform adapter related action here ...
            }
        }
    
    
        override fun getItemCount(): Int {
            return dataSet.size
        }
    
        override fun onBindViewHolder(holder: LeagueHolder, position: Int) {
            // put item position in tag field
            holder.itemView.tag = position
            holder.itemView.setOnClickListener(clickListener)
        }
    
    }
    

    And inside Activity, we have something like this:

    private val headerAdapter = LeagueAdapter { league, i, view ->
        Log.e(TAG, "item clicked $i")
    }
    
    0 讨论(0)
  • 2020-12-01 10:01

    You can let your Activity implements View.OnClickListener and pass it to adapter. Below is an example.

    class  RAdapter extends RecyclerView.Adapter<>{
        View.OnClickListener listner;
        public RAdapter(View.OnClickListener listner) {
            this.listner = listner;
        }
        public void onBindViewHolder(MyHolder holder, final int position) {
            holder.vname.setOnClickListener(listner);
    
        }
    }
    

    But to handle click in Activity you will going to need clicked position. You can have it with adapter.getAdapterPosition() to validate which item is clicked.

    Apart from that if you already have reference of Activity you can have OnClick inside adapter and call a public method of Activity with position to perform action.

    A better way to handle clicks in ViewHolder. See the below example.

    class Holder extends RecyclerView.ViewHolder implements View.OnClickListener {
            Button button;
            public Holder(View itemView) {
                super(itemView);
                button=itemView.findViewById(R.id.b1);
                button.setOnClickListener(this);
            }
            @Override
            public void onClick(View v) {
                if(v.getId()==R.id.b1){
                    int position=getAdapterPosition();
                    // Call a public method of Activity here 
                    // with postion
                }
            }
        }
    
    0 讨论(0)
  • 2020-12-01 10:01

    I always have one Generic Adapter in my project to avoid make a Adapter class every I use a Recyclerview. Here some example

    public class AdapterRecyclerviewTextOnly extends RecyclerView.Adapter<AdapterRecyclerviewTextOnly.ViewHolder> {
    private RecyclerView recyclerView;
    private OnRecyclerviewListener onRecyclerviewListener;
    
    public interface OnRecyclerviewListener {
        void onRecyclerviewBind(RecyclerView recyclerView, AdapterRecyclerviewTextOnly.ViewHolder viewHolder, int position);
        void onRecyclerviewClick(RecyclerView recyclerView, int position);
        int onItemCount(RecyclerView recyclerView);
    }
    
    public void setOnRecyclerviewListener(OnRecyclerviewListener listener) { this.onRecyclerviewListener = listener; }
    
    public AdapterRecyclerviewTextOnly(RecyclerView recyclerView) {
        super();
        this.recyclerView = recyclerView;
    }
    
    
    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        RecyclerView recyclerView;
        public TextView textView;
    
        ViewHolder(RecyclerView recyclerView, View itemView) {
            super(itemView);
            this.recyclerView = recyclerView;
            this.itemView.setOnClickListener(this);
    
            this.textView = itemView.findViewById(R.id.textview_title);
        }
    
        void onBind(int position) { onRecyclerviewListener.onRecyclerviewBind(this.recyclerView, this, position); }
    
        @Override
        public void onClick(View v) {
            onRecyclerviewListener.onRecyclerviewClick(this.recyclerView, getAdapterPosition());
        }
    }
    
    @Override
    public AdapterRecyclerviewTextOnly.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View inflatedView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyclerview_text_only, parent, false);
        return new ViewHolder(this.recyclerView, inflatedView);
    }
    
    @Override
    public void onBindViewHolder(AdapterRecyclerviewTextOnly.ViewHolder holder, int position) {
        holder.onBind(position);
    }
    
    @Override
    public int getItemCount() {
        return onRecyclerviewListener.onItemCount(this.recyclerView);
    }
    }
    

    And then in your Activity Class, you can use this adapter with :

        this.recyclerView = findViewById(R.id.recyclerview);
        this.recyclerView.setHasFixedSize(true);
        this.recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        AdapterRecyclerviewTextOnly recyclerViewAdapter = new AdapterRecyclerviewTextOnly(this.recyclerView);
        this.recyclerView.setAdapter(this.recyclerViewAdapter);
        this.recyclerViewAdapter.setOnRecyclerviewListener(new AdapterRecyclerviewTextOnly.OnRecyclerviewListener() {
            @Override
            public void onRecyclerviewBind(RecyclerView recyclerView, AdapterRecyclerviewTextOnly.ViewHolder viewHolder, int position) {
    
            }
    
            @Override
            public void onRecyclerviewClick(RecyclerView recyclerView, int position) {
    
            }
    
            @Override
            public int onItemCount(RecyclerView recyclerView) { 
    }
    });
    

    You can reuse this with 2 or 3 recyclerview too. First, declare a globar listener private AdapterRecyclerviewTextOnly.OnRecyclerviewListener listener;.

    Then init the listener with new object then set the your every recyclerview with the listener. Use specific identifier:

    if (recyclerView == recyclerViewA){ } else if (recyclerView == recyclerViewB) { } to manage your recyclerview inside the adapter.

    0 讨论(0)
提交回复
热议问题