From 353028420b92774d9a6ccdfe9318481de00b17e7 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Tue, 22 Aug 2017 21:49:55 -0500 Subject: [PATCH] Add an adapter for binding an ObservableList to a LinearLayout EditTexts do not work in ListViews. Signed-off-by: Jason A. Donenfeld --- .../wireguard/android/BindingAdapters.java | 45 +++++-- .../wireguard/android/ItemChangeListener.java | 126 ++++++++++++++++++ .../android/ObservableListAdapter.java | 4 +- .../android/ObservableMapAdapter.java | 4 +- app/src/main/res/values/ids.xml | 4 + 5 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/wireguard/android/ItemChangeListener.java create mode 100644 app/src/main/res/values/ids.xml diff --git a/app/src/main/java/com/wireguard/android/BindingAdapters.java b/app/src/main/java/com/wireguard/android/BindingAdapters.java index ec465930..d5c07ed6 100644 --- a/app/src/main/java/com/wireguard/android/BindingAdapters.java +++ b/app/src/main/java/com/wireguard/android/BindingAdapters.java @@ -1,10 +1,11 @@ package com.wireguard.android; import android.databinding.BindingAdapter; -import android.databinding.ObservableArrayMap; import android.databinding.ObservableList; +import android.databinding.adapters.ListenerUtil; import android.graphics.Typeface; import android.text.InputFilter; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; @@ -15,11 +16,34 @@ import android.widget.TextView; @SuppressWarnings("unused") public final class BindingAdapters { @BindingAdapter({"items", "layout"}) - public static void listBinding(final ListView view, - final ObservableList oldList, final int oldLayoutId, - final ObservableList newList, final int newLayoutId) { - // Remove any existing binding when there is no new list. - if (newList == null) { + public static void setItems(final LinearLayout view, + final ObservableList oldList, final int oldLayoutId, + final ObservableList newList, final int newLayoutId) { + if (oldList == newList && oldLayoutId == newLayoutId) + return; + ItemChangeListener listener = ListenerUtil.getListener(view, R.id.item_change_listener); + // If the layout changes, any existing listener must be replaced. + if (listener != null && oldList != null && oldLayoutId != newLayoutId) { + listener.setList(null); + listener = null; + } + // Avoid adding a listener when there is no new list or layout. + if (newList == null || newLayoutId == 0) + return; + if (listener == null) { + listener = new ItemChangeListener<>(view, newLayoutId); + ListenerUtil.trackListener(view, listener, R.id.item_change_listener); + } + // Either the list changed, or this is an entirely new listener because the layout changed. + listener.setList(newList); + } + + @BindingAdapter({"items", "layout"}) + public static void setItems(final ListView view, + final ObservableList oldList, final int oldLayoutId, + final ObservableList newList, final int newLayoutId) { + // Remove any existing binding when there is no new list or layout. + if (newList == null || newLayoutId == 0) { view.setAdapter(null); return; } @@ -39,11 +63,12 @@ public final class BindingAdapters { } @BindingAdapter({"items", "layout"}) - public static , V> void sortedMapBinding( - final ListView view, final ObservableSortedMap oldMap, final int oldLayoutId, + public static , V> void setItems( + final ListView view, + final ObservableSortedMap oldMap, final int oldLayoutId, final ObservableSortedMap newMap, final int newLayoutId) { - // Remove any existing binding when there is no new map. - if (newMap == null) { + // Remove any existing binding when there is no new map or layout. + if (newMap == null || newLayoutId == 0) { view.setAdapter(null); return; } diff --git a/app/src/main/java/com/wireguard/android/ItemChangeListener.java b/app/src/main/java/com/wireguard/android/ItemChangeListener.java new file mode 100644 index 00000000..e3f34019 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ItemChangeListener.java @@ -0,0 +1,126 @@ +package com.wireguard.android; + +import android.databinding.DataBindingUtil; +import android.databinding.ObservableList; +import android.databinding.ViewDataBinding; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.lang.ref.WeakReference; + +/** + * Helper class for binding an ObservableList to the children of a ViewGroup. + */ + +class ItemChangeListener { + private final OnListChangedCallback callback = new OnListChangedCallback<>(this); + private final ViewGroup container; + private final int layoutId; + private final LayoutInflater layoutInflater; + private ObservableList list; + + ItemChangeListener(final ViewGroup container, final int layoutId) { + this.container = container; + this.layoutId = layoutId; + layoutInflater = LayoutInflater.from(container.getContext()); + } + + private View getView(final int position, final View convertView) { + ViewDataBinding binding = DataBindingUtil.getBinding(convertView); + if (binding == null) + binding = DataBindingUtil.inflate(layoutInflater, layoutId, container, false); + binding.setVariable(BR.item, list.get(position)); + binding.executePendingBindings(); + return binding.getRoot(); + } + + public void setList(final ObservableList newList) { + if (list != null) + list.removeOnListChangedCallback(callback); + list = newList; + if (list != null) { + list.addOnListChangedCallback(callback); + callback.onChanged(list); + } else { + container.removeAllViews(); + } + } + + private static class OnListChangedCallback + extends ObservableList.OnListChangedCallback> { + + private final WeakReference> weakListener; + + private OnListChangedCallback(final ItemChangeListener listener) { + weakListener = new WeakReference<>(listener); + } + + @Override + public void onChanged(final ObservableList sender) { + final ItemChangeListener listener = weakListener.get(); + if (listener != null) { + // TODO: recycle views + listener.container.removeAllViews(); + for (int i = 0; i < sender.size(); ++i) + listener.container.addView(listener.getView(i, null)); + } else { + sender.removeOnListChangedCallback(this); + } + } + + @Override + public void onItemRangeChanged(final ObservableList sender, final int positionStart, + final int itemCount) { + final ItemChangeListener listener = weakListener.get(); + if (listener != null) { + for (int i = positionStart; i < positionStart + itemCount; ++i) { + final View child = listener.container.getChildAt(i); + listener.container.removeViewAt(i); + listener.container.addView(listener.getView(i, child)); + } + } else { + sender.removeOnListChangedCallback(this); + } + } + + @Override + public void onItemRangeInserted(final ObservableList sender, final int positionStart, + final int itemCount) { + final ItemChangeListener listener = weakListener.get(); + if (listener != null) { + for (int i = positionStart; i < positionStart + itemCount; ++i) + listener.container.addView(listener.getView(i, null)); + } else { + sender.removeOnListChangedCallback(this); + } + } + + @Override + public void onItemRangeMoved(final ObservableList sender, final int fromPosition, + final int toPosition, final int itemCount) { + final ItemChangeListener listener = weakListener.get(); + if (listener != null) { + final View[] views = new View[itemCount]; + for (int i = 0; i < itemCount; ++i) + views[i] = listener.container.getChildAt(fromPosition + i); + listener.container.removeViews(fromPosition, itemCount); + for (int i = 0; i < itemCount; ++i) + listener.container.addView(views[i], toPosition + i); + } else { + sender.removeOnListChangedCallback(this); + } + } + + @Override + public void onItemRangeRemoved(final ObservableList sender, final int positionStart, + final int itemCount) { + final ItemChangeListener listener = weakListener.get(); + if (listener != null) { + listener.container.removeViews(positionStart, itemCount); + } else { + sender.removeOnListChangedCallback(this); + } + } + } +} diff --git a/app/src/main/java/com/wireguard/android/ObservableListAdapter.java b/app/src/main/java/com/wireguard/android/ObservableListAdapter.java index c69af4b5..8e240b71 100644 --- a/app/src/main/java/com/wireguard/android/ObservableListAdapter.java +++ b/app/src/main/java/com/wireguard/android/ObservableListAdapter.java @@ -17,14 +17,14 @@ import java.lang.ref.WeakReference; */ class ObservableListAdapter extends BaseAdapter implements ListAdapter { + private final OnListChangedCallback callback = new OnListChangedCallback<>(this); private final int layoutId; private final LayoutInflater layoutInflater; private ObservableList list; - private final OnListChangedCallback callback = new OnListChangedCallback<>(this); ObservableListAdapter(final Context context, final int layoutId, final ObservableList list) { - layoutInflater = LayoutInflater.from(context); this.layoutId = layoutId; + layoutInflater = LayoutInflater.from(context); setList(list); } diff --git a/app/src/main/java/com/wireguard/android/ObservableMapAdapter.java b/app/src/main/java/com/wireguard/android/ObservableMapAdapter.java index 3090ed5e..308c818d 100644 --- a/app/src/main/java/com/wireguard/android/ObservableMapAdapter.java +++ b/app/src/main/java/com/wireguard/android/ObservableMapAdapter.java @@ -19,16 +19,16 @@ import java.util.Collections; */ class ObservableMapAdapter, V> extends BaseAdapter implements ListAdapter { + private final OnMapChangedCallback callback = new OnMapChangedCallback<>(this); private ArrayList keys; private final int layoutId; private final LayoutInflater layoutInflater; private ObservableSortedMap map; - private final OnMapChangedCallback callback = new OnMapChangedCallback<>(this); ObservableMapAdapter(final Context context, final int layoutId, final ObservableSortedMap map) { - layoutInflater = LayoutInflater.from(context); this.layoutId = layoutId; + layoutInflater = LayoutInflater.from(context); setMap(map); } diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 00000000..7f34f808 --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + +