一个关于类似TextInputLayout的功能的自定义控件进行双向绑定
用法:
什么是BindingAdapter?
BindingAdapter用来设置布局中View的自定义属性,当使用该属性时,可以自定义其行为。
1、作用于方法
2、它定义了xml的属性赋值的java实现
3、方法必须为公共静(public static)方法,可以有一到多个参数。
@BindingAdapter("app:text")
public static void setText(TitleWithHintTextInputLayout customTextInputLayout, String text) {
customTextInputLayout.setText(text);
}
什么是InverseBindingAdapter?
InverseBindingAdapter用于关联某个用于接收View变更的方法,典型的例子EditText.TextWatcher接收输入字符的变更。这与BindingAdapters有一定的相似性:
@InverseBindingAdapter(attribute = "app:text", event = "app:textAttrChanged")
public static String getText(CustomTextInputLayout customTextInputLayout) {
return customTextInputLayout.getText();
}
事件的默认值是带有AttrChanged的属性名称。在上面的例子中,默认值是android:textAttrChanged,即使它没有提供。
事件属性用于通知数据绑定系统值已更改。开发人员通常会创建一个BindingAdapter来分配事件。比如:
@BindingAdapter(value = "app:textAttrChanged", requireAll = false)
public static void setListener(CustomTextInputLayout customTextInputLayout, final InverseBindingListener listener) {
if (listener != null) {
SimpleTextWatcher newTextWatch = new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
listener.onChange();
}
};
SimpleTextWatcher oldTextWatch = ListenerUtil.trackListener(customTextInputLayout, newTextWatch, R.id.textWatcher);
if (oldTextWatch != null) {
customTextInputLayout.removeTextWatch(oldTextWatch);
}
customTextInputLayout.addTextWatch(newTextWatch);
}
}
完整代码:
public class CustomTextInputLayout extends RelativeLayout {
private AppCompatTextView tvHint;
private MultipleLinesEditText editText;
public CustomTextInputLayout(Context context) {
this(context, null);
}
public CustomTextInputLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
private void initView(Context context, AttributeSet attributeSet) {
LayoutInflater.from(context).inflate(R.layout.custom_text_input_layout, this, true);
tvHint = findViewById(R.id.tv_hint);
editText = findViewById(R.id.et_content);
TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.CustomTextInputLayout);
String title = typedArray.getString(R.styleable.CustomTextInputLayout_title);
String hint = typedArray.getString(R.styleable.CustomTextInputLayout_hint);
String text = typedArray.getString(R.styleable.CustomTextInputLayout_text);
int maxLines = typedArray.getIndex(R.styleable.CustomTextInputLayout_maxLines);
editText.setMaxLines(maxLines);
if (!TextUtils.isEmpty(hint)) {
setHint(hint);
}
if (!TextUtils.isEmpty(title)) {
setTitle(title);
}
if (!TextUtils.isEmpty(text)) {
setText(text);
}
typedArray.recycle();
}
public void setHint(String hint) {
editText.setHint(hint);
}
public void setTitle(String text) {
tvHint.setText(text);
}
public void setText(String text) {
if (!getText().equals(text)) {
editText.setText(text);
}
}
private String getText() {
Editable text = editText.getText();
if (text != null) {
return text.toString();
} else {
return "";
}
}
private void addTextWatch(TextWatcher textWatcher) {
editText.addTextChangedListener(textWatcher);
}
private void removeTextWatch(TextWatcher textWatcher) {
editText.removeTextChangedListener(textWatcher);
}
@BindingAdapter("app:text")
public static void setText(CustomTextInputLayout customTextInputLayout, String text) {
customTextInputLayout.setText(text);
}
@InverseBindingAdapter(attribute = "app:text", event = "app:textAttrChanged")
public static String getText(CustomTextInputLayout customTextInputLayout) {
return customTextInputLayout.getText();
}
@BindingAdapter(value = "app:textAttrChanged", requireAll = false)
public static void setListener(CustomTextInputLayout customTextInputLayout, final InverseBindingListener listener) {
if (listener != null) {
SimpleTextWatcher newTextWatch = new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
listener.onChange();
}
};
SimpleTextWatcher oldTextWatch = ListenerUtil.trackListener(customTextInputLayout, newTextWatch, R.id.textWatcher);
if (oldTextWatch != null) {
customTextInputLayout.removeTextWatch(oldTextWatch);
}
customTextInputLayout.addNotesTextWatch(newTextWatch);
}
}
}
自定义属性:
<declare-styleable name="CustomTextInputLayout">
<attr name="hint" format="string" />
<attr name="title" format="string" />
<attr name="text" format="string" />
<attr name="maxLines" format="integer" />
</declare-styleable>
自定义控件:custom_text_input_layout
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<com.xogrp.planner.MultipleLinesEditText
android:id="@+id/et_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
android:background="@drawable/bg_text_input_layout"
android:gravity="top"
android:inputType="textMultiLine|textCapSentences"
android:minHeight="120dp"
android:paddingStart="14dp"
android:paddingTop="10dp"
android:paddingEnd="14dp"
android:paddingBottom="10dp"
android:scrollbars="vertical" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:background="@color/white"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="@color/coolgray_500"
tools:text="Notes" />
</merge>
自定义EditText background:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_text_input_layout_foucs" android:state_focused="true" />
<item android:drawable="@drawable/bg_text_input_normal" />
</selector>
自定义shape):bg_text_input_layout_foucs 和bg_text_input_normal
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="3dp" />
<stroke
android:width="2dp"
android:color="#6D7179" />
<solid android:color="@color/transparent" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="3dp" />
<stroke
android:width="1dp"
android:color="#CACCD0" />
<solid android:color="@color/transparent" />
</shape>
自定义EditText:MultipleLinesEditText
为什么要自定义一个EditText呢?因为如果EditeText编辑是多行的话,EditText需要可以内部滚动,我们就可以在这里进行滚动事件的处理。在CustomTextInputLayout处理这个监听是无效的
class MultipleLinesEditText : AppCompatEditText {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onTouchEvent(event: MotionEvent): Boolean {
if (canScrollVertically(-1) || canScrollVertically(0)) {
parent.requestDisallowInterceptTouchEvent(true)
}
return super.onTouchEvent(event)
}
}
用法:
<com.liang.custom.view.CustomTextInputLayout
android:id="@+id/edt_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="@dimen/dp_32"
app:hint="Label"
app:text="@={viewModel.storyContent}"
app:title="@string/wws_our_story_story_prompt"
app:maxLines="6"
android:scrollbars="vertical" />
这篇博客介绍了如何在Android中实现自定义控件的双向绑定DataBinding,特别是针对EditText的内部滚动功能。通过使用BindingAdapter和InverseBindingAdapter,详细阐述了自定义View属性的方法,包括设置自定义属性、处理文本变化事件。同时,文章提供了自定义控件custom_text_input_layout、背景shape以及多行EditText的实现,强调了自定义EditText的原因是为了处理内部滚动事件,因为在CustomTextInputLayout中处理此事件是无效的。
1707





