我可以将错误消息绑定到TextInputLayout吗?

时间:2016-05-06 15:14:19

标签: android android-layout android-databinding

我想将错误消息直接绑定到android.support.design.widget.TextInputLayout。我找不到通过布局设置错误的方法。这甚至可能吗?

这就是我想象的工作方式:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <import type="android.view.View" />
        <variable
            name="error"
            type="String" />
    </data>
    <android.support.v7.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:errorEnabled="true"
            app:errorText="@{error}">
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/username"
                android:inputType="textEmailAddress" />
        </android.support.design.widget.TextInputLayout>
    </android.support.v7.widget.LinearLayoutCompat>
</layout>

3 个答案:

答案 0 :(得分:33)

在撰写此答案时,没有与setError()方法对应的XML属性,因此您无法直接在XML中设置错误消息,这有点奇怪,知道errorEnabled就在那里。但是,通过创建填补缺口并提供缺失实现的Binding Adapter,可以轻松解决这个问题。像这样:

@BindingAdapter("app:errorText")
public static void setErrorMessage(TextInputLayout view, String errorMessage) {
   view.setError(errorMessage);
}

请参阅official binding docs,&#34;属性设置器&#34;特别是#34; Custom Setters&#34;。

修改

  

可能是愚蠢的问题,但我应该把它放在哪里?我是否需要扩展TextInputLayout并将其放在那里?

它实际上根本不是一个愚蠢的问题,仅仅是因为你无法通过阅读官方文档得到完整的答案。幸运的是,它非常简单:您不需要扩展任何东西 - 只需将方法放在项目中。您可以创建单独的类(即DataBindingAdapters),或者只是将此方法添加到项目中的任何现有类中 - 这并不重要。只要您使用@BindingAdapter对此方法进行注释,请确保其为public static它与哪个类无关。

答案 1 :(得分:2)

我在How to set error on EditText using DataBinding Framwork MVVM上做了一个像我的回答一样的约束。但这一次它使用TextInputLayout作为样本,就像前一个一样。

