Làm cách nào để giữ onItemSelected không bắn vào Spinner mới được khởi tạo?


419

Tôi đã nghĩ ra một số cách ít thanh lịch hơn để giải quyết vấn đề này, nhưng tôi biết tôi phải thiếu một cái gì đó.

My onItemSelectedtắt ngay lập tức mà không có bất kỳ tương tác nào với người dùng và đây là hành vi không mong muốn. Tôi muốn cho UI chờ cho đến khi người dùng chọn một cái gì đó trước khi nó làm bất cứ điều gì.

Tôi thậm chí đã thử thiết lập trình nghe trong onResume(), hy vọng điều đó sẽ có ích, nhưng không được.

Làm cách nào tôi có thể ngăn điều này bắn ra trước khi người dùng có thể chạm vào điều khiển?

public class CMSHome extends Activity { 

private Spinner spinner;

@Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Heres my spinner ///////////////////////////////////////////
    spinner = (Spinner) findViewById(R.id.spinner);
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
            this, R.array.pm_list, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);
    };

public void onResume() {
    super.onResume();
    spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
}

    public class MyOnItemSelectedListener implements OnItemSelectedListener {

    public void onItemSelected(AdapterView<?> parent,
        View view, int pos, long id) {

     Intent i = new Intent(CMSHome.this, ListProjects.class);
     i.putExtra("bEmpID", parent.getItemAtPosition(pos).toString());
        startActivity(i);

        Toast.makeText(parent.getContext(), "The pm is " +
          parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
    }

    public void onNothingSelected(AdapterView parent) {
      // Do nothing.
    }
}
}

2
Bạn có thể nhìn vào giải pháp này, nó rất dễ dàng và thiết thực. stackoverflow.com/a/10102356/621951
Günay Gültekin

1
Một giải pháp đơn giản là làm cho mục đầu tiên Spinnertrống và bên trong onItemSelectedbạn có thể phát hiện nếu Chuỗi không trống rồi startActivity!
Muhammad Babar

Câu trả lời:


78

Tôi đã mong đợi giải pháp của bạn hoạt động - Tôi mặc dù sự kiện lựa chọn sẽ không kích hoạt nếu bạn đặt bộ điều hợp trước khi thiết lập trình nghe.

Điều đó đang được nói, một cờ boolean đơn giản sẽ cho phép bạn phát hiện sự kiện lựa chọn đầu tiên lừa đảo và bỏ qua nó.


15
ừ, ừ. Đó là những gì tôi có nghĩa là một giải pháp không phù hợp. Có vẻ như phải có một cách tốt hơn. Cảm ơn bạn mặc dù.
FauxReal

5
Chủ đề này trên Dev ml có cái nhìn sâu sắc hơn về điều này: Groups.google.com/group/android-developers/browse_thread/thread/ cảm - Thật không may, không có giải pháp nào được đưa ra ...
BoD

25
Quá trình đặt ra các thành phần kích hoạt người nghe lựa chọn. Do đó, bạn phải thêm người nghe sau khi bố trí đã được thực hiện. Tôi đã không thể tìm thấy một nơi đơn giản phù hợp để làm điều này như bố trí dường như xảy ra tại một số điểm sau onResume()onPostResume(), vì vậy tất cả các móc bình thường đã hoàn thành vào thời điểm đó bố trí xảy ra.
Dan Dyer

28
Tôi sẽ tránh xa cờ boolean này - như thể hành vi thay đổi trong tương lai nó có thể gây ra lỗi. Một giải pháp chống đạn hơn sẽ là giữ một biến với "chỉ mục được chọn hiện tại", được khởi tạo cho mục đầu tiên được chọn. Sau đó vào sự kiện lựa chọn - kiểm tra xem nó có bằng vị trí mới không - quay lại và không làm gì cả. Tất nhiên cập nhật các biến trên lựa chọn.
daniel.gindi

2
Điều này không hoạt động. Trả lời bởi @casanova hoạt động. Đó nên là câu trả lời được chấp nhận.
Siddharth

379

Việc sử dụng Runnables là hoàn toàn không chính xác.

Sử dụng setSelection(position, false);trong lựa chọn ban đầu trướcsetOnItemSelectedListener(listener)

Bằng cách này, bạn đặt lựa chọn của mình mà không có hình động làm cho trình nghe được chọn trên mục được gọi. Nhưng người nghe là null nên không có gì được chạy. Sau đó, người nghe của bạn được chỉ định.

Vì vậy, làm theo trình tự chính xác này:

Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);

48
+1 Viên ngọc ẩn! Truyền sai thành tham số "animate" không gọi lại cuộc gọi người nghe. Tuyệt vời!
pkk

3
+1 Giải pháp kỳ lạ nhưng tao nhã :) May mắn thay, dù sao tôi cũng đã phải gọi setSelection ...
Martin T.

35
Người nghe vẫn sẽ kích hoạt khi phần tử Spinner UI được lắp ráp, vì vậy nó sẽ kích hoạt bất kể điều gì không ngăn chặn hành vi không mong muốn được mô tả bởi OP. Điều này hoạt động rất tốt nếu không được khai báo trong hoặc trước onCreateView (), nhưng đó không phải là những gì họ yêu cầu.
Rudi Kershaw

6
Hữu ích, nhưng giải quyết một vấn đề khác với OP trình bày. OP đề cập đến một sự kiện lựa chọn (không may) tự động kích hoạt khi chế độ xem xuất hiện lần đầu tiên mặc dù lập trình viên không thực hiện setSelection .
ToolmakerSteve

2
Tham số giá trị "false" trong phương thức setSelection (..) là giải pháp cho tôi. ty!
Dani

195

Tham khảo câu trả lời của Dan Dyer, hãy thử đăng ký OnSelectListenertheo post(Runnable)phương thức:

spinner.post(new Runnable() {
    public void run() {
        spinner.setOnItemSelectedListener(listener);
    }
});

Bằng cách làm điều đó cho tôi hành vi mong muốn cuối cùng đã xảy ra.

Trong trường hợp này cũng có nghĩa là người nghe chỉ bắn vào một mục đã thay đổi.


1
Tôi gặp lỗi khi nói: Phương thức setOnItemSelectedListener (AdapterView.OnItemSelectedListener) trong loại AdapterView <SpinnerAd CHƯƠNG> không áp dụng được cho các đối số (mới Runnable () {}) tại sao vậy?
Jakob

Đây không phải là về cơ bản thiết lập một điều kiện cuộc đua giữa Runnable và UI Thread sao?
kenny_k

6
@theFunkyEngineer - Mã này phải được chạy từ một trong các phương thức luồng chính onCreate(), onResume()v.v. Tôi thường sử dụng thủ thuật này onCreate()chỉ sau mã bố cục.
Richard Le Mesurier

1
Đây là một giải pháp tuyệt vời và chắc chắn không phải là hack! Loại chức năng này là cách mọi thứ được thực hiện sâu trong khung. Thật xấu hổ khi Spinner không làm điều này trong nội bộ. Tuy nhiên, đây là cách sạch nhất để có một số mã được đảm bảo để chạy sau khi tạo Hoạt động. Điều này hoạt động vì người nghe chưa được đặt trên Spinner khi Activity cố gắng thông báo cho họ.
jophde

1
Đây là một giải pháp chấp nhận được . không phải là một cú mù giải pháp khác dễ xảy ra vấn đề thay đổi hành vi trong tương lai.
Kuldeep Singh Dhaka

50

Tôi đã tạo một phương thức tiện ích nhỏ để thay đổi Spinnerlựa chọn mà không thông báo cho người dùng:

private void setSpinnerSelectionWithoutCallingListener(final Spinner spinner, final int selection) {
    final OnItemSelectedListener l = spinner.getOnItemSelectedListener();
    spinner.setOnItemSelectedListener(null);
    spinner.post(new Runnable() {
        @Override
        public void run() {
            spinner.setSelection(selection);
            spinner.post(new Runnable() {
                @Override
                public void run() {
                    spinner.setOnItemSelectedListener(l);
                }
            });
        }
    });
}

Nó vô hiệu hóa người nghe, thay đổi lựa chọn và kích hoạt lại người nghe sau đó.

Thủ thuật là các cuộc gọi không đồng bộ với luồng UI, vì vậy bạn phải thực hiện nó trong các bài đăng xử lý liên tiếp.


Tuyệt vời. Tôi đã có nhiều spinners và cố gắng đặt tất cả các trình nghe của họ thành null trước khi đặt giá trị của chúng, sau đó tôi đặt tất cả chúng trở lại với những gì chúng được cho là, nhưng vì một số lý do không hoạt động. thay vào đó đã thử chức năng này và nó đã hoạt động. Tôi không biết tại sao của tôi không hoạt động, nhưng điều này hoạt động vì vậy tôi không quan tâm: D
JStephen

4
Lưu ý: nếu bạn gọi setSpinnerSelectionWithoutCallingListenernhanh hai lần, để cuộc gọi thứ hai được thực hiện trong khi cuộc gọi thứ nhất đã thiết lập trình nghe null, spinner của bạn sẽ bị kẹt với nullngười nghe mãi mãi. Tôi đề nghị sửa chữa sau: thêm if (listener == null) return;sau spinner.setSelection(selection).
Hươu cao cổ Violet

34

Thật không may, có vẻ như hai giải pháp được đề xuất phổ biến nhất cho vấn đề này, đó là đếm số lần gọi lại và đăng Runnable để đặt lại cuộc gọi sau đó có thể thất bại khi các tùy chọn trợ năng được bật. Đây là một lớp trợ giúp làm việc xung quanh những vấn đề này. Giải thích thêm là trong khối bình luận.

import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;

/**
 * Spinner Helper class that works around some common issues 
 * with the stock Android Spinner
 * 
 * A Spinner will normally call it's OnItemSelectedListener
 * when you use setSelection(...) in your initialization code. 
 * This is usually unwanted behavior, and a common work-around 
 * is to use spinner.post(...) with a Runnable to assign the 
 * OnItemSelectedListener after layout.
 * 
 * If you do not call setSelection(...) manually, the callback
 * may be called with the first item in the adapter you have 
 * set. The common work-around for that is to count callbacks.
 * 
 * While these workarounds usually *seem* to work, the callback
 * may still be called repeatedly for other reasons while the 
 * selection hasn't actually changed. This will happen for 
 * example, if the user has accessibility options enabled - 
 * which is more common than you might think as several apps 
 * use this for different purposes, like detecting which 
 * notifications are active.
 * 
 * Ideally, your OnItemSelectedListener callback should be
 * coded defensively so that no problem would occur even
 * if the callback was called repeatedly with the same values
 * without any user interaction, so no workarounds are needed.
 * 
 * This class does that for you. It keeps track of the values
 * you have set with the setSelection(...) methods, and 
 * proxies the OnItemSelectedListener callback so your callback
 * only gets called if the selected item's position differs 
 * from the one you have set by code, or the first item if you
 * did not set it.
 * 
 * This also means that if the user actually clicks the item
 * that was previously selected by code (or the first item
 * if you didn't set a selection by code), the callback will 
 * not fire.
 * 
 * To implement, replace current occurrences of:
 * 
 *     Spinner spinner = 
 *         (Spinner)findViewById(R.id.xxx);
 *     
 * with:
 * 
 *     SpinnerHelper spinner = 
 *         new SpinnerHelper(findViewById(R.id.xxx))
 *         
 * SpinnerHelper proxies the (my) most used calls to Spinner
 * but not all of them. Should a method not be available, use: 
 * 
 *      spinner.getSpinner().someMethod(...)
 *
 * Or just add the proxy method yourself :)
 * 
 * (Quickly) Tested on devices from 2.3.6 through 4.2.2
 * 
 * @author Jorrit "Chainfire" Jongma
 * @license WTFPL (do whatever you want with this, nobody cares)
 */
public class SpinnerHelper implements OnItemSelectedListener {
    private final Spinner spinner;

    private int lastPosition = -1;
    private OnItemSelectedListener proxiedItemSelectedListener = null;  

    public SpinnerHelper(Object spinner) {
         this.spinner = (spinner != null) ? (Spinner)spinner : null;        
    }

    public Spinner getSpinner() {
        return spinner;
    }

    public void setSelection(int position) { 
        lastPosition = Math.max(-1, position);
        spinner.setSelection(position);     
    }

    public void setSelection(int position, boolean animate) {
        lastPosition = Math.max(-1, position);
        spinner.setSelection(position, animate);        
    }

    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        proxiedItemSelectedListener = listener;
        spinner.setOnItemSelectedListener(listener == null ? null : this);
    }   

    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (position != lastPosition) {
            lastPosition = position;
            if (proxiedItemSelectedListener != null) {
                proxiedItemSelectedListener.onItemSelected(
                        parent, view, position, id
                );
            }
        }
    }

    public void onNothingSelected(AdapterView<?> parent) {
        if (-1 != lastPosition) {
            lastPosition = -1;
            if (proxiedItemSelectedListener != null) {
                proxiedItemSelectedListener.onNothingSelected(
                        parent
                );
            }
        }
    }

    public void setAdapter(SpinnerAdapter adapter) {
        if (adapter.getCount() > 0) {
            lastPosition = 0;
        }
        spinner.setAdapter(adapter);
    }

    public SpinnerAdapter getAdapter() { return spinner.getAdapter(); } 
    public int getCount() { return spinner.getCount(); }    
    public Object getItemAtPosition(int position) { return spinner.getItemAtPosition(position); }   
    public long getItemIdAtPosition(int position) { return spinner.getItemIdAtPosition(position); }
    public Object getSelectedItem() { return spinner.getSelectedItem(); }
    public long getSelectedItemId() { return spinner.getSelectedItemId(); }
    public int getSelectedItemPosition() { return spinner.getSelectedItemPosition(); }
    public void setEnabled(boolean enabled) { spinner.setEnabled(enabled); }
    public boolean isEnabled() { return spinner.isEnabled(); }
}

3
Đây phải là câu trả lời được bình chọn cao nhất. Nó đơn giản nhưng rực rỡ. Nó cho phép bạn giữ tất cả các triển khai hiện tại của bạn giống nhau, ngoại trừ một dòng nơi bạn khởi tạo. Chắc chắn thực hiện các dự án cũ phù hợp retro khá dễ dàng. Trên hết, tôi đã giết hai con chim bằng một hòn đá bằng cách thực hiện giao diện OnTouchLisener để đóng bàn phím khi spinner mở. Bây giờ tất cả các spinners của tôi cư xử chính xác theo cách tôi muốn.
dùng3829751

Câu trả lời đẹp. Nó vẫn kích hoạt phần tử thứ 0 khi tôi thêm ALL () vào bộ điều hợp nhưng phần tử thứ 0 của tôi là dấu chấm lửng cho hành vi trung lập (không làm gì).
jwehrle

31

Tôi đã gặp RẤT NHIỀU vấn đề với spinner bắn khi tôi không muốn, và tất cả các câu trả lời ở đây đều không đáng tin cậy. Họ làm việc - nhưng chỉ đôi khi. Cuối cùng, bạn sẽ chạy vào các tình huống trong đó chúng sẽ thất bại và đưa lỗi vào mã của bạn.

Điều làm việc cho tôi là lưu trữ chỉ mục được chọn cuối cùng trong một biến và đánh giá nó trong trình nghe. Nếu nó giống như chỉ mục mới được chọn không làm gì và trả về, thì tiếp tục với người nghe. Làm cái này:

//Declare a int member variable and initialize to 0 (at the top of your class)
private int mLastSpinnerPosition = 0;

//then evaluate it in your listener
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {

  if(mLastSpinnerPosition == i){
        return; //do nothing
  }

  mLastSpinnerPosition = i;
  //do the rest of your code now

}

Hãy tin tôi khi tôi nói điều này, đây là giải pháp đáng tin cậy nhất. Một hack, nhưng nó hoạt động!


Điều này thậm chí sẽ hoạt động nếu bạn đang cố gắng thay đổi giá trị? Trong trường hợp của tôi, tôi đang cố gắng đặt giá trị thành 3 như khi nó thực sự bằng 0 mà không kích hoạt người nghe thay đổi. Bạn đang nói rằng int i chỉ trả về một giá trị khác nếu người dùng đang chọn nó?
JStephen

