一个关于类似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);
}
}
}
自定义属性:
自定义控件:custom_text_input_layout
自定义EditText background:
自定义shape):bg_text_input_layout_foucs 和bg_text_input_normal
自定义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)
}
}
用法: