안드로이드 커스텀 리스트뷰에서 검색된 아이템 보여주기. (Android Custom ListView Item Filtering)

2016. 12. 12. 11:32


1. 커스텀 ListView와 아이템 필터링(filtering).

이전 글 [안드로이드 리스트뷰에서 검색된 아이템 보여주기. [textFilterEnabled] ]에서 TextView 하나로 구성된 기본 ListView의 아이템을 필터링(Filtering)하는 방법에 대해 살펴보았습니다.


[안드로이드 리스트뷰에서 검색된 아이템 보여주기. [textFilterEnabled] ]에서 설명했듯이, 안드로이드 ListView에는 아이템 필터링을 지원하기 위한 옵션이 이미 준비되어 있습니다. textFilterEnabled 속성이 바로 그것이죠.


하지만 ListView에 textFilterEnabled 속성이 제공된다고 하더라도 필터링 기능 자체가 ListView에서 구현되어 있다고 착각하면 안됩니다. 앞 서 여러 번 강조했듯이, ListView에 표시될 데이터를 처리하는 것은 Adapter의 역할이기 때문에, 실질적으로 아이템을 필터링하는 기능 또한 Adapter에 구현되어야 하기 때문입니다.

2. 필터링(filtering)을 지원하는 Adapter 구현.

Adapter가 필터링 기능을 지원하도록 만들기 위한 필수 요건은, Adapter가 Filterable 인터페이스를 implements해야 한다는 것입니다. [안드로이드 리스트뷰에서 검색된 아이템 보여주기. [textFilterEnabled] ]에서 ArrayAdapter를 사용한 것도, ArrayAdapter가 Filterable 인터페이스를 implements하고 있기 때문이죠.


그리고 Filterable 인터페이스의 implements와 더불어 Filter 클래스를 상속한 커스텀 Filter도 구현해야 합니다.


음, Filterable 인터페이스와 Filter 클래스에 대한 설명에 앞서, 둘 간의 관계 및 처리 흐름을 나타내는 아래의 그림을 먼저 보도록 하겠습니다.

커스텀 리스트뷰 필터링 처리 과정


2.1 Filterable 인터페이스

Filterable 인터페이스는 필터링 기능이 필요한 곳에서 사용되는 인터페이스입니다.

Filterable 인터페이스


필터링 기능을 제공해야 하는 클래스를 정의할 때, Filterable 인터페이스를 implements 한 다음, 해당 클래스의 참조를 전달하는 형식으로 사용하는 것이죠. 여기서는 Adapter가 그 역할을 수행하므로 커스텀 Adapter를 정의할 때 Filterable 인터페이스를 implements하면 됩니다.

class ListViewAdapter extends BaseAdapter implements Filterable {

    @Override
    public Filter getFilter() {
        // TODO : return custom filter.
    }
}

그런데 Filterable 인터페이스에 실질적인 필터링을 처리하는 함수가 정의되어 있을 것이란 예상과 달리, Filterable 인터페이스에 정의된 public 메소드는 getFilter() 함수가 유일합니다.

Filterable getFilter() 함수


그렇다면 실질적인 필터링 기능은 어디서 구현해야 하는 것일까요? 바로, getFilter() 함수의 정의와 설명으로 유추할 수 있듯이, getFilter() 함수가 리턴하는 Filter 클래스에 필터링 기능을 구현해야 합니다. Filterable 인터페이스를 implements하는 것은, 이 Filter 클래스의 참조를 획득하기 위한 과정인 것이죠.

2.2 Filter 클래스

Filterable 인터페이스의 getFilter() 함수를 통해 리턴되는 Filter 클래스는 추상(abstract) 클래스로 정의되어 있습니다.

Filter 클래스


개발자는 필터링 기능을 구현하기 위해, Filter 클래스를 상속한 커스텀 Filter 클래스를 정의한 다음, protected로 정의된 추상 함수인 performFiltering() 함수와 publishResults() 함수를 override해야 합니다.

abstract Filter.FilterResults performFiltering(CharSequence constraint) ;
abstract void publishResults(CharSequence constraint, Filter.FilterResults results) ;

performFiltering() 함수는 이름 그대로, 필터링을 수행하는 함수입니다. 즉, 필터링을 수행하는 루프를 이 함수에 구현한 다음, 필터링된 결과 리스트를 FilterResults에 담아서 리턴하면 됩니다.


publishResults() 함수는 performFiltering() 함수에서 필터링된 결과를 UI에 갱신시키는 역할을 수행합니다. 즉, publishResults() 함수에서 커스텀 Adapter를 통한 ListView 갱신 작업을 구현하면 됩니다.

3. 커스텀 ListView에서 아이템 필터링(filtering) 하기

이제 커스텀 ListView를 사용할 때, 아이템을 필터링(filtering)하는 예제를 직접 구현하도록 하겠습니다. 커스텀 ListView에 대한 전체적인 설명 및 구현 방법은 [안드로이드 커스텀 리스트뷰 만드는 방법]에서 확인하실 수 있습니다.


