搜索项目时在 ListView 中突出显示错误滚动

Wrong scrolling with highliting in ListView when searching item

我有一个带有搜索字段和列表视图的 Android 应用程序。当用户输入第一个字符时,应用程序应平滑滚动到具有给定字符的第一个项目并突出显示该项目。如果用户删除输入,应用程序应平滑滚动回到第一项而不突出显示它。

经过大量尝试和大量阅读后,我认为我需要一些帮助。作为 Android 初学者,我所做的工作并不正确。该应用程序正在滚动 - 但行为有点奇怪:

还有一些奇怪的事情发生了,这里不能一一列举。例如。当应用程序滚动到最后查看的项目后面并且我向后滚动时,不是位置 0 的第一个项目被高亮显示,而是第二个。再次向下和向后滚动,第二个和第三个项目会突出显示。

它们被突出显示也是错误的,因为我不想突出显示任何项目。

我认为我的索引管理有问题 - 或者我无法解释的其他事情是完全错误的。

这是我的项目结构:

对于列表,我使用自己的 class ItemsTask - 但这对问题来说应该无关紧要。列表的项目适配器是 ToDoListAdapter。

整个项目做的更多,但我想在这里保持简短,因为只为列表中的滚动找到解决方案。 这是 MainActivity.java:

> package com.wbapps.ListScrolling;

/**
 * Created by Andreas on 9/4/2017.
 */

