Wrong scrolling with highliting in ListView when s

2019-08-16 04:04发布

问题:

I have an Android App with a search field and a listview. When the user types in the first character the application should smooth scroll to the first item with the given character and highlight the item. If the user remove the input the application should smooth scroll back to the first item without highlighting it.

After trying a lot and reading a lot I think I need some help. What I did as a Android beginner does not work in the right way. The app is scrolling - but the behaviour is kind of strange:

  • the wrong item is highlighted

  • after trying a second time (to e) two wrong items are highlighted

  • delete search input and this happened

There are some other strange things happened which can not be documented all here. E.g. when the app is scrolling behind the last viewed item and I scroll back then not the first item at position 0 is highlithed but the second. Scrolling down and back again and the second and the third item are highlighted.

And it is also wrong that they are highlighted coz with going back I do not want to highlight any item.

I think something is wrong with my index management - or something else which I can not explain is totally wrong.

Here is my project structure:

For the list I use an own class ItemsTask - but that should not matter too much for the problem. And the item adapter for the list is the ToDoListAdapter.

The whole project does a lot of more but I want to keep it short here due to find a solution only for the scrolling in the list. Here is the 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());
                }
            });


    }
}; 

The data for the list comes from an external xml-file.

And this is the 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;
        }
}

There is one more important point one should know:

I do not use a filter to get the result of the users input. I just want to jump to the listitem. As far as I can see I can not use any onClick event like I have seen in many post for a similar question. So this will not help in my situation.

I just tried some scrolling back and forth and noticed this: with scrolling through the list back and forth (not deleting the input field!) the app highlighted always some different list items. For me I can not see any logical schema for that.

回答1:

Try this:

Adapter:

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;
}

Hope that helps!!