【问题标题】:Can i bind an error message to a TextInputLayout?我可以将错误消息绑定到 TextInputLayout 吗?
【发布时间】:2016-05-06 15:14:18
【问题描述】:

我想将错误消息直接绑定到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>

【问题讨论】:

  • 使用可以阅读:developer.android.com/intl/vi/tools/data-binding/guide.html。从活动中获取绑定,例如: MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);并通过 binding.setError("String you want") 设置值
  • 没有真正解决这个问题。我已经知道如何进行基本绑定了。

标签: android android-layout android-databinding


【解决方案1】:

截至撰写此答案时(2016 年 5 月),没有与 setError() 方法对应的 XML 属性,因此您不能直接在 XML 中设置错误消息,知道有 errorEnabled 有点奇怪。但是通过创建Binding Adapter 可以很容易地解决这个问题,这将填补空白并提供缺失的实现。像这样的:

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

参见official binding docs,“属性设置器”部分,尤其是“自定义设置器”。


编辑

可能是愚蠢的问题,但我应该把这个放在哪里?我需要扩展TextInputLayout 并将其放入其中吗?

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

【讨论】:

  • 可能是愚蠢的问题,但我应该把这个放在哪里?我需要扩展 TextInputLayout 并将其放在那里吗?
  • 谢谢您的指导。
  • @BindingAdapter 方法没有明确的注册,这绝对让我感到困扰。它们基本上可以在您的代码中的任何位置工作,并且在每个项目中都处于不同的位置。
  • @MarcinOrlowski 您能否展示如何在 TextInputLayout 中设置 errorText?以及我需要添加到我的 build.gradle 文件中的任何其他内容,除非我不在我的项目中使用数据绑定?基本上我只想在xml中设置errorMessage
  • 在 android.support.design.widget.TextInputLayout 上找不到值类型为 java.lang.String 的属性“app:errorText”的 getter
【解决方案2】:

我已经做了一个绑定,就像我在How to set error on EditText using DataBinding Framwork MVVM 上的回答一样。但这次它使用 TextInputLayout 作为示例,就像上一个一样。

这个想法的目的:

  1. 让xml尽可能的可读和独立
  2. 独立进行activity端验证和xml端验证

当然,你可以自己做验证,用xml中的&lt;variable&gt;标签设置

首先,实现静态绑定方法和相关的String验证规则,以备准备。

绑定

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

字符串规则

  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)对activity进行最终验证

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();

    });
  }
}

【讨论】:

  • 只是一个验证有点矫枉过正,你有一些简单的。
  • 在 MVVM 中,绑定不应该有任何逻辑。视图控制器应该处理验证。
【解决方案3】:

像这样定义你的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

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

【讨论】:

    猜你喜欢
    • 2012-09-23
    • 1970-01-01
    • 2017-03-31
    • 2017-01-06
    • 1970-01-01
    • 1970-01-01
    • 2011-11-17
    • 2018-09-04
    • 1970-01-01
    相关资源
    最近更新 更多