또한 예제의 전체적인 화면은 [안드로이드 리스트뷰에서 검색된 아이템 보여주기. [textFilterEnabled] ]에서 작성한 예제와 동일하게 구성하고, ListView의 아이템은 [안드로이드 커스텀 리스트뷰 만드는 방법]의 예제 화면을 사용하겠습니다.

커스텀 ListView 필터링 예제 화면 구성도


3.1 MainActivity의 Layout 구성.

먼저, MainActivity의 Layout 리소스 XML을 작성합니다. 프로젝트의 "activity_main.xml" 또는 "content_main.xml" 파일에 작성하면 됩니다. 특히, ListView의 "textFilterEnabled" 속성을 "true"로 지정한 것을 주의하세요. "textFilterEnabled" 속성을 "true"로 지정해야만, setFilterText() 함수를 사용하여 필터링 기능을 사용할 수 있습니다.

[STEP-1] "activity_main.xml" - MainActivity의 Layout 구성.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.recipes4dev.examples.listviewcustomfilterexample.MainActivity"
    tools:showIn="@layout/activity_main">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="26sp"
        android:layout_marginRight="8dp"
        android:id="@+id/textView1"
        android:text="Filter Text" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/textView1"
        android:id="@+id/editTextFilter"/>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/editTextFilter"
        android:textFilterEnabled="true"
        android:id="@+id/listview1"/>

</RelativeLayout>

3.2 ListView 아이템에 대한 Layout 구성.

다음 단계로, ListView 아이템에 대한 화면 구성 작업을 진행합니다. "/res/layout/listview_item.xml" 파일에 아래의 내용을 작성하면 됩니다.

[STEP-2] "/res/layout/listview_item.xml" - ListView 아이템 Layout 작성.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="4">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="New Text"
            android:id="@+id/textView1"
            android:textSize="24dp"
            android:textColor="#000000"
            android:gravity="center_vertical"
            android:layout_weight="2" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="New Text"
            android:id="@+id/textView2"
            android:textSize="16dp"
            android:textColor="#666666"
            android:layout_weight="1" />
    </LinearLayout>

</LinearLayout>

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

ListView의 아이템에 표시될 데이터 클래스를 정의합니다. 클래스의 이름은 "ListViewItem"로 정의합니다.

[STEP-3] "ListViewItem.java" - ListView 아이템 데이터 클래스 정의.
import android.graphics.drawable.Drawable;

public class ListViewItem {
    private Drawable iconDrawable ;
    private String titleStr ;
    private String descStr ;

    public void setIcon(Drawable icon) {
        iconDrawable = icon ;
    }
    public void setTitle(String title) {
        titleStr = title ;
    }
    public void setDesc(String desc) {
        descStr = desc ;
    }

    public Drawable getIcon() {
        return this.iconDrawable ;
    }
    public String getTitle() {
        return this.titleStr ;
    }
    public String getDesc() {
        return this.descStr ;
    }
}

3.4 커스텀 Adapter 추가 및 기본 동작 구현.

이제, 커스텀 ListView 기능 동작의 핵심 요소인 커스텀 Adapter를 구현하겠습니다.
이 글의 앞부분에서 언급했듯이, Adapter가 필터링 기능을 지원하기 위해서는 Filterable 인터페이스를 사용해야 합니다. 그래서 아래의 코드와 같이 커스텀 Adapter 클래스를 정의할 때, Filterable 인터페이스를 implements하도록 만들어야 합니다.

public class ListViewAdapter extends BaseAdapter implements Filterable {
    // TODO
}

ListView 아이템 표시를 위한 커스텀 Adapter의 기본 동작은 아래와 같이 구현합니다.

[STEP-4] "ListViewAdapter.java" - BaseAdapter 상속 및 ListViewAdapter 구현.
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;

public class ListViewAdapter extends BaseAdapter implements Filterable {
    // Adapter에 추가된 데이터를 저장하기 위한 ArrayList. (원본 데이터 리스트)
    private ArrayList<ListViewItem> listViewItemList = new ArrayList<ListViewItem>() ;
    // 필터링된 결과 데이터를 저장하기 위한 ArrayList. 최초에는 전체 리스트 보유.
    private ArrayList<ListViewItem> filteredItemList = listViewItemList ;

    Filter listFilter ;

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

    }

    // Adapter에 사용되는 데이터의 개수를 리턴. : 필수 구현
    @Override
    public int getCount() {
        return filteredItemList.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 titleTextView = (TextView) convertView.findViewById(R.id.textView1) ;
        TextView descTextView = (TextView) convertView.findViewById(R.id.textView2) ;

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

        // 아이템 내 각 위젯에 데이터 반영
        iconImageView.setImageDrawable(listViewItem.getIcon());
        titleTextView.setText(listViewItem.getTitle());
        descTextView.setText(listViewItem.getDesc());

        return convertView;
    }

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

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

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

        item.setIcon(icon);
        item.setTitle(title);
        item.setDesc(desc);

        listViewItemList.add(item);
    }

    // TODO : filtering item.
}

3.5 Filterable 인터페이스의 getFilter() 함수 override.

