[java] 왜 RecyclerView에 onItemClickListener ()가 없습니까?

나는 탐험 RecyclerView하고 있었고 그것이 RecyclerView없는 것을보고 놀랐습니다 onItemClickListener().

두 가지 질문이 있습니다.

주요 질문

Google이 왜 삭제되었는지 알고 싶습니다 onItemClickListener().

성능 문제 또는 다른 것이 있습니까?

이차 질문

내 글을 작성 onClick하여 문제를 해결했습니다 RecyclerView.Adapter.

public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

    public TextView txtViewTitle;
    public ImageView imgViewIcon;

    public ViewHolder(View itemLayoutView) {
        super(itemLayoutView);
        txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
        imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
    }

    @Override
    public void onClick(View v) {

    }
}

괜찮습니까 / 더 좋은 방법이 있습니까?



답변

tl; dr 2016 RxJava 및 PublishSubject를 사용하여 클릭에 대한 Observable을 표시하십시오.

public class ReactiveAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    String[] mDataset = { "Data", "In", "Adapter" };

    private final PublishSubject<String> onClickSubject = PublishSubject.create();

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        final String element = mDataset[position];

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               onClickSubject.onNext(element);
            }
        });
    }

    public Observable<String> getPositionClicks(){
        return onClickSubject.asObservable();
    }
}

원본 게시물 :

의 도입 이후 ListView, onItemClickListener문제가되고있다. 내부 요소에 대한 클릭 리스너가있는 순간 콜백이 트리거되지는 않지만 알림이나 문서화가 제대로되지 않았으므로 혼동과 질문이 많이있었습니다.

그 감안할 때 RecyclerView한 단계 더 걸립니다과 행 / 열의 개념이없는, 오히려 아이들의 임의의 배치 양, 그들은 그들의 각 하나에 온 클릭을 위임 한 또는 프로그래머 구현에.

1 : 1 교체가 아니라 복잡한 사용 사례를위한보다 유연한 구성 요소 Recyclerview로 생각하십시오 ListView. 그리고 당신이 말한 것처럼 귀하의 솔루션은 Google이 기대 한 것입니다. 지금 당신은 모두에 대한 올바른 패턴 생성자에 전달 된 인터페이스에 onclick을 위임 할 수있는 어댑터가 ListViewRecyclerview.

public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

    public TextView txtViewTitle;
    public ImageView imgViewIcon;
    public IMyViewHolderClicks mListener;

    public ViewHolder(View itemLayoutView, IMyViewHolderClicks listener) {
        super(itemLayoutView);
        mListener = listener;
        txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
        imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
        imgViewIcon.setOnClickListener(this);
        itemLayoutView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v instanceof ImageView){
           mListener.onTomato((ImageView)v);
        } else {
           mListener.onPotato(v);
        }
    }

    public static interface IMyViewHolderClicks {
        public void onPotato(View caller);
        public void onTomato(ImageView callerImage);
    }

}

그런 다음 어댑터에서

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

   String[] mDataset = { "Data" };

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

       MyAdapter.ViewHolder vh = new ViewHolder(v, new MyAdapter.ViewHolder.IMyViewHolderClicks() {
           public void onPotato(View caller) { Log.d("VEGETABLES", "Poh-tah-tos"); };
           public void onTomato(ImageView callerImage) { Log.d("VEGETABLES", "To-m8-tohs"); }
        });
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager) 
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // Get element from your dataset at this position 
        // Replace the contents of the view with that element 
        // Clear the ones that won't be used
        holder.txtViewTitle.setText(mDataset[position]);
    }

    // Return the size of your dataset (invoked by the layout manager) 
    @Override
    public int getItemCount() {
        return mDataset.length;
    }
  ...

