Android 自定义控件的双向绑定(DataBinding)和EditText的内部滚动

这篇博客介绍了如何在Android中实现自定义控件的双向绑定DataBinding,特别是针对EditText的内部滚动功能。通过使用BindingAdapter和InverseBindingAdapter,详细阐述了自定义View属性的方法,包括设置自定义属性、处理文本变化事件。同时,文章提供了自定义控件custom_text_input_layout、背景shape以及多行EditText的实现,强调了自定义EditText的原因是为了处理内部滚动事件,因为在CustomTextInputLayout中处理此事件是无效的。

一个关于类似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" />
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值