"""
Copyright (c) 2017-2022, CodeLV.
Distributed under the terms of the MIT License.
The full license is in the file LICENSE, distributed with this software.
Created on May 20, 2017
"""
from atom.api import Dict, Instance, Property, Typed, observe
from enamlnative.widgets.list_view import ProxyListItem, ProxyListView
from enamlnative.core.bridge import encode
from .android_content import Context
from .android_toolkit_object import AndroidToolkitObject
from .android_view_group import AndroidViewGroup, ViewGroup
from .bridge import JavaBridgeObject, JavaCallback, JavaMethod
package = "androidx.recyclerview.widget"
class RecylerView(ViewGroup):
__nativeclass__ = f"{package}.RecyclerView"
invalidate = JavaMethod()
setHasFixedSize = JavaMethod(bool)
scrollTo = JavaMethod(int, int)
scrollToPosition = JavaMethod(int)
setItemViewCacheSize = JavaMethod(int)
setAdapter = JavaMethod(f"{package}.RecyclerView$Adapter")
setHasFixedSize = JavaMethod(bool)
setLayoutManager = JavaMethod(f"{package}.RecyclerView$LayoutManager")
setRecyclerListener = JavaMethod(f"{package}.RecyclerView$RecyclerListener")
class LayoutManager(JavaBridgeObject):
__nativeclass__ = f"{package}.RecyclerView$LayoutManager"
scrollToPosition = JavaMethod(int)
setItemPrefetchEnabled = JavaMethod(bool)
HORIZONTAL = 0
VERTICAL = 1
class StaggeredLayoutManager(RecylerView.LayoutManager):
__nativeclass__ = f"{package}.StaggeredLayoutManager"
__signature__ = [int, int]
setOrientation = JavaMethod(int)
setSpanCount = JavaMethod(int)
class LinearLayoutManager(RecylerView.LayoutManager):
__nativeclass__ = f"{package}.LinearLayoutManager"
__signature__ = [Context, int, bool]
scrollToPositionWithOffset = JavaMethod(int, int)
setInitialPrefetchItemCount = JavaMethod(int)
setOrientation = JavaMethod(int)
setRecycleChildrenOnDetach = JavaMethod(bool)
setReverseLayout = JavaMethod(bool)
setSmoothScrollbarEnabled = JavaMethod(bool)
setStackFromEnd = JavaMethod(bool)
class GridLayoutManager(LinearLayoutManager):
__nativeclass__ = f"{package}.GridLayoutManager"
__signature__ = [Context, int, int, bool]
setSpanCount = JavaMethod(int)
# class BridgedListAdapter(JavaBridgeObject):
# """ An adapter that implements a recycleview pattern.
#
# """
# __nativeclass__ = 'com.codelv.enamlnative.adapters.BridgedListAdapter'
# setListView = JavaMethod('android.widget.ListView',
# 'com.codelv.enamlnative.adapters.'
# 'BridgedListAdapter$BridgedListAdapterListener')
# setCount = JavaMethod('int')
# setRecycleViews = JavaMethod('[Landroid.view.View;')
# clearRecycleViews = JavaMethod()
#
# #: BridgedListAdapterListener API
# onRecycleView = JavaCallback('int', 'int', 'int')
# onVisibleCountChanged = JavaCallback('int','int')
# onScrollStateChanged = JavaCallback('android.widget.AbsListView','int')
class BridgedRecyclerAdapter(JavaBridgeObject):
"""An adapter that implements a recycleview pattern."""
__nativeclass__ = "com.codelv.enamlnative.adapters.BridgedRecyclerAdapter"
__signature__ = [f"{package}.RecyclerView"]
setRecyleListener = JavaMethod(
"com.codelv.enamlnative.adapters.BridgedRecyclerAdapter"
"$BridgedListAdapterListener"
)
setItemCount = JavaMethod(int)
setRecycleViews = JavaMethod("[Landroid.view.View;")
clearRecycleViews = JavaMethod()
#: BridgedListAdapterListener API
onRecycleView = JavaCallback(int, int)
onVisibleCountChanged = JavaCallback(int, int)
onScrollStateChanged = JavaCallback("android.widget.AbsListView", int)
notifyDataSetChanged = JavaMethod()
notifyItemChanged = JavaMethod(int)
notifyItemInserted = JavaMethod(int)
notifyItemRemoved = JavaMethod(int)
notifyItemRangeChanged = JavaMethod(int, int)
notifyItemRangeInserted = JavaMethod(int, int)
notifyItemRangeRemoved = JavaMethod(int, int)
[docs]class AndroidListView(AndroidViewGroup, ProxyListView):
"""An Android implementation of an Enaml ProxyListView."""
#: A reference to the widget created by the proxy.
widget = Typed(RecylerView)
#: Reference to adapter
adapter = Typed(BridgedRecyclerAdapter)
#: Layout manager
layout_manager = Instance(RecylerView.LayoutManager)
def _get_list_items(self):
return [c for c in self.children() if isinstance(c, AndroidListItem)]
#: List items
list_items = Property(cached=True)
#: List mapping from index to view
item_mapping = Dict()
# -------------------------------------------------------------------------
# Initialization API
# -------------------------------------------------------------------------
# w = self.widget
# w.setOnItemClickListener(w.getId())
# w.setOnItemLongClickListener(w.getId())
# w.onItemClick.connect(self.on_item_click)
# w.onItemLongClick.connect(self.on_item_long_click)
# self.widget.setOnScrollListener(self.widget.getId())
# self.widget.onScroll.connect(self.on_scroll)
#: Selection listener
# self.widget.setOnItemSelectedListener(self.widget.getId())
# self.widget.onItemSelected.connect(self.on_item_selected)
# self.widget.onNothingSelected.connect(self.on_nothing_selected)
[docs] def get_declared_items(self):
"""Override to do it manually"""
for k, v in super().get_declared_items():
if k == "layout":
yield k, v
break
[docs] def init_layout(self):
"""Initialize the underlying widget."""
super().init_layout()
d = self.declaration
w = self.widget
# Prepare adapter
adapter = self.adapter = BridgedRecyclerAdapter(w)
# I'm sure this will make someone upset haha
adapter.setRecyleListener(adapter.getId())
adapter.onRecycleView.connect(self.on_recycle_view)
# adapter.onVisibleCountChanged.connect(self.on_visible_count_changed)
# adapter.onScrollStateChanged.connect(self.on_scroll_state_changed)
self.set_items(d.items)
w.setAdapter(adapter)
# self.set_selected(d.selected)
self.refresh_views()
# -------------------------------------------------------------------------
# BridgedListAdapterListener API
# -------------------------------------------------------------------------
[docs] def on_recycle_view(self, index, position):
"""Update the item the view at the given index should display"""
item = self.list_items[index]
self.item_mapping[position] = item
item.recycle_view(position)
def on_scroll_state_changed(self, view, state):
pass
# -------------------------------------------------------------------------
# ProxyListView API
# -------------------------------------------------------------------------
[docs] def refresh_views(self, change=None):
"""Set the views that the adapter will cycle through."""
adapter = self.adapter
# Set initial ListItem state
item_mapping = self.item_mapping
for i, item in enumerate(self.list_items):
item_mapping[i] = item
item.recycle_view(i)
if adapter:
adapter.clearRecycleViews()
adapter.setRecycleViews([encode(li.get_view()) for li in self.list_items])
def set_items(self, items):
adapter = self.adapter
adapter.setItemCount(len(items))
adapter.notifyDataSetChanged()
@observe("declaration.items")
def _on_items_changed(self, change):
"""Observe container events on the items list and update the
adapter appropriately.
"""
if change["type"] != "container":
return
op = change["operation"]
if op == "append":
i = len(change["value"]) - 1
self.adapter.notifyItemInserted(i)
elif op == "insert":
self.adapter.notifyItemInserted(change["index"])
elif op in ("pop", "__delitem__"):
self.adapter.notifyItemRemoved(change["index"])
elif op == "__setitem__":
self.adapter.notifyItemChanged(change["index"])
elif op == "extend":
n = len(change["items"])
i = len(change["value"]) - n
self.adapter.notifyItemRangeInserted(i, n)
elif op in ("remove", "reverse", "sort"):
# Reset everything for these
self.adapter.notifyDataSetChanged()
def set_arrangement(self, arrangement):
ctx = self.get_context()
d = self.declaration
reverse = False
orientation = (
LinearLayoutManager.VERTICAL
if d.orientation == "vertical"
else LinearLayoutManager.HORIZONTAL
)
if arrangement == "linear":
manager = LinearLayoutManager(ctx, orientation, reverse)
elif arrangement == "grid":
manager = GridLayoutManager(ctx, d.span_count, orientation, reverse)
elif arrangement == "staggered":
manager = StaggeredLayoutManager(d.span_count, orientation)
self.layout_manager = manager
self.widget.setLayoutManager(manager)
def set_span_count(self, count):
if not self.layout_manager:
return
self.layout_manager.setSpanCount(count)
def set_orientation(self, orientation):
if not self.layout_manager:
return
orientation = (
LinearLayoutManager.VERTICAL
if orientation == "vertical"
else LinearLayoutManager.HORIZONTAL
)
self.layout_manager.setOrientation(orientation)
def set_selected(self, index):
self.widget.setSelection(index)
def scroll_to(self, x, y):
self.widget.scrollTo(x, y)
def scroll_to_position(self, position):
self.widget.scrollToPosition(position)
[docs]class AndroidListItem(AndroidToolkitObject, ProxyListItem):
# -------------------------------------------------------------------------
# Initialization API
# -------------------------------------------------------------------------
[docs] def init_layout(self):
"""The list item has no widget, it's a placeholder."""
pass
# -------------------------------------------------------------------------
# ListAdapter API
# -------------------------------------------------------------------------
[docs] def recycle_view(self, position):
"""Tell the view to render the item at the given position"""
d = self.declaration
if position < len(d.parent.items):
d.index = position
d.item = d.parent.items[position]
else:
d.index = -1
d.item = None
[docs] def get_view(self):
"""Return the view for this item (first child widget)"""
for w in self.child_widgets():
return w