RecyclerView(Item Click) 2탄

Posted by ITPangPang
2016.04.07 00:11 안드로이드(android)/Recyclerview


RecyclerView(Item Click) 2탄


- 이번글에서는 이전글에 이어서 RecyclerView에서 Item을 
   클릭(선택)을 했을때 Toast를 띄울것이다.

- 이전글에서는 Adapter에 onBindViewHolder에서 바로 
   Listener를 달아서 사용 했었다.

- 이번에는 MainActivity에서 바로 사용 하는 방법을 알아 볼 
   것이다.

- 사실 클릭하는 방법은 이 외에도 많으나 Main에서 클릭, Adapter에서 클릭하는 경우 2가지만 우선 써 볼 것이다.

- 레이아웃은 전부 그대로 두고 지난번 Adapter에서 Click 처리한 부분을 주석처리하고, MainActivity만 수정할 것이다.

- 살짝 내용을 길게 쓸 수도 있는데 시간 남을때 볼 수 있으면 봐도 좋을 내용일 것 같다.


1. CountAdapter
public class CountAdapter extends RecyclerView.Adapter<CountAdapter.MyViewHolder>
{
Context mContext;
List<String> items;
String text;


public CountAdapter(Context c, List<String> items, String text)
{
this.mContext = c;
this.items = items;
this.text = text;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i)
{
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_custom, viewGroup, false);

return new MyViewHolder(v);
}

@Override
public void onBindViewHolder(final MyViewHolder myViewHolder, int position)
{
final String item = items.get(position);
myViewHolder.tv.setText("" + item);
}

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

public class MyViewHolder extends RecyclerView.ViewHolder
{
TextView tv;
public MyViewHolder(View itemView)
{
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
}

onBindViewHolder에서 setOnClickListener부분 삭제한것 말곤 똑같다.


2. MainActivity.java 작성(천천히 단계별로 작성)


  ① RecyclerView(rv) 입력하고 .을 써보면 addOnItemTouchListener 라는 것이 있다 넣어보자

rv.addOnItemTouchListener(new RecyclerView.OnItemTouchListener()
{
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
return false;
}

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

}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

}
});

일단 뭐가 많이 Override 되었는데 어디다가 뭘 써야할지 모르겠다.

이럴때는 역시 맨땅에 헤딩이다. 


  ② 전부 로그를 달고 돌려보자.

rv.addOnItemTouchListener(new RecyclerView.OnItemTouchListener()
{
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
return false;
}

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

}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

}
});


  ③ 앱이 실행되면 List에 몇개 넣어보고 화면을 클릭해본후에 Logcat을 본다


화면을 터치해보니 onInterceptTouchEvent에만 들어갔다.

여기서 뭔가를 처리하면 될 것 같다.


  ④ 일단 무엇을 넣을지 모르겠으니 Developer사이트를 가서 참조문서를 본다.

     참조문서를 보니 Position이라던가 Holder라던가 뭐 Item에 관한걸 먼저 가져와야 할 것같다.

     정보를 가져오려면 뭔가 get을 해야하니 RecyclerView의 get부분을 본다.


  ⑤ getChildAdapterPosition, getChildLayoutPosition, getChildPosition, getChildViewHolder등 눈에 많이 띈다.

     그런데 꽁짜로 얻을수는 없다 옆에 (View child)가 적혀있다.. 먼저 View를 구해야 한다.


조금 더 위를 올려보니 View를 구할 수 있다.

findChildViewUnder(float x, float y) x,y값을 주면 된단다.


x,y가 정확히 뭔지 모르니 클릭해봤다.

뭐 수직 수평 위치란다. 

옆에 in pixels이란다.


  ⑥ 안드로이드 스튜디오로 돌아와서 onIntercept 부분을 보자. 

     역시 안드로이드는 친절하다. MotionEvent e를 사용할 수 있다. 바로 활용해서 써보자.

rv.addOnItemTouchListener(new RecyclerView.OnItemTouchListener()
{
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onInterceptTouchEvent");
View child = rv.findChildViewUnder(e.getX(), e.getY());

Log.d(TAG, "e.getX==>"+e.getX());

Log.d(TAG, "e.getY==>"+e.getY());
Log.d(TAG, "child==>"+child);

return false;
}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onTouchEvent");

}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)
{
Log.d(TAG,"onRequestDisallowInterceptTouchEvent");
}
});

e.getX(), e.getY(),child가 