这个想法的目的:

  1. 使xml尽可能可读且独立
  2. 独立进行活动方验证和xml方验证
  3. 当然,您可以自己进行验证并使用xml中的<variable>标记进行设置

    首先,实现静态绑定方法和相关的字符串验证规则以进行准备。

    结合

      @BindingAdapter({"app:validation", "app:errorMsg"})
      public static void setErrorEnable(TextInputLayout textInputLayout, StringRule stringRule,
          final String errorMsg) {
      }
    

    StringRule

      public static class Rule {
    
        public static StringRule NOT_EMPTY_RULE = s -> TextUtils.isEmpty(s.toString());
        public static StringRule EMAIL_RULE = s -> s.toString().length() > 18;
      }
    
      public interface StringRule {
    
        boolean validate(Editable s);
      }
    

    其次,在TextInputLayout中放置默认验证和错误消息,以便更容易地知道验证,在xml中绑定

    <android.support.design.widget.TextInputLayout
          android:id="@+id/imageUrlValidation"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          app:validation="@{Rule.NOT_EMPTY_RULE}"
          app:errorMsg='@{"Cannot be empty"}'
          >
          <android.support.design.widget.TextInputEditText
            android:id="@+id/input_imageUrl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Image Url"
            android:text="@={feedEntry.imageUrl}" />
        </android.support.design.widget.TextInputLayout>
    

    第三,当单击按钮触发时,您可以使用TextInputLayout中的预定义ID(例如imageUrlValidation)对活动进行最终验证

    Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
            button.setOnClickListener(view1 -> {
              // TODO Do something
              //to trigger auto error enable
              FeedEntry inputFeedEntry = dialogFeedEntryBinding.getFeedEntry();
              Boolean[] validations = new Boolean[]{
                  dialogFeedEntryBinding.imageUrlValidation.isErrorEnabled(),
                  dialogFeedEntryBinding.titleValidation.isErrorEnabled(),
                  dialogFeedEntryBinding.subTitleValidation.isErrorEnabled()
              };
              boolean isValid = true;
              for (Boolean validation : validations) {
                if (validation) {
                  isValid = false;
                }
              }
              if (isValid) {
                new AsyncTask<FeedEntry, Void, Void>() {
                  @Override
                  protected Void doInBackground(FeedEntry... feedEntries) {
                    viewModel.insert(feedEntries);
                    return null;
                  }
                }.execute(inputFeedEntry);
                dialogInterface.dismiss();
              }
            });
    

    完整的代码如下:

    <强> dialog_feedentry.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
      <data>
    
        <import type="com.example.common.components.TextInputEditTextBindingUtil.Rule" />
    
        <variable
          name="feedEntry"
          type="com.example.feedentry.repository.bean.FeedEntry" />
    
      </data>
      <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp"
        android:orientation="vertical">
        <android.support.design.widget.TextInputLayout
          android:id="@+id/imageUrlValidation"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          app:validation="@{Rule.NOT_EMPTY_RULE}"
          app:errorMsg='@{"Cannot be empty"}'
          >
          <android.support.design.widget.TextInputEditText
            android:id="@+id/input_imageUrl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Image Url"
            android:text="@={feedEntry.imageUrl}" />
        </android.support.design.widget.TextInputLayout>
    
        <android.support.design.widget.TextInputLayout
          android:id="@+id/titleValidation"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          app:validation="@{Rule.NOT_EMPTY_RULE}"
          app:errorMsg='@{"Cannot be empty"}'
          >
    
          <android.support.design.widget.TextInputEditText
            android:id="@+id/input_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Title"
            android:text="@={feedEntry.title}"
    
            />
        </android.support.design.widget.TextInputLayout>
        <android.support.design.widget.TextInputLayout
          android:id="@+id/subTitleValidation"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          app:validation="@{Rule.NOT_EMPTY_RULE}"
          app:errorMsg='@{"Cannot be empty"}'
          >
    
          <android.support.design.widget.TextInputEditText
            android:id="@+id/input_subtitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Subtitle"
            android:text="@={feedEntry.subTitle}"
    
            />
        </android.support.design.widget.TextInputLayout>
      </LinearLayout>
    </layout>
    

    <强> TextInputEditTextBindingUtil.java

    package com.example.common.components;
    
    import android.databinding.BindingAdapter;
    import android.support.design.widget.TextInputLayout;
    import android.text.Editable;
    import android.text.TextUtils;
    import android.text.TextWatcher;
    
    /**
     * Created by Charles Ng on 7/9/2017.
     */
    
    public class TextInputEditTextBindingUtil {
    
    
      @BindingAdapter({"app:validation", "app:errorMsg"})
      public static void setErrorEnable(TextInputLayout textInputLayout, StringRule stringRule,
          final String errorMsg) {
        textInputLayout.getEditText().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) {
          }
    
          @Override
          public void afterTextChanged(Editable editable) {
            textInputLayout
                .setErrorEnabled(stringRule.validate(textInputLayout.getEditText().getText()));
            if (stringRule.validate(textInputLayout.getEditText().getText())) {
              textInputLayout.setError(errorMsg);
            } else {
              textInputLayout.setError(null);
            }
          }
        });
        textInputLayout
            .setErrorEnabled(stringRule.validate(textInputLayout.getEditText().getText()));
        if (stringRule.validate(textInputLayout.getEditText().getText())) {
          textInputLayout.setError(errorMsg);
        } else {
          textInputLayout.setError(null);
        }
      }
    
      public static class Rule {
    
        public static StringRule NOT_EMPTY_RULE = s -> TextUtils.isEmpty(s.toString());
        public static StringRule EMAIL_RULE = s -> s.toString().length() > 18;
      }
    
      public interface StringRule {
    
        boolean validate(Editable s);
      }
    
    }
    

    <强> FeedActivity.java

    public class FeedActivity extends AppCompatActivity {
    
      private FeedEntryListViewModel viewModel;
    
      @SuppressLint("StaticFieldLeak")
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_feed);
        viewModel = ViewModelProviders.of(this)
            .get(FeedEntryListViewModel.class);
        viewModel.init(this);
        ViewPager viewPager = findViewById(R.id.viewpager);
        setupViewPager(viewPager);
        // Set Tabs inside Toolbar
        TabLayout tabs = findViewById(R.id.tabs);
        tabs.setupWithViewPager(viewPager);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(view -> {
          //insert sample data by button click
          final DialogFeedentryBinding dialogFeedEntryBinding = DataBindingUtil
              .inflate(LayoutInflater.from(this), R.layout.dialog_feedentry, null, false);
          FeedEntry feedEntry = new FeedEntry("", "");
          feedEntry.setImageUrl("http://i.imgur.com/DvpvklR.png");
          dialogFeedEntryBinding.setFeedEntry(feedEntry);
          final Dialog dialog = new AlertDialog.Builder(FeedActivity.this)
              .setTitle("Create a new Feed Entry")
              .setView(dialogFeedEntryBinding.getRoot())
              .setPositiveButton("Submit", null)
              .setNegativeButton("Cancel", (dialogInterface, i) -> dialogInterface.dismiss())
              .create();
          dialog.setOnShowListener(dialogInterface -> {
            Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
            button.setOnClickListener(view1 -> {
              // TODO Do something
              //to trigger auto error enable
              FeedEntry inputFeedEntry = dialogFeedEntryBinding.getFeedEntry();
              Boolean[] validations = new Boolean[]{
                  dialogFeedEntryBinding.imageUrlValidation.isErrorEnabled(),
                  dialogFeedEntryBinding.titleValidation.isErrorEnabled(),
                  dialogFeedEntryBinding.subTitleValidation.isErrorEnabled()
              };
              boolean isValid = true;
              for (Boolean validation : validations) {
                if (validation) {
                  isValid = false;
                }
              }
              if (isValid) {
                new AsyncTask<FeedEntry, Void, Void>() {
                  @Override
                  protected Void doInBackground(FeedEntry... feedEntries) {
                    viewModel.insert(feedEntries);
                    return null;
                  }
                }.execute(inputFeedEntry);
                dialogInterface.dismiss();
              }
            });
          });
          dialog.show();
    
        });
      }
    }
    

