I just got a hold of Butterknife and have been trying my best to standardize all of my 'OnClick's to be bound via Butterknife.
I have found though, that it's difficult to follow Butterknife's standard binding pattern when dynamically populating views (via adapters for example) since the individual views don't have id's
#OnClick(What Do I put here if I have no ID?)
public void OnClickMethod(View view) {
//Body
}
Specifically, I'm having problems adding onClicks to views that are part of a TabLayout. I know I can use the built in
TabLayout.setOnTabSelectedListener()
But ideally I'd like to be consistent in binding all forms of onClick via Butterknife. Is there a clean way of doing this?
Set an id in res/values/ids.xml like :
<item name="my_view" type="id"/>
And then add the id to the view :
myView.setId(R.id.my_view);
#OnClick(R.id.my_view)
public void OnClickMethod(View view) {
//Body
}
Related
I'm trying to call multiple methods through onClick and pass it thru ViewModel to data binding.
I'm calling view in xml file like that
android:onClick="#{() -> gameViewModel.increaseScore()}"
and I want to add another one method, but I receive error "Duplicate attribute onClick".
Theoreticlly I find solution here - Multiple click listeners on buttons but I think that was not purpose of getting rid of setOnClickListners, to now implement View.OnClickListener listener and do the same in very similar way. Is there any clever way to combine this all together?
With kotlin you can do something like this
#BindingAdapter("customOnClickListener")
fun View.customOnClickListener(viewModel: YourViewModel) {
setOnClickListener {
viewModel.actionOne()
viewModel.actionActionTwo()
//action three...
}
}
Then in xml you use it like this:
app:customOnClickListener="#{viewModel}"
Here is the tutorial
https://www.youtube.com/watch?time_continue=289&v=OGfZpfn-dGI
In my android studio it doesn't recognize the button iv'e name them
android:id="#+id/top_button"
android:text="top button"
android:text="button 2"
android: android:id="#+id/button_2"
top_button.setOnClicklistner {
println(top button was clicked)
Button_2.setOnClicklistner {
println(Button)
We're missing a lot of context here that would probably help us help you.
A few things first though:
- The android:id property in your XML layout is how you name the View in question. This is most often how you will reference the View in code.
- The android:text property is the user visible text on views like TextView.
- In order for top_button to refer to your desired View in your XML layout file, it needs to be bound in code. There's a couple of normal ways of doing it findViewById() and data-binding.
I'm going to assume, for now, that the last step is what you are missing (it seems the most likely culprit at this point)... Here's a few ways to bind it:
Method 1: when using an Activity class
If you're binding top_button to your View from an Activity class, this should work:
private lateinit var top_button // replace View here with your widget's type
fun onCreate(...) {
super.onCreate(...)
setContentView(R.layout.<i>your_layout_file_name_here</i>)
top_button = findViewById(R.id.top_button)
...
}
Method 2: when using a Fragment class
If you're binding top_button to your View from a Fragment class, it's more like this:
private lateinit var top_button: View // replace View here with your widget's type
fun onCreateView(...): View {
val rootView = layoutInflater.inflate(R.layout.<i>your_layout_file_name_here</i>)
top_button = rootView.findViewById(R.id.top_button)
...
return rootView
}
Method 3: when using data-binding
I prefer this method myself, but you should Google how to setup data-binding in Android as you'll need changes in your build.gradle and all.
First, change your XML layout to be:
<layout>
<!-- your existing layout XML here -->
</layout>
Then in your Activity/Fragment, let's say your XML layout file is named activity_my_cool_screen.xml, you can do:
val binding = ActivityMyCoolScreenBinding.inflate(getLayoutInflater())
binding.topButton.setOnClickListener(...)
Notice here that the ActivityMyCoolScreenBinding class is auto-generated for you. If it turns red at first, then first verify you've accurately setup data-binding in your project, then if that's good to go, make sure to import the ActivityMyCoolScreenBinding class. If you change your XML layout's filename, then the ActivityMyCoolScreenBinding class name will change to match automatically. But, I'd recommend if you do change the name, that you use Android Studio's refactoring/renaming tools as it'll search your codebase and update it everywhere. Otherwise, you have to do it by hand (doable, but potentially tedious and error prone).
Good luck!
Using two-way Android Databinding, is it possible to have a generic inverse boolean converter? For example, I would like to do something like this:
<Switch android:checked="#={!viewModel.myBoolean}" />
When I run this in Android, the switch just rapidly fires back and forth. I tried to create a two way binding app:inverseChecked following some examples from George Mount, but I was not successful (just kept getting error stating cannot find event 'inverseCheckedAttrChanged' on View type 'android.widget.Switch').
As a comparison, using Aurelia this just works as you would expect for two way binding. In WPF, probably the first converter you make is some sort of InverseBooleanConverter to easily tackle these sorts of things. So, am assuming I am just missing something obvious here.
I actually didn't expect it to work at all. I assume, it's switching back and forth all the time, because the bindings don't apply inverse function of your binding expression.
That said, I tested the behavior with the current data binding library version and checked the generated sources. With the simple example of android:checked these show notes how the inverse should look like and apply it appropriately.
Also George Mount wrote a Blog post about it a short while ago: https://medium.com/google-developers/android-data-binding-inverse-functions-95aab4b11873
If you try to implement an app:inverseChecked, you'd also have to implement a #BindingAdapter("inverseChecked") as setter, #InverseBindingAdapter(attribute="inverseChecked") as getter and #BindingAdapter("inverseCheckedAttrChanged") for setting up the change listener.
The latter could look like the following:
#BindingAdapter("inverseCheckedAttrChanged")
public static void setupInverseCheckedAttrChanged(Switch view, InverseBindingListener listener) {
OnCheckedChangeListener newListener = null;
if (listener != null) {
newListener = (v,b) -> listener.onChange();
}
view.setOnCheckedChangeListener(newListener);
}
As we all know,if we want to use Butterknife to bind click event onto a normal view,we just need to declare the below method:
#onClick(R.id.yourview)
public void submit(){
// do your things
}
But,how can I bind click event onto a view in a layout.
Answer is the same. You can do the same way:
#OnClick(R.id.yourview)
public void submit(){
// do your things
}
When you have a inside a XML layout definition Android inflate all the content into a viewgroup replacing the include.
So, the view id is valid to do a reference like the code above.
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.