어떻게 들어오는지 모르니 일단 Log로 한번 찍어본다.



처음에 빈공간에 한번 터치했더니 child는 null값, 

x,y는 float형의 point index가 나왔다.

두번째는 List의 Item을 선택했더니 child는 LinearLayout값

x,y는 float형의 point index가 나왔다.

세번째는 빈공간을 손가락으로 한번 훑었더니

child는 null값이 나왔다.


이 말은 child가 null값이 아닐때는 터치가 

Item위에서 발생했다는 것이다


  ⑦ 일단 View child를 구했으니 아까 참조문서에서 봤던 쓸만한것들을 다 써본다.

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onInterceptTouchEvent");
View child = rv.findChildViewUnder(e.getX(), e.getY());
if(child!=null)
{
Log.d(TAG, "getChildAdapterPosition=>" + rv.getChildAdapterPosition(child));
Log.d(TAG,"getChildLayoutPosition=>"+rv.getChildLayoutPosition(child));
Log.d(TAG,"getChildViewHolder=>" + rv.getChildViewHolder(child));

}
return false;
}


일단 Item이 없는 공간에 클릭했을때에는 if문에 안들어오니 값이 안찍힌다.

다음 1,2번째 Item을 한번씩 터치 해보니 위와 같은 결과가 나왔다.

Adapter,LayoutPosition은 같은 결과가 나왔고

ChildViewHolder는 해당 ViewHolder의 정보가 나왔다.


근데 여기서 한가지 이상한건 분명히 한번 터치했는데 로그캣이 2번씩 찍힌다는 것이다.

이건 Motion e와 관련이 있다. 터치를 계속 하고 있으면 로그캣이 계속 찍힐것이다.

여기서 터치를 하고 손을 땔떼만 값을 얻을 수 있도록 코드를 살짝 더 추가한다.


⑧ GestureDetector 추가

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

rv.addOnItemTouchListener(new RecyclerView.OnItemTouchListener()
{
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onInterceptTouchEvent");
View child = rv.findChildViewUnder(e.getX(), e.getY());
if(child!=null&&gestureDetector.onTouchEvent(e))
{
Log.d(TAG, "getChildAdapterPosition=>" + rv.getChildAdapterPosition(child));
Log.d(TAG,"getChildLayoutPosition=>"+rv.getChildLayoutPosition(child));
Log.d(TAG,"getChildViewHolder=>" + rv.getChildViewHolder(child));

}
return false;
}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onTouchEvent");

}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)
{
Log.d(TAG,"onRequestDisallowInterceptTouchEvent");
}
});


코드를 수정하니 한번 터치당 값 한번이 정확히 들어온다.

여기까지 구한건 position이다. 

position만 알아도 일단 원하는 Toast까지는 띄울 수 있다.


⑨ Toast 띄우기

rv.addOnItemTouchListener(new RecyclerView.OnItemTouchListener()
{
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onInterceptTouchEvent");
View child = rv.findChildViewUnder(e.getX(), e.getY());
if(child!=null&&gestureDetector.onTouchEvent(e))
{
         /*Log.d(TAG, "getChildAdapterPosition=>" + rv.getChildAdapterPosition(child));
         Log.d(TAG,"getChildLayoutPosition=>"+rv.getChildLayoutPosition(child));
         Log.d(TAG,"getChildViewHolder=>" + rv.getChildViewHolder(child));*/
Toast.makeText(getApplication(), count.get(rv.getChildLayoutPosition(child)).toString(), Toast.LENGTH_SHORT).show();
//Toast.makeText(getApplication(), count.get(rv.getChildAdapterPosition(child)).toString(), Toast.LENGTH_SHORT).show();

}
return false;
}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onTouchEvent");

}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)
{
Log.d(TAG,"onRequestDisallowInterceptTouchEvent");
}
});

지금은 LayoutPosition이든 AdapterPostion이든 

원하는것을 사용하면 된다

