선택 기능을 지원하는 커스텀 리스트뷰 만드는 방법. (How to make custom choice ListView)

2016. 8. 9. 11:20


1. 선택 기능을 가진 커스텀 ListView의 필요 조건.

"다중 선택" 기능을 지원하는 "커스텀 ListView"를 만들기 위해서 앞서 살펴본 [안드로이드 커스텀 리스트뷰 만드는 방법]의 내용과 [안드로이드 리스트뷰 다중 선택 처리하기]의 내용을 적절히 혼합하면 될 것 같지만, 중요한 한 가지가 빠져 있습니다. 그것은 바로 ListView 아이템이 선택 가능(Checkable)한지, 그리고 현재 선택(Checked)되어 있는지 판단할 수 있는 기능이 추가되어야 한다는 것입니다. 무슨 의미인지 선뜻 이해가 되지 않죠?


일반적인 Custom ListView 만드는 방법으로 다중 선택 기능을 만든다고 가정해보죠.


일반적으로 선택(Checked) 여부를 표시하기 위해 ListView 아이템 Layout에 CheckBox를 배치할 것입니다. CheckBox는 선택(Checked) 기능을 포함하고 있으므로, CheckBox를 배치하는 것만으로 ListView 아이템 선택 기능을 사용할 수 있을 것 같네요. 하지만 그렇지 않습니다. 여기서 필요한 건 ListView 아이템 자체가 선택 가능(Checkable)한지 여부인 것이지, ListView 아이템 내의 CheckBox가 선택(Checked)되어 있는지가 아니기 때문입니다.

리스트뷰 선택 범위


"ListView 아이템에 속한 CheckBox의 선택(Checked) 여부와 ListView 아이템 자체의 선택(Checked) 여부가 무슨 차이가 있는가?", "어차피 CheckBox도 ListView 아이템에 속한 것이니 똑같은 거 같은데?" 이렇게 생각할 수도 있습니다.


그렇다면 [안드로이드 리스트뷰 다중 선택 처리하기]에서 본 대로, ListView의 선택된 아이템을 얻어오기 위해 ListView의 멤버 함수인 getCheckedItemPositions() 함수를 호출하게 되는 경우를 생각해보죠. getCheckedItemPositions() 함수는 ListView의 아이템이 선택(Checked) 상태인지를 리턴합니다. 아이템 내의 CheckBox 상태를 확인하는 것이 아니고, ListView 아이템 자체의 선택(Checked) 상태를 확인하는 것입니다.


만약 CheckBox의 선택(Checked) 상태를 확인하려면 ListView를 상속받는 새로운 클래스를 만들고 getCheckedItemPositions() 함수를 오버라이딩 한 다음, 해당 함수 내에서 CheckBox의 선택 여부를 판단해야겠죠. 음... 복잡하네요. 역시 ListView 아이템 자체가 선택 가능(Checkable)하도록 만드는 게 더 명료하겠네요.


결론적으로 선택(Checked) 기능을 지원하는 Custom ListView를 만들기 위해서는 ListView 아이템 자체에 선택(Checked) 여부를 판단할 수 있는 기능이 추가되어야 합니다.

1.1 Checkable 인터페이스

ListView 아이템에 선택(Checked) 여부를 판단할 수 있는 기능을 추가하려면 어떻게 해야 할까요? 바로 ListView 아이템의 루트(Root) 역할을 하는 ViewGroup이 Checkable 인터페이스를 implements 하도록 만들면 됩니다.


여기서 말하는 "루트(Root) 역할을 하는 ViewGroup"이란 ListView 아이템을 구성할 때 사용하는 Layout 리소스 XML 파일의 최상위 Layout 클래스(LinearLayout, RelativeLayout 등)를 지칭하는 것입니다.


예제를 작성하는 과정에서 다시 보겠지만, Checkable 인터페이스를 implements하는 Layout 클래스를 추가하여 사용하는 방법은 간단합니다. 원하는 종류의 Layout 클래스에서 상속받은 다음 Layout 클래스를 만들어서, 넣기만하면 되니까요.

package com.recipes4dev.examples.customchoicelistviewexample1;

class CheckableLinearLayout extends LinearLayout implements Checkable {
    // ... 
}
<com.recipes4dev.examples.customchoicelistviewexample1.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Deploy widgets for ListView Item -->

</com.recipes4dev.examples.customchoicelistviewexample1.CheckableLinearLayout>

2. 다중 선택 기능을 지원하는 커스텀 ListView 만드는 방법.

2.1 다중 선택 예제 구성도

다중 선택 예제를 위한 ListView 아이템은 ImageView, TextView, CheckBox 위젯으로 구성됩니다. 다중 선택 예제를 위한 예제 구성도는 다음과 같습니다.

custom choice listview item layout


2.2 ListView 추가.

먼저 ListView가 추가된 Layout 리소스 XML을 작성합니다. 다중 선택 기능을 위해 ListView의 "choiceMode" 속성에 "multipleChoice" 값을 지정합니다.

[STEP-1] "activity_main.xml" - MainActitivy에 ListView 추가.
    <ListView
        android:id="@+id/listview1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:choiceMode="multipleChoice" />

2.3 ListView 아이템 Layout 구성.