Xin chào JStephen, tôi không chắc chắn 100% ý của bạn là gì. Nhưng int i sẽ là vị trí của spinner bất cứ khi nào onItemSelected được kích hoạt. Vấn đề là onItemSelected được kích hoạt bất cứ khi nào spinner được tải lần đầu tiên, mà không có bất kỳ tương tác người dùng thực tế nào, dẫn đến hành vi không mong muốn trong trường hợp này. int i sẽ bằng 0 tại điểm ban đầu này vì đây là chỉ số bắt đầu mặc định khi spinner được tải lần đầu tiên. Vì vậy, giải pháp của tôi kiểm tra để đảm bảo rằng một mục thực tế khác được chọn thay vì mục hiện được chọn đang được chọn lại ... Điều này có trả lời câu hỏi của bạn không?
Chris

Xin chào Chris, tôi có một trang lấy thông tin từ cơ sở dữ liệu để người dùng chỉnh sửa. Khi trang mở ra, tôi điền vào các spinners và sau đó đặt vị trí của chúng thành các giá trị trong cơ sở dữ liệu. Vì vậy, nếu tôi đặt vị trí của họ thành 3 chẳng hạn, điều này khiến onItemSelected kích hoạt với i được đặt thành 3 khác với ban đầu. Tôi đã nghĩ rằng bạn đang nói rằng tôi chỉ được đặt nếu người dùng thực sự thay đổi nó.
JStephen

4
Nếu người dùng chọn vị trí 0 thì sao? Họ sẽ bị bỏ qua.
Yetti99

Tôi không nghĩ rằng vị trí cuối cùng là ý tưởng tốt. Tôi khởi tạo spinners bằng cách tải vị trí từ SharedPreferences và sử dụng setSelection. Rất thường các giá trị trong SharedPrefs không giống với các giá trị mặc định khi các spinners được tạo, do đó onItemSelected sẽ được kích hoạt khi bắt đầu.
Arthez

26

Tôi đã ở trong tình huống tương tự, và tôi có một giải pháp đơn giản làm việc cho tôi.

Có vẻ như phương pháp setSelection(int position)setSelected(int position, boolean animate) có thực hiện nội bộ khác nhau.

Khi bạn sử dụng phương thức thứ hai setSelected(int position, boolean animate)với cờ animate giả, bạn sẽ có được lựa chọn mà không kích hoạt trình onItemSelectednghe.


Cách tiếp cận tốt hơn là không lo lắng về các cuộc gọi thêm vào onItemSelected, nhưng để đảm bảo rằng nó hiển thị lựa chọn đúng. Vì vậy, việc gọi spinner.setSelection (chọn Index) trước khi thêm trình nghe khiến nó hoạt động ổn định với tôi.
andude

1
không có phương thức setSelected (int
location

4
Cuộc gọi thực tế bạn cần làsetSelection(int position, boolean animate);
Brad

+1 cho bạn. Điều này giải quyết một vấn đề tổng quát hơn khi mã sửa đổi nhiều lần Nội dung & lựa chọn của Spinner giữ trênItemSelected chỉ để tương tác với người dùng
alrama

4
đáng tiếc cờ hoạt hình sai vẫn gọi onItemSelectedtrong API23
mcy 24/2/2017

23

Chỉ cần đưa ra gợi ý về việc sử dụng onTouchListener để phân biệt giữa các cuộc gọi tự động đến setOnItemSelectedListener (là một phần của khởi tạo Activity, v.v.) so với các cuộc gọi đến nó được kích hoạt bởi tương tác người dùng thực tế, tôi đã làm như sau sau khi thử một số đề xuất khác ở đây và thấy rằng nó hoạt động tốt với ít dòng mã nhất.

Chỉ cần đặt trường Boolean cho Hoạt động / Đoạn của bạn như:

private Boolean spinnerTouched = false;

Sau đó, ngay trước khi bạn đặt setOnItemSelectedListener của spinner, hãy đặt onTouchListener:

    spinner.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            System.out.println("Real touch felt.");
            spinnerTouched = true;
            return false;
        }
    });

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    ...
         if (spinnerTouched){
         //Do the stuff you only want triggered by real user interaction.
        }
        spinnerTouched = false;

1
Điều đó hoạt động rất tốt và kể từ Android 6+, đó là phương pháp duy nhất hoạt động. NHƯNG, bạn cũng phải làm tương tự với setOnKeyListener () hoặc nó không hoạt động khi người dùng điều hướng bằng bàn phím.
Stéphane

Hoạt động tuyệt vời, tất cả các giải pháp khác có một số vấn đề với các điện thoại khác nhau.
Ziwei Zeng

Điều này là đơn giản và hoàn toàn hoàn hảo! Không cần thêm vô nghĩa, chỉ cần ghi nhớ logic. Tôi mừng vì tôi đã di chuyển đến tận đây!
dùng3833732

Thay vì setOnKeyListener (), bạn có thể phân lớp spinner và đặt cờ spinnerTouched = true trong phương thức preformClick () bị ghi đè, được gọi trong cả hai trường hợp (touch / key). Nghỉ ngơi là như nhau.
Toàn năng

Chỉ muốn đề cập đến xuất hiện này để giải quyết các lỗi tương tự với DropDownPreferences Gần đây tôi đã đăng ở đây: stackoverflow.com/questions/61867118/... Tôi không thể f * cking tin rằng nó tbh: D
Daniel Wilson

