RecyclerView의 행 내부에서 버튼 클릭 처리


81

행 클릭을 처리하기 위해 다음 코드를 사용하고 있습니다. ( 출처 )

static class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

    private GestureDetector gestureDetector;
    private ClickListener clickListener;

    public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
        this.clickListener = clickListener;
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
                if (child != null && clickListener != null) {
                    clickListener.onLongClick(child, recyclerView.getChildPosition(child));
                }
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

        View child = rv.findChildViewUnder(e.getX(), e.getY());
        if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
            clickListener.onClick(child, rv.getChildPosition(child));
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    }
}

그러나 이것은 각 행에 삭제 버튼을 말하고 싶다면 작동합니다. 나는 이것을 구현하는 방법을 잘 모르겠습니다.

OnClick 리스너를 연결하여 작동하는 삭제 버튼 (행 삭제)을 연결했지만 전체 행에서 onclick을 실행합니다.

누구든지 단일 버튼을 클릭하면 전체 행 클릭을 피하는 방법에 대해 나를 도울 수 있습니까?

감사.

답변:


128

이것이 recyclerView 내에서 여러 onClick 이벤트를 처리하는 방법입니다.

편집 : 콜백을 포함하도록 업데이트되었습니다 (다른 주석에서 언급 됨). 내가 사용하고 WeakReference의를 ViewHolder메모리 누수의 가능성을 제거 할 수 있습니다.

인터페이스 정의 :

public interface ClickListener {

    void onPositionClicked(int position);
    
    void onLongClicked(int position);
}

그런 다음 어댑터 :

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    
    private final ClickListener listener;
    private final List<MyItems> itemsList;

    public MyAdapter(List<MyItems> itemsList, ClickListener listener) {
        this.listener = listener;
        this.itemsList = itemsList;
    }

    @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_row_layout), parent, false), listener);
    }

    @Override public void onBindViewHolder(MyViewHolder holder, int position) {
        // bind layout and data etc..
    }

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

    public static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

        private ImageView iconImageView;
        private TextView iconTextView;
        private WeakReference<ClickListener> listenerRef;

        public MyViewHolder(final View itemView, ClickListener listener) {
            super(itemView);

            listenerRef = new WeakReference<>(listener);
            iconImageView = (ImageView) itemView.findViewById(R.id.myRecyclerImageView);
            iconTextView = (TextView) itemView.findViewById(R.id.myRecyclerTextView);

            itemView.setOnClickListener(this);
            iconTextView.setOnClickListener(this);
            iconImageView.setOnLongClickListener(this);
        }

        // onClick Listener for view
        @Override
        public void onClick(View v) {

            if (v.getId() == iconTextView.getId()) {
                Toast.makeText(v.getContext(), "ITEM PRESSED = " + String.valueOf(getAdapterPosition()), Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(v.getContext(), "ROW PRESSED = " + String.valueOf(getAdapterPosition()), Toast.LENGTH_SHORT).show();
            }
            
            listenerRef.get().onPositionClicked(getAdapterPosition());
        }


        //onLongClickListener for view
        @Override
        public boolean onLongClick(View v) {

            final AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
            builder.setTitle("Hello Dialog")
                    .setMessage("LONG CLICK DIALOG WINDOW FOR ICON " + String.valueOf(getAdapterPosition()))
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                        }
                    });

            builder.create().show();
            listenerRef.get().onLongClicked(getAdapterPosition());
            return true;
        }
    }
}

그런 다음 활동 / 조각에서-구현할 수있는 모든 것 : Clicklistener-또는 원하는 경우 익명 클래스 :

MyAdapter adapter = new MyAdapter(myItems, new ClickListener() {
            @Override public void onPositionClicked(int position) {
                // callback performed on click
            }

            @Override public void onLongClicked(int position) {
                // callback performed on click
            }
        });

클릭 된 항목을 확인하려면 뷰 ID ievgetId () == whateverItem.getId ()와 일치합니다.

이 방법이 도움이되기를 바랍니다.


1
감사합니다.이 패턴은 내 구현에만 사용했습니다. 그러나 문제는 다른 것이었다. 여기에서보세요 stackoverflow.com/questions/30287411/…
Ashwani K

1
"this"는 어디에서 가져 오나요? 이 당신이 컨텍스트를 일치하지 않는 어댑터 내에이 없다, 나는 그것으로 View.OnclickListener 캐스팅되는 어떤 이유로 그렇게 할 때뿐만 아니라
Lion789

2
this자신을 참조하는 ViewHolder (이 경우 별도의 정적 클래스)-에서 데이터가 바인딩 된 Viewholder에 리스너를 설정하고 onBindViewHolder()있으며 어댑터의 컨텍스트와 관련이 없습니다. 정확히 어떤 문제가 있는지 모르겠지만이 솔루션은 잘 작동합니다.
Mark Keen

1
@YasithaChinthaka이 속성을 설정하려고 했습니까 android:background="?attr/selectableItemBackground"?보기에 대한 XML에서?
Mark Keen

2
들러서 고맙다고 말하고 싶을뿐입니다.이 솔루션은 따르고 구현하기가 정말 쉽습니다.
CodeGeass

50

일반적으로 다음과 같습니다.

  • 여러 개의 버튼이 있기 때문에 여러 리스너를 사용해야합니다.
  • 내 로직이 어댑터 나 뷰 홀더가 아닌 액티비티에 있기를 원합니다.

따라서 @ mark-keen의 대답은 잘 작동하지만 인터페이스가 있으면 더 많은 유연성을 제공합니다.

public static class MyViewHolder extends RecyclerView.ViewHolder {

    public ImageView iconImageView;
    public TextView iconTextView;

    public MyViewHolder(final View itemView) {
        super(itemView);

        iconImageView = (ImageView) itemView.findViewById(R.id.myRecyclerImageView);
        iconTextView = (TextView) itemView.findViewById(R.id.myRecyclerTextView);

        iconTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onClickListener.iconTextViewOnClick(v, getAdapterPosition());
            }
        });
        iconImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onClickListener.iconImageViewOnClick(v, getAdapterPosition());
            }
        });
    }
}

onClickListener가 어댑터에 정의 된 위치 :

public MyAdapterListener onClickListener;

public interface MyAdapterListener {

    void iconTextViewOnClick(View v, int position);
    void iconImageViewOnClick(View v, int position);
}

그리고 아마도 생성자를 통해 설정됩니다.

public MyAdapter(ArrayList<MyListItems> newRows, MyAdapterListener listener) {

    rows = newRows;
    onClickListener = listener;
}

그런 다음 Activity 또는 RecyclerView가 사용되는 모든 곳에서 이벤트를 처리 할 수 ​​있습니다.

mAdapter = new MyAdapter(mRows, new MyAdapter.MyAdapterListener() {
                    @Override
                    public void iconTextViewOnClick(View v, int position) {
                        Log.d(TAG, "iconTextViewOnClick at position "+position);
                    }

                    @Override
                    public void iconImageViewOnClick(View v, int position) {
                        Log.d(TAG, "iconImageViewOnClick at position "+position);
                    }
                });
mRecycler.setAdapter(mAdapter);

이것은 또 다른 방법이며 내 대답을 기반으로 작성됩니다 (나와 비슷한 것 중 하나).하지만 onClickListener정적 중첩 Viewholder 클래스에 어떻게 전달 합니까? 내가 뭔가를 놓친 경우가 아니면 ViewHolder로 전달하는 방법을 볼 수 없습니다. 또한 하나의 인터페이스 방법 만 사용하는 경우 모든 것을 압축하는 Lambda 표현식을 사용할 수 있습니다.
Mark Keen

public MyAdapterListener onClickListener; 위 코드에서 어댑터에 정의되고 어댑터 생성자에 설정된 멤버 변수입니다. (또는 위에 표시되지 않았지만 setOnClickListener와 같은 사용자 지정 setter를 사용할 수도 있습니다.)
LordParsley

5
정적 중첩 클래스 (ViewHolder) 내에서 Adapter 클래스의 멤버 / 인스턴스 변수에 액세스하는 방법을 묻고있었습니다.
Mark Keen

Mark처럼 어댑터 내부에 중첩 할 수 없었습니다. 둥지 것을 방지하는 방법에 대한 내 대답을 참조
토니 BenBrahim

@MarkKeen .. 내가 가진 정확히 같은 질문.
user2695433

6

나중에 가비지 수집해야 할 추가 개체 (예 : 리스너)를 생성 하지 않고 어댑터 클래스 내부에 뷰 홀더를 중첩 할 필요 가없는 솔루션을 원했습니다 .

에서 ViewHolder클래스

private static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private final TextView ....// declare the fields in your view
        private ClickHandler ClickHandler;

        public MyHolder(final View itemView) {
            super(itemView);
            nameField = (TextView) itemView.findViewById(R.id.name);
            //find other fields here...
            Button myButton = (Button) itemView.findViewById(R.id.my_button);
            myButton.setOnClickListener(this);
        }
        ...
        @Override
        public void onClick(final View view) {
            if (clickHandler != null) {
                clickHandler.onMyButtonClicked(getAdapterPosition());
            }
        }

주의 할 점 : ClickHandler인터페이스는 정의되어 있지만 여기서 초기화되지 않았으므로 onClick메서드가 초기화되었다고 가정하지 않습니다 .

ClickHandler인터페이스는 다음과 같습니다 :

private interface ClickHandler {
    void onMyButtonClicked(final int position);
} 

어댑터에서 생성자에 'ClickHandler'의 인스턴스를 설정하고을 재정 onBindViewHolder의하여 뷰 홀더에서 'clickHandler'를 초기화합니다.

private class MyAdapter extends ...{

    private final ClickHandler clickHandler;

    public MyAdapter(final ClickHandler clickHandler) {
        super(...);
        this.clickHandler = clickHandler;
    }

