エラーメッセージを直接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>
この回答を書いている時点では、setError()
メソッドに対応するXML属性がないため、XMLに直接エラーメッセージを設定することはできません。これは、errorEnabled
が存在することを知っているのは少し奇妙です。しかし、この不作法は、ギャップを埋め、不足している実装を提供する Binding Adapter を作成することで簡単に修正できます。このようなもの:
@BindingAdapter("app:errorText")
public static void setErrorMessage(TextInputLayout view, String errorMessage) {
view.setError(errorMessage);
}
公式バインディングドキュメント の「属性セッター」セクション、特に「カスタムセッター」を参照してください。
[〜#〜]編集[〜#〜]
おそらくばかげた質問ですが、これはどこに置くべきですか?
TextInputLayout
を拡張して、そこに配置する必要がありますか?
公式のドキュメントを読んで完全な答えを得ることができないからといって、実際にはまったくばかげた質問ではありません。幸いなことに、これは非常に単純です。何も拡張する必要はありません。そのメソッドanywhereをプロジェクトに追加するだけです。個別のクラス(つまりDataBindingAdapters
)を作成するか、このメソッドをプロジェクト内の既存のクラスに追加するだけでかまいません。実際には問題ありません。このメソッドに@BindingAdapter
の注釈を付ける限り、public
およびstatic
であることを確認してくださいそれが住んでいるクラス。
DataBinding Framwork MVVMを使用してEditTextにエラーを設定する方法 のように、バインディングを作成しました。しかし今回は、前の例と同様に、TextInputLayoutをサンプルとして使用しました。
このアイデアの目的:
もちろん、独自の検証を作成し、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);
}
2番目に、デフォルトの検証とエラーメッセージを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>
3番目に、クリックボタンがトリガーされたときに、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();
});
}
}