13
spinner.setSelection(Adapter.NO_SELECTION, false);

3
Mã có thể tự nói lên, nhưng một chút giải thích sẽ đi một chặng đường dài :)
nhaarman

8

Sau khi nhổ tóc được một lúc lâu, tôi đã tạo ra lớp Spinner của riêng mình. Tôi đã thêm một phương thức để ngắt kết nối và kết nối người nghe một cách thích hợp.

public class SaneSpinner extends Spinner {
    public SaneSpinner(Context context) {
        super(context);
    }

    public SaneSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SaneSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    // set the ceaseFireOnItemClickEvent argument to true to avoid firing an event
    public void setSelection(int position, boolean animate, boolean ceaseFireOnItemClickEvent) {
        OnItemSelectedListener l = getOnItemSelectedListener();
        if (ceaseFireOnItemClickEvent) {
            setOnItemSelectedListener(null);
        }

        super.setSelection(position, animate);

        if (ceaseFireOnItemClickEvent) {
            setOnItemSelectedListener(l);
        }
    }
}

Sử dụng nó trong XML của bạn như thế này:

<my.package.name.SaneSpinner
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/mySaneSpinner"
    android:entries="@array/supportedCurrenciesFullName"
    android:layout_weight="2" />

Tất cả những gì bạn phải làm là truy xuất thể hiện của SaneSpinner sau khi lạm phát và lựa chọn tập hợp cuộc gọi như thế này:

mMySaneSpinner.setSelection(1, true, true);

Với điều này, không có sự kiện nào được kích hoạt và tương tác người dùng không bị gián đoạn. Điều này làm giảm độ phức tạp mã của tôi rất nhiều. Điều này nên được bao gồm trong kho Android vì nó thực sự là một PITA.


1
Điều này không làm việc cho tôi, nó vẫn kích hoạt onItemSelected.
Arthez

Arthez vui lòng kiểm tra lại nếu bạn thực sự chuyển sang đối số thứ ba. Nếu có một cái gì đó khác là sai ở đây. Nếu có thể gửi mã của bạn.
fusion44

8

Không có sự kiện không mong muốn từ giai đoạn bố trí nếu bạn trì hoãn thêm người nghe cho đến khi bố cục kết thúc:

spinner.getViewTreeObserver().addOnGlobalLayoutListener(
    new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once works for JELLY_BEAN and later
            spinner.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            // add the listener
            spinner.setOnItemSelectedListener(new OnItemSelectedListener() {

                @Override
                public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
                    // check if pos has changed
                    // then do your work
                }

                @Override
                public void onNothingSelected(AdapterView<?> arg0) {
                }

            });

        }
    });

Điều này hoạt động và IMO nó là giải pháp sạch nhất cho vấn đề cụ thể của OP. Tôi muốn lưu ý rằng bạn có thể xóa các ViewTreeObserver.OnGlobalLayoutListenerphiên bản trên J bên dưới bằng cách gọi ViewTreeObserver.removeGlobalOnLayoutListener, không dùng nữa và có tên tương tự với phương thức mà câu trả lời này sử dụng.
Jack Meister

7

Điều này sẽ xảy ra nếu bạn đang thực hiện lựa chọn trong mã như;

   mSpinner.setSelection(0);

Thay vì sử dụng câu lệnh trên

   mSpinner.setSelection(0,false);//just simply do not animate it.

Chỉnh sửa: Phương pháp này không hoạt động cho Mi Android Phiên bản Mi UI.


2
Điều này chắc chắn đã giải quyết vấn đề cho tôi. Tôi đã đọc tài liệu về tiện ích Spinner .. thật khó để hiểu được sự khác biệt: setSelection (int vị trí, boolean animate) -> Chuyển trực tiếp đến một mục cụ thể trong dữ liệu bộ điều hợp. setSelection (vị trí int) -> Đặt mục hiện được chọn.
Matt

5

Tôi đã có một câu trả lời rất đơn giản, chắc chắn 100% nó hoạt động:

boolean Touched=false; // this a a global variable

public void changetouchvalue()
{
   Touched=true;
}

// this code is written just before onItemSelectedListener

 spinner.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            System.out.println("Real touch felt.");
            changetouchvalue();
            return false;
        }
    });

//inside your spinner.SetonItemSelectedListener , you have a function named OnItemSelected iside that function write the following code

if(Touched)
{
 // the code u want to do in touch event
}

3

Tôi đã tìm thấy giải pháp thanh lịch hơn nhiều cho việc này. Nó liên quan đến việc đếm số lần ArrayAd CHƯƠNG (trong trường hợp của bạn là "bộ điều hợp") đã được gọi. Giả sử bạn có 1 spinner và bạn gọi:

int iCountAdapterCalls = 0;

ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
            this, R.array.pm_list, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);

Khai báo một bộ đếm int sau phương thức onCreate và sau đó bên trong phương thức onItemSelected () đặt một điều kiện "nếu" để kiểm tra số lần at CHƯƠNG được gọi. Trong trường hợp của bạn, bạn có nó được gọi chỉ một lần như vậy:

if(iCountAdapterCalls < 1)
{
  iCountAdapterCalls++;
  //This section executes in onCreate, during the initialization
}
else
{
  //This section corresponds to user clicks, after the initialization
}

2

Đóng góp nhỏ của tôi là một biến thể của một số ở trên đã phù hợp với tôi một vài lần.

