I've successfully made my first bind adapter and I wish to know a bit more about it.
I want to know how to make an attribute that can get only specific strings for a different state for my view.
For example every view has the visibility attribute that it can be"gone", "visible", "invisible"
<TextView
android:id="#+id/loading_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="#id/inventory_items_recycler"
app:layout_constraintEnd_toEndOf="#+id/inventory_items_recycler"
app:layout_constraintStart_toStartOf="#id/inventory_items_recycler"
app:layout_constraintBottom_toBottomOf="#id/inventory_items_recycler"
android:textSize="18sp"
android:visibility="gone"
app:item_id="#{ItemID.BLACK_GLOVES.ordinal()}"
/>
I've made a custom attribute called item_id that get a number that represent enum value. And in my binding utils I have this code:
#BindingAdapter("item_id")
public static void setItemName(TextView tv, int itemId) {
tv.setText(ItemData.get(ItemID.values()[itemId]).getName());
}
I prefer to have something similar to the visibility attribute that it value can be either "visible", "invisible" or "gone"
Bonus::
I wish android studio can auto-complete me for the possibilities that I can use.
You could pass directly the enum to your binding adapter, instead of converting it first to an int and than back to enum.
#BindingAdapter("item_id")
public static void setItemName(TextView tv, ItemID itemId) {
..
}
Then you could pass directly the enum in your xml:
app:item_id="#{ItemID.BLACK_GLOVES}"
This way you'll have a limited number of possibilities to enter and will be less likely to accidentally enter a meaningless integer.
However, binding adapters and custom attibutes are different. With a binding adapter, you still need to use the syntax of binding expression, ie: "#{ }".
android:visibility , on the other hand, is an attribute. You can also define custom attributes for your custom views and get something similar (have a limited number of input options and IDE shows you your options etc). But you shouldn't confuse that with binding adapters. These are two different concepts.
Try this:
#BindingAdapter("isGone")
#JvmStatic
fun View.setVisibility(isGone: Boolean) {
if (isGone) this.visibility = View.GONE else View.VISIBLE
}
Inside your xml:
<com.google.android.material.checkbox.MaterialCheckBox
android:id="#+id/cb_class"
style="#style/TextStyleNormal.White"
android:layout_marginStart="#dimen/margin_large"
isGone="#{isSharedDailyActivity}"// it take boolean value
app:buttonTint="#color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:useMaterialThemeColors="true" />
Related
I have 2 edit texts in my view and I'm using data binding. What I want to achieve is whenever the first text view had 5 characters, the focus passes on to the next edit text, and whenever that edit text also had 5 characters, the focus should be removed from the whole view.
This is the code I wrote for my binding adapter:
#BindingAdapter("setMaxLength", "nextPart")
fun EditText.onTextChange(maxLength: Int, nextPart: EditText) {
filters = arrayOf<InputFilter>(InputFilter.LengthFilter(maxLength))
addOnTextChangeListener {
if (it.length == maxLength) {
clearFocus()
nextPart.requestFocus()
}
}
}
I don't know how I should pass these 2 arguments to my function in xml.
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/firstPart"
nextPart="alphabet"
setMaxLength="#{5}" />
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/alphabet"
setMaxLength="#{5}" />
This code has build issues with error:
Cannot find a setter for <androidx.appcompat.widget.AppCompatEditText setMaxLength> that accepts parameter type 'int'
If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.
I'm not sure how I could do this, also addOnTextChangeListener is a text watcher and the functionality is tested.
Am I even on the right path here? Any ideas would be appreciated.
Try doing it like this
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/firstPart"
nextPart="#{alphabet}"
setMaxLength="#{5}" />
How to set the child view's width using data binding. The value to set is dynamic and it depends on the width of the parent layout.
item_bar.xml
<LinearLayout
android:id="#+id/barLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="#{model.usedBarWidthPercentage}"
android:layout_height="4dp"/>
</LinearLayout>
The model can give me a percentage. Ex, if the percentage is 40%, it means the width of the textview should be 40% of the parent layout.
I know the idea of using data binding adapters, but dont know how to do it with the parent layout's width.
You can create a class file, example MyBingding.class
#BindingAdapter({ "bindWidth" })
public static void bindWidth(TextView textView, double perc) {
//You can do something by java code here
textView.xxxxxxxx;
}
Then use bindWidth method in XML:
<TextView
app:bindWidth="#{model.usedBarWidthPercentage}"
android:layout_width="wrap_content"
android:layout_height="4dp"/>
Make sure the usedBarWidthPercentage's data type is same as bindWidth method's perc.
I'm afraid you will be disappointed on this one iori24. I tried for qhite a while to figure out how to accomplish this, but some variables can be done post draw and some have to be done pre draw. Binding is meant to be for post draw type of values. layout_width and layout_height are pre draw values that need to be set ahead of time.
Unfortunately there is no current way to do this. I found many articles on it and can confirm this statement. However, I managed to find ways to manipulate my UI with various match parents or weights to accomplish what I needed or adjust in code.
I would love if they brought data binding to layout_width and height, and I think they will eventually, but for now you will just have to get more creative with your design.
If you would like you could provide your full sample code for someone to modify and hand back with potential fixes, but I'm guessing you will need a code option here and not an XML option. Now namezhouyu posted a binding adapter option. This is a postdraw that will take the textview and potentially modify it's value after it has been drawn. This could work, but you may see strange behavior as the original size would be wrap_content and then each subsequent change would cause it to jump to correct size. But I do like his work around, it is a clever solution.
So if you are bound and determined to do it through binding, then I would follow namez houyu's option.
What I end up doing is follow #Raghunandan's suggestion
class ViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(model: Model) {
Logger.d("START")
binding.model = model
val treeObserver = binding.viewLayout.viewTreeObserver
if (treeObserver.isAlive) {
treeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.viewLayout.viewTreeObserver.removeOnGlobalLayoutListener(this) // need to call viewTreeObserver again
val maxValue = binding.viewLayout.width
val usePerc = binding.model.getUsedBarWidthPercentage()
Logger.d("maxValue=$maxValue, usePerc=$usePerc")
binding.view.layoutParams.width = (maxValue * usePerc).toInt()
}
})
}
}
}
I should be able to use a #BindingAdapter in Android dataBinding so that i can override a certain attribute. I am able to do it with a cusutom attribute but with a android built-in attribute how is it accomplished?
what i have so far:
in my viewModel i have a method that is annotated with #BindingAdapter and looks like this:
#BindingAdapter({"android:text"})
public static void setText(Button view,boolean language) {//i need to pass one more variable in here for area code , its just a integer, but how ?
if("french".equals(language))//i want to test if french && area code
view.setText("si vous play");
else if ("English".equals(language)) //i want to test if french && area code
view.setText("please");
}
but i have a few problems. Lets see the xml :
<Button
android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{`french,844`}"/>
See my issue, i want to pass in more then one parameter to dataBinding. But how ? Do i have to make a POJO object and pass it in ? even if i did that how do i set the object from xml ?
So if someone can just tell me the following i'll be fine:
1. how to pass multiple values to a bindingAdapter and
2. How to override a built-in android attribute in any view.
If you want to set two different values in your BindingAdapter, you should use two different attributes:
<Button
android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{`french`}"
app:areaCode="#{`844`}"/>
Then have two different attributes in your BindingAdapter:
#BindingAdapter({"android:text", "areaCode"})
public static void setText(Button view, String language, String areaCode) {
...
}
But it would probably be better to set a different "app:language" as that would be more clear to the developer.
I followed the instructions here to bind my view model validation to the input form.
Using MVVMCross to bind to error messages
The problem I have now is that there is a lot of extra spacing on the form due to the validation elements. How do I make these spacing problems go away? It's a bit difficult to use a Visibility converter due to the fact there is no property for each field. Same problem with Android and iOS. I suppose maybe some sort of custom visibility converter?
I think a quick fix might be to use a binding like Visible Errors['Email'] - however, you're reporting that's not working (so transferred that to https://github.com/MvvmCross/MvvmCross/issues/494 - thanks)
Since that doesn't work directly, then you you should be able to bind the Visible boolean property using something like (in Android):
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:text="My error text"
local:MvxBind="Visible ErrorExists(Errors['Email'],FallbackValue=null)"
/>
where ErrorExists is:
public class ErrorExistsValueConverter : MvxValueConverter
{
public override object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value != null);
}
}
For iOS, if you are showing/hiding UIViews, then you would need to ensure your UI layout auto-updates - e.g. using constraints
As an alternative UI technique, you should also be able to use binding on the background color of an EditText - similar to the color binding in https://github.com/MvvmCross/MvvmCross-Tutorials/blob/master/ValueConversion/ValueConversion.UI.Droid/Resources/Layout/View_Colors.axml
While using the new data binding api, I found that you can't bind to the "style" attribute. Compiler complains that it can't find the style. However, if I simply set the style as is, it'll find it just fine. For example:
doesn't work:
style="#{TextUtils.isEmpty(row.getSubtitle()) ? #style/SubTitle : #style/Title}"
works:
style="#style/SubTitle"
Error:
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Identifiers must have user defined types from the XML file. SubTitle is missing it file:/~/test/app/src/main/res/layout/row.xml loc:48:71 - 48:78 ****\ data binding error ****
The data binding unfortunately is not supported for styles:
https://code.google.com/p/android-developer-preview/issues/detail?id=2613
Although #bwhite is correct, there may be workarounds you can do. It depends what you need to conditionally change. For instance, if you want to change the font based on the condition (which I needed to do), you can do it by making a custom binding adapter.
In other words, doing something like this:
public class FontBindingAdapter {
#BindingAdapter({"bind:font"})
public static void setFont(TextView textView, String typefaceName){
Typeface typeface = ResourcesCompat.getFont(context, R.font.myfont);
// You'd probably want to actually use `typefaceName` to determine the font to use
textView.setTypeface(typeface);
}
Then in your layout, like this:
<TextView
app:font="#{some_condition ? #string/typeface_string_name_bold: #string/typeface_string_name_bold_light}"
I used this in my code, based on a great post: https://plus.google.com/+LisaWrayZeitouni/posts/LTr5tX5M9mb
I have a found a rather elegant solution for applying styles with data binding. I use the Paris library and then create binding adapters for interested views. for example:
#BindingAdapter("bindTextViewStyle")
fun TextView.bindTextViewStyle(styleResourceId: Int) {
this.style(styleResourceId)
}
and then in XML:
<TextView
app:bindTextViewStyle="#{viewModel.priceStyleResource}"
.../>
viewModel.priceStyleResource is a MutableLiveData in my view model which is set with the style resource ID.
priceStyleResource.value = R.style.QuoteDetailsHeaderItem_Up
EXTRA NOTE
You can also probably make a generic bindStyle binding adapter directly for the View class, but in that case the attribute items specifically for textviews (textColor for example) will not get applied. So up to you to find the right balance and naming.