Как написать собственный фильтр для ListView с ArrayAdapter - PullRequest
14 голосов
/ 25 марта 2010

У меня есть ListView, который связан с ArrayAdapter, где Artist - это мой простой класс, который имеет только идентификатор и имя.

Теперь я хочу отфильтровать ListView, поэтому я вызываю:

artistAdapter.getFilter().filter("bla", new Filter.FilterListener() {
    public void onFilterComplete(int count) {
        Log.d(Config.LOG_TAG, "filter complete! count: " + count); // returns 8
        Log.d(Config.LOG_TAG, "adapter count: " + artistAdapter.getCount()); // return 1150
    }
});

Первый оператор отладки печатает счетчик 8. Это счетчик значений для списочных элементов, которые начинаются с «bla», но адаптер его не получает. Второй оператор отладки печатает 1150 элементов. Это полное количество элементов в списке.

Таким образом, каким-то образом фильтр не сообщает адаптеру, что он отфильтровал базовые данные.

Я хочу знать сейчас: нужно ли что-нибудь кодировать в моем адаптере, чтобы он получал обновления из фильтра? Должен ли я написать собственный фильтр? Что мне делать?

Ответы [ 2 ]

27 голосов
/ 21 марта 2011

На самом деле

Я заметил, что должен был использовать список 'originalItems' для создания нового отфильтрованного списка в executeFiltering.

Это исправит все проблемы, которые вы увидите в отношении изменения текста в фильтре. Например. Вы ищите «Хлеб», затем возвращаетесь на «B», и вы должны увидеть все «B». В моем оригинальном посте ты бы не стал.

    private class GlycaemicIndexItemAdapter extends ArrayAdapter<GlycaemicIndexItem> {

    private ArrayList<GlycaemicIndexItem> items;
    private ArrayList<GlycaemicIndexItem> originalItems = new ArrayList<GlycaemicIndexItem>();
    private GlycaemicIndexItemFilter filter;
    private final Object mLock = new Object();

    public GlycaemicIndexItemAdapter(Context context, int textViewResourceId, ArrayList<GlycaemicIndexItem> newItems) {
            super(context, textViewResourceId, newItems);
            this.items = newItems;
            cloneItems(newItems);
    }

    protected void cloneItems(ArrayList<GlycaemicIndexItem> items) {
        for (Iterator iterator = items.iterator(); iterator
        .hasNext();) {
            GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next();
            originalItems.add(gi);
        }
    }

    @Override
    public int getCount() {
        synchronized(mLock) {
            return items!=null ? items.size() : 0;  

    }

    @Override
    public GlycaemicIndexItem getItem(int item) {
        GlycaemicIndexItem gi = null;
        synchronized(mLock) {
                gi = items!=null ? items.get(item) : null;

        }
        return gi;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = vi.inflate(R.layout.row, null);
            }

            GlycaemicIndexItem i  = null;
            synchronized(mLock) {
                i = items.get(position);
            }

            if (i != null) {
                    TextView tt = (TextView) v.findViewById(R.id.rowText);
                    TextView bt = (TextView) v.findViewById(R.id.rowText2);
                    if (tt != null) {
                          tt.setText("Name: "+i.getName());                            
                    }
                    if(bt != null){
                          bt.setText("GI Value: " + i.getGlycaemicIndex());
                    }
            }
            return v;
    }
    /**
     * Implementing the Filterable interface.
     */
    public Filter getFilter() {
        if (filter == null) {
            filter = new GlycaemicIndexItemFilter();
        }
        return filter;
    }   

    /**
     * Custom Filter implementation for the items adapter.
     *
     */
    private class GlycaemicIndexItemFilter extends Filter {
        protected FilterResults performFiltering(CharSequence prefix) {
            // Initiate our results object
            FilterResults results = new FilterResults();

            // No prefix is sent to filter by so we're going to send back the original array
            if (prefix == null || prefix.length() == 0) {
                synchronized (mLock) {
                    results.values = originalItems;
                    results.count = originalItems.size();
                }
            } else {
                synchronized(mLock) {
                        // Compare lower case strings
                    String prefixString = prefix.toString().toLowerCase();
                    final ArrayList<GlycaemicIndexItem> filteredItems = new ArrayList<GlycaemicIndexItem>();
                    // Local to here so we're not changing actual array
                    final ArrayList<GlycaemicIndexItem> localItems = new ArrayList<GlycaemicIndexItem>();
                    localItems.addAll(originalItems);
                    final int count = localItems.size();

                    for (int i = 0; i < count; i++) {
                        final GlycaemicIndexItem item = localItems.get(i);
                        final String itemName = item.getName().toString().toLowerCase();

                        // First match against the whole, non-splitted value
                        if (itemName.startsWith(prefixString)) {
                            filteredItems.add(item);
                        } else {} /* This is option and taken from the source of ArrayAdapter
                            final String[] words = itemName.split(" ");
                            final int wordCount = words.length;

                            for (int k = 0; k < wordCount; k++) {
                                if (words[k].startsWith(prefixString)) {
                                    newItems.add(item);
                                    break;
                                }
                            }
                        } */
                    }

                    // Set and return
                    results.values = filteredItems;
                    results.count = filteredItems.size();
                }//end synchronized
            }

            return results;
        }

        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence prefix, FilterResults results) {
            //noinspection unchecked
            synchronized(mLock) {
                final ArrayList<GlycaemicIndexItem> localItems = (ArrayList<GlycaemicIndexItem>) results.values;
                notifyDataSetChanged();
                clear();
                //Add the items back in
                for (Iterator iterator = localItems.iterator(); iterator
                        .hasNext();) {
                    GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next();
                    add(gi);
                }
            }//end synchronized
        }
    }
}