Khai báo một biến số nguyên làm giá trị mặc định (hoặc giá trị được sử dụng cuối cùng được lưu trong tùy chọn). Sử dụng spinner.setSelection (myDefault) để đặt giá trị đó trước khi người nghe được đăng ký. Trong kiểm tra onItemSelected, xem giá trị spinner mới có bằng với giá trị bạn đã gán trước khi chạy bất kỳ mã nào nữa không.

Điều này có thêm lợi thế là không chạy mã nếu người dùng chọn lại cùng một giá trị.


1

Sau khi gặp vấn đề tương tự, tôi đã tìm đến giải pháp này bằng cách sử dụng thẻ. Ý tưởng đằng sau nó rất đơn giản: Bất cứ khi nào spinner được thay đổi theo chương trình, hãy đảm bảo thẻ phản ánh vị trí đã chọn. Trong trình nghe, sau đó bạn kiểm tra xem vị trí đã chọn có bằng thẻ không. Nếu có, lựa chọn spinner đã được thay đổi theo chương trình.

Dưới đây là lớp "proxy spinner" mới của tôi:

package com.samplepackage;

import com.samplepackage.R;
import android.widget.Spinner;

public class SpinnerFixed {

    private Spinner mSpinner;

    public SpinnerFixed(View spinner) {
         mSpinner = (Spinner)spinner;
         mSpinner.setTag(R.id.spinner_pos, -2);
    }

    public boolean isUiTriggered() {
         int tag = ((Integer)mSpinner.getTag(R.id.spinner_pos)).intValue();
         int pos = mSpinner.getSelectedItemPosition();
         mSpinner.setTag(R.id.spinner_pos, pos);
         return (tag != -2 && tag != pos);
    }

    public void setSelection(int position) {
        mSpinner.setTag(R.id.spinner_pos, position);
        mSpinner.setSelection(position);
    }

    public void setSelection(int position, boolean animate) {
        mSpinner.setTag(R.id.spinner_pos, position);
        mSpinner.setSelection(position, animate);
    }

    // If you need to proxy more methods, use "Generate Delegate Methods"
    // from the context menu in Eclipse.
}

Bạn cũng sẽ cần một tệp XML với thiết lập thẻ trong Valuesthư mục của bạn . Tôi đặt tên cho tập tin của tôi spinner_tag.xml, nhưng điều đó tùy thuộc vào bạn. Nó trông như thế này:

<resources xmlns:android="http://schemas.android.com/apk/res/android">
  <item name="spinner_pos" type="id" />
</resources>

Bây giờ thay thế

Spinner myspinner;
...
myspinner = (Spinner)findViewById(R.id.myspinner);

trong mã của bạn với

SpinnerFixed myspinner;
...
myspinner = new SpinnerFixed(findViewById(R.id.myspinner));

Và làm cho trình xử lý của bạn trông giống như thế này:

myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (myspinner.isUiTriggered()) {
            // Code you want to execute only on UI selects of the spinner
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
    }
});

Hàm isUiTriggered()sẽ trả về true nếu và chỉ khi spinner được người dùng thay đổi. Lưu ý rằng chức năng này có tác dụng phụ - nó sẽ đặt thẻ, vì vậy cuộc gọi thứ hai trong cùng một cuộc gọi người nghe sẽ luôn trả vềfalse .

Trình bao bọc này cũng sẽ xử lý vấn đề với người nghe được gọi trong quá trình tạo bố cục.

Hãy vui vẻ, Jens.


1

Vì không có gì làm việc cho tôi và tôi có nhiều hơn 1 spinner trong chế độ xem của mình (và IMHO giữ bản đồ bool là quá mức cần thiết), tôi sử dụng thẻ để đếm số lần nhấp:

spinner.setTag(0);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            Integer selections = (Integer) parent.getTag();
            if (selections > 0) {
                // real selection
            }
            parent.setTag(++selections); // (or even just '1')
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });

1

Rất nhiều câu trả lời, đây là của tôi.

Tôi mở rộng AppCompatSpinnervà thêm một phương thức pgmSetSelection(int pos)cho phép cài đặt lựa chọn theo chương trình mà không kích hoạt cuộc gọi lại lựa chọn. Tôi đã mã hóa điều này với RxJava để các sự kiện lựa chọn được phân phối thông qua một Observable.

package com.controlj.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;

import io.reactivex.Observable;

/**
 * Created by clyde on 22/11/17.
 */

public class FilteredSpinner extends android.support.v7.widget.AppCompatSpinner {
    private int lastSelection = INVALID_POSITION;


    public void pgmSetSelection(int i) {
        lastSelection = i;
        setSelection(i);
    }

    /**
     * Observe item selections within this spinner. Events will not be delivered if they were triggered
     * by a call to setSelection(). Selection of nothing will return an event equal to INVALID_POSITION
     *
     * @return an Observable delivering selection events
     */
    public Observable<Integer> observeSelections() {
        return Observable.create(emitter -> {
            setOnItemSelectedListener(new OnItemSelectedListener() {
                @Override
                public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                    if(i != lastSelection) {
                        lastSelection = i;
                        emitter.onNext(i);
                    }
                }

                @Override
                public void onNothingSelected(AdapterView<?> adapterView) {
                    onItemSelected(adapterView, null, INVALID_POSITION, 0);
                }
            });
        });
    }

    public FilteredSpinner(Context context) {
        super(context);
    }

    public FilteredSpinner(Context context, int mode) {
        super(context, mode);
    }

    public FilteredSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
        super(context, attrs, defStyleAttr, mode);
    }
}