이제 마지막 코드를 살펴보십시오. onCreateViewHolder(ViewGroup parent, int viewType)서명은 이미 다른 뷰 유형을 제안합니다. 이들 각각에 대해 다른 조회수도 필요하며, 각 사용자마다 다른 클릭 집합을 가질 수 있습니다. 또는 모든 뷰를 가져 와서 onClickListener적절하게 적용 할 수있는 일반 뷰 홀더를 만들 수 있습니다 . 또는 한 단계를 오케 스트레이터에게 위임하여 여러 조각 / 활동이 서로 다른 클릭 동작을 갖는 동일한 목록을 갖도록합니다. 다시 말하지만 모든 유연성은 당신 편입니다.

그것은 실제로 필요한 구성 요소이며 ListView현재까지 내부 구현 및 개선 사항에 상당히 가깝습니다 . Google이 마침내 인정하는 것이 좋습니다.


답변

RecyclerView에없는 이유 onItemClickListener

RecyclerView이전의 대조적으로, 도구 상자입니다 ListView그것은 기능과 유연성 덜 빌드를 가지고있다. 이 onItemClickListener있는 ListView에서 제거되는 유일한 기능이 아닙니다. 그러나 원하는대로 확장 할 수있는 청취자와 방법이 많이 있습니다. 오른손에 훨씬 강력합니다.).

제 생각에는 가장 복잡한 기능 RecyclerView빠른 스크롤 입니다. 다른 기능의 대부분은 쉽게 다시 구현할 수 있습니다.

다른 멋진 기능을 RecyclerView추가 하려면 다른 질문에 대한 답변을 읽으십시오 .

메모리 효율적-onItemClickListener를위한 드롭 인 솔루션

이 솔루션이되었습니다 제안 에 의해 우고 프랜트 , 안드로이드 GDE, 직후에 RecyclerView발표되었다. 그는 코드를 작성하여 사용하기 위해 무면허 클래스를 만들었습니다.

RecyclerView을 사용하여 도입 된 다목적 성을 보여줍니다 RecyclerView.OnChildAttachStateChangeListener.

2019 편집 : Hugo Visser의 kotlin 버전, Java one, 아래 유지

코 틀린 / 자바

파일을 작성 values/ids.xml하고 여기에 넣으십시오.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="item_click_support" type="id" />
</resources>

그런 다음 아래 코드를 소스에 추가하십시오.

코 틀린

용법:

recyclerView.onItemClick { recyclerView, position, v ->
    // do it
}

(또한 긴 항목 클릭을 지원하며 추가 한 다른 기능에 대해서는 아래를 참조하십시오).

구현 (Hugo Visser Java 코드에 대한 나의 적응) :

typealias OnRecyclerViewItemClickListener = (recyclerView: RecyclerView, position: Int, v: View) -> Unit
typealias OnRecyclerViewItemLongClickListener = (recyclerView: RecyclerView, position: Int, v: View) -> Boolean

class ItemClickSupport private constructor(private val recyclerView: RecyclerView) {

    private var onItemClickListener: OnRecyclerViewItemClickListener? = null
    private var onItemLongClickListener: OnRecyclerViewItemLongClickListener? = null

    private val attachListener: RecyclerView.OnChildAttachStateChangeListener = object : RecyclerView.OnChildAttachStateChangeListener {
        override fun onChildViewAttachedToWindow(view: View) {
            // every time a new child view is attached add click listeners to it
            val holder = this@ItemClickSupport.recyclerView.getChildViewHolder(view)
                    .takeIf { it is ItemClickSupportViewHolder } as? ItemClickSupportViewHolder

            if (onItemClickListener != null && holder?.isClickable != false) {
                view.setOnClickListener(onClickListener)
            }
            if (onItemLongClickListener != null && holder?.isLongClickable != false) {
                view.setOnLongClickListener(onLongClickListener)
            }
        }

        override fun onChildViewDetachedFromWindow(view: View) {

        }
    }

    init {
        // the ID must be declared in XML, used to avoid
        // replacing the ItemClickSupport without removing
        // the old one from the RecyclerView
        this.recyclerView.setTag(R.id.item_click_support, this)
        this.recyclerView.addOnChildAttachStateChangeListener(attachListener)
    }

    companion object {
        fun addTo(view: RecyclerView): ItemClickSupport {
            // if there's already an ItemClickSupport attached
            // to this RecyclerView do not replace it, use it
            var support: ItemClickSupport? = view.getTag(R.id.item_click_support) as? ItemClickSupport
            if (support == null) {
                support = ItemClickSupport(view)
            }
            return support
        }

        fun removeFrom(view: RecyclerView): ItemClickSupport? {
            val support = view.getTag(R.id.item_click_support) as? ItemClickSupport
            support?.detach(view)
            return support
        }
    }

    private val onClickListener = View.OnClickListener { v ->
        val listener = onItemClickListener ?: return@OnClickListener
        // ask the RecyclerView for the viewHolder of this view.
        // then use it to get the position for the adapter
        val holder = this.recyclerView.getChildViewHolder(v)
        listener.invoke(this.recyclerView, holder.adapterPosition, v)
    }

    private val onLongClickListener = View.OnLongClickListener { v ->
        val listener = onItemLongClickListener ?: return@OnLongClickListener false
        val holder = this.recyclerView.getChildViewHolder(v)
        return@OnLongClickListener listener.invoke(this.recyclerView, holder.adapterPosition, v)
    }

    private fun detach(view: RecyclerView) {
        view.removeOnChildAttachStateChangeListener(attachListener)
        view.setTag(R.id.item_click_support, null)
    }

    fun onItemClick(listener: OnRecyclerViewItemClickListener?): ItemClickSupport {
        onItemClickListener = listener
        return this
    }

    fun onItemLongClick(listener: OnRecyclerViewItemLongClickListener?): ItemClickSupport {
        onItemLongClickListener = listener
        return this
    }

}

/** Give click-ability and long-click-ability control to the ViewHolder */
interface ItemClickSupportViewHolder {
    val isClickable: Boolean get() = true
    val isLongClickable: Boolean get() = true
}

// Extension function
fun RecyclerView.addItemClickSupport(configuration: ItemClickSupport.() -> Unit = {}) = ItemClickSupport.addTo(this).apply(configuration)

fun RecyclerView.removeItemClickSupport() = ItemClickSupport.removeFrom(this)

fun RecyclerView.onItemClick(onClick: OnRecyclerViewItemClickListener) {
    addItemClickSupport { onItemClick(onClick) }
}
fun RecyclerView.onItemLongClick(onLongClick: OnRecyclerViewItemLongClickListener) {
    addItemClickSupport { onItemLongClick(onLongClick) }
}

(아래의 XML 파일도 추가해야합니다)

코 틀린 버전의 보너스 기능

때로는 RecyclerView의 모든 항목을 클릭 할 수있는 것을 원하지 않습니다.

이를 처리하기 위해 클릭 가능한 항목을 제어하는 ​​데 ItemClickSupportViewHolder사용할 수 있는 인터페이스를 소개했습니다 ViewHolder.

예:

class MyViewHolder(view): RecyclerView.ViewHolder(view), ItemClickSupportViewHolder {
    override val isClickable: Boolean get() = false
    override val isLongClickable: Boolean get() = false
}

자바

용법:

ItemClickSupport.addTo(mRecyclerView)
        .setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
    @Override
    public void onItemClicked(RecyclerView recyclerView, int position, View v) {
        // do it
    }
});

(긴 항목 클릭도 지원)

구현 (댓글 추가)

