diff options
author | Samuel Holland <samuel@sholland.org> | 2017-08-22 21:49:55 -0500 |
---|---|---|
committer | Samuel Holland <samuel@sholland.org> | 2017-08-22 23:10:35 -0500 |
commit | 353028420b92774d9a6ccdfe9318481de00b17e7 (patch) | |
tree | f29a4a2b8a89053b41759f0496da7dca60c92b84 /app/src/main/java/com | |
parent | fb919a72262b43ffa3d93239afa6fb1621341f28 (diff) |
Add an adapter for binding an ObservableList to a LinearLayout
EditTexts do not work in ListViews.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'app/src/main/java/com')
4 files changed, 165 insertions, 14 deletions
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 <T> void listBinding(final ListView view, - final ObservableList<T> oldList, final int oldLayoutId, - final ObservableList<T> newList, final int newLayoutId) { - // Remove any existing binding when there is no new list. - if (newList == null) { + public static <T> void setItems(final LinearLayout view, + final ObservableList<T> oldList, final int oldLayoutId, + final ObservableList<T> newList, final int newLayoutId) { + if (oldList == newList && oldLayoutId == newLayoutId) + return; + ItemChangeListener<T> 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 <T> void setItems(final ListView view, + final ObservableList<T> oldList, final int oldLayoutId, + final ObservableList<T> 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 <K extends Comparable<K>, V> void sortedMapBinding( - final ListView view, final ObservableSortedMap<K, V> oldMap, final int oldLayoutId, + public static <K extends Comparable<K>, V> void setItems( + final ListView view, + final ObservableSortedMap<K, V> oldMap, final int oldLayoutId, final ObservableSortedMap<K, V> 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<T> { + private final OnListChangedCallback<T> callback = new OnListChangedCallback<>(this); + private final ViewGroup container; + private final int layoutId; + private final LayoutInflater layoutInflater; + private ObservableList<T> 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<T> 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<T> + extends ObservableList.OnListChangedCallback<ObservableList<T>> { + + private final WeakReference<ItemChangeListener<T>> weakListener; + + private OnListChangedCallback(final ItemChangeListener<T> listener) { + weakListener = new WeakReference<>(listener); + } + + @Override + public void onChanged(final ObservableList<T> sender) { + final ItemChangeListener<T> 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<T> sender, final int positionStart, + final int itemCount) { + final ItemChangeListener<T> 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<T> sender, final int positionStart, + final int itemCount) { + final ItemChangeListener<T> 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<T> sender, final int fromPosition, + final int toPosition, final int itemCount) { + final ItemChangeListener<T> 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<T> sender, final int positionStart, + final int itemCount) { + final ItemChangeListener<T> 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<T> extends BaseAdapter implements ListAdapter { + private final OnListChangedCallback<T> callback = new OnListChangedCallback<>(this); private final int layoutId; private final LayoutInflater layoutInflater; private ObservableList<T> list; - private final OnListChangedCallback<T> callback = new OnListChangedCallback<>(this); ObservableListAdapter(final Context context, final int layoutId, final ObservableList<T> 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<K extends Comparable<K>, V> extends BaseAdapter implements ListAdapter { + private final OnMapChangedCallback<K, V> callback = new OnMapChangedCallback<>(this); private ArrayList<K> keys; private final int layoutId; private final LayoutInflater layoutInflater; private ObservableSortedMap<K, V> map; - private final OnMapChangedCallback<K, V> callback = new OnMapChangedCallback<>(this); ObservableMapAdapter(final Context context, final int layoutId, final ObservableSortedMap<K, V> map) { - layoutInflater = LayoutInflater.from(context); this.layoutId = layoutId; + layoutInflater = LayoutInflater.from(context); setMap(map); } |