web-dev-qa-db-ja.com

Android Data Bindingで双方向バインディングを作成します

新しいAndroid data-bindingを実装し、実装後に双方向バインディングをサポートしていないことに気付きました。これを手動で解決しようとしましたが、 EditTextにバインドするときに使用します。レイアウトには次のビューがあります。

<EditText
Android:id="@+id/firstname"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:inputType="textCapWords|textNoSuggestions"
Android:text="@{statement.firstName}"/>

別のビューにも結果が表示されます。

<TextView
style="@style/Text.Large"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:text="@{statement.firstName}"/>

私のフラグメントでは、次のようなバインディングを作成します。

FragmentStatementPersonaliaBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_statement_personalia, container, false);
binding.setStatement(mCurrentStatement);

これは機能し、firstNameの現在の値をEditTextに入れます。問題は、テキストが変更されたときにモデルを更新する方法です。 OnTextChanged-listenerをeditTextに配置して、モデルを更新してみました。これにより、アプリが強制終了されるループが作成されました(model-updateはGUIを更新し、textChanged回無限を呼び出します)。次に、実際の変更が次のように発生した場合にのみ通知しようとしました。

@Bindable
public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
        boolean changed = !TextUtils.equals(this.firstName, firstName);
        this.firstName = firstName;
        if(changed) {
            notifyPropertyChanged(BR.firstName);
        }
    }

これはうまく機能しましたが、手紙を書くたびにGUIが更新され、編集カーソルが前面に移動します。

どんな提案も大歓迎です

44
Gober

EDIT 04.05.16:Androidデータバインディングは、自動的に2ウェイバインディングをサポートします!

Android:text="@{viewModel.address}"

で:

Android:text="@={viewModel.address}"

たとえば、EditTextで双方向バインディングを取得します。これを有効にするには、Android Studio/gradle/build-toolsの最新バージョンに更新してください。

(以前の回答):

Bhavdip Patharのソリューションを試しましたが、同じ変数にバインドしていた他のビューを更新できませんでした。独自のEditTextを作成することで、これを別の方法で解決しました。

public class BindableEditText extends EditText{

public BindableEditText(Context context) {
    super(context);
}

public BindableEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public BindableEditText(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

private boolean isInititalized = false;

@Override
public void setText(CharSequence text, BufferType type) {
    //Initialization
    if(!isInititalized){
        super.setText(text, type);
        if(type == BufferType.EDITABLE){
            isInititalized = true;
        }
        return;
    }

    //No change
    if(TextUtils.equals(getText(), text)){
        return;
    }

    //Change
    int prevCaretPosition = getSelectionEnd();
    super.setText(text, type);
    setSelection(prevCaretPosition);
}}

このソリューションを使用すると、任意の方法(TextWatcher、OnTextChangedListenerなど)でモデルを更新でき、無限の更新ループを自動的に処理します。このソリューションを使用すると、モデルセッターを次のように簡単に実装できます。

public void setFirstName(String firstName) {
    this.firstName = firstName;
    notifyPropertyChanged(BR.firstName);
}

これにより、モデルクラスのコードが少なくなります(リスナーをフラグメントに保持できます)。

私の問題に対するコメント、改善、またはその他の/より良い解決策をいただければ幸いです

81
Gober

これは、Gradleプラグイン2.1+を使用する場合、Android Studio 2.1+でサポートされています。

次のように、EditTextのテキスト属性を@{}から@={}に変更するだけです。

<EditText
Android:id="@+id/firstname"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:inputType="textCapWords|textNoSuggestions"
Android:text="@={statement.firstName}"/>

詳細については、以下を参照してください: https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-Android/

19
Jeremy Dowdall

@Gober Androidデータバインディングは双方向バインディングをサポートします。したがって、手動で作成する必要はありません。OnTextChanged-listenerをeditTextに配置してみました。 。

OnTextChanged-listenerをeditTextに配置して、モデルを更新してみました。これにより、アプリが強制終了されるループが作成されました(model-updateはGUIを更新し、textChanged回無限を呼び出します)。

双方向バインディングを実装するバインディングフレームワークは、通常このチェックを行うことに注意してください...

変更されたビューモデルの例は次のとおりです。変更がウォッチャーで発生した場合、データバインディング通知を生成しません。

オーバーライドするメソッドが1つだけ必要なSimpleTextWatcherを作成しましょう。

public abstract class SimpleTextWatcher implements TextWatcher {

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void afterTextChanged(Editable s) {
        onTextChanged(s.toString());
    }

    public abstract void onTextChanged(String newValue);
}

次に、ビューモデルで、ウォッチャーを公開するメソッドを作成できます。ウォッチャーは、コントロールの変更された値をビューモデルに渡すように構成されます。

@Bindable
public TextWatcher getOnUsernameChanged() {

    return new SimpleTextWatcher() {
        @Override
        public void onTextChanged(String newValue) {
            setUsername(newValue);
        }
    };
}

最後に、ビューでaddTextChangeListenerを使用してウォッチャーをEditTextにバインドできます。

<!-- most attributes removed -->
<EditText
    Android:id="@+id/input_username"
    Android:addTextChangedListener="@{viewModel.onUsernameChanged}"/>

通知無限を解決するビューモデルの実装を次に示します。

public class LoginViewModel extends BaseObservable {