Một ví dụ về cách sử dụng của nó, được gọi onCreateView()trong một Fragmentví dụ:

    mySpinner = view.findViewById(R.id.history);
    mySpinner.observeSelections()
        .subscribe(this::setSelection);

trong đó setSelection()một phương thức trong chế độ xem kèm theo trông như thế này và được gọi là cả hai từ các sự kiện lựa chọn của người dùng thông qua Observablevà ở nơi khác theo lập trình, do đó logic để xử lý các lựa chọn là phổ biến cho cả hai phương thức lựa chọn.

private void setSelection(int position) {
    if(adapter.isEmpty())
        position = INVALID_POSITION;
    else if(position >= adapter.getCount())
        position = adapter.getCount() - 1;
    MyData result = null;
    mySpinner.pgmSetSelection(position);
    if(position != INVALID_POSITION) {
        result = adapter.getItem(position);
    }
    display(result);  // show the selected item somewhere
}

0

Tôi sẽ cố gắng gọi

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

sau khi bạn gọi setAdOG (). Ngoài ra hãy thử gọi trước khi chuyển đổi.

Bạn luôn có giải pháp để đi với phân lớp, trong đó bạn có thể bọc một cờ boolean cho phương thức setAd CHƯƠNG overriden của bạn để bỏ qua sự kiện.


0

Giải pháp với cờ boolean hoặc bộ đếm không giúp tôi, vì trong quá trình thay đổi định hướng, onItemSelected () gọi "overflew" cờ hoặc bộ đếm.

Tôi phân lớp android.widget.Spinnervà thực hiện bổ sung nhỏ. Các phần có liên quan dưới đây. Giải pháp này đã làm việc cho tôi.

private void setHandleOnItemSelected()
{
  final StackTraceElement [] elements = Thread.currentThread().getStackTrace();

  for (int index = 1; index < elements.length; index++)
  {
     handleOnItemSelected = elements[index].toString().indexOf("PerformClick") != -1; //$NON-NLS-1$

     if (handleOnItemSelected)
     {
        break;
     }
  }
}

@Override
public void setSelection(int position, boolean animate)
{
  super.setSelection(position, animate);

  setHandleOnItemSelected();
}

@Override
public void setSelection(int position)
{
  super.setSelection(position);

  setHandleOnItemSelected();
}

public boolean shouldHandleOnItemSelected()
{
  return handleOnItemSelected;
}

0

Đây cũng không phải là một giải pháp tao nhã. Trên thực tế, nó khá là Rube-Goldberg nhưng có vẻ như nó hoạt động. Tôi chắc chắn rằng spinner đã được sử dụng ít nhất một lần bằng cách mở rộng bộ điều hợp mảng và ghi đè getDropDownView của nó. Trong phương thức getDropDownView mới, tôi có một cờ boolean được đặt để hiển thị menu thả xuống đã được sử dụng ít nhất một lần. Tôi bỏ qua các cuộc gọi đến người nghe cho đến khi cờ được đặt.

MainActivity.onCreate ():

ActionBar ab = getActionBar();
ab.setDisplayShowTitleEnabled(false);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
ab.setListNavigationCallbacks(null, null);

ArrayList<String> abList = new ArrayList<String>();
abList.add("line 1");
...

ArAd  abAdapt = new ArAd (this
   , android.R.layout.simple_list_item_1
   , android.R.id.text1, abList);
ab.setListNavigationCallbacks(abAdapt, MainActivity.this);

overriden mảng adaptor:

private static boolean viewed = false;
private class ArAd extends ArrayAdapter<String> {
    private ArAd(Activity a
            , int layoutId, int resId, ArrayList<String> list) {
        super(a, layoutId, resId, list);
        viewed = false;
    }
    @Override
    public View getDropDownView(int position, View convertView,
            ViewGroup parent) {
        viewed = true;
        return super.getDropDownView(position, convertView, parent);
    }
}

người nghe sửa đổi:

@Override
public boolean onNavigationItemSelected(
   int itemPosition, long itemId) {
   if (viewed) {
     ...
   }
   return false;
}

0

nếu bạn cần tạo lại hoạt động một cách nhanh chóng, ví dụ: thay đổi chủ đề, một cờ / bộ đếm đơn giản sẽ không hoạt động

sử dụng hàm onUserInteraction () để phát hiện hoạt động của người dùng,

tham khảo: https://stackoverflow.com/a/25070696/4772917


0

Tôi đã thực hiện với cách đơn giản nhất:

private AdapterView.OnItemSelectedListener listener;
private Spinner spinner;

onCreate ();

spinner = (Spinner) findViewById(R.id.spinner);

listener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {

            Log.i("H - Spinner selected position", position);
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    };

 spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            spinner.setOnItemSelectedListener(listener);
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });

Làm xong


Đó là một giải pháp thú vị. Có thể sử dụng giải thích thêm. Về cơ bản, nó cố tình bỏ qua sự kiện onItemSelected đầu tiên. Chúng có thể hoạt động tốt trong một số trường hợp nhưng không phải như vậy khi các tùy chọn trợ năng được bật (xem phần giải thích của Jorrit) .
jk7

0
if () {        
       spinner.setSelection(0);// No reaction to create spinner !!!
     } else {
        spinner.setSelection(intPosition);
     }


spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

         if (position > 0) {
           // real selection
         }

      }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

     }
});

0

Đó là giải pháp cuối cùng và dễ sử dụng của tôi:

public class ManualSelectedSpinner extends Spinner {
    //get a reference for the internal listener
    private OnItemSelectedListener mListener;

    public ManualSelectedSpinner(Context context) {
        super(context);
    }

    public ManualSelectedSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ManualSelectedSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
        mListener = listener;
        super.setOnItemSelectedListener(listener);
    }

    public void setSelectionWithoutInformListener(int position){
        super.setOnItemSelectedListener(null);
        super.setSelection(position);
        super.setOnItemSelectedListener(mListener);
    }

    public void setSelectionWithoutInformListener(int position, boolean animate){
        super.setOnItemSelectedListener(null);
        super.setSelection(position, animate);
        super.setOnItemSelectedListener(mListener);
    }
}

Sử dụng mặc định setSelection(...)cho hành vi mặc định hoặc sử dụng setSelectionWithoutInformListener(...)để chọn một mục trong công cụ quay vòng mà không kích hoạt cuộc gọi lại OnItemSelectedListener.


0

Tôi cần sử dụng mSpinnertrong ViewHolder, vì vậy cờ mOldPositionđược đặt trong lớp bên trong ẩn danh.

mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            int mOldPosition = mSpinner.getSelectedItemPosition();

            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
                if (mOldPosition != position) {
                    mOldPosition = position;
                    //Do something
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {
                //Do something
            }
        });

0

Tôi sẽ lưu trữ chỉ mục ban đầu trong khi tạo đối tượng onClickListener.

   int thisInitialIndex = 0;//change as needed

   myspinner.setSelection(thisInitialIndex);

   myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

      int initIndex = thisInitialIndex;

      @Override
      public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
         if (id != initIndex) { //if selectedIndex is the same as initial value
            // your real onselecteditemchange event
         }
      }

      @Override
      public void onNothingSelected(AdapterView<?> parent) {
      }
  });

0

Giải pháp của tôi sử dụng onTouchListenernhưng không hạn chế sử dụng. Nó tạo ra một trình bao bọc onTouchListenernếu cần thiết khi thiết lập onItemSelectedListener.

public class Spinner extends android.widget.Spinner {
    /* ...constructors... */

    private OnTouchListener onTouchListener;
    private OnItemSelectedListener onItemSelectedListener;

    @Override
    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        onItemSelectedListener = listener;
        super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
    }

    @Override
    public void setOnTouchListener(OnTouchListener listener) {
        onTouchListener = listener;
        super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
    }

    private OnTouchListener wrapTouchListener(final OnTouchListener onTouchListener, final OnItemSelectedListener onItemSelectedListener) {
        return onItemSelectedListener != null ? new OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Spinner.super.setOnItemSelectedListener(onItemSelectedListener);
                return onTouchListener != null && onTouchListener.onTouch(view, motionEvent);
            }
        } : onTouchListener;
    }
}

0

Tôi có thể trả lời quá muộn qua bài đăng, tuy nhiên tôi đã xoay sở để đạt được điều này bằng thư viện ràng buộc dữ liệu Android Android Databinding . Tôi đã tạo một ràng buộc tùy chỉnh để đảm bảo người nghe không được gọi cho đến khi mục được chọn được thay đổi để ngay cả khi người dùng chọn cùng một vị trí lặp đi lặp lại sự kiện không được kích hoạt.

Bố cục tập tin xml

    <layout>
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin"
xmlns:app="http://schemas.android.com/apk/res-auto">


<Spinner
    android:id="@+id/spinner"
    android:layout_width="150dp"
    android:layout_height="wrap_content"
    android:spinnerMode="dropdown"
    android:layout_below="@id/member_img"
    android:layout_marginTop="@dimen/activity_vertical_margin"
    android:background="@drawable/member_btn"
    android:padding="@dimen/activity_horizontal_margin"
    android:layout_marginStart="@dimen/activity_horizontal_margin"
    android:textColor="@color/colorAccent"
    app:position="@{0}"
    />
 </RelativeLayout>
 </layout>

app:position là nơi bạn đang vượt qua vị trí được chọn.

Ràng buộc tùy chỉnh

  @BindingAdapter(value={ "position"}, requireAll=false)
  public static void setSpinnerAdapter(Spinner spinner, int selected) 
  {

    final int [] selectedposition= new int[1];
    selectedposition[0]=selected;


    // custom adapter or you can set default adapter
        CustomSpinnerAdapter customSpinnerAdapter = new CustomSpinnerAdapter(spinner.getContext(), <arraylist you want to add to spinner>);
        spinner.setAdapter(customSpinnerAdapter);
            spinner.setSelection(selected,false);


    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

            String item = parent.getItemAtPosition(position).toString();
        if( position!=selectedposition[0]) {
                        selectedposition[0]=position;
            // do your stuff here
                    }
                }


        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });
}

Bạn có thể đọc thêm về ràng buộc dữ liệu tùy chỉnh tại đây Android Custom Setter

GHI CHÚ

  1. Đừng quên bật tính năng ghi dữ liệu trong tệp Gradle của bạn

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. Bao gồm các tệp bố trí của bạn trong <layout>thẻ


-1
mYear.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View arg1, int item, long arg3) {
                if (mYearSpinnerAdapter.isEnabled(item)) {

                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });

2
1) Hãy định dạng đúng mã của bạn. 2) Một lời giải thích về những gì mã của bạn đang làm cũng sẽ được đánh giá cao. Không phải tất cả các đoạn mã được hiểu ngay lập tức khi đọc mã.
Mike Koch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.