Создание setError() для Spinner

Как создать функцию setError() (аналогичную функции TextView/EditText) для Spinner? Не работает следующее:

Я попытался расширить класс Spinner и в конструкторе:

ArrayAdapter<String> aa = new ArrayAdapter<String>(getContext(),
                    android.R.layout.simple_spinner_item, android.R.id.text1,
                    items);
            aa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            setAdapter(aa);
             tv = (TextView) findViewById(android.R.id.text1);
            // types_layout_list_tv

            ctv = (CheckedTextView) aa.getDropDownView(1, null, null);
            tv2 = (TextView) aa.getView(1, null, null);

setError метод:

    public void setError(String str) {
        if (tv != null)
            tv.setError(str);
        if(tv2!=null)
            tv2.setError(str);
        if (ctv != null)
            ctv.setError(str);
    }

person Sameer Segal    schedule 20.09.2010    source источник
comment
Что меня озадачивает в вашем примере: откуда берутся TextView tv, ctv и tv2 и какое отношение они имеют к счетчику? Приветствую, Ready4Android   -  person Ready4Android    schedule 18.09.2011
comment
У вас есть решение для этого?\   -  person Code_Life    schedule 06.02.2012


Ответы (9)


Подобно решению @Gábor, но мне не нужно было создавать собственный адаптер. Я просто вызываю следующий код в моей функции проверки (т.е. при нажатии кнопки отправки)

        TextView errorText = (TextView)mySpinner.getSelectedView();                  
        errorText.setError("anything here, just to add the icon");
        errorText.setTextColor(Color.RED);//just to highlight that this is an error
        errorText.setText("my actual error text");//changes the selected item text to this
person EdmundYeung99    schedule 08.12.2014
comment
не мог заставить это работать. У меня есть два счетчика, оба возвращают null в getSelectedView(), за исключением того, что решение выглядело лучше всего - person John; 17.12.2014
comment
@John Я сохранил свой android.widget.Spinner как переменную экземпляра в методе onCreateView, а затем снова получил к нему доступ другим методом, в моем случае методом onDoneAction. Обязательно вызывайте getSelectedView после фактического выбора элемента в счетчике, т.е. getSelectedItem() также возвращает ненулевое значение. - person EdmundYeung99; 06.01.2015
comment
отличный ответ! выбранный вид на самом деле является TextView, поэтому он будет работать так, как ожидалось! - person Ryan Amaral; 24.07.2015

У меня есть решение, которое не требует создания дополнительного поля редактирования, но вам, как обычно, нужен собственный SpinnerAdapter.

Убедитесь, что у вас есть хотя бы один TextView в макете, который вы используете в getView() вашего адаптера (во всяком случае, он у вас обычно есть).

Добавьте в свой адаптер следующую функцию (измените name на идентификатор вашего TextView):

public void setError(View v, CharSequence s) {
  TextView name = (TextView) v.findViewById(R.id.name);
  name.setError(s);
}

Вызовите setError() из своего кода следующим образом:

YourAdapter adapter = (YourAdapter)spinner.getAdapter();
View view = spinner.getSelectedView();
adapter.setError(view, getActivity().getString(R.string.error_message));

По сути, как и с любым другим элементом управления, только вы вызываете его на своем адаптере, и вы также должны предоставить представление.

Это отобразит значок ошибки на счетчике, как и в случае с другими элементами управления.

person Gábor    schedule 24.03.2014
comment
Это отличное решение - person EladHackim; 27.12.2014
comment
Когда width=wrap_content, значок ошибки отображается неправильно - он скрыт под значком счетчика. - person Wooff; 27.07.2015

Использование скрытого TextView для отображения всплывающего сообщения

Это решение включает в себя добавление дополнительного скрытого текстового поля чуть ниже счетчика в нужном месте, чтобы разрешить отображение диалогового окна ошибки TextView, а также использование TextView, установленного в макете XML счетчика, чтобы разрешить отображение красного (!) значка. Таким образом, используются два текстовых представления: одно для значка, а другое (скрытое) для разрешения диалога об ошибке.

Вот как это выглядит, когда нет ошибки (используйте SetError(null)):

Счетчик в допустимом состоянии

Вот как это выглядит при возникновении ошибки (используйте SetError("my error text, ideally from a resource!")):

Счетчик в недопустимом состоянии