    private String username;
    private String password;
    private boolean isInNotification = false;

    private Command loginCommand;

    public LoginViewModel(){
        loginCommand = new Command() {
            @Override
            public void onExecute() {
                Log.d("db", String.format("username=%s;password=%s", username, password));
            }
        };
    }

    @Bindable
    public String getUsername() {
        return this.username;
    }

    @Bindable
    public String getPassword() {
        return this.password;
    }

    public Command getLoginCommand() { return loginCommand; }

    public void setUsername(String username) {
        this.username = username;

        if (!isInNotification)
            notifyPropertyChanged(com.petermajor.databinding.BR.username);
    }

    public void setPassword(String password) {
        this.password = password;

        if (!isInNotification)
            notifyPropertyChanged(com.petermajor.databinding.BR.password);
    }

    @Bindable
    public TextWatcher getOnUsernameChanged() {

        return new SimpleTextWatcher() {
            @Override
            public void onTextChanged(String newValue) {
                isInNotification = true;
                setUsername(newValue);
                isInNotification = false;
            }
        };
    }

    @Bindable
    public TextWatcher getOnPasswordChanged() {

        return new SimpleTextWatcher() {
            @Override
            public void onTextChanged(String newValue) {
                isInNotification = true;
                setPassword(newValue);
                isInNotification = false;
            }
        };
    }
}

これがあなたが探しているものであり、確かにあなたを助けることができることを願っています。ありがとう

7
Bhavdip Sagar

より簡単な解決策があります。実際に変更されていない場合は、フィールドを更新しないでください。

@Bindable
public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
     if(this.firstName.equals(firstName))
        return;

     this.firstName = firstName;
     notifyPropertyChanged(BR.firstName);
}
2
rpattabi

ポジョ:

public class User {
    public final ObservableField<String> firstName =
            new ObservableField<>();
    public final ObservableField<String> lastName =
            new ObservableField<>();

    public User(String firstName, String lastName) {
        this.firstName.set(firstName);
        this.lastName.set(lastName);

    }


    public TextWatcherAdapter firstNameWatcher = new TextWatcherAdapter(firstName);
    public TextWatcherAdapter lastNameWatcher = new TextWatcherAdapter(lastName);

}

レイアウト:

 <TextView Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@{user.firstName,  default=First_NAME}"/>
        <TextView Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@{user.lastName, default=LAST_NAME}"/>

        <EditText
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:id="@+id/editFirstName"
            Android:text="@{user.firstNameWatcher.value}"
            Android:addTextChangedListener="@{user.firstNameWatcher}"/>
        <EditText
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:id="@+id/editLastName"
            Android:text="@{user.lastNameWatcher.value}"
            Android:addTextChangedListener="@{user.lastNameWatcher}"/>

ウォッチャー:

public class TextWatcherAdapter implements TextWatcher {

    public final ObservableField<String> value =
            new ObservableField<>();
    private final ObservableField<String> field;

    private boolean isInEditMode = false;

    public TextWatcherAdapter(ObservableField<String> f) {
        this.field = f;

        field.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback(){
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (isInEditMode){
                    return;
                }
                value.set(field.get());
            }
        });
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        //
    }

    @Override public void afterTextChanged(Editable s) {
        if (!Objects.equals(field.get(), s.toString())) {
            isInEditMode = true;
            field.set(s.toString());
            isInEditMode = false;
        }
    }

}
1

2方向のデータバインディングの完全な例を見つけるのに苦労しました。これがお役に立てば幸いです。完全なドキュメントはこちら: https://developer.Android.com/topic/libraries/data-binding/index.html

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <data>
        <variable
            name="item"
            type="com.example.abc.twowaydatabinding.Item" />
    </data>

    <LinearLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:orientation="vertical">

        <TextView
            Android:id="@+id/tv_title"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@={item.name}"
            Android:textSize="20sp" />


        <Switch
            Android:id="@+id/switch_test"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:checked="@={item.checked}" />

        <Button
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="change"
            Android:onClick="button_onClick"/>

    </LinearLayout>
</layout>

Item.Java:

import Android.databinding.BaseObservable;
import Android.databinding.Bindable;

public class Item extends BaseObservable {
    private String name;
    private Boolean checked;
    @Bindable
    public String getName() {
        return this.name;
    }
    @Bindable
    public Boolean getChecked() {
        return this.checked;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
    public void setChecked(Boolean checked) {
        this.checked = checked;
        notifyPropertyChanged(BR.checked);
    }
}

MainActivity.Java:

public class MainActivity extends AppCompatActivity {

    public Item item;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        item = new Item();
        item.setChecked(true);
        item.setName("a");

        /* By default, a Binding class will be generated based on the name of the layout file,
        converting it to Pascal case and suffixing “Binding” to it.
        The above layout file was activity_main.xml so the generate class was ActivityMainBinding */

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setItem(item);
    }

    public void button_onClick(View v) {
        item.setChecked(!item.getChecked());
        item.setName(item.getName() + "a");
    }
}

build.gradle:

Android {
...
    dataBinding{
        enabled=true
    }

}
0
live-love