I have a problem in implementing a CustomAdapter for a Spinner using Android DataBinding with BaseAdapter.
The data has two values. I want to use two TextViews. The CustomAdapter must be inherited from BaseAdapter, the simpler variant with ArrayAdapter supports only one TextView. In the long run the second TextView could be an ImageView, so merging the two values into one String to be able to use an ArrayAdapter would still not help.
What I tried also: To make sure the idea and the Spinner works as normal, I implemented a version without DataBinding And I based my data binding implementation on chrislizh's project at https://github.com/chrislizh/SpinnerTwoWayDataBindingDemo, which is using an ArrayAdapter.
I also tried to call: binding.executePendingBindings()
, and tried to not use the ViewHolder pattern.
The problem results in detail: The project is about planets. The Spinner allows the selection of a planet. Each planet has a name and a distance. Both values will be displayed. The result of my implementation of the CustomAdapter, called PlanetAdapter, with DataBinding displays the first item twice, after a selection. See screenshots. Selection of any other planet than the first item, 'switches' its position with the selection, and keeps being displayed twice. This way one planet is always missing in the displayed list.
The code of the PlanetAdapter and the call to create it in the MainActivty's onCreate method:
PlanetAdapter
public class PlanetAdapter extends BaseAdapter
{
private int itemLayoutResourceId;
private final List<Planet> planets;
private LayoutInflater inflater;
public PlanetAdapter(@NonNull Context context, @LayoutRes int itemLayoutResourceId, List<Planet> planets)
{
inflater = LayoutInflater.from(context);
this.itemLayoutResourceId = itemLayoutResourceId;
this.planets = planets;
}
@Override
public int getCount()
{
return planets.size();
}
@Override
public Object getItem(int position)
{
return planets.get(position);
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
PlanetViewHolder holder;
if (convertView == null) {
PlanetSpinnerItemBinding itemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.planet_spinner_item, parent, false);
itemBinding.setPlanet(planets.get(position));
holder = new PlanetViewHolder(itemBinding);
holder.view = itemBinding.getRoot();
holder.view.setTag(holder);
}
else {
holder = (PlanetViewHolder) convertView.getTag();
}
return holder.view;
}
private static class PlanetViewHolder
{
private View view;
PlanetViewHolder(PlanetSpinnerItemBinding binding)
{
this.view = binding.getRoot();
}
}}
Creation and setting the Adapter:
public class MainActivity extends AppCompatActivity implements IDataChangeListener
{
private static final String BUNDLE_SELECTED_PLANET = "bundle_selected_planet";
private ActivityMainBinding activityMainBinding_;
private List<Planet> planets_;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
activityMainBinding_ = DataBindingUtil.setContentView(this, R.layout.activity_main);
planets_ = loadPlanets(this, R.array.planetsInSolarSystem);
if (planets_ != null) {
planets_.add(0, new Planet("", 0)); // insert a blank item on the top of the list
PlanetAdapter planetAdapter = new PlanetAdapter(this, R.layout.planet_spinner_item, planets_);
activityMainBinding_.setSpinAdapterPlanet(planetAdapter);
Planet selectedPlanet = savedInstanceState != null ? savedInstanceState.<Planet>getParcelable(BUNDLE_SELECTED_PLANET) : planets_.get(3);//initial selected planet is Earth, 3 is the index of Earth after a blank item inserted
activityMainBinding_.setBindingPlanet(new BindingPlanet(this, selectedPlanet));
}
} // loadPlanets skipped.
}
The planet class:
public class Planet implements Parcelable {
private String name_;
private float distance_; //distance to Sun in AU(Astronomical Unit)
public Planet(String name, float distance) {
name_ = name;
distance_ = distance;
}
protected Planet(Parcel in) {
name_ = in.readString();
distance_ = in.readFloat();
}
public static final Creator<Planet> CREATOR = new Creator<Planet>() {
@Override
public Planet createFromParcel(Parcel in) {
return new Planet(in);
}
@Override
public Planet[] newArray(int size) {
return new Planet[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name_);
dest.writeFloat(distance_);
}
@Override
public String toString() {
return name_ != null ? name_ : super.toString();
}
public String getName() {
return name_;
}
public void setName(String name) {
name_ = name;
}
public float getDistance() {
return distance_;
}
public void setDistance(float distance) {
distance_ = distance;
}
}
The XML of the item layout: spinner_planet_item.xml `
<data>
<variable
name="planet"
type="au.com.chrisli.spinnertwowaydatabindingdemo.Planet">
</variable>
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/planetName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{planet.name}"
tools:text="Dummy Planet"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
<TextView
android:id="@+id/planetDistance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:text="@{String.valueOf(planet.distance)}"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="2393.0 km"/>
</LinearLayout>
In the github code, I also included the classic case (without data binding) in the class PlanetAdapter, at the end of the file, but commented out. In case you want to see that it works - just switch getView und PlaneViewHolder implementations there.
The full modified project is on my github at: https://github.com/Minsky/SO_Question_SpinnerDataBindingBaseAdapter
What's wrong with my Binding implementation of the BaseAdapter?
As Commonsware wrote in his comment, binding the views in the recycle case was missing. The working code for the method getView() in PlanetAdapter is something like this:
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
PlanetViewHolder holder;
if (convertView == null) {
PlanetSpinnerItemBinding itemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.planet_spinner_item, parent, false);
holder = new PlanetViewHolder(itemBinding);
holder.view = itemBinding.getRoot();
holder.view.setTag(holder);
}
else {
holder = (PlanetViewHolder) convertView.getTag();
}
holder.binding.setPlanet(planets.get(position));
return holder.view;
}
And the PlanetViewHolder holds now the binding:
private static class PlanetViewHolder {
private View view;
private PlanetSpinnerItemBinding binding;
PlanetViewHolder(PlanetSpinnerItemBinding binding) {
this.view = binding.getRoot();
this.binding = binding;
}
}
final Github projet in branch "solved": https://github.com/Minsky/SO_Question_SpinnerDataBindingBaseAdapter/tree/solved
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With