현재 예제에서는 어떤걸 사용해도 된다.

 음 써놓고 보니 .toString()은 빼도 되고 냅둬도 된다.



   ⑩ 일단 Toast까지는 끝났으나 추가적으로 몇개 더 해본다(ViewHolder편)

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onInterceptTouchEvent");
View child = rv.findChildViewUnder(e.getX(), e.getY());
if(child!=null&&gestureDetector.onTouchEvent(e))
{
        /*Log.d(TAG, "getChildAdapterPosition=>" + rv.getChildAdapterPosition(child));
        Log.d(TAG,"getChildLayoutPosition=>"+rv.getChildLayoutPosition(child));
        Log.d(TAG,"getChildViewHolder=>" + rv.getChildViewHolder(child));*/
//Toast.makeText(getApplication(), count.get(rv.getChildLayoutPosition(child)), Toast.LENGTH_SHORT).show();
//Toast.makeText(getApplication(), count.get(rv.getChildAdapterPosition(child)).toString(), Toast.LENGTH_SHORT).show();

Log.d(TAG,"AdapterPosition=>"+rv.findViewHolderForAdapterPosition(rv.getChildLayoutPosition(child)));
Log.d(TAG,"LayoutPosition=>"+rv.findViewHolderForLayoutPosition(rv.getChildLayoutPosition(child)));
Log.d(TAG, "getChildViewHolder=>" + rv.getChildViewHolder(child));
//Toast.makeText(getApplication(), rv.getChildViewHolder(child).itemView.toString(), Toast.LENGTH_SHORT).show();

}
return false;
}

방금은 position으로만 했는데 

깜빡했던 getChildViewHolder로 ViewHolder를 구한게 생각난다.

position을 구하고 났으니 참조문서를 더 보자

findViewHolderForAdapterPosition과 findViewHolderForLayoutPosition을 보니

position만 넣어주면 ViewHolder를 구할수 있단다.

한번 구해보고 Log를 찍어본다.


역시 예상대로 3가지 전부 같은 결과가 나온다.

이래서 참조문서를 항상 잘 봐야하는것 같다.


   ⑪ 자 ViewHolder까지 구했으니 Toast로 Text도 한번 찍어봐야겠다. 

      혹시 CountAdapter-MyViewHolder class에서 TextView를 선언하고 사용했던 기억이 나는가?

      기억이 난다면 비슷하게 써보면 그대로 구할 수 있다.

rv.addOnItemTouchListener(new RecyclerView.OnItemTouchListener()
{
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
Log.d(TAG,"onInterceptTouchEvent");
View child = rv.findChildViewUnder(e.getX(), e.getY());
if(child!=null&&gestureDetector.onTouchEvent(e))
{
         /*Log.d(TAG, "getChildAdapterPosition=>" + rv.getChildAdapterPosition(child));
         Log.d(TAG,"getChildLayoutPosition=>"+rv.getChildLayoutPosition(child));
         Log.d(TAG,"getChildViewHolder=>" + rv.getChildViewHolder(child));*/
//Toast.makeText(getApplication(), count.get(rv.getChildAdapterPosition(child)).toString(), Toast.LENGTH_SHORT).show();

Log.d(TAG,"AdapterPosition=>"+rv.findViewHolderForAdapterPosition(rv.getChildLayoutPosition(child)));
Log.d(TAG,"LayoutPosition=>"+rv.findViewHolderForLayoutPosition(rv.getChildLayoutPosition(child)));
Log.d(TAG, "getChildViewHolder=>" + rv.getChildViewHolder(child).itemView);
//TextView tv = (TextView) rv.findViewHolderForAdapterPosition(rv.getChildLayoutPosition(child)).itemView.findViewById(R.id.tv);
//TextView tv = (TextView) rv.findViewHolderForLayoutPosition(rv.getChildLayoutPosition(child)).itemView.findViewById(R.id.tv);
TextView tv = (TextView) rv.getChildViewHolder(child).itemView.findViewById(R.id.tv);

Toast.makeText(getApplication(),tv.getText().toString(), Toast.LENGTH_SHORT).show();

}
return false;
}



3.MainActivity.java 완성

public class MainActivity extends Activity
{
RecyclerView rv;
LinearLayoutManager llm;
List<String> count = null;
Button btn;
EditText et = null;
String text;

private final static String TAG = "ITPANGPANG";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

et = (EditText) findViewById(R.id.et);
rv = (RecyclerView) findViewById(R.id.rv);
btn = (Button) findViewById(R.id.btn);
llm = new LinearLayoutManager(this);
rv.setHasFixedSize(true);
rv.setLayoutManager(llm);


rv.setItemAnimator(new DefaultItemAnimator());


count = new ArrayList<>();

btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
text = et.getText().toString();
count.add(text);
rv.setAdapter(new CountAdapter(getApplication(), count, text));
}
});

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