ListView 아이템을 위한 Layout 리소스 XML을 작성합니다. 이 때 사용하는 아이템의 루트(Root)는, 위에서 잠깐 설명한대로 Checkable 인터페이스를 implements한 ViewGroup이어야 하므로 다음 단계에서 정의할 "CheckableLinearLayout"을 지정합니다.

[STEP-2] "listview_item.xml" - ListView 아이템 Layout 구성.
<?xml version="1.0" encoding="utf-8"?>
<com.recipes4dev.examples.customchoicelistviewexample1.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="blocksDescendants">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/imageView1"
        android:layout_weight="1" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:text="TEXT"
        android:id="@+id/textView1"
        android:textSize="20sp"
        android:gravity="center"
        android:layout_weight="5" />

    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/checkBox1"
        android:focusable="false"
        android:clickable="false"
        android:layout_weight="1" />

</com.recipes4dev.examples.customchoicelistviewexample1.CheckableLinearLayout>

2.4 Checkable 인터페이스를 implements한 LinearLayout 클래스 정의

다음으로 LinearLayout 클래스를 상속하고, Checkable 인터페이스를 implements한 클래스를 추가합니다.

[STEP-3] "CheckableLinearLayout.java" - Checkable 인터페이스를 implements 한 LinearLayout 클래스 정의.
public class CheckableLinearLayout extends LinearLayout implements Checkable {

    // 만약 CheckBox가 아닌 View를 추가한다면 아래의 변수 사용 가능.
    // private boolean mIsChecked ;

    public CheckableLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        // mIsChecked = false ;
    }

    @Override
    public boolean isChecked() {
        CheckBox cb = (CheckBox) findViewById(R.id.checkBox1) ;

        return cb.isChecked() ;
        // return mIsChecked ;
    }

    @Override
    public void toggle() {
        CheckBox cb = (CheckBox) findViewById(R.id.checkBox1) ;

        setChecked(cb.isChecked() ? false : true) ;
        // setChecked(mIsChecked ? false : true) ;
    }

    @Override
    public void setChecked(boolean checked) {
        CheckBox cb = (CheckBox) findViewById(R.id.checkBox1) ;

        if (cb.isChecked() != checked) {
            cb.setChecked(checked) ;
        }

        // CheckBox 가 아닌 View의 상태 변경.
    }
}

Checkable 인터페이스를 implements 하면 3개의 abstract 함수를 override해야 합니다. 각 함수에 대한 간략한 설명은 다음과 같습니다.

  1. boolean isCheck() : 현재 Checked 상태를 리턴.
  2. void setChecked(boolean checked) : Checked 상태를 checked 변수대로 설정.
  3. void toggle() : 현재 Checked 상태를 바꿈. (UI에 반영)

위의 세 가지 함수를 의미에 맞게 override하면 나머지는 ListView에서 발생하는 이벤트에 따라 적절한 함수가 호출됩니다.


그리고 예제에서는 아이템의 Checked 상태를 표시하기 위해 CheckBox 위젯을 사용했습니다. CheckBox는 자체적으로 Checked 상태를 관리할 수 있는 함수를 제공하기 때문에 별 다른 상태 관리 없이 제공되는 함수를 그대로 이용할 수 있죠. 하지만 만약 CheckBox 대신 ImageView 또는 TextView 위젯등을 사용한다면, Checked 상태를 별도로 관리해야 하므로, 위의 코드에서 주석으로 작성된 "mIsChecked" 변수를 사용하면 됩니다.

2.5 ListView 아이템 데이터 클래스 정의.

ListView 아이템에 출력될 데이터를 위한 클래스를 정의합니다. ListView 아이템이 ImageView와 TextView로 구성되므로, 각각 Drawable과 String으로 변수를 정의합니다. (CheckBox는 아이템 선택 여부를 표시하기 위한 용도이므로 매칭되는 변수를 정의하지 않았지만, 기능에 따라 boolean 변수를 추가하여 CheckBox의 초기 선택 여부를 위한 변수로 활용할 수 있습니다.)

[STEP-4] "ListViewItem.java" - ListView 아이템 데이터 클래스 정의.
public class ListViewItem {
    private Drawable icon ;
    private String text ;

    public void setIcon(Drawable icon) {
        this.icon = icon ;
    }
    public void setText(String text) {
        this.text = text ;
    }

    public Drawable getIcon() {
        return this.icon ;
    }

    public String getText() {
        return this.text ;
    }
}

2.6 ListView 아이템 View를 위한 Adapter 구현.

Adapter 클래스를 생성 및 정의합니다.

[STEP-5] : "CustomChoiceListViewAdapter.java" - BaseAdapter 상속 및 CustomChoiceListViewAdapter 구현.
public class CustomChoiceListViewAdapter extends BaseAdapter {
    // Adapter에 추가된 데이터를 저장하기 위한 ArrayList
    private ArrayList<ListViewItem> listViewItemList = new ArrayList<ListViewItem>() ;

    // ListViewAdapter의 생성자
    public CustomChoiceListViewAdapter() {

    }

    // Adapter에 사용되는 데이터의 개수를 리턴. : 필수 구현
    @Override
    public int getCount() {
        return listViewItemList.size() ;
    }

    // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴. : 필수 구현
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final int pos = position;
        final Context context = parent.getContext();

