Wasserwecken
Wasserwecken

Reputation: 109

Android data binding to listview like WPF?

I'm trying to get into the data binding in android. Because I'm much more experienced with data binding in WPF I'm very confused.

What 've tried: enter image description here

In WPF it is ridiculous easy to bind a list of objects to list view with custom items. Here is a example from a private project:

enter image description here

That's it. There is no need for a glue code or adapter.

My Question:

Because im quite confused, about adapters / inflating things, etc. There is a nice tutorial about binding to lists, but there i still have to write code for the binding.

http://blog.trsquarelab.com/2016/01/data-binding-in-android-listview.html

Upvotes: 0

Views: 1201

Answers (2)

Yessy
Yessy

Reputation: 1352

Android DataBinding require a little code to accomplish that

The preceding step 1 can be reused by all your projects, the following step 2 & 3 are all layout resource file, just like what WPF do

Step 1. define a BindingAdapter class for ALL AbsListView, this class can be reused by your other project AbsListView

public class AbsListViewBindingAdapter {
    @BindingAdapter(value = {"android:items", "android:itemTemplate", "android:dropDownItemTemplate"}, requireAll = false)
    public static <T> void setListAdapter(AbsListView view, List<T> items, @LayoutRes int itemTemplateLayout, @LayoutRes int dropDownItemTemplateLayout) {
        final ListAdapter oldAdapter = view.getAdapter();
        if (oldAdapter instanceof ObservableListAdapter) {
            ((ObservableListAdapter<T>) oldAdapter).setParams(items, itemTemplateLayout, dropDownItemTemplateLayout);
        } else {
            view.setAdapter(new ObservableListAdapter<>(view.getContext(), items, itemTemplateLayout, dropDownItemTemplateLayout));
        }
    }

    @BindingAdapter(value = {"android:items", "android:itemTemplate", "android:dropDownItemTemplate"}, requireAll = false)
    public static <T> void setListAdapter(AbsListView view, T[] items, @LayoutRes int itemTemplateLayout, @LayoutRes int dropDownItemTemplateLayout) {
        setListAdapter(view, items != null ? Arrays.asList(items) : null, itemTemplateLayout, dropDownItemTemplateLayout);
    }

    @BindingAdapter(value = {"android:items", "android:itemTemplate", "android:dropDownItemTemplate"}, requireAll = false)
    public static <T> void setListAdapter(AbsListView view, int[] items, @LayoutRes int itemTemplateLayout, @LayoutRes int dropDownItemTemplateLayout) {
        setListAdapter(view, items != null ? IntStream.of(items).boxed().collect(Collectors.toList()) : null, itemTemplateLayout, dropDownItemTemplateLayout);
    }

    static class ObservableListAdapter<T> extends BaseAdapter {
        private List<T> mList;
        private int mDropDownResourceId = 0;
        private int mResourceId = 0;
        private final LayoutInflater mLayoutInflater;

        final ObservableList.OnListChangedCallback mListChangedCallback = new ObservableList.OnListChangedCallback() {
            @Override
            public void onChanged(ObservableList observableList) {
                notifyDataSetChanged();
            }

            @Override
            public void onItemRangeChanged(ObservableList observableList, int i, int i1) {
                notifyDataSetChanged();
            }

            @Override
            public void onItemRangeInserted(ObservableList observableList, int i, int i1) {
                notifyDataSetChanged();
            }

            @Override
            public void onItemRangeMoved(ObservableList observableList, int i, int i1, int i2) {
                notifyDataSetChanged();
            }

            @Override
            public void onItemRangeRemoved(ObservableList observableList, int i, int i1) {
                notifyDataSetChanged();
            }
        };


        public ObservableListAdapter(Context context, List<T> list, @LayoutRes int itemTemplate, @LayoutRes int dropDownItemTemplate) {
            mLayoutInflater = LayoutInflater.from(context);
            setParams(list, itemTemplate, dropDownItemTemplate);
        }


        public void setParams(List<T> list, @LayoutRes int itemTemplate, @LayoutRes int dropDownItemTemplate) {
            boolean requireNotifyChange = mResourceId != itemTemplate || mDropDownResourceId != dropDownItemTemplate || !Objects.equals(list, mList);
            mResourceId = itemTemplate;
            mDropDownResourceId = dropDownItemTemplate;
            if (!Objects.equals(list, mList)) {
                if (mList instanceof ObservableList) {
                    ((ObservableList) mList).removeOnListChangedCallback(mListChangedCallback);
                }
                mList = list;
                if (mList instanceof ObservableList) {
                    ((ObservableList) mList).addOnListChangedCallback(mListChangedCallback);
                }
            }
            if (requireNotifyChange) {
                notifyDataSetChanged();
            }
        }

        @Override
        public int getCount() {
            return (mList != null) ? mList.size() : 0;
        }

        @Override
        public T getItem(int position) {
            return mList != null ? mList.get(position) : null;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return getViewForResource(mResourceId, position, convertView, parent);
        }

        @Override
        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            return getViewForResource(mDropDownResourceId, position, convertView, parent);
        }

        public View getViewForResource(int resourceId, int position, View convertView, ViewGroup parent) {
            final ViewDataBinding binding = (convertView != null)
                    ? DataBindingUtil.getBinding(convertView)
                    : DataBindingUtil.inflate(mLayoutInflater, resourceId, parent, false);
            binding.setVariable(BR.viewModel, getItem(position));
            return binding.getRoot();
        }
    }
}

Step 2. declare your own item template like WPF, and you must declare your viewModel data type, because Android always use static type binding, but WPF use dynamic/reflection type binding, make sure a variable viewModel (= DataContext of WPF) declare in data section for convenience

for example: java.io.File

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewModel"
            type="java.io.File" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0px"
            android:layout_height="wrap_content"
            android:layout_weight="0.2"
            android:text='@{viewModel.directory ? "Folder" : "File"}'
            android:textSize="20sp" />

        <TextView
            android:layout_width="0px"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@{viewModel.name}"
            android:textSize="20sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text='@{viewModel.length()+ ""}'
            android:textSize="20sp" />
    </LinearLayout>
</layout>

Step 3. add your item template to your ListView layout file, this example list all the file in the external storage directory

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.os.Environment" />

        <variable
            name="viewModel"
            type="com.mycompany.databindingtest.MainActivity.ViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:animateLayoutChanges="true"
            android:dropDownItemTemplate="@{@layout/file_list_item_template}"
            android:itemTemplate="@{@layout/file_list_item_template}"
            android:items="@{Environment.externalStorageDirectory.listFiles()}" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

full source code: sample3

Upvotes: 0

Ayush Khare
Ayush Khare

Reputation: 1842

Things in Android are different

Is it possible to bind items to a list view without any code in the background?

You have to use data binding code within the adapter class for the list view.

Can i define a "list view item" within the layout of a listview?

No you cannot! A list view item must have a layout of its own.

Since you have confusion about adapters, here are a few points that can make it more clear:

Think of an adapter as a manager that manages the data model and adapts it to the individual entries of the list view. The adapter will populate the layout for each row and assign the data to the individual view in the row.

Without data binding, adapter class can contain a lot code depending on how complex your row UI is. So using data binding will help to remove all the unnecessary code from your adapter class with just a few lines of binding code.

The link you posted is good enough to get started, but I would suggest using a Recycler view instead. Here are other links that you can look at

  1. https://medium.com/google-developers/android-data-binding-recyclerview-db7c40d9f0e4
  2. https://android.jlelse.eu/recyclerview-with-endlessscroll-2c503008522f

Upvotes: 1

Related Questions