바로 위 단계에서 Filterable 인터페이스를 implements 하였으므로, Filterable 인터페이스의 public 메소드인 getFilter() 함수를 override해야 합니다. 참고로 getFilter() 함수를 통해 리턴되는 Filter 클래스는 다음 단계의 구현 내용을 참고하시기 바랍니다.

[STEP-5] "ListViewAdapter.java" - getFilter() 함수 override
public class ListViewAdapter extends BaseAdapter implements Filterable {
 
    Filter listFilter ;

    // 코드 계속 ...

    @Override
    public Filter getFilter() {
        if (listFilter == null) {
            listFilter = new ListFilter() ;
        }
        
        return listFilter ;
    }

    // ... 코드 계속
}

3.6 Filter 클래스 추가 및 구현.

이 글의 초반부에서 설명한 Filter 클래스의 역할에 따라, 커스텀 Adapter 내부에 커스텀 Filter 클래스를 정의하고 구현하도록 하겠습니다.

[STEP-6] "ListViewAdapter.java" - 커스텀 Filter 클래스 정의 및 구현.
public class ListViewAdapter extends BaseAdapter implements Filterable {
 
    // 코드 계속 ...

    private class ListFilter extends Filter {

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults() ;

            if (constraint == null || constraint.length() == 0) {
                results.values = listViewItemList ;
                results.count = listViewItemList.size() ;
            } else {
                ArrayList<ListViewItem> itemList = new ArrayList<ListViewItem>() ;

                for (ListViewItem item : listViewItemList) {
                    if (item.getTitle().toUpperCase().contains(constraint.toString().toUpperCase()) ||
                            item.getDesc().toUpperCase().contains(constraint.toString().toUpperCase()))
                    {
                        itemList.add(item) ;
                    }
                }

                results.values = itemList ;
                results.count = itemList.size() ;
            }
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {

            // update listview by filtered data list.
            filteredItemList = (ArrayList<ListViewItem>) results.values ;

            // notify
            if (results.count > 0) {
                notifyDataSetChanged() ;
            } else {
                notifyDataSetInvalidated() ;
            }
        }
    }
}

3.7 Adapter 생성 후 ListView에 지정.

이전 단계에서 정의한 Adapter를 생성하여 ListView에 지정하는 코드를 작성합니다.

[STEP-7] "MainActivity.java" - onCreate() 함수에서 Adapter를 생성하여 ListView에 지정.
public class MainActivity extends AppCompatActivity {

    ListView listview = null ;

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

        ListViewAdapter adapter;

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

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

        // 코드 계속 ...
    }
}

3.8 데이터 추가.

ListView 아이템의 ImageView에 표시될 이미지를 추가합니다.

drawable 리소스에 이미지 추가


그리고 예제 실행 결과를 확인하기 위한 임의의 데이터를 추가하도록 하겠습니다.

[STEP-8] "MainActivity.java" - 데이터 추가.
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // 코드 계속 ...

        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_account_box_black_36dp),
                "Sam Smith", "I'm not the only one.\r\nStay with me.\r\n") ;
        
        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_account_circle_black_36dp),
                "Bryan Adams", "heaven.\r\nI do it for you.") ;
        
        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_assignment_ind_black_36dp),
                "Eric Clapton", "Tears in heaven.\r\nChange the world.") ;

        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_account_box_black_36dp),
                "Gary Moore", "Still got the blues.\r\nOne day.") ;
        
        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_account_circle_black_36dp),
                "Helloween", "A tale that wasn't right.\r\nI want out.") ;
        
        adapter.addItem(ContextCompat.getDrawable(this, R.drawable.ic_assignment_ind_black_36dp),
                "Adele", "Hello.\r\nSomeone like you.") ;
    }

3.9 EditText 텍스트 변경 이벤트 처리.

EditText(@id/editTextFilter)를 통해 ListView의 아이템을 필터링할 텍스트를 입력받은 다음, ListView의 setFilterText() 함수를 호출하여 필터링을 수행하도록 만듭니다.

[STEP-9] "MainActivity.java" - EditText 텍스트 변경 이벤트 처리
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ... 코드 계속

        EditText editTextFilter = (EditText)findViewById(R.id.editTextFilter) ;
        editTextFilter.addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable edit) {
                String filterText = edit.toString() ;
                if (filterText.length() > 0) {
                    listview.setFilterText(filterText) ;
                } else {
                    listview.clearTextFilter() ;
                }
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
        }) ;
    }

4. 예제 실행 결과.

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

Custom ListView 필터링 예제 실행화면


EditText에 "g"를 입력하면, ListView 아이템의 텍스트 중에서 "g"(또는 "G")를 포함하는 아이템이 ListView에 표시됩니다. 그리고 입력된 "g"를 지우면 모든 아이템이 표시됩니다.

Custom ListView 필터링 예제 실행화면 2


다시 다른 텍스트를 입력하면, 해당 텍스트에 필터링(filtering)된 출력 결과가 표시되는 것을 확인할 수 있습니다.

ListView 필터링 예제 실행화면 3


5. 선택 수정 사항.

5.1 필터링 대상 요소 결정.

예제에서 ListView 아이템의 문자열 필터링 대상은 아이템 뷰를 구성하는 두 개의 TextView 입니다. 그런데 만약 이 중 하나의 TextView에 대해서만 필터링을 수행하고자 한다면, [STEP-6]의 performFiltering() 함수의 내용을, 아래와 같이 수정하면 됩니다.

    // Desc 항목에 대해서만 필터링하도록 변경.
    //  if (item.getTitle().toUpperCase().contains(constraint.toString().toUpperCase()) ||
    //      item.getDesc().toUpperCase().contains(constraint.toString().toUpperCase())) {}
    if (item.getDesc().toUpperCase().contains(constraint.toString().toUpperCase())) {
        itemList.add(item) ;
    }

5.2 필터링 사용 시 팝업 텍스트 표시하지 않기.

예제의 실행화면을 보면 다른 예제들에서는 볼 수 없는 한 가지 특이한 UI가 표시되는 것을 확인할 수 있습니다. 바로 EditText에 입력한 필터링 텍스트가 별도의 팝업 윈도우 형태로 출력된다는 것인데요. 아래 그림과 같습니다.

ListView 필터링 setFilterText 팝업 텍스트


필터링 텍스트 팝업은 ListView가 표시하는 UI이며, ListView의 setFilterText() 함수를 통해 필터링 텍스트가 전달되면 무조건 표시되게 만들어져 있습니다. 그래서 필터링 텍스트 팝업을 보이지 않게 만들고자 한다면, setFilterText() 함수 이외의 방법을 사용하여 필터링을 수행하면 됩니다. 즉, ListView를 통하지 않고 Adapter로부터 직접 Filter 객체의 참조를 가져와서 filter() 함수를 호출하면 되는 것이죠.


[STEP-9]의 소스 내용 중, afterTextChanged() 함수에서 setFilterText() 함수를 호출하는 코드를 아래와 같이 변경하면 됩니다.

/*
    if (filterText.length() > 0) {
        listview.setFilterText(filterText) ;
    } else {
        listview.clearTextFilter() ;
    }
*/
    ((ListViewAdapter)listview.getAdapter()).getFilter().filter(filterText) ;

6. 참고.

