Android Data Binding with a Custom View

android data binding with a custom view

In your Custom View, inflate layout however you normally would and provide a setter for the attribute you want to set:

private MyCustomViewBinding mBinding;
public MyCustomView(...) {
...
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mBinding = MyCustomViewBinding.inflate(inflater);
}

public void setMyViewModel(MyViewModelObject obj) {
mBinding.setMyViewModel(obj);
}

Then in the layout you use it in:

<layout xmlns...>
<data>
<variable
name="myViewModel"
type="com.mypath.MyViewModelObject" />
</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.mypath.MyCustomView
android:id="@+id/my_view"
app:myViewModel="@{myViewModel}"
android:layout_width="match_parent"
android:layout_height="40dp"/>

</LinearLayout>
</layout>

In the above, an automatic binding attribute is created for app:myViewModel because there is a setter with the name setMyViewModel.

How to use View Binding on custom views

Just inform the root, and whether you want to attach to it

init { // inflate binding and add as view
binding = ResultProfileBinding.inflate(LayoutInflater.from(context), this)
}

or

init { // inflate binding and add as view
binding = ResultProfileBinding.inflate(LayoutInflater.from(context), this, true)
}

which inflate method to use will depend on the root layout type in xml.

Android ViewBinding with CustomView

ViewDataBinding.inflate doesn't generate of child view accessor inside custom view.

thus, you can't touch line1(TextView) via only use ViewDataBinding.

If you don't want using findViewById or kotlin synthetic, MyCustomView also needs to apply ViewDataBinding. try as below.

CustomView

class MyCustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val binding =
CustomLayoutBinding.inflate(LayoutInflater.from(context), this, true)

val line1
get() = binding.line1

val line2
get() = binding.line2
}

MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)

with(binding.customView) {
line1.text = "Hello"
line2.text = "World"
}
}

Two-way databinding custom property on custom view

Turns out the answer was in the docs the whole time (crazy, right?).

The key takeaway seems to be that it's not enough to wrap the [Inverse]BindingAdapter functions in a companion object. They also have to be annotated with @JvmStatic.

My full custom view now looks like:

class SearchView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs, 0) {

var selected: String? = null

init {
inflate(context, R.layout.view_search, this)
}

companion object {
@BindingAdapter("selectedAttrChanged")
@JvmStatic
fun setListener(view: SearchView, listener: InverseBindingListener) {
val input: AutoCompleteTextView = view.findViewById(R.id.search_input)
input.threshold = 1
input.setAdapter(SearchAdapter(view.context))
input.setOnItemClickListener { adapterView, _, position, _ ->
val item: SearchModel = adapterView.getItemAtPosition(position) as SearchModel
input.setText(item.name)
selected = item.code
listener.onChange()
}
}

@BindingAdapter("selected")
@JvmStatic
fun setTextValue(view: SearchView, value: String?) {
if (value != view.selected) view.selected = value
}

@InverseBindingAdapter(attribute = "selected")
@JvmStatic
fun getTextValue(view: SearchView): String? = view.selected
}
}

Custom View with data binding - Cannot find setter

You have to use BindingAdapter in your customview class

@BindingAdapter("app:customToolbarTitle")
fun setCustomToolbarTitle(view: MyAppBarLayoutCustomView, text: Int) {
// call the method in customview which sets the string value for title view
view.methodnameForsettingTitle(text)

}

Data bindings with custom listeners on custom view

@BindingMethods(@BindingMethod(type = SuperCustomView.class, attribute = "app:onToggle", method = "setOnToggleListener"))
public class SuperCustomView extends FrameLayout {
private OnToggleListener mToggleListener;

public interface OnToggleListener {
void onToggle(boolean switchPosition);
}

public void setOnToggleListener(OnToggleListener listener) {
mToggleListener = listener;
}
.../...
}

My hack to test code was:

public void setOnToggleListener(final OnToggleListener listener) {
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
toggle = !toggle;
listener.onToggle(toggle);
}
});
}

And on my controller object:

 public class MyController {

private Context context;

public MyController(Context context) {
this.context = context;
}

public void toggleStrokeLimitation(boolean switchPosition) {
Toast.makeText(context, "Toggle" + switchPosition, Toast.LENGTH_SHORT).show();
}
}

Yeah!! it worked

Alternatively you can use xml like:

 <com.androidbolts.databindingsample.model.SuperCustomView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:onToggleListener="@{controller.toggleStrokeLimitation}" />

Now no need to add @BindingMethods annotation.

Documentation says:
"Some attributes have setters that don't match by name. For these methods, an attribute may be associated with the setter through BindingMethods annotation. This must be associated with a class and contains BindingMethod annotations, one for each renamed method. "

Android 2-Way DataBinding With Custom View and Custom Attr

This works for me:

@InverseBindingMethods(value = {
@InverseBindingMethod(type = FilterPositionView.class, attribute = "bind:filterStringValue", method = "getFilterValue", event = "android:filterStringValuetAttrChanged")
})
public class FilterPositionView extends LinearLayout {
private FilterPositionBinding mBinding;

public FilterPositionView(Context context) {
super(context);
init(context);
}

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

public FilterPositionView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

public FilterPositionView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}

private void init(Context context) {
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.filter_position, this, true);
setOrientation(HORIZONTAL);

mBinding.filterPositionCheck.setOnCheckedChangeListener((buttonView, isChecked) -> {
mBinding.filterPositionValue.setEnabled(isChecked);
if (!isChecked) mBinding.filterPositionValue.setText("");
});
}

/**
* Zwraca wpisywany text
*
* @return wpisane litery tekstu
*/
public String getFilterValue() {
return mBinding.filterPositionValue.getText().toString();
}

@BindingAdapter(value = {"bind:filterTitle", "bind:filterStringValue", "bind:filterDateValue"}, requireAll = false)
public static void setFilterBinding(FilterPositionView positionView, String filterTitle,
String filterStringValue, Long filterDateValue) {
positionView.mBinding.filterPositionTitle.setText(filterTitle);
if (filterStringValue != null)
positionView.mBinding.filterPositionValue.setText(filterStringValue);
if (filterDateValue != null)
positionView.mBinding.filterPositionValue.setText(DateTimeFormatUtil.format(filterDateValue));
}

@BindingAdapter(value = {"android:afterTextChanged", "android:filterStringValuetAttrChanged"}, requireAll = false)
public static void setTextWatcher(FilterPositionView filterPositionView, final TextViewBindingAdapter.AfterTextChanged after,
final InverseBindingListener textAttrChanged) {
TextWatcher newValue = new TextWatcher() {

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

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

@Override
public void afterTextChanged(Editable s) {
if (after != null) {
after.afterTextChanged(s);
}
if (textAttrChanged != null) {
textAttrChanged.onChange();
}
}
};
TextWatcher oldValue = ListenerUtil.trackListener(filterPositionView.mBinding.filterPositionValue, newValue, R.id.textWatcher);
if (oldValue != null) {
filterPositionView.mBinding.filterPositionValue.removeTextChangedListener(oldValue);
}
filterPositionView.mBinding.filterPositionValue.addTextChangedListener(newValue);
}
}

Of course You have to add @={} in your XML layouts like below:

<com.example.customviews.FilterPositionView
style="@style/verticalLabeledValueStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
bind:filterTitle="@{@string/filter_product}"
bind:filterStringValue="@={sfmodel.product}"/>


Related Topics



Leave a reply



Submit