    @Override
    public void onBindViewHolder(final MyViewHolder viewHolder, final int position) {
        super.onBindViewHolder(viewHolder, position);
        viewHolder.clickHandler = this.clickHandler;
    }

참고 : viewHolder.clickHandler가 잠재적으로 똑같은 값으로 여러 번 설정된다는 것을 알고 있지만 이것은 null 및 분기를 확인하는 것보다 저렴하며 메모리 비용이 없으며 추가 명령 만 있습니다.

마지막으로 어댑터를 만들 때 다음 ClickHandler과 같이 인스턴스를 생성자 에 전달해야합니다 .

adapter = new MyAdapter(new ClickHandler() {
    @Override
    public void onMyButtonClicked(final int position) {
        final MyModel model = adapter.getItem(position);
        //do something with the model where the button was clicked
    }
});

참고 adapter멤버 변수는 여기가 아닌 지역 변수


감사합니다 답변 : 난 오히려 사용 yourmodel.get보다 adapter.getItem (위치) (위치)를 사용하지 않는 여기에 추가 할 한 가지
Khubaib 라자

5

리사이클 러 터치 리스너가 이미 있고 뷰 홀더에서 버튼 터치 이벤트를 개별적으로 처리하는 대신 모든 터치 이벤트를 처리하려는 경우 다른 솔루션을 추가하고 싶었습니다. 이 조정 된 버전의 클래스가 수행하는 주요 작업은 항목 컨테이너가 아니라 탭할 때 onItemClick () 콜백에서 버튼보기를 반환하는 것입니다. 그런 다음 뷰가 버튼인지 테스트하고 다른 작업을 수행 할 수 있습니다. 버튼을 길게 두드리는 것은 전체 행을 길게 두드리는 것으로 해석됩니다.

public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener
{
    public static interface OnItemClickListener
    {
        public void onItemClick(View view, int position);
        public void onItemLongClick(View view, int position);
    }

    private OnItemClickListener mListener;
    private GestureDetector mGestureDetector;

    public RecyclerItemClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener)
    {
        mListener = listener;

        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener()
        {
            @Override
            public boolean onSingleTapUp(MotionEvent e)
            {
                // Important: x and y are translated coordinates here
                final ViewGroup childViewGroup = (ViewGroup) recyclerView.findChildViewUnder(e.getX(), e.getY());

                if (childViewGroup != null && mListener != null) {
                    final List<View> viewHierarchy = new ArrayList<View>();
                    // Important: x and y are raw screen coordinates here
                    getViewHierarchyUnderChild(childViewGroup, e.getRawX(), e.getRawY(), viewHierarchy);

                    View touchedView = childViewGroup;
                    if (viewHierarchy.size() > 0) {
                        touchedView = viewHierarchy.get(0);
                    }
                    mListener.onItemClick(touchedView, recyclerView.getChildPosition(childViewGroup));
                    return true;
                }

                return false;
            }

            @Override
            public void onLongPress(MotionEvent e)
            {
                View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());

                if(childView != null && mListener != null)
                {
                    mListener.onItemLongClick(childView, recyclerView.getChildPosition(childView));
                }
            }
        });
    }

    public void getViewHierarchyUnderChild(ViewGroup root, float x, float y, List<View> viewHierarchy) {
        int[] location = new int[2];
        final int childCount = root.getChildCount();

        for (int i = 0; i < childCount; ++i) {
            final View child = root.getChildAt(i);
            child.getLocationOnScreen(location);
            final int childLeft = location[0], childRight = childLeft + child.getWidth();
            final int childTop = location[1], childBottom = childTop + child.getHeight();

            if (child.isShown() && x >= childLeft && x <= childRight && y >= childTop && y <= childBottom) {
                viewHierarchy.add(0, child);
            }
            if (child instanceof ViewGroup) {
                getViewHierarchyUnderChild((ViewGroup) child, x, y, viewHierarchy);
            }
        }
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e)
    {
        mGestureDetector.onTouchEvent(e);

        return false;
    }

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

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

그런 다음 활동 / 조각에서 사용 :

recyclerView.addOnItemTouchListener(createItemClickListener(recyclerView));

    public RecyclerItemClickListener createItemClickListener(final RecyclerView recyclerView) {
        return new RecyclerItemClickListener (context, recyclerView, new RecyclerItemClickListener.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                if (view instanceof AppCompatButton) {
                    // ... tapped on the button, so go do something
                } else {
                    // ... tapped on the item container (row), so do something different
                }
            }

            @Override
            public void onItemLongClick(View view, int position) {
            }
        });
    }

1

onInterceptTouchEvent()클릭 이벤트를 처리 할 때 내부에서 true를 반환해야합니다 .


1
안녕하세요, 좀 더 자세히 설명해 주 시겠어요? 다음 코드를 사용하여 삭제 버튼 btnDelete = (ImageButton) itemView.findViewById (R.id.btnDelete); btnDelete.setOnClickListener (new View.OnClickListener () {@Override public void onClick (View view) {remove (getLayoutPosition ());}});
Ashwani K

onTouchEvent ()와 마찬가지로 반환 값은 이벤트가 처리되었는지 여부와 이벤트가 전체 행에 전달되지 않은 경우를 나타냅니다.
Eliyahu Shwartz

0

먼저 비슷한 항목이 있는지 확인하고 크기가 0 인 컬렉션을 가져 오면 새 쿼리를 시작하여 저장할 수 있습니다.

또는

더 전문적이고 빠른 방법. 클라우드 트리거 생성 (저장 전)

이 답변을 확인하십시오 https://stackoverflow.com/a/35194514/1388852


0

getItemId라는 이름의 재정의 메서드를 넣으십시오. 오른쪽 클릭> 생성> 메서드 재정의> getItemId이 메서드를 Adapter 클래스에 넣습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.