答案 2 :(得分:0)

这样定义您的xml

 <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/emailTextInputLayout"
            style="@style/myTextInputLayoutStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="80dp"
            android:layout_marginEnd="16dp"
            **app:errorEnabled="true"**
            **app:errorText="@{viewModel.emailErrorMessage}"**
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/include">

            <com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/email"
                android:inputType="textEmailAddress"
                **android:text="@={viewModel.emailText}" />**
  </com.google.android.material.textfield.TextInputLayout>

然后将这种方法放在任何地方

@BindingAdapter("app:errorText")
fun setErrorText(view: TextInputLayout, errorMessage: String) {
    if (errorMessage.isEmpty())
        view.error = null
    else
        view.error = errorMessage;
}

让我们说您将在单击按钮后进行验证,这样您的按钮就会像这样

   <Button
        android:id="@+id/signInButtonView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="68dp"
        android:layout_marginEnd="16dp"
        **android:onClick="@{() -> viewModel.logIn()}"**
        android:text="@string/sign_in"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/passwordTextInputLayout" />

然后在您的视图模型中

您将拥有这些

class SignInViewModel : ViewModel() {
  private val _emailErrorMessage = MutableLiveData("")
    val emailText = MutableLiveData("")    

    val emailErrorMessage: LiveData<String> = _emailErrorMessage

    fun logIn() {
        if (validateInput()) {

        }
    }

    private fun validateInput(): Boolean {
        if (emailText.value?.length!! < 5) {
            _emailErrorMessage.value = "no way"
            return false
        }
        _emailErrorMessage.value = ""
        return true

    }
.
.

不要忘记在活动或片段中添加它

    binding.lifecycleOwner = this

代码很长,所以我在重要行上添加了双星号