        // "listview_item" Layout을 inflate하여 convertView 참조 획득.
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.listview_item, parent, false);
        }

        // 화면에 표시될 View(Layout이 inflate된)으로부터 위젯에 대한 참조 획득
        ImageView iconImageView = (ImageView) convertView.findViewById(R.id.imageView1) ;
        TextView textTextView = (TextView) convertView.findViewById(R.id.textView1) ;

        // Data Set(listViewItemList)에서 position에 위치한 데이터 참조 획득
        ListViewItem listViewItem = listViewItemList.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        iconImageView.setImageDrawable(listViewItem.getIcon());
        textTextView.setText(listViewItem.getText());

        return convertView;
    }

    // 지정한 위치(position)에 있는 데이터와 관계된 아이템(row)의 ID를 리턴. : 필수 구현
    @Override
    public long getItemId(int position) {
        return position ;
    }

    // 지정한 위치(position)에 있는 데이터 리턴 : 필수 구현
    @Override
    public Object getItem(int position) {
        return listViewItemList.get(position) ;
    }

    // 아이템 데이터 추가를 위한 함수. 개발자가 원하는대로 작성 가능.
    public void addItem(Drawable icon, String text) {
        ListViewItem item = new ListViewItem();

        item.setIcon(icon);
        item.setText(text);

        listViewItemList.add(item);
    }
}

2.7 Adapter 생성 및 아이템 추가.

MainActivity 클래스의 onCreate() 함수에서 Adapter를 생성하고, ListView에 지정합니다.

[STEP-6] : Adapter 생성 및 아이템 추가.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // 코드 계속 ... 

        ListView listview ;
        CustomChoiceListViewAdapter adapter;

        // Adapter 생성
        adapter = new CustomChoiceListViewAdapter() ;

        // 리스트뷰 참조 및 Adapter달기
        listview = (ListView) findViewById(R.id.listview1);
        listview.setAdapter(adapter);

        // 첫 번째 아이템 추가.
        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_account_box_black_36dp),
                "Account Box Black 36dp") ;
        // 두 번째 아이템 추가.
        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_account_circle_black_36dp),
                "Account Circle Black 36dp") ;
        // 세 번째 아이템 추가.
        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_assignment_ind_black_36dp),
                "Assignment Ind Black 36dp") ;
    }
}

3. 다중 선택 커스텀 ListView 실행 화면.

예제 코드를 작성하고 실행하면, 다음과 같은 화면이 표시됩니다.

다중 선택 커스텀 ListView 실행 화면


그리고 이 글의 앞 부분에서 설명한대로 CheckBox가 아닌 ListView 아이템 영역 전체를 선택하면 아이템 선택 표시가 변경되는 것을 확인할 수 있습니다.

다중 선택 커스텀 ListView 실행 화면2


4. 추가 작업.

4.1 단일 선택(singleChoice) 모드로 사용하기.

예제의 소스 코드는 다중 선택(multipleChoice) 모드를 기준으로 작성되어 있는데, 만약 단일 선택(singleChoice)로 사용하고자 한다면, [STEP-1]의 코드 내용 중, "android:choiceMode" 속성을 "singleChoice"로 변경하면 됩니다.

4.2 아이템 추가, 삭제, 수정 기능 추가하기.

아이템에 대한 추가, 수정, 삭제 기능은 단일 선택(singleChoice) 모드를 사용할 경우, [안드로이드 리스트뷰 아이템 추가,수정,삭제]의 내용을 참고하시면 되고, 다중 선택(multipleChoice) 모드를 사용한다면 [안드로이드 리스트뷰 다중 선택 처리하기]의 예제 코드를 그대로 활용할 수 있습니다.

5. 참고.

.END.


ANDROID 프로그래밍/LISTVIEW , , , , , , , ,

  1. 이전 댓글 더보기
  2. Blog Icon
    체크박스안되는거같아요

    예제대로해서 실행하면 리스트뷰를 터치해도 체크버튼이 체크가 되지를 않네요

  3. 혹시 리스트뷰에 choiceMode 속성 값을 지정해 줬나요?

    android:choiceMode="multipleChoice"

    작성하신 코드와 예제 코드를 다시 한번 잘 비교해 보세요.

    아마 조금 다르게 작성하신 내용이 있으실 것 같습니다.

    감사합니다.

  4. Blog Icon
    질문입니다

    지금 게시물에서 있는 CheckableLinearLayout class는 제가 보기에는 사용하지 않는 것 같은데 혹시 사용이 되고 있는 건가요 ?

    -----
    위에분 댓글 천천히 읽어보다가
    xml에 import하신것을 발견하였습니다 감사합니당

  5. 네. 본문의 소스를 조금 더 깔끔하게 작성했다면, 헤매지 않으셨을텐데.. 혼동을 드려 죄송합니다.

    방문해 주셔서 감사합니다.

  6. Blog Icon
    어렵다ㅜㅜ

    안녕하세요.. 정말 많은 도움이 되었습니다. 감사합니다.
    그런데, 오늘 하루종일 고민해봤지만 제대로 답을 찾지 못해 문의드립니다.

    1. 제가 구현하고 싶은 것은, 체크된 상태를 보관하고 있다가, 액티비티의 시작 때 그 상태를 체크박스에 표현하는 것 입니다.

    2. listview.setOnItemClickListener와 SharedPreference를 활용해서 선택상태를 저장하고 복원해서 쓰는 것 까지는 문제가 없습니다.

    3. 그런데, 아무리 고민해봐도 1번을 구현하질 못하겠습니다.
    위에 어떤분이 질문하신대로, 체크박스 자체를 setChecked 하는 것은 의미가 없는것은 이해가 되는데,

    어떻게 해야 getView에서 뷰 하나에 대한 선택 여부를 미리 표현해 둘 수 있을까요??
    (데이터는, 선택 여부를 저장해둔 boolean 배열을 이용하려 합니다.)

    *본문글중에, ""기능에 따라 boolean 변수를 추가하여 CheckBox의 초기 선택 여부를 위한 변수로 활용할 수 있습니다."" 를 활용하는 것 같긴 한데, 조금만 상세히 알려주실수 없을까요?


    감사합니다.

  7. 문제를 좀 어렵게 생각하신 것 같습니다.
    getView() 메서드에서 선택 여부를 표시하지 않아도 됩니다.

    리스트 뷰에 아이템을 로드한 다음, 리스트뷰의 getView()가 아닌 리스트뷰 외부에서 ListView의 setItemChecked()를 통해 리스트의 체크 상태를 변경하시면 됩니다.

    답변이 되었는지 모르겠네요.
    잘 이해되지 않는 내용은 다시 질문글 남겨주세요.

    감사합니다.

  8. Blog Icon
    어렵다ㅜㅜ

    안녕하세요. 윗글 질문자인데요, 뽀따님 댓글보고 1분만에 문제해결 했습니다.

    답변 너무 감사드립니다. 혼자 취미로 개발중이라 물어볼 곳도 마땅치 않은데, 좋은 자료에 답변까지 너무 감사드려요. 좋은 주말 보내세요!!!

  9. 문제해결에 도움을 드릴 수 있어서 다행입니다.
    혼자서 개발하는게 쉽지 않을텐데, 열정이 대단하시네요.

    언제든 도움이 필요하면 댓글 남겨주세요.
    최대한 도움드릴 수 있도록 노력하겠습니다.

    감사합니다.

  10. 정말 큰 도움이 돼었는데요, 혹시 리스트뷰가 아닌 체크박스와 리사이클러뷰로 글을 써주실수 있으신가요.... 구글링 하는데 잘 안나와서요 초보개발자는 웁니다 ㅠㅠ

  11. 계획에는 다 담겨져 있지만, 요즘 너무 정신이 없네요.
    지금 정리해놓은 글들도 마무리해서 올려놔야 하는데, 전혀 손을 못대고 있습니다.
    가끔 들어와서 이렇게 답글만 다는 정도...

    이게 바로 슬럼프 인가 싶기도 하고 그러네요.

    그래도 좀 더 노력해서 더 많은 내용 담을 수 있도록 노력하겠습니다.

    감사합니다.

  12. 초보 개발자로서 어려움에 처할때마다 뽀따 님의 정리글에 큰 힘을 얻네요!
    매일 매일 들어올때마다 정말 감사함을 느끼고 갑니다!

  13. 그렇게 칭찬받을만한 내용들은 아닌데..
    자주 방문해주시고, 이렇게 격려의 댓글까지 남겨주셔서 정말 감사합니다.

  14. 아 질문이 있는데요!
    현재 앱을 만들고 있는데 textView와 CheckBox만 있는 간단한 세로형 목록 화면을 만들고 있습니다.
    이때 RecyclerView가 더 효율이 높다해서 이 글을 참조하고, 뽀따님의 RecyclerView글도 참조해 만들려 해봤습니다.
    RecyclerView로 화면을 만들었을 때 화면은 잘 뜨지만, 체크박스가 체크되지 않더라고요.

    그래서 제 질문은 간단한 textView만 있는경우 ListView도 큰 문제가 되지 않는다는 글을 본것 같은데 그냥 RecylcerView대신 ListView를 사용해도 큰 문제가 생기지 않을까요?

  15. 전혀 문제가 되지 않습니다.
    어차피 쓰라고 만들어놓은 것이고, 동작에 문제가 없기 때문에 ListView를 쓰셔도 됩니다.

    하지만.. 이왕이면 RecyclerView를 사용하시는 게 좋겠죠. 구글에서 권고하기도 하고..

    한번 잘 익혀두면 활용하는 건 어렵지 않으니, 지금 당장 어려워도 앞으로 자주 사용할 것들을 익혀두시는 게 좋을 것 같습니다.

    감사합니다.

  16. button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    for(int i=0;i<adapter.listViewItemList.size();i++){
    if(adapter.listViewItemList.get(i).isChecked) {
    s1.ListViewCategory.add(adapter.listViewItemList.get(i).getText());
    }
    }
    Intent intent = new Intent(getApplicationContext(), Select.class);
    startActivity(intent);
    finish();
    }
    });

    뽀따님 질문이 있는데요..
    위의 코드는 뽀따님의 ListView예제를 그대로 따라한 후 그걸 조금 응용한 코드인데요
    버튼을 눌렀을때 만약 특정 row가 체크되었다면 그 row의 String(그 줄의 글자들)을
    Singleton 클래스의 ArrayList에 저장하는 기능을 만들려합니다.
    이때 if문안에 isChecked때문에 빨간줄이 뜨는데요, isChecked()는 Checkable Layout클래스의 메소드 여서, listViewItemList의 객체들이 체크되었는지를 확인하기 위해 쓸 수 가 없네요...

    제가 궁금한점은 위에서 각 객체들이 체크박스가 체크되었다면 체크여부를 어떤 변수 또는 메소드를 통해 확인할수 있는지 가 궁금하네요...
    혹시 시간이 있으실 때 답변해주시면 감사하겠습니다!

  17. Blog Icon
    DirkDoncic

    // 아이템 데이터 추가를 위한 함수. 개발자가 원하는대로 작성 가능.
    public void addItem(Drawable icon, String text) {
    CategoryItem06 item = new CategoryItem06();

    // item.setIcon(icon);
    item.setText(text);

    listViewItemList.add(item);
    }

    전 Item클래스에
    setCheck(), getCheck()만들고
    add(int drawable, String text,boolean check){
    setCheck
    }
    뭐 이런식으로 해보려고 했는데요
    Activity에서 add시 맨 마지막 매개변수에 뭘 넣어야 할지 막막해서 결국 실패했네요.. 어떤 방법이 있을까요?

  18. 아래 질문에 답글 정리할게요.

  19. Blog Icon
    익명

    비밀댓글입니다

  20. 제가 이해한 바로는, 지금 만드려고 하는 내용이,
    리스트뷰 아이템에 체크박스를 배치하고, 해당 체크박스가 체크된 아이템에 대한 정보(텍스트 등)를 어딘가에 저장하려고 하시는 것 같습니다.

    일단, 구현하시고자 하는 기능은 본문의 내용을 참고하시면 어렵지 않게 만드실 수 있을거라 생각합니다.
    아니면 본문의 내용 말고 답글 아래 부분에 적혀 있는 링크를 먼저 확인해보시는 것도 도움이 될 것 같습니다.
    음, 본문의 내용을 그대로 따랐다면, 리스트뷰 아이템 선택 정보가 리스트뷰에 저장되고,
    리스트뷰의 getCheckedItemPositions() 메서드를 통해 읽어와서 사용할 수 있다는 것을 알 수 있을텐데요.

    만약 본문에서 설명한 리스트뷰 Checked 상태를 이용하지 않고, 아이템 내부의 특정 요소(체크박스와 같은)의 상태를 별도로 관리하시려고 한다면, 해당 이벤트에 대한 처리는 추가적으로 처리해주셔야 합니다.

    하지만 질문글의 내용만으로 볼 때, 본문 또는 아래 링크의 내용으로 충분히 구현할 수 있을 것 같습니다.
    위의 질문글에서 isChecked 에서 에러가 난다고 하셨는데, 일단 isChecked에 "()"를 붙이지 않으시기도 하셨지만, isChecked() 메서드는 리스트뷰 "아이템 데이터"의 메서드가 아닙니다. 리스트뷰 "아이템 뷰"의 메서드이죠. (CheckableLinearLayout를 통해 제공되는)

    그리고 isChecked() 메서드는 리스트뷰 외부에서 아이템의 체크 여부를 판단할 때 사용하는 메서드가 아니라, 리스트뷰 아이템 선택 시 리스트뷰 내부에서 체크 상태를 판단할 때 사용하는 메서드입니다.
    리스트뷰 외부에서 아이템 체크 여부를 판단할 때는, getCheckedItemPosition() 또는 getCheckedItemPositions() 메서드를 사용합니다.
    사용법은 아래 링크를 확인해 주세요.

    조금 어렵고 헷갈리실 수 있겠지만, 선택 기능을 가진 리스트뷰가 어떻게 동작하는지 다시 한번 살펴보실 필요가 있을 것 같네요.
    답글의 내용이 잘 이해가지 않으시면 다시 질문글 남겨주세요.

    리스트뷰 선택 기능 관련 참고 링크.
    https://recipes4dev.tistory.com/48 (리스트뷰 아이템 추가, 수정, 삭제)
    https://recipes4dev.tistory.com/59 (리스트뷰 다중 선택 처리하기)
    https://recipes4dev.tistory.com/68 (선택 기능을 가진 커스텀 ListView의 필요 조건)

    감사합니다.

  21. ㅋㅋ 너무너무 감사합니다... 답변이 너무나 필요했어요 ㅠㅠ

  22. 답글을 남겨드리긴 했는데, 제대로 된 답변이 되었는지는 모르겠네요.

    답변의 내용대로 잘 살펴보시고, 추가적으로 궁금한 점이나 도움드릴 내용이 있으면 다시 질문글 남겨주세요.

    감사합니다.

  23. Blog Icon
    익명

    비밀댓글입니다

  24. 질문글에 대한 해답을 찾기 위해 어떻게 코드를 작성해야 하는가.....보다는 어떤 논리적 흐름으로 처리할 것인지를 먼저 고민하셔야 할 것 같습니다.

    사실, 질문글에 작성해주신 코드의 내용을 보면, 어떻게든 구현은 하려고 했지만... 어떠한 흐름으로 처리해야 할지에 대한 고민은 안 한 것으로 보이거든요.
    (그러니깐 질문글 남겨주셨겠지만...)

    조금 직설적으로 표현하자면 "고민없이" 코드부터 작성해보았다... 라는 느낌.

    어떤 처리를 루프로 돌려야 하는지, 어떤 값을 비교해야 하는지... 등에 대해 조금 더 고민하셔야 할 것 같습니다.
    (물론, 딱 한 가지 방법만 있는 것도 아닙니다.)

    특히, "WHERE와 for 문을 동시에 쓰는 예제가 없다"라고 하는 게 정말 이상하거든요. ^^;; (그런 코드가 너무나 많아서 없다고 느끼는... 왜 그런 예제가 따로 있어야 하는지 의문이 드는 느낌적인 느낌...)

    어쨌든 코드부터 작성하지 마시고,
    말로, 그림으로, 글로 설명할 수 있는 논리적 흐름을 먼저 정리해보시길 바랄게요.

    감사합니다.

  25. 음.. 좀더 뽀따님말대로 논리적 흐름을 정리해봤는데요 WHERE NOT IN이라는 SQLite쿼리문을 이용하는 방법을 공부하는 도중 알게 되었습니다. NOT IN을 써서 해보니 잘되는 것 같네요. 조언 감사합니다!

  26. 네. 만들고자 하는 내용에 딱 알맞는 API를 만나는 것도 참 기분 좋은 일이죠.

    하지만 이번에 질문하신 내용에 대해서는, 조금 더 기본에 충실한 방법으로 접근하시는 게 더 좋지 않을까.... 생각이 드네요.

    그래도, 가능한 방법을 찾으셨다니 다행입니다. ^^

  27. 뽀따님 잘된 것 같았는데 에러가 뜨네요.. Logcat을 봐도 잘 답이 안보여요...
    밑은 제 코드입니다

    public void DBSearchCategory(String tableName) {
    String inClause = s1.ListViewCategory.toString();
    // inClause에 s1(싱글톤클래스)의 ArrayList인 ListViewCategory에 저장된 체크된 카테고리들 넣기

    inClause = inClause.replace("[", "(");
    inClause = inClause.replace("]", ")");
    // [ ] 를 ( ) 로 바꿔주기

    Cursor cursor = database.rawQuery("SELECT CATEGORY FROM " + tableName
    + " WHERE CATEGORY NOT IN " + inClause
    + " ORDER BY RANDOM() LIMIT 1 ", null);

    이렇게 했는데 이런 Logcat이 뜨더라고요

    Error Code : 1 (SQLITE_ERROR)
    Caused By : SQL(query) error or missing database.
    (near "그룹": syntax error (code 1): , while compiling: SELECT CATEGORY FROM KeyWordDB WHERE CATEGORY NOT IN (애완동물, 아이돌 그룹) ORDER BY RANDOM() LIMIT 1)

    이때 Database가 있다는 건 확인했고요, 그럼 query문제일텐데.. query 중 어떤 걸 잘못썻는지 잘 감이 안잡히네요...

  28. 에러 메시지를 보면 SQL 문에서 에러가 발생했네요.

    near "그룹" 이라고 되어 있으니.. "그룹" 근처에서 에러가 난 것일텐데..

    그런데 왜 "그룹"이라는 글자에만 문제가 있다고 할까요? "애완동물"에서는 문제가 생기지 않고, "아이돌 그룹"에 문제가 있는 것도 아니고...

    이 때 가장 쉽게 문제를 찾을 수 있는 방법은, 문제가 된 SQL 문장을 직접 DB 매니저 프로그램을 통해 실행시켜 보는 것입니다.

    그리고 문제가 된 곳의 코드를 수정해가면서 무엇이 문제인지 확인해보는 거죠.

    가령,
    NOT IN()에 사용된 필드 수를 줄여본다던가...
    NOT IN()에 포함된 필드에 공백을 없애본다던가...
    NOT IN()에 포함된 필드를 '' 또는 ""로 둘러싸 본다던가...

    감이 안 잡힐수록.. 이것 저것 많이 시도해보세요.
    그럼 해결책에 대한 실마리를 얻을 수 있을 것입니다.

    감사합니다.

  29. Blog Icon
    안드로이드 초보

    안녕하세용~//
    포스팅된 자료를 보고 체크박스 리스트를 만들어 볼 수 있었습니다.
    여기서 궁금한 점은, 저는 한 행당 체크박스가 1개인걸 2개로 추가했습니다.
    여기서 문제가
    예를들어, 체크박스가 yes, no라는 체크박스가 있다고 가정했을 때,
    양자택일 방법을 구현하려 합니다만, 쉽지가 않네요.

    adapter의 getView 메서드에서 다음과 같이 코드를 작성해 보았습니다
    다음 코드는 액티비티 화면에서 정상 작동하는 코드였습니다.
    하지만 어댑터에서 구현을 해도 작동이 되지 않더라구요.
    그것을 작성자님의 포스팅된 글을보고 이해할 수 있었습니다.
    체크박스가 체크상태를 가진것이 아니라, 리스트자체가 체크된 상태를 가지고 있음을요.

    아무튼 각설하고,
    코드는 다음과 같습니다.
    //////////////////////////////////////////
    //yes버튼 눌렀을 시
    chk_yes.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if(chk_yes.isChecked() == true && chk_no.isChecked() == false )
    {
    System.out.println("확인 체크된 상태");
    chk_no.setOnClickListener(new CheckBox.OnClickListener() {
    @Override
    public void onClick(View v) {
    chk_no.setChecked(true);
    chk_yes.setChecked(false);
    }
    });


    } else if (chk_no.isChecked() == true && chk_yes.isChecked() == false) {
    System.out.println("확인안함 체크된 상태");
    chk_yes.setOnClickListener(new CheckBox.OnClickListener() {
    @Override
    public void onClick(View v) {
    chk_yes.setChecked(true);
    chk_no.setChecked(false);
    }
    });
    }
    }
    });
    //no버튼 눌렀을 시
    chk_no.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if(chk_yes.isChecked() == true && chk_no.isChecked() == false )
    {
    System.out.println("확인 체크된 상태");
    chk_no.setOnClickListener(new CheckBox.OnClickListener() {
    @Override
    public void onClick(View v) {
    chk_no.setChecked(true);
    chk_yes.setChecked(false);
    }
    });


    } else if (chk_no.isChecked() == true && chk_yes.isChecked() == false) {
    System.out.println("확인안함 체크된 상태");
    chk_yes.setOnClickListener(new CheckBox.OnClickListener() {
    @Override
    public void onClick(View v) {
    chk_yes.setChecked(true);
    chk_no.setChecked(false);
    }
    });
    }
    }
    });
    //////////////////////////////////////////

    이렇게 getView 메서드에 작성했으나, 코드가 먹히지 않아
    작성자님의 글 중에서 CheckableLinearLayout.java단의 isChecked, setChecked,toogle 메서드에
    각각 구현해보았습니다만, 해당코드가 전혀 정상작동하지 않더라구요..ㅠ
    문제가 무엇인지 분석해주셨으면 좋겠습니다.

  30. 답변에 앞서, 먼저 두 가지 사항에 대해 먼저 언급을 하겠습니다.

    본문의 내용이 "선택 기능을 지원하는 커스텀 리스트뷰"를 다루고 있긴 하지만, "선택(Checked)"이라는 기능이 필요한 모든 상황에서 적용 가능한 방법은 아닙니다.
    이 점은 명확하게 인지하고 있어야 하고요.

    그리고 본문의 서두에서도 언급했듯이, 본문에서 사용된 체크박스는 선택을 변경하기 위한 용도가 아닌, 변경된 선택사항을 화면에 표시하기 위한 용도입니다.
    본문에서 리스트뷰 아이템의 선택 기능은 "CheckBox가 선택(Checked)되어 있는지가 아니라, ListView 아이템 자체가 선택 가능한지에 의해 동작"한다고 언급되어 있죠.
    (위의 문장이 잘 이해되지 않으면 본문의 앞부분을 다시 한번 읽어보시기 바랍니다.)

    그런데 질문글에 올려주신 코드의 내용을 보면, 체크박스 두 개를 배치한 다음, 어떤 체크박스를 선택하는지에 따라 아이템 체크 여부를 결정하려고 하는 것 같습니다. 그렇다면, 본문의 내용과는 맞지 않죠.
    본문에서는 리스트뷰 아이템 전체에 대해 선택 기능이 동작하고, 개별 아이템에 대한 선택은 동작하지 않습니다.

    이런 경우, 굳이 본문의 예제처럼 Checkable 인터페이스를 통해 처리할 필요가 있을까... 의문이 드는데요.
    그래도 만약 본문의 내용을 참고해서 작성하고 싶다면,

    1. 리스트뷰 아이템 레이아웃에서 아이템 전체가 아닌, 개별 아이템이 클릭 가능하도록 변경.
    --> "listview_item.xml" 파일에서 android:descendantFocusability 값을 다른 값으로 지정.
    2. 각 체크 박스 선택에 따라, 리스트뷰 아이템 선택 여부를 변경.

    이렇게 구현해보시면 될 것 같습니다.

    감사합니다.

  31. Blog Icon
    Dawn

    CheckableLinearLayout.java를 수정해야 되는건가요?
    findViewById(R.id.checkBox1)을 하니 CheckableLinearLayout.java에 constant field 'R''을 만들어야 한다고 하는데 표현을 어떻게 해야되는 건가요?

  32. 소스가 어떻게 작성되어 있는지, 어떤 작업을 하려는지 잘 몰라서 어디가 문제인지는 정확히 알 수가 없는데요.

    일단 증상만 봐서는 리소스 빌드가 제대로 되지 않았을 수도 있어요.

    혹시 모르니 clean 한번 해본 다음, 다시 build해보세요.

    해보시고 잘 안되면 다시 질문글 남겨주세요.

    감사합니다.

  33. Blog Icon
    궁금이

    안녕하세요 좋은 글 항상 감사합니다.
    저는 리스트에 총 3개의 check박스가 있습니다. 리스트에 클릭 리스너로는 각각 눌렸을때 정의 가 안되서 어댑터에서 생성시 체크박스 리스너를 주고 있습니다.

    체크박스 클릭 시 convertView = inflater.inflate(R.layout.listview_item, parent, false);
    에서 지정된 경우 convertView.findViewById 이런식으로 접근이 가능합니다.

    하지만 저는 다른 layout에 있는 Textview에 접근하여 값을 수정 하고 싶을 경우 어떻게 진행해야 할까요? 인플레이터를 다른 것도 해와서 적용까지는 해봤는데 바로 업데이트가 안되던데 return convertView 여기에 인자를 써줄수도 없고요..힌트라도 부탁드립니다.

  34. 다른 layout에 있는 TextView... 가 정확히 어떤 걸 의미하는지에 따라 구현 방법이 다를 것 같습니다.

    리스트뷰의 같은 아이템뷰에 있는 TextView를 의미하는 것인지...
    리스트뷰 외부에 있는 TextView를 의미하는 것인지..

    리스트뷰의 같은 아이템뷰에 있는 TextView의 내용을 변경하시려면, TextView에 대한 참조를 직접 가져와서 값을 변경하겠다는 생각을 하지 마시고, "어댑터를 통해 데이터 값을 변경한 다음 리스트뷰를 다시 그린다"라고 접근하시면 될 것 같습니다.

    즉, findViewById로 뷰 참조를 가져와서 setText를 하는 게 아니라, 어댑터를 통해 전달했던 데이터 리스트의 값을 변경한 다음 notifyDataSetChanged() 등을 통해 리스트뷰를 갱신하도록 만드는 것입니다.

    만약 리스트뷰 외부에 있는 TextView를 변경하는 경우라면, 일단 TextView의 참조를 가져올 수 있는 경우 그대로 변경하시면 되고요.
    그게 힘든 경우, 액티비티 등에 리스너를 추가하여 리스트뷰 아이템을 액티비티로 전달할 수 있습니다.
    관련 내용은 "https://recipes4dev.tistory.com/45"에서 확인하실 수 있습니다.

    감사합니다.

  35. Blog Icon
    궁금이

    안녕하세요 위에 남겨주신 글은 잘 보고 해결 하였습니다. 뒷 기능을 구현하려고 했었는데 45번에 잘 되어있어서 해결했습니다.

    궁금한것이 listview의 특성인거 같은데 한번 스크롤이 일어나면 이전에 그려져서 업데이트된 내용이 사라지던데..예를 들면 1번리스트에 있는 체크박스를 체크하고 난 이후 스크롤을 내리고 올리면 사라져 있습니다. 이부분은 어떻게 해결해야 하는지요?ㅠ

  36. 리스트뷰는 기본적으로 화면에 보이는 아이템뷰를 재활용하도록 되어 있습니다.

    리스트뷰에 표시할 아이템뷰는 화면에 보이는 만큼만 만들어라... 라고 생각하면 됩니다.
    즉, 리스트뷰에 데이터가 100개 있다고 하더라도, 화면에 보여지는 아이템이 10개라면 딱 10개의 아이템 뷰만 유지하여 재활용한다... 라고 보시면 됩니다.

    그래서 스크롤이 발생되면, 현재 표시되어야 할 데이터를 아이템뷰에 업데이트하게 됩니다. 아이템뷰의 값을 바꿔 다시 그리게 되는 것이죠.

    여기까지 설명이 일반적인 리스트뷰의 아이템 갱신 과정이고요. 만약 체크박스를 일반적인 방법으로 아이템뷰에 하나의 뷰로 만드셨다면, 아이템 체크 여부를 직접 저장하고 갱신하도록 만들어줘야 합니다. 꽤 번거롭겠죠?

    하지만 아이템 선택 여부를 저장하고 갱신하도록 만들어주는 기능은 이미 리스트뷰에 구현되어있고요.
    이 글 본문의 내용이 그 내용을 담고 있습니다.

    본문의 예제를 다시 한번 잘 살펴보시고, 직접 실행하여 원리를 이해해보시기 바랍니다.
    특히, [STEP-3]의 CheckbleLinearLayout 클래스의 역할을 잘 보시면 도움이 되실 것 같습니다.

    감사합니다.

  37. 안녕하세요 덕분에 리스트 뷰 공부에 정말 많은 도움이 되고 있습니다.
    커스텀 리스트 뷰 다중 선택 관련해서 공부하다가 의문점이 생겨서 글을 남깁니다.
    커스텀 리스트 뷰 다중 선택 예제를 공부하고 따라 했을 때에는 문제가 생기지 않았는데
    위에 있는 예제랑 다르게 아이템에 버튼을 추가하니 체크박스 선택이 안되는 문제가 발생했습니다
    (그런데 아이템에 있는 버튼은 선택되구요)
    아이템에 버튼을 추가하면 뭔가 코드 설정을 바꿔야 할꺼 같긴한데
    어느 부분을 바꿔야할지 잘 모르겠습니다

  38. 질문하신 문제는,
    아이템뷰로 표시되는 레이아웃의 포커스 문제 때문인데요. 아이템뷰에 버튼이 있으면 버튼이 포커스를 가져가서 아이템뷰로 클릭 이벤트가 전달되지 않기 때문입니다.

    아래 코드를 아이템뷰의 루트 레이아웃의 속성으로 추가해보세요.

    android:descendantFocusability="blocksDescendants"

    아마 질문하신 내용을 해결하실 수 있을거라 생각합니다.
    만약 원하는 결과가 나오지 않더라도, 위의 속성을 구글링하시면 비슷한 문제와 해결방법이 찾아질거라 생각드네요.

    한번 해보시고, 잘 안되면 다시 질문글 남겨주세요.

    감사합니다.

  39. Blog Icon
    PARK

    오늘도 도움 많이 받고 갑니다~

  40. 방문해 주셔서 감사합니다.

  41. Blog Icon
    초보

    체크박스 영역에서 클릭해야만 클릭되고 리스트뷰 전체 영역에서 클릭하면 체크가 되지 않습니다ㅜㅜ
    클래스를 작성하고 넣었는데도 왜 그럴까요? ㅜ

  42. STEP-2 코드 내용 중에,

    android:descendantFocusability="blocksDescendants"

    이 부분을 동일하게 작성해 보세요.

    아마...... 해결될거라 생각이...... 듭니다. ^^