В основном я создаю приложение для здоровья и питания, и на одном экране будет список элементов, основанных на гликемическом / гликемическом индексе. Я хочу, чтобы пользователи могли печатать и иметь автофильтр на экране. Теперь, если вы используете только строки, вы получаете автофильтрацию бесплатно. Я не являюсь, хотя, у меня есть свой собственный класс GlycaemicIndexItem, который имеет свойства на нем. Мне нужно предусмотреть свою собственную фильтрацию, чтобы гарантировать, что список, используемый для отображения на экране, обновляется при вводе пользователем.

В настоящее время экран представляет собой простой ListActivity с ListView и EditText (который вводит пользователь). Мы добавим TextWatcher к этому EditText, чтобы получать уведомления об обновлениях. Это означает, что он должен работать на всех устройствах, независимо от того, что пользователь печатает на жесткой или программной клавиатуре (у меня HTC DesireZ и старый G1).

Вот макет XML для экрана / действия (может кто-нибудь сказать мне, как вставить сюда код XML, поскольку, когда я пытаюсь использовать блок кода, XML не вставляется / отображается правильно, а интерпретируется):

layout for the activity - giatoz.xml

Поскольку мы хотим отображать наши строки в собственном стиле, у нас также есть файл макета для самой строки: Row xml file

Вот код для всей самой Деятельности. Расширяясь от ListActivity, этот класс имеет внутренний класс, который действует как адаптер, который выходит из ArrayAdapter. Это создается в onCreate для Activity и пока передается простой список строк. Обратите внимание на то, как оно создано по строкам 39-40. Наш специальный макет для строки передается со списком элементов.

Ключ к заполнению пользовательских строк находится в методе адаптера getView .