rv.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
Log.d(TAG, "onInterceptTouchEvent");
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null && gestureDetector.onTouchEvent(e)) {
/*Log.d(TAG, "getChildAdapterPosition=>" + rv.getChildAdapterPosition(child));
Log.d(TAG,"getChildLayoutPosition=>"+rv.getChildLayoutPosition(child));
Log.d(TAG,"getChildViewHolder=>" + rv.getChildViewHolder(child));*/
//Toast.makeText(getApplication(), count.get(rv.getChildAdapterPosition(child)).toString(), Toast.LENGTH_SHORT).show();
Log.d(TAG, "AdapterPosition=>" + rv.findViewHolderForAdapterPosition(rv.getChildLayoutPosition(child)));
Log.d(TAG, "LayoutPosition=>" + rv.findViewHolderForLayoutPosition(rv.getChildLayoutPosition(child)));
Log.d(TAG, "getChildViewHolder=>" + rv.getChildViewHolder(child).itemView);
//TextView tv = (TextView) rv.findViewHolderForAdapterPosition(rv.getChildLayoutPosition(child)).itemView.findViewById(R.id.tv);
//TextView tv = (TextView) rv.findViewHolderForLayoutPosition(rv.getChildLayoutPosition(child)).itemView.findViewById(R.id.tv);
TextView tv = (TextView) rv.getChildViewHolder(child).itemView.findViewById(R.id.tv);
Toast.makeText(getApplication(), tv.getText().toString(), Toast.LENGTH_SHORT).show();
}
return false;
}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
Log.d(TAG, "onTouchEvent");
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
Log.d(TAG, "onRequestDisallowInterceptTouchEvent");
}
});
}
}


- 개발을 하다보면 평생 한번 사용할 코드와, 몇번 사용할 코드, 늘 사용하는 코드가 있다.

- 평생 한번 사용할 코드를 구해서 사용할때 만약 내가 쓸 기능과 완전히 똑같다면 솔직히 코드 볼 필요도 없다고 생각한다.

- RecyclerView는 ListView가 점차 사라지면서 아마 늘 사용하는 View가 될 것이다.

- 시간이 여유로운 사람들은 직접 테스트 해보면서 RecyclerView의 매력에 빠져보길 권한다.


참고사이트 : http://developer.android.com/intl/ko/reference/android/support/v7/widget/RecyclerView.html


저작자 표시 비영리 변경 금지
신고

'안드로이드(android) > Recyclerview' 카테고리의 다른 글

RecyclerView(Item Click) 2탄  (5) 2016.04.07
RecyclerView(Item Click) 1탄  (0) 2016.04.06
RecyclerView.Adapter, ViewHolder(추가)  (1) 2016.04.03
RecyclerView.Adapter, ViewHolder  (1) 2016.04.02
RecyclerView 기본  (2) 2016.04.02
RecyclerView  (0) 2016.04.01
이 댓글을 비밀 댓글로
    • 박창현
    • 2016.04.08 00:57 신고
    감사합니다. 잘보고갑니다~

    제 코드에서 Main에서 클릭하는 부분이 필요했는데

    전 ChildAt()으로 접근하려고 했는데 에러가 계속 떠서 고민했었는데..

    완전 동일한 경우는 아니었지만 도움받아서 해결하고 갑니다.
    • 해결하셨다니 다행이네요
      RecyclerView에서 childAt() 사용한 코드가 있었는데
      다음번에 관련해서 글 한번 올리겠습니다.
    • 김기훈
    • 2016.07.29 14:43 신고
    딱 제가 고민해 하던 부분이었습니다.
    도음이 많이 되었습니다. 감사합니다.
    • Thanks
    • 2017.01.24 17:10 신고
    리사이클러뷰에 대해서 어디서도 이렇게 명쾌한 해설을 찾지 못했는데 ViewPager를 검색해서 들어왔다가 모든 안드로이드 포스팅을 보고 있습니다. 알아야할 것과 알지 않아도 되는 것을 구분해주시고 친절하고 쉽고 편한 예제들로 알려주셔서 정말 감사 말씀 드립니다.
    번외로 drawable에서 모양을 주는 코드들(shape, selector 등등 )은 검색을 해도 이해가 되지 않는데 혹시 그 부분에 대해서도 포스팅을 할 예정인지 여쭤보고 싶습니다.
    정말 감사말씀드립니다 :)
      • Thanks
      • 2017.01.24 17:11 신고
      아! xml에 관한 카테고리가 있군요! 허허