How do I clear ListView selection?

前端 未结 4 539
名媛妹妹
名媛妹妹 2020-12-01 19:15

TL;DR: You choose an option from (a) my listview. Then, you change your mind and type something in (b) my edit text. How do I clear your list

相关标签:
4条回答
  • 2020-12-01 19:38

    Long Story Short

    • ListView selector (android:listSelector) is designed to indicate a click event, but not selected items.
    • If a ListView selector is drawn (after first click) it won't dissapear without drastic changes in the ListView
    • Hence use only drawables with transparent background if no state is applied to it as a ListView selector. Don't use a plain color resource for it, don't confuse yourself.
    • Use ListView choice mode (android:choiceMode) to indicate selected items.
    • ListView tells which row is selected by setting android:state_activated on their root view. Provide your adapter with corresponding layout/views to represent selected items correctly.

    Theory

    Well, the built-in selection in ListView is utterly tricky at a first glance. However there are two main distinctions you should keep in mind to avoid confusing like this - list view selector and choice mode.

    ListView selector

    ListView selector is a drawable resource that is assumed to indicate an event of clicking a list item. You can specify it either by XML-property android:listSelector or using method setSelector(). I couldn't find it in docs, but my understanding is that this resource should not be a plain color, because after it's being drawn, it won't vanish without drastic changes in the view (like setting an adapter, that in turn may cause some glitches to appear), hence such drawable should be visible only while particular state (e.g. android:state_pressed) is applied. Here is a simple example of the drawable that can be used as a List View selector

    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:state_pressed="true"
            android:drawable="@android:color/darker_gray" />
        <item
            android:drawable="@android:color/transparent" />
    </selector>
    

    For whatever reason you cannot use a Color State List as List View selector, but still can use plain colors (that are mostly inappropriate) and State List drawables. It makes things somewhat confusing. After the first click on a List View happens, you will not be able to remove List View selector from the List View easily.

    The main idea here is that List View selector is not designed to indicate selected item.

    ListView choice mode

    ListView choice mode is assumed to indicate selected items. As you might know, primarily there are two choice modes we can use in ListView - Single Choice and Multiple Choice. They allow to track a single or multiple rows selected respectively. You can set them via android:choiceMode XML-property or setChoiceMode() method. The ListView itself keeps selected rows in it and let them know which one is selected at any given moment by setting android:state_activated property of the row root view. In order to make your rows reflect this state, their root view must have a corresponding drawable set, e.g. as a background. Here is an example of such drawable:

    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:state_activated="true"
            android:drawable="@android:color/holo_green_light" />
        <item
            android:drawable="@android:color/transparent" />
    </selector>
    

    You can make rows selected/deselected programmatically using the setItemChecked() method. If you want a ListView to clear all selected items, you can use the clearChoices() method. You also can check selected items using the family of the methods: getCheckedItemCount(), getCheckedItemIds(), getCheckedItemPosition() (for single choice mode), getCheckedItemPositions() (for multiple choice mode)

    Conclusion

    If you want to keep things simple, do not use the List View selector to indicate selected items.


    Solving the issue

    Option 1. Dirty fix - hide selector

    Instead of actually removing selector, changing layouts and implementing a robust approach, we can hide the selector drawable when it's needed and show it later when clicking a ListView item:

        public void hideListViewSelector() {
            mListView.getSelector().setAlpha(0);
        }
    
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            if (mListView.getSelector().getAlpha() == 0) {
                mListView.getSelector().setAlpha(255);
            }
        }
    

    Option 2. Thoughtful way

    Let's go through your code and make it comply the rules i described step by step.

    Fix ListView layout

    In your ListView layout the selector is set to a plain color, and therefore your items are colored by it when they are clicked. The drawable you use as the ListView background have no impact, because ListView state doesn't change when its rows are clicked, hence your ListView always has just @color/windowBackground background.

    To solve your problem you need at first remove the selector from the ListView layout:

    <ListView
        android:id="@+id/listview"
        android:layout_width="wrap_content"
        android:layout_height="250dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/toolbar"
        android:listSelector="@color/colorPrimary"
        android:background="@color/windowBackground"
        android:choiceMode="singleChoice"/> 

    Make your rows reflect activated state

    In the comments you give your adapter as follows:

    final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, text1, listOfThings);
    

    You also asked me if it's possible to keep using standard adapter to achieve desired behavior. We can for sure, but anyway a few changes are required. I can see 3 options for this case:

    1. Using standard android checked layout

    You can just specify a corresponding standard layout - either any of the layouts that use CheckedTextView without changed background drawable as the root component or of those that use activatedBackgroundIndicator as their background drawable. For your case the most appropriate option should be the simple_list_item_activated_1. Just set it as in your ArrayAdapter constructor like this:

    final ArrayAdapter<String> adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_activated_1, android.R.id.text1, listOfThings);

    This option is the closest to what i understand by 'standard' adapter.

    2. Customize your adapter

    You can use standard layout and mostly standard adapter with a small exception of getting a view for your items. Just introduce an anonymous class and override the method getView(), providing row views with corresponding background drawable:

    final ArrayAdapter<String> adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1, listOfThings) {
    
        @NonNull
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            final View view = super.getView(position, convertView, parent);
            if (convertView == null) {
                view.setBackgroundResource(R.drawable.list_item_bg);
            }
            return view;
        }
    };

    3. Customize your layout

    The most common way of addressing this issue is of course introducing your own layout for the items view. Here is my simple example:

    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:background="@drawable/list_item_bg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:padding="16dp">
    
        <TextView
            android:id="@android:id/text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    </FrameLayout>
    

    I saved it in a file /res/layout/list_view_item.xml Do not forget setting this layout in your adapter:

    final ArrayAdapter<String> adapter = new ArrayAdapter(this, R.layout.list_view_item, android.R.id.text1, listOfThings);

    Clear choices when needed

    After that your rows will reflect selected state when they are clicked, and you can easily clear the selected state of your ListView by calling clearChoices() and consequence requestLayout() to ask the ListView to redraw itself.

    One little comment here that if you want unselect the item when user start typing, but not when he actually clicks the return (done) button, you need to use a TextWatcher callback instead:

        mEditText.addTextChangedListener(new TextWatcher(){
    
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
    
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (mListView.getCheckedItemCount() > 0) {
                    mListView.clearChoices();
                    mListView.requestLayout();
                }
            }
    
            @Override
            public void afterTextChanged(Editable s) {}
        });
    

    Hopefully, it helped.

    0 讨论(0)
  • 2020-12-01 19:39

    I have a good solution to do that. Add EditText to your layout which contains on your ListView as this layout:

        <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <ListView
            android:id="@+id/list_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="vertical"
            android:choiceMode="singleChoice"
            />
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Comment"
            android:layout_below="@id/list_view"
            android:id="@+id/editText"
            android:nextFocusUp="@id/editText"
            android:nextFocusLeft="@id/editText"/>
    </RelativeLayout>
    

    Then initialize Boolean variable to check whether editText if focused or not for example use this : boolean canBeSelected = true;

    Then after setting adapter use this code:

    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                if (canBeSelected) {
                    listView.setSelector(R.drawable.background);
                    listView.setSelected(true);
                    listView.setSelection(i);
                } else {
                    if (!editText.isFocused()){
                        canBeSelected = true;
                        listView.setSelector(R.drawable.background);
                        listView.setSelected(true);
                        listView.setSelection(i);
                    }
                }
            }
        });
        editText.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                canBeSelected = false;
                Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
                listView.setSelector(transparentDrawable);
                listView.clearChoices();
                listView.setSelected(false);
                return false;
            }
        });
    
    
    
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                if (editText.isFocused()){
                    Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
                    listView.setSelector(transparentDrawable);
                    listView.clearChoices();
                    listView.setSelected(false);
                    canBeSelected = false;
                }
            }
    
            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
                listView.setSelector(transparentDrawable);
                listView.clearChoices();
                listView.setSelected(false);
                canBeSelected = false;
            }
    
            @Override
            public void afterTextChanged(Editable editable) {
                if (editText.isFocused()) {
                    Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
                    listView.setSelector(transparentDrawable);
                    listView.clearChoices();
                    listView.setSelected(false);
                    canBeSelected = false;
                }
            }
        });
    }
    

    Hope it works with you :)

    0 讨论(0)
  • 2020-12-01 19:40

    Just call clear when you make the request for the second data set:

        arrayAdapter!!.clear()
    

    You load your first data set
    The user select one elements,
    This action highlight your item
    For any reason you launch the reload of your data set (because edittext's value changed),
    at this moment call, clear() on your adapter.
    Then you retrieved your dataset, you send it to the arrayAdapter and No one is selected .
    This is because when you clear, it also clear the selected flag

    0 讨论(0)
  • 2020-12-01 19:45

    Re-setting the adapter in the edittext listener worked for me:

    editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    
            }
            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    
                listview.clearChoices();
                listview.setAdapter(adapter);
                Toast.makeText(getApplicationContext(),"Typing" + listview.getSelectedItemPosition(), Toast.LENGTH_SHORT).show();
            }
            @Override
            public void afterTextChanged(Editable editable) {
    
            }
        });
    

    I put the selected index in a toast to check if the item was correctly deselected.

    Hope this works!!

    0 讨论(0)
提交回复
热议问题