Наш класс адаптера также имеет собственный внутренний класс, называемый GlycaemicIndexItemFilter, который выполняет работу при вводе пользователем. Наш фильтр привязан к нашему EditText в строках 43-44 с помощью TextWatcher и его метода afterTextChanged . Линия 47 - ключ к пониманию того, как мы добиваемся фильтрации. Мы называем фильтр на нашем объекте фильтра. Наш фильтр создается при первом вызове getFilter, строка 148-149.

   package com.tilleytech.android.myhealthylife;

     import java.util.ArrayList;
     import java.util.Iterator;

     import android.app.ListActivity;
      import android.content.Context;
     import android.content.res.Resources;
     import android.os.Bundle;
     import android.text.Editable;
      import android.text.TextWatcher;
      import android.view.LayoutInflater;
        import android.view.View;
       import android.view.ViewGroup;
       import android.widget.ArrayAdapter;
       import android.widget.EditText;
       import android.widget.Filter;
       import android.widget.ListView;
       import android.widget.TextView;


        public class GlycaemicIndexAtoZActivity extends ListActivity {
          /** Called when the activity is first created. */
        private GlycaemicIndexItemAdapter giAdapter; 
        private TextWatcher filterTextWatcher;
        private EditText filterText = null;

        @Override
        public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.giatoz);            

        ListView lv = getListView();
        lv.setTextFilterEnabled(true);
        // By using setAdapter method in listview we an add string array in list.
        ArrayList<GlycaemicIndexItem> list = getListItems();

        giAdapter = new GlycaemicIndexItemAdapter(this, R.layout.row, list);
        giAdapter.notifyDataSetChanged();
        setListAdapter(giAdapter);

        filterText = (EditText)findViewById(R.id.GI_AtoZSearchEditText);
        filterTextWatcher = new TextWatcher() {

            public void afterTextChanged(Editable s) {
                giAdapter.getFilter().filter(s); //Filter from my adapter
                giAdapter.notifyDataSetChanged(); //Update my view

            }

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

            public void onTextChanged(CharSequence s, int start, int before,
                    int count) {

            }

        };
        filterText.addTextChangedListener(filterTextWatcher);
    }

    private ArrayList<GlycaemicIndexItem> getListItems() {
        ArrayList<GlycaemicIndexItem> result = new ArrayList<GlycaemicIndexItem>();

        Resources res = getResources();
        //Get our raw strings
        String[] array = res.getStringArray(R.array.GIList);
        for (int i = 0; i < array.length; i++) {
            GlycaemicIndexItem gi = new GlycaemicIndexItem();
            gi.setName(array[i]);
            gi.setGlycaemicIndex(1);
            result.add(gi);
        }

        return result;
    }

    private class GlycaemicIndexItemAdapter extends ArrayAdapter<GlycaemicIndexItem> {

        private ArrayList<GlycaemicIndexItem> items;
        private ArrayList<GlycaemicIndexItem> originalItems = new ArrayList<GlycaemicIndexItem>();
        private GlycaemicIndexItemFilter filter;
        private final Object mLock = new Object();

        public GlycaemicIndexItemAdapter(Context context, int textViewResourceId, ArrayList<GlycaemicIndexItem> newItems) {
                super(context, textViewResourceId, newItems);
                this.items = newItems;
                cloneItems(newItems);
        }

        protected void cloneItems(ArrayList<GlycaemicIndexItem> items) {
            for (Iterator iterator = items.iterator(); iterator
            .hasNext();) {
                GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next();
                originalItems.add(gi);
            }
        }

        @Override
        public int getCount() {
            synchronized(mLock) {
                return items!=null ? items.size() : 0;  
            }
        }

        @Override
        public GlycaemicIndexItem getItem(int item) {
            GlycaemicIndexItem gi = null;
            synchronized(mLock) {
                    gi = items!=null ? items.get(item) : null;

            }
            return gi;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
                View v = convertView;
                if (v == null) {
                    LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    v = vi.inflate(R.layout.row, null);
                }

                GlycaemicIndexItem i  = null;
                synchronized(mLock) {
                    i = items.get(position);
                }

                if (i != null) {
                        TextView tt = (TextView) v.findViewById(R.id.rowText);
                        TextView bt = (TextView) v.findViewById(R.id.rowText2);
                        if (tt != null) {
                              tt.setText("Name: "+i.getName());                            
                        }
                        if(bt != null){
                              bt.setText("GI Value: " + i.getGlycaemicIndex());
                        }
                }
                return v;
        }
        /**
         * Implementing the Filterable interface.
         */
        public Filter getFilter() {
            if (filter == null) {
                filter = new GlycaemicIndexItemFilter();
            }
            return filter;
        }   

        /**
         * Custom Filter implementation for the items adapter.
         *
         */
        private class GlycaemicIndexItemFilter extends Filter {
            protected FilterResults performFiltering(CharSequence prefix) {
                // Initiate our results object
                FilterResults results = new FilterResults();

                // No prefix is sent to filter by so we're going to send back the original array
                if (prefix == null || prefix.length() == 0) {
                    synchronized (mLock) {
                        results.values = originalItems;
                        results.count = originalItems.size();
                    }
                } else {
                    synchronized(mLock) {
                            // Compare lower case strings
                        String prefixString = prefix.toString().toLowerCase();
                        final ArrayList<GlycaemicIndexItem> filteredItems = new ArrayList<GlycaemicIndexItem>();
                        // Local to here so we're not changing actual array
                        final ArrayList<GlycaemicIndexItem> localItems = new ArrayList<GlycaemicIndexItem>();
                        localItems.addAll(originalItems);
                        final int count = localItems.size();

                        for (int i = 0; i < count; i++) {
                            final GlycaemicIndexItem item = localItems.get(i);
                            final String itemName = item.getName().toString().toLowerCase();

                            // First match against the whole, non-splitted value
                            if (itemName.startsWith(prefixString)) {
                                filteredItems.add(item);
                            } else {} /* This is option and taken from the source of ArrayAdapter
                                final String[] words = itemName.split(" ");
                                final int wordCount = words.length;

                                for (int k = 0; k < wordCount; k++) {
                                    if (words[k].startsWith(prefixString)) {
                                        newItems.add(item);
                                        break;
                                    }
                                }
                            } */
                        }

                        // Set and return
                        results.values = filteredItems;
                        results.count = filteredItems.size();
                    }//end synchronized
                }

                return results;
            }

            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(CharSequence prefix, FilterResults results) {
                //noinspection unchecked
                synchronized(mLock) {
                    final ArrayList<GlycaemicIndexItem> localItems = (ArrayList<GlycaemicIndexItem>) results.values;
                    notifyDataSetChanged();
                    clear();
                    //Add the items back in
                    for (Iterator iterator = localItems.iterator(); iterator
                            .hasNext();) {
                        GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next();
                        add(gi);
                    }
                }//end synchronized
            }
        }
    }
}
1 голос
/ 29 ноября 2010

Я думаю, вы можете использовать notifyDataSetChanged(); в методе onFilterComplete.

...