import android.app.SearchManager;
import android.content.Context;
import android.graphics.Color;
import android.support.v7.app.ActionBar;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ListView;
import android.widget.SearchView;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MainActivity extends AppCompatActivity implements
        SearchView.OnQueryTextListener,SearchView.OnCloseListener {
    private SearchView search;
    /* object for the ListView from activity_main.xml*/
    ListView list_view;
    /*wb, 04Oct2017: List for the shopping items */
    List<ItemsTask> list_items;
    /*wb, 04Oct2017: adapter for items */
    TodoListAdapter items_adapter;
    /* wb, 04Oct2017: there must be a parser for the items-xml and another one for the categories-xml */
    XmlParser xmlparser;
    /* wb, 04Oct2017: create the two xml files for the shopping items and the categories */
    File file_shoppingItems;
    ActionBar actionBar;

    /* wb, 06Nov Declaration of variables used for the AlertBuilder multiItemsChecked*/
    ArrayList<Integer> mSelectedItems;

    /* wb, 09Nov Declaration of variables used for the AlertBuilder of singleItemsChecked*/
    String theChoice;
    /* wb, 23Nov2017: no more filter
    wb, 10Nov2017: Flag if filter is set or not
    boolean filtered = false;
    */
    Integer searchedItem = 0;

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

        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        search = (SearchView) findViewById(R.id.search);
        search.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        search.setIconifiedByDefault(false);
        search.setOnQueryTextListener(this);
        //search.setOnCloseListener(this);

        file_shoppingItems = new File(Environment.getExternalStorageDirectory(), "shoppingItems.xml");

        xmlparser = new XmlParser();
        list_items = new ArrayList<ItemsTask>();

        if (file_shoppingItems.exists()) {
            try {
                list_items = xmlparser.read(file_shoppingItems);
                if (list_items.isEmpty()) {
                    //Toast.makeText(this, "File exist but empty", Toast.LENGTH_SHORT).show();
                    file_shoppingItems.delete();
                    file_shoppingItems.createNewFile();
                } else {
                    sortList();
                }

            } catch (XmlPullParserException ex) {
                Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
            } catch (FileNotFoundException ex) {
                //Toast.makeText(this, "Error 2", Toast.LENGTH_SHORT).show();
                Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                //Toast.makeText(this, "Error 3", Toast.LENGTH_SHORT).show();
                Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            try {
                //Toast.makeText(this, "File does not exist - will be created", Toast.LENGTH_SHORT).show();
                file_shoppingItems.createNewFile();
            } catch (IOException ex) {
                //Toast.makeText(this, "Error 4", Toast.LENGTH_SHORT).show();
                Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        /* id list_view identifies the listview from the activity_main.xmll */
        list_view = (ListView) findViewById(R.id.list_view);
        registerForContextMenu(list_view);
        items_adapter = new TodoListAdapter(list_items, this);
        list_view.setAdapter(items_adapter);

        //To swipe away an item from the list
        SwipeDismissListViewTouchListener touchListener =
                new SwipeDismissListViewTouchListener(
                        list_view,
                        new SwipeDismissListViewTouchListener.DismissCallbacks()
                        {
                            /*wb,11Nov2017: Method "canDismiss" is required when calling
                             new SwipeDismissListViewTouchListener.DismissCallbacks()
                             but here not used */
                            @Override
                            public boolean canDismiss(int position) {return true;}

                            @Override
                            public void onDismiss(ListView listView, int[] reverseSortedPositions) {
                                for (int position : reverseSortedPositions) {
                                    list_items.remove(position);
                                    items_adapter.notifyDataSetChanged();
                                }
                            }
                        });
        list_view.setOnTouchListener(touchListener);
    }


    @Override
    public boolean onClose() {
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        return true;
    }

    @Override
    public boolean onQueryTextChange(String query) {
        int duration = 300;  //miliseconds
        int offset = 0;      //fromListTop
        searchedItem = -1;
        if (query.isEmpty()) {
            if (searchedItem > -1) {
                list_view.getChildAt(searchedItem);
                View v = (View) findViewById(R.id.list_task_view);
                v.setBackgroundColor(Color.rgb(176,233,249));
                searchedItem = -1;
            }
            for (int i=0; i < list_items.size();i++) {
                View v = (View) findViewById(R.id.list_task_view);
                v.setBackgroundColor(Color.rgb(176,233,249));
            }
            list_view.smoothScrollToPositionFromTop(0, offset, duration);
        }
        else
        {
            for (int i=0; i < list_items.size();i++) {
                //v.setBackgroundColor(Color.rgb(176,233,249));
                if (list_items.get(i).getTaskContent().toUpperCase().charAt(0) == query.toUpperCase().charAt(0)) {
                    searchedItem = i;
                    list_view.smoothScrollToPositionFromTop(i, offset, duration);
                    //list_view.setSelection(i);
                    View v = (View) findViewById(R.id.list_task_view);
                    v.setBackgroundColor(Color.rgb(238, 202, 197));
                    break;
                }
            }

        }

        return true;
    }

    @Override
    protected void onPause() {
        super.onPause();
        //wb, Sep 13, 2017:
        //before doing something else - like calling a new activity - write the list to
        //the data sourc file list_items.xml file
        try {
            xmlparser.write(list_items, file_shoppingItems);
        } catch (IOException ex) {
            Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
        }
    }


    /* wb, 18Sep2017: sort the list_items list  */
    public void sortList() {
            Collections.sort(list_items, new Comparator<ItemsTask>() {
                @Override
                public int compare(ItemsTask content1, ItemsTask content2) {
                /* ignore case sentsitivity  */
                    return content1.getTaskContent().compareToIgnoreCase(content2.getTaskContent());
                }
            });


    }
}; 

列表的数据来自外部 xml 文件。

这是 ToDoListAdapter:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.wbapps.ListScrolling;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TodoListAdapter extends BaseAdapter {
    private List<ItemsTask> listItemsTasks;
    private List<ItemsTask> savedItemsTasks;
    private final LayoutInflater inflater;
    ItemsTask itemsTask;

    /* wb, 04Oct2017: may be no more!  */
    String MyStr, MySubStr;

    TodoListAdapter.ViewHolder holder;

    public TodoListAdapter(List<ItemsTask> itemsTasks, Context context) {
        this.listItemsTasks = itemsTasks;
        inflater = LayoutInflater.from(context);
    }

    public int getCount() {
        return listItemsTasks.size();
    }

    public Object getItem(int position) {
        return listItemsTasks.get(position);
    }
    public long getItemId(int position) {return position;}

    public View getView(final int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
                convertView = inflater.inflate(R.layout.list_layout, parent, false);

                holder = new TodoListAdapter.ViewHolder();

                View v = convertView.findViewById(R.id.list_checkbox);

                holder.task_view = (TextView) convertView.findViewById(R.id.list_task_view);
                holder.done_box = (CheckBox) convertView.findViewById(R.id.list_checkbox);
                convertView.setTag(holder);
        } else {
            holder = (TodoListAdapter.ViewHolder) convertView.getTag();
        }

        itemsTask = (ItemsTask) getItem(position);

        /* wb, 15Sep,2017: Show checkbox only in MainActivity */
            holder.done_box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    listItemsTasks.get(position).setIsDone(isChecked);

                    if (isChecked) {
                        //wb,21Sep2017: No strike out neccessary
                        //holder.task_view.setPaintFlags(holder.task_view.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
                        holder.task_view.setPaintFlags(holder.task_view.getPaintFlags());
                    } else {
                        holder.task_view.setPaintFlags(0);
                    }
                }
            });

            holder.task_view.setText(itemsTask.getTaskContent());
            holder.done_box.setChecked(itemsTask.isDone());

            return convertView;
        }

    /* wb, 18Sep2017: sort the list_items list */
    public void sortList() {
        Collections.sort(listItemsTasks, new Comparator<ItemsTask>() {
            @Override
            public int compare(ItemsTask content1, ItemsTask content2) {
                /* return o1.getTaskContent().compareTo(o2.getTaskContent()); */
                /* ignore case sentsitivity */
                return content1.getTaskContent().compareToIgnoreCase(content2.getTaskContent());
            }
        });
    }

    static class ViewHolder {
            TextView spin_view;
            TextView task_view;
            CheckBox done_box;
            TextView sl_task;
            TextView sl_category;
        }
}