public class ItemClickSupport {
    private final RecyclerView mRecyclerView;
    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;
    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mOnItemClickListener != null) {
                // ask the RecyclerView for the viewHolder of this view.
                // then use it to get the position for the adapter
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
            }
        }
    };
    private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            if (mOnItemLongClickListener != null) {
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
            }
            return false;
        }
    };
    private RecyclerView.OnChildAttachStateChangeListener mAttachListener
            = new RecyclerView.OnChildAttachStateChangeListener() {
        @Override
        public void onChildViewAttachedToWindow(View view) {
            // every time a new child view is attached add click listeners to it
            if (mOnItemClickListener != null) {
                view.setOnClickListener(mOnClickListener);
            }
            if (mOnItemLongClickListener != null) {
                view.setOnLongClickListener(mOnLongClickListener);
            }
        }

        @Override
        public void onChildViewDetachedFromWindow(View view) {

        }
    };

    private ItemClickSupport(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        // the ID must be declared in XML, used to avoid
        // replacing the ItemClickSupport without removing
        // the old one from the RecyclerView
        mRecyclerView.setTag(R.id.item_click_support, this);
        mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
    }

    public static ItemClickSupport addTo(RecyclerView view) {
        // if there's already an ItemClickSupport attached
        // to this RecyclerView do not replace it, use it
        ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
        if (support == null) {
            support = new ItemClickSupport(view);
        }
        return support;
    }

    public static ItemClickSupport removeFrom(RecyclerView view) {
        ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
        if (support != null) {
            support.detach(view);
        }
        return support;
    }

    public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
        return this;
    }

    public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
        mOnItemLongClickListener = listener;
        return this;
    }

    private void detach(RecyclerView view) {
        view.removeOnChildAttachStateChangeListener(mAttachListener);
        view.setTag(R.id.item_click_support, null);
    }

    public interface OnItemClickListener {

        void onItemClicked(RecyclerView recyclerView, int position, View v);
    }

    public interface OnItemLongClickListener {

        boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
    }
}

작동 방식 (효율적인 이유)

이 클래스는에를 첨부하여 작동 RecyclerView.OnChildAttachStateChangeListener합니다 RecyclerView. 이 리스너는 어린이가에서 연결 또는 분리 될 때마다 알림을받습니다 RecyclerView. 코드는이를 사용하여 탭 / 긴 클릭 리스너를 뷰에 추가합니다. 그 청취자는 물어 RecyclerView에 대한 RecyclerView.ViewHolder어떤 위치가 포함되어 있습니다.

각 뷰에 대해 여러 개의 리스너를 작성하지 않고 RecyclerView스크롤 하는 동안 계속 파괴하고 작성하기 때문에 다른 솔루션보다 더 효율적 입니다.

더 필요한 경우 홀더 자체를 돌려 주도록 코드를 조정할 수도 있습니다.

최종 비고

제안 된 다른 답변과 같이 목록의 각보기에서 클릭 리스너를 설정하여 어댑터에서 처리하는 것이 좋습니다.

가장 효율적인 방법은 아니지만 (보기를 재사용 할 때마다 새 리스너를 작성) 작동하지만 대부분의 경우 문제가되지 않습니다.

또한 문제의 분리에 약간의 영향을 미쳐 실제로 클릭 이벤트를 위임하는 것은 어댑터의 작업이 아닙니다.


답변

나는이 방법을 좋아하고 그것을 사용하고 있습니다

내부

public Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)

놓다

View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_image_and_text, parent, false);
v.setOnClickListener(new MyOnClickListener());

원하는 곳 어디에서나이 수업을 만들 수 있습니다.

class MyOnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
       int itemPosition = recyclerView.indexOfChild(v);
       Log.e("Clicked and Position is ",String.valueOf(itemPosition));
    }
}

나는 더 나은 방법이 있다는 것을 전에 읽었지만이 방법은 쉽고 복잡하지 않습니다.


답변

Android Recyclerview with onItemClickListener, 왜 우리가 시도 할 수 없는지 작동 ListView합니다.

출처 : 링크

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) {

  }
}

그리고 이것을 RecyclerView로 설정하십시오 :

    recyclerView = (RecyclerView)rootView. findViewById(R.id.recyclerView);
    RecyclerView.LayoutManager mLayoutManager = new            LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.addOnItemTouchListener(
            new RecyclerItemClickListener(getActivity(), new   RecyclerItemClickListener.OnItemClickListener() {
                @Override
                public void onItemClick(View view, int position) {
                    // TODO Handle item click
                    Log.e("@@@@@",""+position);
                }
            })
    );


답변

@marmor 덕분에 답변을 업데이트했습니다.