Вот выдержка из макета XML счетчика. Существует RelativeLayout, используемый для обеспечения того, чтобы TextView был как можно ближе к счетчику, и имеет достаточно paddingRight, чтобы стрелка в диалоговом окне сообщения была выровнена прямо под красным значком ошибки (!). Скрытый (поддельный) TextView позиционируется относительно Spinner.

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="top|left"
        >

        <Spinner
            android:id="@+id/spnMySpinner"
            android:layout_width="400dp"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:dropDownSelector="@drawable/selector_listview"
            android:background="@android:drawable/btn_dropdown"
            android:paddingBottom="0dp"
            android:layout_marginBottom="0dp"
            />

        <!-- Fake TextView to use to set in an error state to allow an error to be shown for the TextView -->
        <android.widget.TextView
            android:id="@+id/tvInvisibleError"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_alignRight="@+id/spnMySpinner"
            android:layout_alignBottom="@+id/spnMySpinner"
            android:layout_marginTop="0dp"
            android:paddingTop="0dp"
            android:paddingRight="50dp"
            android:focusable="true"
            android:focusableInTouchMode="true"
            />

    </RelativeLayout>

Примечание. @drawable/selector_listview определено вне области этого решения. см. здесь пример того, как заставить это работать, так как это не по теме этого ответа.

Вот код, чтобы заставить его работать. Просто вызовите SetError(errMsg) либо с помощью null, чтобы сбросить ошибку, либо с текстом, чтобы перевести его в состояние ошибки.

/**
 * When a <code>errorMessage</code> is specified, pops up an error window with the message
 * text, and creates an error icon in the secondary unit spinner. Error cleared through passing
 * in a null string.
 * @param errorMessage Error message to display, or null to clear.
 */
public void SetError(String errorMessage)
{
    View view = spnMySpinner.getSelectedView();

    // Set TextView in Secondary Unit spinner to be in error so that red (!) icon
    // appears, and then shake control if in error
    TextView tvListItem = (TextView)view;

    // Set fake TextView to be in error so that the error message appears
    TextView tvInvisibleError = (TextView)findViewById(R.id.tvInvisibleError);

    // Shake and set error if in error state, otherwise clear error
    if(errorMessage != null)
    {
        tvListItem.setError(errorMessage);
        tvListItem.requestFocus();

        // Shake the spinner to highlight that current selection 
        // is invalid -- SEE COMMENT BELOW
        Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
        spnMySpinner.startAnimation(shake);

        tvInvisibleError.requestFocus();
        tvInvisibleError.setError(errorMessage);
    }
    else
    {
        tvListItem.setError(null);
        tvInvisibleError.setError(null);
    }
}

В приведенной выше функции SetError есть дополнительный код, который вызывает дрожание текста в Spinner при установке ошибки. Это не обязательно, чтобы решение работало, но является хорошим дополнением. Посмотрите здесь, чтобы узнать об этом подходе.

Престижность @Gábor за его решение, которое использует TextView в макете элемента Spinner XML. Код View view = spnMySpinner.getSelectedView(); (на основе решения @Gábor) необходим, потому что он получает отображаемый в данный момент TextView, а не использует findViewById, который просто получит первый TextView в списке (на основе предоставленного идентификатора ресурса) и, следовательно, не будет работать (отображать красную (!) иконку, если не выбран самый первый элемент в списке.

person CJBS    schedule 29.04.2015
comment
В этой части у меня линейный макет, какие-нибудь подсказки? TextView tvListItem = (TextView) вид; - person vinicius gati; 01.06.2018

Это можно сделать без использования пользовательского макета или адаптера.

((TextView)spinner.getChildAt(0)).setError("Message");

Единственным недостатком этого подхода является то, что небольшое всплывающее окно с текстом ошибки не будет отображаться при нажатии на значок.

person Jarett Millard    schedule 10.02.2015

Я бы посоветовал вам поставить пустой EditText прямо за спиннером.

В xml установите, что EditText

android:enabled="false"
    android:inputType="none"

Теперь, когда вы хотите установить ошибку для своего счетчика, просто установите для этой ошибки значение EditText.

Помните, что нельзя устанавливать для EditText значение invisibille/gone. Это не сработает таким образом.

Также обратите внимание, что с помощью этого метода вы можете решить, где именно вы хотите, чтобы ваша ошибка отображалась.

person EladHackim    schedule 28.01.2014
comment
Одно ограничение при размещении EditText за Spinner заключается в том, что некоторые части поведения проверки ошибок не работают. Несмотря на то, что появляется красный восклицательный знак, указывающий на ошибку, при нажатии на восклицательный знак не будет отображаться всплывающее окно с сообщением об ошибке, как обычно. - person Theo; 23.07.2014

Спасибо Габор за ваше фантастическое решение. В дополнение к вашей точке зрения мое решение таково:

Пользовательский адаптер

    public class RequiredSpinnerAdapter<T> extends ArrayAdapter<T> {
        public RequiredSpinnerAdapter(Context context, int textViewResourceId,
                                      java.util.List<T> objects) {
            super(context, textViewResourceId, objects);
        }

        int textViewId = 0;

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = super.getView(position, convertView, parent);
            if (view instanceof TextView) {
                textViewId = view.getId();
            }
            return view;
        }

        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            View row = super.getView(position, convertView, parent);
            return (row);
        }

        public void setError(View v, CharSequence s) {
            if(textViewId != 0){
                TextView name = (TextView) v.findViewById(textViewId);
                name.setError(s);
            }
        }
    }

Используйте адаптер для Spinner

ArrayAdapter<String> arrayAdapter = new RequiredSpinnerAdapter<String>(PropertyAdd.this, R.layout.checked, status_arr);
    marketstatus_spinner.setAdapter(arrayAdapter);
    marketstatus_spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> arg0, View arg1,
                                   int arg2, long arg3) {

            // Put code here
        }

        @Override
        public void onNothingSelected(AdapterView<?> arg0) {
           // Put code here
        }
    });

Проверить на валидацию

private boolean checkValidation() {
    if(marketstatus_spinner.getSelectedItem().toString().equals("")){
        RequiredSpinnerAdapter adapter = (RequiredSpinnerAdapter)marketstatus_spinner.getAdapter();
        View view = marketstatus_spinner.getSelectedView();
        adapter.setError(view, "Please select a value");

        return false;
    }
}
person Kwex    schedule 31.12.2014
comment
Добавьте один, чтобы сказать Во благо! :-) - person ban-geoengineering; 08.07.2015

Я предполагаю, что Spinner - неподходящее место для размещения этого метода. В случае Spinner вы должны выбрать одно значение, а значения в Spinner должны быть отфильтрованы на уровне вашего адаптера. Таким образом, пользователь может выбрать только те значения, которые есть в Spinner.

person Yury    schedule 08.07.2012
comment
Я согласен, но в сценарии с 0 элементом, являющимся Select.. что возможно там, setError был бы необходим, если выбор пользователей остается на индексе 0 - person Bojan; 16.10.2013

Вы можете создать свой собственный адаптер (расширяет BaseAdapter, реализует SpinnerAdapter). Таким образом, вы можете получить доступ к TextViews, которые отображаются в счетчике. (методы getView и createViewFromResource — пример: ArrayAdapter) Когда вы добавляете пустой элемент списка, чтобы пользователь мог оставить поле пустым, пока оно не станет обязательным (первый элемент в счетчике), вы можете сохранить его TextView как частный член в адаптере. Затем, когда придет время вызывать setError("...") из действия или фрагмента, вы можете вызвать его на адаптере, который может передать его пустому TextView.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    return createViewFromResource(position, convertView, parent, mTextViewId);
}

private View createViewFromResource(int position, View convertView, ViewGroup parent, int resource) {
    View view;
    TextView text;

    if (convertView == null) {
        view = inflater.inflate(resource, parent, false);
    } else {
        view = convertView;
    }

    try {
        text = (TextView) view;
    } catch (ClassCastException e) {
        Log.e(TAG, "You must supply a resource ID for a TextView", e);
        throw new IllegalStateException("MyAdapter requires the resource ID to be a TextView", e);
    }

    MyItem i = getItem(position);
    String s = (null != i) ? i.toString() : "";
    text.setText(s);

    if ("".equals(s) && null == mEmptyText) {
        this.mEmptyText = text;
    }

    return view;
}

public void setError(String errorMessage) {
    if (null != mEmptyText) {
        mEmptyText.setError(errorMessage);
    } else {
        Log.d(TAG, "mEmptyText is null");
    }
}
person j00ris    schedule 05.03.2013
comment
В подобном случае (как в вашем примере) у меня есть только значок без текстовой ошибки - person Helpa; 02.05.2013

На самом деле это очень так, вам просто нужно иметь только один TextView в вашем представлении, а затем получить выбранный вид из вашего счетчика, используя getSelectedView(), если основным видом в выбранном вами представлении является TextView, а затем напрямую приведите свой вид к TextView и setError, как это :

((TextView) jobCategory.getSelectedView()).setError("Field Required");

В противном случае, если Textview не является непосредственно MAIN View, вам нужно найти его по идентификатору и снова применить его и setError таким образом:

 ((TextView) jobCategory.getSelectedView().findViewById(R.id.firstName)).setError("Field Required");
person Oussaki    schedule 16.11.2016