还有一点你应该知道:

我不使用过滤器来获取用户输入的结果。我只想跳转到列表项。据我所知,我不能使用任何 onClick 事件,就像我在许多 post 中看到的类似问题一样。所以这对我的情况没有帮助。

我只是尝试来回滚动并注意到这一点:通过来回滚动列表(不删除输入字段!),应用程序始终突出显示一些不同的列表项。对我来说,我看不到任何逻辑模式。

试试这个:

适配器:

public class TodoListAdapter extends BaseAdapter {
private List<ItemsTask> listItemsTasks;
private List<ItemsTask> savedItemsTasks;
private final LayoutInflater inflater;
ItemsTask itemsTask;

private int searchedItem = -1;

/* wb, 04Oct2017: may be no more!  */
String MyStr, MySubStr;

TodoListAdapter.ViewHolder holder;

public TodoListAdapter(List<ItemsTask> itemsTasks, Context context) {
    this.listItemsTasks = itemsTasks;
    inflater = LayoutInflater.from(context);
}

public int getCount() {
    return listItemsTasks.size();
}

public Object getItem(int position) {
    return listItemsTasks.get(position);
}
public long getItemId(int position) {return position;}

public View getView(final int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.list_layout, parent, false);

        holder = new TodoListAdapter.ViewHolder();

        View v = convertView.findViewById(R.id.list_checkbox);

        holder.task_view = (TextView) convertView.findViewById(R.id.list_task_view);
        holder.done_box = (CheckBox) convertView.findViewById(R.id.list_checkbox);
        convertView.setTag(holder);
    } else {
        holder = (TodoListAdapter.ViewHolder) convertView.getTag();
    }

    itemsTask = (ItemsTask) getItem(position);

    /* wb, 15Sep,2017: Show checkbox only in MainActivity */
    holder.done_box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            listItemsTasks.get(position).setIsDone(isChecked);

            if (isChecked) {
                //wb,21Sep2017: No strike out neccessary
                //holder.task_view.setPaintFlags(holder.task_view.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
                holder.task_view.setPaintFlags(holder.task_view.getPaintFlags());
            } else {
                holder.task_view.setPaintFlags(0);
            }
        }
    });

    holder.task_view.setText(itemsTask.getTaskContent());
    holder.done_box.setChecked(itemsTask.isDone());

    if(position == searchedItem){
        convertView.setBackgroundColor(Color.rgb(238, 202, 197));
    }else{
        convertView.setBackgroundColor(Color.rgb(176,233,249));
    }

    return convertView;
}

/* wb, 18Sep2017: sort the list_items list */
public void sortList() {
    Collections.sort(listItemsTasks, new Comparator<ItemsTask>() {
        @Override
        public int compare(ItemsTask content1, ItemsTask content2) {
            /* return o1.getTaskContent().compareTo(o2.getTaskContent()); */
            /* ignore case sentsitivity */
            return content1.getTaskContent().compareToIgnoreCase(content2.getTaskContent());
        }
    });
}

public void setSearchedItem(Integer position){
    this.searchedItem = position;
    notifyDataSetChanged();
}

static class ViewHolder {
    TextView spin_view;
    TextView task_view;
    CheckBox done_box;
    TextView sl_task;
    TextView sl_category;
}
}

onQueryTextChange:

   @Override
public boolean onQueryTextChange(String query) {
    int duration = 300;  //miliseconds
    int offset = 0;      //fromListTop
    if (query.isEmpty()) {
        items_adapter.setSearchedItem(-1);
        list_view.smoothScrollToPositionFromTop(0, offset, duration);
    }
    else
    {
        for (int i=0; i < list_items.size();i++) {
            if (list_items.get(i).getTaskContent().toUpperCase().charAt(0) == query.toUpperCase().charAt(0)) {
                items_adapter.setSearchedItem(i);
                list_view.smoothScrollToPositionFromTop(i, offset, duration);
                break;
            }
        }

    }

    return true;
}

希望对您有所帮助!!