I have a problem with filling android:entries with String[] from my ViewModel. Code looks like that:
attrs.xml
<declare-styleable name="AutoCompleteDropDown">
<attr name="android:entries" />
</declare-styleable>
My custom dropdown, AutoCompleteDropDown
public class AutoCompleteDropDown extends AppCompatAutoCompleteTextView {
public AutoCompleteDropDown(Context context, AttributeSet attributes) {
super(context, attributes);
TypedArray a = context.getTheme().obtainStyledAttributes(attributes, R.styleable.AutoCompleteDropDown, 0, 0);
CharSequence[] entries = a.getTextArray(R.styleable.AutoCompleteDropDown_android_entries);
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(context, android.R.layout.simple_dropdown_item_1line, entries);
setAdapter(adapter);
}
...
ViewModel
...
private String[] genders;
public String[] getGenders() {
return genders;
}
public void setGenders(String[] genders) {
this.genders = genders;
}
...
genders are filled in ViewModel constructor:
genders = dataRepository.getGenders();
xml file
<AutoCompleteDropDown
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={vm.title}"
android:entries="#{vm.genders}"
bind:addTextChangedListener="#{vm.titleValidationChangeListener}"/>
ViewModel is binded correctly, i'm using it many times in that xml file. When i try to run the app i'm getting:
Cannot find the setter for attribute 'android:entries' with parameter
type java.lang.String[] on AutoCompleteDropDown
It works when i use android:entries="#array/genders" but i need this list to be dynamic. Project is in MVVM pattern. Appreciate any help :)
You can use BindingAdapter for this. Like :
#BindingAdapter("entries")
public static void entries(AutoCompleteDropDown view, String[] array) {
view.updateData(array);
}
updateData it's method which you must create in your AutoCompleteDropDown.
And in xml it's using same
app:entries="#{vm.genders}"
<AutoCompleteDropDown
xmlns:customNS="http://schemas.android.com/apk/res/com.example.yourpackage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={vm.title}"
customNS:entries="#{vm.genders}"
/>
You can check this example Android - custom UI with custom attributes
Related
I want to use the ternary operator to change the color of a custom view in terms of a Boolean.
Here is my custom view
class AddButton(context: Context, attrs: AttributeSet): RelativeLayout(context, attrs) {
private var imageView: AppCompatImageView
private var textView: TextView
init {
inflate(context, R.layout.add_button, this)
imageView = findViewById(R.id.add_button_icon)
textView = findViewById(R.id.add_button_text)
val attributes = context.obtainStyledAttributes(attrs, R.styleable.AddButton)
val iconTint = attributes.getResourceId(R.styleable.AddButton_iconColor, 0)
imageView.setImageDrawable(attributes.getDrawable(R.styleable.AddButton_icon))
textView.text = attributes.getString(R.styleable.AddButton_text)
setIconTint(iconTint)
attributes.recycle()
}
fun setIconTint(colorId: Int) {
imageView.setColorFilter(ContextCompat.getColor(context, colorId), android.graphics.PorterDuff.Mode.SRC_IN);
}
fun setText(text: String) {
textView.text = text
}
}
values/attr.xml :
<declare-styleable name="AddButton">
<attr name="icon" format="reference"/>
<attr name="iconColor" format="color"/>
<attr name="text" format="string"/>
</declare-styleable>
In the layout :
<com.my.package.ui.customview.AddButton
app:icon="#drawable/ic_add"
app:iconColor="#{selected ? #color/colorRed : #color/colorBlack}"
app:text="#{selected ? #string/selected : #string/not_selected}"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
It is working as expected for the app:text but when i want to do it for the iconColor i have this error :
Cannot find a setter for <com.my.package.ui.customview.AddButton app:iconColor> that accepts parameter type 'int'
For now to solve the problem, i have to change the color in the code behind by listening when the selected boolean changes and then call the setIconTint of my AddButton view.
Is there a way to change the color directly in the layout file using the ternary operator ?
You are looking for a custom data binding adapter for your custom view. First, check out this for the complete document.
So, in short, you need to define a method named: setIconColor that accepts iconId and sets the icon color by the provided resource id.
But if you want to use your current method named setIconTint, you just need to annotate your class with:
#BindingMethods(value = [
BindingMethod(
type = AddButton::class,
attribute = "iconColor",
method = "setIconTint")])
After all, I again suggest you check other variants of binding adapters in the official document.
My Adapter requires a context in order to apply resources to views, therefore when instantiating it, I might do the following within my Activity:
myAdapter = new MyAdapter(this);
As my adapter also needs data from an activity, I might do this:
myAdapter = new MyAdapter(myItemsArrayList,this);
As my adapter might also need to know which items in the ArrayList are selected, I might pass it that list too:
myAdapter = new MyAdapter(myArrayItemsList,mySelectedItemsArrayList,this);
And as there may be other states (e.g. whether to display photos in a list of people, the constructor call is starting to get quite lengthy:
myAdapter = new MyAdapter(myArrayItemsList,mySelectedItemsArrayList,myPreference1,myPreference2,this);
Given that the only place this adapter will be used is from a particular activity, how bad would it be to just make those attributes in the activity public, so that I can access them via the activity that has been passed (e.g myActivity.myArrayItemsList)?
Many thanks in advance for any advice!
Given that the only place this adapter will be used is from a particular activity, how bad would it be to just make those attributes in the activity public, so that I can access them via the activity that has been passed (e.g myActivity.myArrayItemsList)?
That's a bad code and bad behavior. You're code will be tightly coupled. And usually, you will borrow the same behavior to your next project.
Instead of passing each state to your constructor, you can simplify it by passing a State object to your adapter. Create the State class something like this:
public class State {
List<String> selectedItems;
boolean displayPeople;
}
then you can create a simple constructor like this:
State state = new State();
state.selectedItems = mSelectedItems;
state.displayPeople = true;
myAdapter = new MyAdapter(items, state, this);
So, whenever you need to update a new state, you just need to add it the State class and update the Adapter according to it.
Considering you are using the Item object for myArrayItemsList.
So your list should look like this:
ArrayList<Item> myArrayItemsList = new ArrayList();
and then you want to add the selected items in the list you could add a boolean to the Item object ex:
public class Item {
private String itemName;
private boolean selected = false;
public Item(){}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName= itemName;
}
public boolean isSelected () {
return selected;
}
public void setSelected(boolean selected) {
this.selected= selected;
}
}
and just check your item list in the adapter if the item is selected.
So your adapter would only pass two parameters:
MyAdapter myAdapter = new MyAdapter(myArrayItemsList, this);
But then again you want to pass only one parameter in adapter, you can set your ArrayList to static
public static ArrayList<Item> myArrayItemsList = new ArrayList();
and pass only this your adapter
MyAdapter myAdapter = new MyAdapter(this);
used the static ArrayList in your adapter but it is not advisable using those static data because the data could be Garbage Collected in the memory.
I just run into a problem, using android databinding library.
Here is the xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.test.app.ObservableFieldWrapper"/>
<variable
name="org"
type="ObservableFieldWrapper"/>
</data>
<LinearLayout
android:id="#+id/headerListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.test.app.NSpinner
android:id="#+id/orgSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:org="#{org.getSilent ? org.content : "silent"}"/>
</LinearLayout>
Here is my NSpinner:
public class ObservableFieldWrapper{
private final ObservableBoolean silent;
private final ObservableField<String> content;
#BindingAdapter("org")
public static void setOrg(Spinner view, String org) {
assert org != null;
if (org.equals("silent")) {
Log.i("ObsWrapper", "SET ORG called via binding adapter but got denied, because of SILENCE");
} else {
Log.i("ObsWrapper", "SET ORG called via binding adapter NORMALLY");
view.setSelection(Cache.GetOrgIndexForSpinner(), true);
}
}
public ObservableFieldWrapper(String startValue) {
content = new ObservableField<>(startValue);
silent = new ObservableBoolean();
silent.set(false);
}
public void setContent(String newValue) {
silent.set(false);
content.set(newValue);
content.notifyChange();
}
public void setContentSilent(String newValue) {
silent.set(true);
content.set(newValue);
}
//Bunch of getters
}
And this call should invoke the static getter provided, by ObservableFieldWrapper class (assume, that all bindings were already set):
ObservableFieldWrapper someField = new ObservableFieldWrapper("someString");
someField.setContent("some other string");
Well, problem is... It invokes nothing. But if I change my xml part from
app:org="#{org.getSilent ? org.content : "silent"}"
to common
app:org="#{org.content}"
It starts working! I realy need this extra functionality with boolean, and I am really lost trying to find the issue.
Found a work around, where didn't use any logics in xml expressions, I just passed 2 parameters to my function and did all job there.
#Bindable ("{org, silent}")
Yet, the question remains unanswered.
As George Mount mentioned - it's important to remove any getters on observable fields, otherwise it won't work, I spent pretty much time with this issue and then mentioned that I have a getter, after removing it - everything started working.
I'm so close to finishing the functions for my app and one thing won't work.
I have an adapter which makes the backgrounds for my views by taking colors I've specified in the color.XML. The function which returns a view with the specified color works excellent.
textView.setBackground works great. However, textView.setText returns "false" in my views and therefore they look like this.
I am trying to get the "android:text" from my TextView in my XML.
What am I missing?
The code:
textView.setBackground(mContext.getResources().getDrawable(mColorID[position]));
textView.setText(mContext.getResources().getString(mThumbIds[position]));
return textView;
}
private Integer[] mColorID = {
R.color.turquoise,R.color.greenSea,
R.color.emerald,R.color.nephritis,
};
private Integer[] mThumbIds = {
R.id.text_test,R.id.text_test,
R.id.text_test,R.id.text_test,
};
EDIT: Added the TextView with the ID I would like to get the text from (text_test).
<TextView
android:id="#+id/text_test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello World!"
</TextView>
You are referencing View ID's and not string resources. Try changing to this:
private Integer[] mThumbIds = {
R.string.text_test,R.string.text_test,
R.string.text_test,R.string.text_test,
mContext.getResources().getString()
returns a localized formatted string from the application's package's
default string table
So if your texts are located in file strings in your array you should have
R.string.text_test,
I have a custom view:
public class Loading extends View {
private long movieStart;
private Movie movie;
public Loading(Context context, InputStream inputStream) {
super(context);
movie = Movie.decodeStream(inputStream);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
super.onDraw(canvas);
final long now = SystemClock.uptimeMillis();
if(movieStart == 0)
movieStart = now;
final int relTime = (int)((now - movieStart) % movie.duration());
movie.setTime(relTime);
movie.draw(canvas, 0, 0);
this.invalidate();
}
}
How can I use this view in XML layout? How can I pass the parameters (Context, InputStream) in XML layout?
How can I use this view in XML layout?
..
<pacakge_of_class.Loading
android:id="#+id/y_view1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
http://developer.android.com/guide/topics/ui/custom-components.html
There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.
How can I pass the parameters
https://stackoverflow.com/a/4495745/804447
Error referencing an inner class View in layout/main.xml
<view class="Your_package.MainClass$Loading" />
The short answer is you can't directly do that.
The long answer is that you can indirectly do that.
Add the view to the XML by its fully qualified name (as others have mentioned), then:
What you need to do is implement the normal constructors from View. Define a custom attribute that declares the resource to use to create the InputStream in your constructor. The view system will give you the context automatically, you'd then need to open the InputStream based on the provided attribute value.
You can use a custom View in an XML-Layout like this:
<com.your.package.Loading
android:id="#+id/y_view1"
... />
But you cannot use your own constructor, you have to use the constructors as shown in this answer.
So you have to access your Loading View by code an set the InputStream manually:
Loading yourView = (Loading) findViewById(R.id.yourLoadingView);
yourView.setInputStream();
where you have this setter method in your Loading class:
public void setInputStream(InputStream inputStream){
movie = Movie.decodeStream(inputStream);
}