.END.


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

  1. 이전 댓글 더보기
  2. Blog Icon
    안녕하세요 질문좀할게요...

    저두 윗분처럼 필터링된 리스트를 클릭해도 원래 리스트의 데이터를 가져오는데 조언좀 해주실 수 있을까요?
    그대로 따라했는데도 잘 작동하질 않습니다.. ㅜㅜ

  3. 윗 분과 동일한 문제를 겪고 계신다면, 위에서 작성된 답변을 다시 한번 참고해 보세요.
    특히 직접 작성하신 코드 내용과 본문의 코드 내용에서, 어댑터의 소스 코드를 비교해 보시길 바랍니다.

    소스 코드를 꼼꼼하게 비교해보시고, 코드의 흐름을 따라서 디버깅을 해보시기 바랍니다.

    그 과정에서 추가적인 문제점이나 궁금한 내용이 있으면 다시 질문글 남겨주세요.

    감사합니다.

  4. Blog Icon
    감사합니다

    팝업으로 텍스트가 뜨는걸 없애고 싶었는데 포스트내용보고 해결하였습니다. 감사합니다

  5. 다른 내용들도 도움이 되었으면 좋겠네요.
    감사합니다.

  6. Blog Icon
    질문좀 해도 될까요?

    아마 리스트뷰의 position을 인텐트로 전달하는 데이터로 사용하셨을텐데요.
    전체 리스트가 표시된 상태에서 선택된 아이템의 position을 데이터 리스트에 사용하면 원하는 결과를 얻을 수 있지만, 필터링 된 리스트 아이템의 position을 데이터 리스트에 사용하면 데이터의 위치가 일치하지 않게 됩니다.

    그래서 질문하신 내용과 같은 문제가 발생한 것이지요.

    일단 간단한 방법으로,
    아이템 식별을 위한 값을 추가적으로 저장하신 다음, 그 키를 이용해서 데이터 리스트의 값을 가져오게 만들면 해결하실 수 있습니다.

    그 값을 getItemId() 에서 리턴하게 만들고 onItemClick 이벤트에서 position이 아닌 id 값을 통해 데이터 리스트를 참조하면 됩니다.

    설명이 좀 복잡하게 들리실 수 있을텐데..
    이해가 안되는 부분이 있으면 다시 질문 올려주세요.

    감사합니다.

    이렇게 답변을 주셨던대 조금더 쉽게 설명해주시면 안될까요?

  7. 조금 더 쉽게, 한번에 이해되도록 설명해드릴 수 있다면 좋겠지만, 텍스트만으로 설명을 드리는 것이 한계가 있어서 쉽진 않을 것 같습니다.
    대신, 앞서 정리된 내용에 대해 어느 부분이 이해가 안되는지 말씀해주시면 부가적인 설명을 해드릴게요.

    지면과 방법에 한계가 있음을 양해 부탁드릴게요.

    감사합니다.

  8. Blog Icon
    OMG

    블로그 정말 잘 보고 구현에 성공하였습니다 먼저 감사하다는 말씀 드리고 싶습니다. 그러나 혹시 처음 리스트뷰를 띄울 때 전체리스트가 뜨지않고 특정 검색 값을 미리 넣어놓고 그 특정 검색에 맞는 리스트들이 처음 리스트뷰를 띄울 때 뜨도록 하는 방법이 있을까요?

  9. 음, 질문하신 내용은 어렵지 않게 구현할 수 있을 것 같은데요.
    리스트뷰를 표시할 때 사용할 데이터리스트에, 전체 값이 아닌 검색된 값만 미리 넣어두면 될 것 같습니다.

    결국 리스트뷰에 어떤 값을 표시할 것인지는 개발자가 결정하는 것이니깐, 리스트뷰가 표시되기 전에 미리 필터링을 거친 데이터만 데이터 리스트에 넣어두는 것이죠.

    물론, OMG님이 어떻게 구조를 만들어놓았는지에 따라 적용하기가 까다로울 수 있지만, 직관적으로 쉽게 접근해 보시기 바랍니다.

    감사합니다.

  10. Blog Icon
    유산균음료

    안녕하세요! 리스트뷰 관련 글 잘 보았습니다.

    혹시 시간이 되신다면 데이터베이스랑 리스트뷰랑 연동시켜서 하는 법도 올려주실수 있나요?

    사용자가 데이터입력 -> 데이터베이스에 저장(이미지,텍스트등) -> 리스트뷰에 뿌려줌

    부탁드립니다! 많은 도움이 될 것 같아요.

  11. 요청하신 내용은 기획만 되어 있고, 아직 작성은 못했습니다.
    최근 몇 달 간 건강이 많이 안 좋아져서, 블로그에 전혀 신경을 쓰지 못했네요.

    빠른 시일 내에 관련 내용 정리하여 블로그에 올릴 수 있도록 노력해보겠습니다.

    감사합니다.

  12. Blog Icon
    하하핳

    혹시 커스텀리스트뷰에서 리스트에 각각 버튼을 클릭해서 텍스트필드에 숫자가 올라가려고하는데 어떻게하는지 알수있을까요?? 계속해서 숫자가 초기화되지않아요

    소스코드입니다 package com.example.psi.mount_groot;

    import android.content.Context;
    import android.graphics.drawable.Drawable;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.Button;
    import android.widget.ImageView;
    import android.widget.TextView;

    import java.util.List;

    public class animer_Adapter extends BaseAdapter {

    private Context context;
    private ViewHolder viewHolder = new ViewHolder();
    private List<animer_fragment_item> animerlist;
    int levels;



    public animer_Adapter(Context context, List<animer_fragment_item> anmerlist) {
    this.context = context;
    this.animerlist = anmerlist;
    }

    @Override
    public int getCount() {
    return animerlist.size();
    }

    @Override
    public Object getItem(int i) {
    return animerlist.get(i);
    }

    @Override
    public long getItemId(int i) {
    return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
    View v = View.inflate(context, R.layout.activity_animer_fragment, null);


    viewHolder.animerimage = (ImageView) v.findViewById(R.id.animer_image);
    viewHolder.animername = (TextView) v.findViewById(R.id.animer_name);
    viewHolder.animerlevel = (TextView) v.findViewById(R.id.animer_level);
    viewHolder.animerbuff = (TextView) v.findViewById(R.id.animer_buff);
    viewHolder.upbutton = (Button) v.findViewById(R.id.animer_upbutton);

    viewHolder.animerimage.setImageDrawable(animerlist.get(i).getAnimer_image());
    viewHolder.animername.setText(animerlist.get(i).getName());
    viewHolder.animerlevel.setText(animerlist.get(i).getWoodcutter_level());
    viewHolder.animerbuff.setText(animerlist.get(i).getWoodcutter_buff());

    viewHolder.upbutton.setTag(i);
    viewHolder.upbutton.setOnClickListener(buttonClickListener);



    return v;
    }

    private View.OnClickListener buttonClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    v.getTag();
    levels ++;
    viewHolder.animerlevel.setText(Integer.toString((Integer) v.getTag(levels)));

    }
    };

    class ViewHolder{
    public TextView animername =null;
    public ImageView animerimage =null;
    public TextView animerlevel = null;
    public TextView animerbuff = null;
    public Button upbutton = null;


    }
    }


  13. 질문글에 올려주신 내용만으로 보자면 뷰홀더를 잘 못 쓰신 것 같은데요. 보통 뷰 홀더는 리스트뷰 성능(스크롤 등) 문제를 개선하기 위해 리스트뷰 아이템 단위로 미리 만들어진 뷰 객체를 보관했다가 재활용하는 방법입니다.

    그런데 질문 내용의 소스 코드에는 전혀 그 의미가 맞지 않게 사용된 것 같네요.

    일단 뷰홀더와 별개로, 가장 먼저 구현할 내용은 리스트뷰 버튼을 클릭했을 때, 해당 버튼이 위치한 아이템 인덱스를 가져와서 인덱스에 해당하는 데이터 리스트 값을 변경하는 것입니다.

    그리고나서 리스트뷰를 갱신하기만 하면, 원하는 기능을 만들 수 있을 것입니다.

    답글이 다소 늦었지만, 도움 되시길 바랍니다.

    감사합니다.

  14. Blog Icon
    신나라소프트

    문학 앺

    目 錄
     
    君考
    臣考
    地理考
    職官考
    儀章考
    物産考
    國書考
    國語考
    屬國考로

    본문 아래서:

    震國公

     
    震國公姓大氏 名乞乞仲像 粟末靺鞨人也 粟末靺鞨者 臣於高句麗者也 或言大氏 出 自大庭氏 東夷之有大氏自大連始也 唐高宗總
    章元年 高句麗滅 仲象與子祚榮 率家屬 徙居營州 稱舍利 舍利者 契丹語帳官也 武后萬歲通天二年 契丹松漠都督李盡忠 歸 誠州
    刺史孫萬榮 叛唐陷營州 殺都督趙文 仲象懼 與靺鞨酋乞四比羽及高句麗破部 東走度遼水 保太白山之東北 阻奧婁河 樹壁自固 武
    后封仲象爲震國公 比羽爲許國公 比羽不受命 武后詔玉鈐衛大將軍李楷固 中郞將索仇 擊斬比羽 是時仲象已卒


    震國公 검색시

    Android Coding 할려는데
    어떻게 하는 프로그램 방법 쫌?
    위 본문 나오게 ...

  15. 질문 글의 내용을 이해하질 못하겠어요.

    조금 더 구체적으로 글의 내용을 작성해주시면 도움드릴 수 있을 것 같습니다.

  16. Blog Icon
    chobo

    도움이 많이 되었습니다 감사합니다~

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

  18. Blog Icon
    12213

    잘보고갑니다! 도움이 많이 되었어요

  19. 도움이 되었다니 다행이네요.

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

  20. Blog Icon
    임민규

    작성자님이 저번에 쓰신 글을 토대로 custom listview를 만들었는데요 저런식으로 filter를 추가할려면 어떤 부분을 바꿔야하나요? 글대로 했는데 잘안되네요...

  21. 일단 작성하신 소스 코드의 내용과 본문의 소스 내용을 꼼꼼하게 비교해 보시길 바랍니다.
    아마도 소스 작성할 때 놓치신 부분이 있지 않을까 생각되네요.

    그리고 혹시 글대로 했을 때 잘안된다고 하셨는데, 어떻게 잘 안되는지 설명해주시면 도움을 드리기 더 용이할 것 같습니다.

    에러가 나는지, 동작이 안되는지, 동작 결과가 의도한 바와 다른건지..
    구체적인 메시지 또는 현상을 같이 올려주시면, 원인과 해결 방법을 찾기가 더 수월할 것 같네요.

    감사합니다.

  22. Blog Icon
    이재연

    안녕하세요. 작성자님께서 쓰신 ListFragment에 custom listview 만드는 방법(https://recipes4dev.tistory.com/63#)글보고 잘 따라해서 커스텀리스트뷰 띄우는거 성공했는데요.
    이번에 커스텀리스트뷰 띄운 프래그먼트에 검색기능도 추가하고싶어서 작성하신 예제보고 따라했는데 오류나서 질문드립니다. 잘 따라하다가 리스트뷰 참조하는 부분에서 오류뜨네요..

    우선 customlist view가 있는 fragment_home.xml에서 아래와 같이 fragment선언했고요.
    <fragment
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/customlistfragment"
    tools:layout="@layout/fragment_home"
    android:textFilterEnabled="true"
    android:name="com.example.seoulapp.ui.home.CustomListFragment"/>

    HomeFragment 파일의 onCreateView 안에 아래와같이 코드 작성했는데 오류뜹니다..
    Fragment listView = root.findViewById(R.id.customlistfragment);

    오류 내용 : Type parameter t has incompatible upper bounds view and fragment

    뭐가 문제이고 어떻게 고쳐야 될까요ㅠㅠ

  23. 단순 에러 메시지만 봐서는 정확한 원인을 알 수 없을 것 같습니다.
    소스를 같이 올려주시면 도움을 드리기가 더 수월할 것 같은데요.

    그 전에.. 기본적인 사항 몇 가지만 확인부탁드릴게요.

    1. Project Clean 후 Build한 다음 결과 확인.
    2. ID(R.id.customlistfragment)를 중복해서 사용하고 있는지 확인.
    3. 기존 support library와 androidx를 혼용해서 사용하고 있는 것은 아닌지 확인.

    위의 몇 가지 사항들 확인해보시고, 그래도 문제가 있으면 소스 코드를 질문글에 같이 올려주세요.

    감사합니다.

  24. Blog Icon
    ClimbCoder

    안녕하세요. 설명 잘보고 갑니다.
    근데 제가 욕심을 조금 더 내서, filtered list 의 element 수를 출력하고 싶은데요.((ListViewAdapter)listview.getAdapter()).getFilter().filter(filterText) ; 를 써서 필터링은 되는데 이후 ((ListViewAdapter)listview.getAdapter()).getCount() 를 써서 업데이트를 하니 숫자가 맞지를 않습니다. 어찌 된 일일까요.. 디버깅 해보면 그때만 잘되고 중단점을 지우면 또 안됩니다.

  25. 답글이 많이 늦었네요. 죄송합니다.
    질문하신 내용에 대한 원인과 해결방법은 찾으셨는지 모르겠네요. 흠..

    딱히 문제는 없어보이는데요.
    아마 코딩하실 때, 변수를 사용하신게 아닌가 생각이 드네요.

    getCount()에서 filteredItemList 를 제대로 사용하고 있는지 확인하시면 좋을 것 같고요.

    어댑터 내에서 각 단계마다 로그 메시지를 출력해보면 정확한 원인을 확인하시는데 도움될 것 같습니다.

    다시 한번, 늦은 답변드려서 죄송합니다.

    감사합니다.

  26. Blog Icon
    Han

    안녕하세요 설명이 많은 도움이 되었습니다.
    이전에 올려주신 오름차순으로 리스트뷰를 정렬하는 기능과 현재 페이지의 검색 기능을 동시에 구현해봤습니다

    오름차순으로 정렬 후 특정 문구를 검색을 하면 작동이 잘 되는데 이와 반대로 먼저 특정 문구를 검색 후 오름차순으로 정렬하니 아무런 변화가 나타나지 않습니다

    정리하자면 특정 문구로 필터링 되어 나온 리스트뷰들을 다시 오름차순으로 정렬해보고 싶은데 어떻게 해야할까요...?

  27. 댓글 남기기 전에 이미 해결하셨군요.

  28. Blog Icon
    Han

    올려주신 글 다시 천천히 읽으면서 동작방식을 고민해보고 해결 완료했습니다! 좋은 글 감사합니다

  29. 좀 더 글을 쉽게 이해할 수 있게 작성해놨다면, 어렵지 않게 해결하실 수 있었을텐데...

    어쨌든 해결하셨다니 다행이네요.

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

    감사합니다.

  30. Blog Icon
    빠삐

    안녕하세요 질문 하나 드려도 될까요??
    혹시 리스트뷰를 메인화면에서 검색도 할수있고 다른화면에서도 검색할수 있게 못만드나요??

  31. 그건 리스트뷰 자체에 대한 검토 내용이라기 보다는,
    실제 구현을 어떻게 하느냐에 따른 문제입니다.

    메인화면이 정확히 무엇을 의미하는지는 모르겠지만,
    (일단 메인액티비티로 가정한다면,)
    메인화면에서 검색할 수도 있고, 다른 화면에서도 검색할 수도 있습니다.

    그렇게 만드시면 됩니다.

    설계와 구현을 어떻게 하느냐의 문제일 뿐 구현 불가능한 기능은 아닙니다.

    감사합니다.

  32. Blog Icon
    질문있습니다

    안녕하세요. 설명보고 따라서 커스텀 리스트뷰를 만들고 검색기능을 가능하게 만들었습니다. 여기서 추가로 정렬기능을 추가 해보고 싶어서 리스트뷰 아이템 정렬 내용을 보고 만들었는데 리스트뷰가 없어지네요. 혹시 정렬기능을 추가 할려면 어떻게 만들어야할지 궁금합니다

  33. 정렬 기능을 추가하는 것은 어렵지 않습니다.

    어댑터를 통해 전달되는 데이터 리스트를 원하는대로 정렬하여 표시되도록 만들기만 하면 됩니다.

    관련 내용은 아래 링크에서 좀 더 자세하게 확인하실 수 있습니다.

    https://recipes4dev.tistory.com/78

    확인해보시고, 이해가 잘 안되시면 다시 질문글 남겨주세요.

    감사합니다.

  34. Blog Icon
    버튼

    안녕하세요 혹시 EditText에서 검색이 바로 되는게 아니라 버튼을 눌렀을때 검색이 되는 기능을 만들려고 하는데 어떻게 하는지 여쭤봐도 될까요??

  35. 본문 예제에서는 addTextChangedListener를 사용했는데요. addTextChangedListener는 EditText의 텍스트가 변경될 때마다 실행됩니다.

    원하시는 기능을 구현하시려면,
    간단하게, addTextChangedListener()에 있는 코드를 버튼이 눌려졌을 때 실행되도록 만들면 됩니다.

    addTextChangedListener()에 작성된 코드는 없애시고요.

    감사합니다.

  36. Blog Icon
    질문있습니다

    글을 보고 한 번 따라 만들어 보았는데요 버튼을 눌러도 정렬이 안되네요 혹시 어뎁터랑 데이터리스트를 어떻게 해야할까요...

  37. 먼저 작성된 코드를 본문 내용과 다시 한번 꼼꼼하게 비교해보시기 바랍니다.

    그리고 본문의 내용은 정렬이 아닌 필터링 관련 내용인데요. 혹시 정렬 기능은 따로 추가하신 건가요?
    (참고로 정렬 관련 내용은 "https://recipes4dev.tistory.com/78"에서 확인하실 수 있습니다.)

    다시 한번 꼼꼼하게 살펴보신 다음 질문글 남겨주세요.

    감사합니다.

  38. Blog Icon
    kdg

    좋은글 감사히 잘 읽었습니다

    질문이 하나 있는데

    검색 이후 문장을 지우면 모든 리스트가 돌아오지 않습니다

    ETH
    BTC 가 데이터로 들어가 있다고 가정하고
    TH를 검색했다가 지우면
    "T" -> ETH, BTC
    "TH" -> ETH
    "T" -> ETH, BTC
    "" -> ETH,BTC
    이런식이고요

    =================
    장문의 질문글이었는데
    끝 부분에 알려주신 팝업창 대체명령어
    ((ListViewAdapter)coin_list.getAdapter()).getFilter().filter(filterText) ;
    대신
    coin_list.setFilterText(filterText) ;
    를 사용했더니 해결되었습니다
    혹시 위의 명령어로 사용해도 문제없는 방법이 없을까요?

    일단 저도 여러가지 시도해보겠습니다
    afterTextChanged가 지우는 것도 인식하는건 확인했는데

    그럼 위의 명령어로는 썼다 지워도 공백이 아니라고 인식하는 걸까요
    제가 지식이 부족해서 잘 알 수 없네요

    확인해봤는데 위의 명령어로는
    어댑터의
    if(constraint == null || constraint.length() == 0){
    results.values = listViewItemList;
    results.count = listViewItemList.size();
    }
    이 if문에 걸리지 않는것으로 확인 했습니다

    =========

    위의 명령어를 쓰면
    아예 어댑터의 performFiltering 함수가 작동하지 않는것을 확인했습니다

    ========================
    최종적으로 확인한 것을 적어보면
    coin_list.setFilterText(filterText);
    를 사용하는 경우 다 지워 공백이 되더라도 전송이 되고

    ((ListViewAdapter)coin_list.getAdapter()).getFilter().filter(filterText);
    를 사용하는 경우에는 공백인 경우 아예 전송이 안 되는 것 같습니다

    추가문자를 붙여 보낸 뒤 어댑터에서 후처리를 하든 해서 설정해봐야겠네요



    =========================
    임시방편으로 메인액티비티의 afterTextChanged는 아래와 같이 edit의 길이에 상관없이 항상 filter명령어를 수행

    public void afterTextChanged(Editable edit) {
     String filterText = edit.toString();
     ((ListViewAdapter)coin_list.getAdapter()).getFilter().filter(filterText+"1");
    }

    어댑터의 경우 아래 같이 performFiltering함수의 두번째줄에 constraint를 변경하는 명령어를 추가하여 해결했습니다

    FilterResults results = new FilterResults();
    constraint = (CharSequence) constraint.toString().substring(0, constraint.toString().length()-1);

    =======================
    그런데 이렇게 코드를 바꾸고 보니 그냥 애초에 길이상관없이 coin_list.clearTextFilter();를 쓰지 않고
    filter 명령어를 썼으면 됐지 않나 싶어


    public void afterTextChanged(Editable edit) {
     String filterText = edit.toString();
     ((ListViewAdapter)coin_list.getAdapter()).getFilter().filter(filterText);
    }

    이렇게하고
    어댑터는 그냥 변경전으로 돌렸더니
    잘 수행하네요;; ㅋㅋ
    공백이라고 인식하지 않던게 아니었나봐요

    음 길이에 따라 clearTextFilter를 쓰는 이유가 따로 있긴 하겠지만(계산 횟수 그런건가?) 일단은 이렇게 써야겠네요clearTextFilter를 쓰면 이득보는건 뭔가요? 리스트뷰 자체의 함수라고 들었는데 그냥 필터초기화인가

  39. 제가 첨언드릴 여유도 없이, 혼자서 해답을 잘 찾아가셨네요.

    clearTextFilter() 메서드는 생각하신대로 필터 초기화를 하는 메서드입니다.

    filter input 텍스트 초기화하고, 필터 적용 여부 초기화 하고.. 뭐 그런거죠.
    이득은...? 잘 모르겠네요. 반드시 뭔가를 써야한다라기 보단, "문제없이 동작하면 장땡"이야.. 라는 입장이라.. ^^

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

  40. Blog Icon
    HW

    제가 코틀린으로 배우다가 어디 물어 볼 곳도 없고 했는데, 여기서 답을 찾고 가네요.
    JAVA로 갈아타야겠습니다.ㅠㅠ
    정말 많은 도움이 되었습니다. 정말 감사합니다.

  41. 답을 늦게 달아드리게 되어 죄송합니다.

    JAVA로 갈아타지 마세요. ㅜㅜ
    안드로이드는 코틀린으로 가시는 게 맞습니다!

    감사합니다.