ViewHolder 클래스 생성자 에서 onClick ()을 처리하고 OnItemClickListener 인터페이스 를 통해 부모 클래스에 전달 하는 것이 좋은 솔루션이라고 생각합니다 .

MyAdapter.java

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

private LayoutInflater layoutInflater;
private List<MyObject> items;
private AdapterView.OnItemClickListener onItemClickListener;

public MyAdapter(Context context, AdapterView.OnItemClickListener onItemClickListener, List<MyObject> items) {
    layoutInflater = LayoutInflater.from(context);
    this.items = items;
    this.onItemClickListener = onItemClickListener;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = layoutInflater.inflate(R.layout.my_row_layout, parent, false);
    return new ViewHolder(view);
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    MyObject item = items.get(position);
}

public MyObject getItem(int position) {
    return items.get(position);
}


class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private TextView title;
    private ImageView avatar;

    public ViewHolder(View itemView) {
        super(itemView);
        title = itemView.findViewById(R.id.title);
        avatar = itemView.findViewById(R.id.avatar);

        title.setOnClickListener(this);
        avatar.setOnClickListener(this);
        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        //passing the clicked position to the parent class
        onItemClickListener.onItemClick(null, view, getAdapterPosition(), view.getId());
    }
}
}

다른 클래스에서의 어댑터 사용법 :

MyFragment.java

public class MyFragment extends Fragment implements AdapterView.OnItemClickListener {

private RecyclerView recycleview;
private MyAdapter adapter;

    .
    .
    .

private void init(Context context) {
    //passing this fragment as OnItemClickListener to the adapter
    adapter = new MyAdapter(context, this, items);
    recycleview.setAdapter(adapter);
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    //you can get the clicked item from the adapter using its position
    MyObject item = adapter.getItem(position);

    //you can also find out which view was clicked
    switch (view.getId()) {
        case R.id.title:
            //title view was clicked
            break;
        case R.id.avatar:
            //avatar view was clicked
            break;
        default:
            //the whole row was clicked
    }
}

}


답변

여러분의 주요 활동에서이 코드를 사용합니다. 매우 효율적인 방법

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.users_list);
UsersAdapter adapter = new UsersAdapter(users, this);
recyclerView.setAdapter(adapter);
adapter.setOnCardClickListner(this);

다음은 어댑터 클래스입니다.

public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.UserViewHolder> {
        private ArrayList<User> mDataSet;
        OnCardClickListner onCardClickListner;


        public UsersAdapter(ArrayList<User> mDataSet) {
            this.mDataSet = mDataSet;
        }

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

        @Override
        public void onBindViewHolder(UserViewHolder holder, final int position) {
            holder.name_entry.setText(mDataSet.get(position).getUser_name());
            holder.cardView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onCardClickListner.OnCardClicked(v, position);
                }
            });
        }

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

        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
        }


        public static class UserViewHolder extends RecyclerView.ViewHolder {
            CardView cardView;
            TextView name_entry;

            public UserViewHolder(View itemView) {
                super(itemView);
                cardView = (CardView) itemView.findViewById(R.id.user_layout);
                name_entry = (TextView) itemView.findViewById(R.id.name_entry);
             }
        }

        public interface OnCardClickListner {
            void OnCardClicked(View view, int position);
        }

        public void setOnCardClickListner(OnCardClickListner onCardClickListner) {
            this.onCardClickListner = onCardClickListner;
        }
    }

그 후에는 액티비티에서이 재정의 메소드를 얻게됩니다.

@Override
    public void OnCardClicked(View view, int position) {
        Log.d("OnClick", "Card Position" + position);
    }


답변

> RecyclerView는 Listview와 어떻게 다릅니 까?

한 가지 차이점이 있다는 것입니다 LayoutManager당신이 관리 할 수있는 RecyclerView와 클래스 RecyclerView같은 -가

가로 또는 세로 스크롤LinearLayoutManager

에 의해 GridLayout GridLayoutManager

스 태거 드 그리드 StaggeredGridLayoutManager

RecyclerView의 가로 스크롤과 유사합니다.

LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(llm);