From b530e42850394438fdfb6a84336d95f0cd24e0c2 Mon Sep 17 00:00:00 2001 From: Jeff Pittman Date: Mon, 30 Jul 2012 22:39:15 -0500 Subject: [PATCH] Started writing formal examples for ListView in its doc header, which prompted several clean-ups. ListItemButton and ListItemLabel were added to listview.py, so that various custom ListItem classes made for the examples and tests could be removed. The todo list for this uix-listview branch was moved to a gist. A new adapters/util.py file was added, containing only list_item_args_adapter(). ListAdapter was modified to check for an args_converter argument, and if not present, to use the default one in adapters/util.py. --- examples/widgets/list_cascade.py | 27 +-- examples/widgets/list_disclosure.py | 69 ++----- examples/widgets/list_master_detail.py | 27 +-- examples/widgets/list_multiple_cascade.py | 30 +--- kivy/adapters/adapter.py | 5 + kivy/adapters/util.py | 11 ++ kivy/tests/test_adapters.py | 27 +-- kivy/tests/test_selection.py | 36 +--- kivy/tests/test_uix_listview.py | 32 +--- kivy/uix/listview.py | 208 ++++++++++++++++------ 10 files changed, 205 insertions(+), 267 deletions(-) create mode 100644 kivy/adapters/util.py diff --git a/examples/widgets/list_cascade.py b/examples/widgets/list_cascade.py index 71d9e9ee4..848e5a6f6 100644 --- a/examples/widgets/list_cascade.py +++ b/examples/widgets/list_cascade.py @@ -3,7 +3,7 @@ from kivy.adapters.listadapter import ListAdapter, \ from kivy.uix.gridlayout import GridLayout from kivy.uix.label import Label from kivy.uix.button import Button -from kivy.uix.listview import ListView +from kivy.uix.listview import ListView, ListItemButton from kivy.adapters.mixins.selection import SingleSelectionObserver, \ SelectableItem from kivy.properties import ListProperty, StringProperty, ObjectProperty @@ -18,27 +18,6 @@ from kivy.properties import ListProperty, StringProperty, ObjectProperty # Generic list item will do fine for both list views: -class ListItem(SelectableItem, Button): - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = None - - def __init__(self, **kwargs): - super(ListItem, self).__init__(**kwargs) - - # Set deselected_color to be default Button bg color. - self.deselected_color = self.background_color - - def select(self, *args): - print self.text, 'is now selected' - self.background_color = self.selected_color - - def deselect(self, *args): - print self.text, 'is now unselected' - self.background_color = self.deselected_color - - def __repr__(self): - return self.text - class DetailView(SingleSelectionObserver, GridLayout): fruit_name = StringProperty('') @@ -97,7 +76,7 @@ class CascadingView(GridLayout): args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=False, - cls=ListItem) + cls=ListItemButton) fruit_categories_list_view = \ ListView(adapter=fruit_categories_list_adapter, size_hint=(.2, 1.0)) @@ -113,7 +92,7 @@ class CascadingView(GridLayout): args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=False, - cls=ListItem) + cls=ListItemButton) fruits_list_view = \ ListView(adapter=fruits_list_adapter, size_hint=(.2, 1.0)) diff --git a/examples/widgets/list_disclosure.py b/examples/widgets/list_disclosure.py index e15be2fa4..640636d94 100644 --- a/examples/widgets/list_disclosure.py +++ b/examples/widgets/list_disclosure.py @@ -5,7 +5,7 @@ from kivy.uix.button import Button from kivy.properties import ObjectProperty, \ NumericProperty, ListProperty, \ StringProperty -from kivy.uix.listview import ListView +from kivy.uix.listview import ListView, ListItemButton from kivy.adapters.mixins.selection import SelectionObserver, SelectableItem from kivy.adapters.listadapter import ListAdapter @@ -33,60 +33,17 @@ from kivy.adapters.listadapter import ListAdapter # For the master list, we need to create a custom "list item" type that # subclasses SelectableItem. -# "Sub" to indicate that this class is for "sub" list items -- a list item -# could consist of a button on the left, several labels in the middle, and -# another button on the right. Not sure of the merit of allowing, perhaps, -# some "sub" list items to react to touches and others not, if that were to -# be enabled. - -class ListItemSubButton(SelectableItem, Button): - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = None - - def __init__(self, **kwargs): - super(ListItemSubButton, self).__init__(**kwargs) - - # Set deselected_color to be default Button bg color. - self.deselected_color = self.background_color - - # [TODO] At least there is some action on this set, but - # the color gets somehow composited. - def select(self, *args): - self.background_color = self.selected_color - - # [TODO] No effect seen, but it is grey, so might be happening. - def deselect(self, *args): - self.background_color = self.deselected_color - - def __repr__(self): - return self.text +# A composite list item could consist of a button on the left, several labels +# in the middle, and another button on the right. Not sure of the merit of +# allowing, perhaps, some "sub" list items to react to touches and others not, +# if that were to be enabled. -class ListItemSubLabel(SelectableItem, Label): - # Same idea as "sub" for button above. - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = ListProperty([.33, .33, .33, 1]) - - def __init__(self, **kwargs): - super(ListItemSubLabel, self).__init__(**kwargs) - - # [TODO] Should Label have background_color, like Button, etc.? - # [TODO] Not tested yet. - def select(self, *args): - self.bold = True - - def deselect(self, *args): - self.bold = False - - def __repr__(self): - return self.text - - -class ListItem(SelectableItem, BoxLayout): - # ListItem (BoxLayout) by default uses orientation='horizontal', +class CompositeListItem(SelectableItem, BoxLayout): + # CompositeListItem (BoxLayout) by default uses orientation='horizontal', # but could be used also for a side-to-side display of items. # - # ListItemSubButton sublasses Button, which has background_color. + # ListItemButton sublasses Button, which has background_color. # Here we must add this property. background_color = ListProperty([1, 1, 1, 1]) @@ -97,11 +54,11 @@ class ListItem(SelectableItem, BoxLayout): content_button = ObjectProperty(None) def __init__(self, **kwargs): - super(ListItem, self).__init__(**kwargs) + super(CompositeListItem, self).__init__(**kwargs) # Now this button just has text '>', but it would be neat to make the # left button hold icons -- the list would be heterogeneous, containing - # different ListItem types that could be filtered perhaps (an option + # different list item types that could be filtered perhaps (an option # for selecting all of a given type, for example). # For sub list items, set selection_target to self (this is a kind of @@ -115,10 +72,10 @@ class ListItem(SelectableItem, BoxLayout): icon_kwargs = kwargs.copy() icon_kwargs['text'] = '>' icon_kwargs['size_hint_x'] = .05 - self.icon_button = ListItemSubButton(**icon_kwargs) + self.icon_button = ListItemButton(**icon_kwargs) # Use the passed in kwargs for the "content" button. - self.content_button = ListItemSubButton(**kwargs) + self.content_button = ListItemButton(**kwargs) self.add_widget(self.icon_button) self.add_widget(self.content_button) @@ -197,7 +154,7 @@ class MasterDetailView(GridLayout): args_converter=args_converter, selection_mode='single', allow_empty_selection=False, - cls=ListItem) + cls=CompositeListItem) self.master_list_view = ListView(adapter=self.list_adapter, size_hint=(.3, 1.0)) self.add_widget(self.master_list_view) diff --git a/examples/widgets/list_master_detail.py b/examples/widgets/list_master_detail.py index d914cfce3..0da7283e3 100644 --- a/examples/widgets/list_master_detail.py +++ b/examples/widgets/list_master_detail.py @@ -4,7 +4,7 @@ from kivy.uix.button import Button from kivy.properties import ObjectProperty, \ NumericProperty, ListProperty, \ StringProperty -from kivy.uix.listview import ListView +from kivy.uix.listview import ListView, ListItemButton from kivy.adapters.mixins.selection import SingleSelectionObserver, \ SelectableItem from kivy.adapters.listadapter import ListAdapter @@ -13,29 +13,6 @@ from kivy.adapters.listadapter import ListAdapter # (the master) and another view (detail view) that gets updated upon # selection. -# For the master list, we need to create a custom "list item" type that -# subclasses SelectableItem. - -class ListItem(SelectableItem, Button): - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = None - - def __init__(self, **kwargs): - super(ListItem, self).__init__(**kwargs) - - # Set deselected_color to be default Button bg color. - self.deselected_color = self.background_color - - def select(self, *args): - self.background_color = self.selected_color - - def deselect(self, *args): - self.background_color = self.deselected_color - - def __repr__(self): - return self.text - - # DetailView is an observer of the selection of the associated list view, # so SelectionObserver is mixed in, requiring an override of the # observed_selection_changed() method. @@ -96,7 +73,7 @@ class MasterDetailView(GridLayout): args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=False, - cls=ListItem) + cls=ListItemButton) master_list_view = ListView(adapter=list_adapter, size_hint=(.3, 1.0)) self.add_widget(master_list_view) diff --git a/examples/widgets/list_multiple_cascade.py b/examples/widgets/list_multiple_cascade.py index d986700d6..25f519653 100644 --- a/examples/widgets/list_multiple_cascade.py +++ b/examples/widgets/list_multiple_cascade.py @@ -3,7 +3,7 @@ from kivy.adapters.listadapter import ListAdapter, \ from kivy.uix.gridlayout import GridLayout from kivy.uix.label import Label from kivy.uix.button import Button -from kivy.uix.listview import ListView +from kivy.uix.listview import ListView, ListItemButton from kivy.adapters.mixins.selection import SingleSelectionObserver, \ SelectableItem from kivy.properties import ListProperty, StringProperty, ObjectProperty @@ -13,30 +13,6 @@ from kivy.properties import ListProperty, StringProperty, ObjectProperty # to have one list allow multiple selection and the other to show the # multiple items selected in the first. -# Generic list item will do fine for both list views: - -class ListItem(SelectableItem, Button): - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = None - - def __init__(self, **kwargs): - super(ListItem, self).__init__(**kwargs) - - # Set deselected_color to be default Button bg color. - self.deselected_color = self.background_color - - def select(self, *args): - print self.text, 'is now selected' - self.background_color = self.selected_color - - def deselect(self, *args): - print self.text, 'is now unselected' - self.background_color = self.deselected_color - - def __repr__(self): - return self.text - - class MultipleCascadingView(GridLayout): '''Implementation of a master-detail style view, with a scrollable list of fruits on the left and the selection in that list on the right in @@ -57,7 +33,7 @@ class MultipleCascadingView(GridLayout): args_converter=list_item_args_converter, selection_mode='multiple', allow_empty_selection=False, - cls=ListItem) + cls=ListItemButton) fruits_list_view = \ ListView(adapter=fruits_list_adapter, size_hint=(.2, 1.0)) @@ -72,7 +48,7 @@ class MultipleCascadingView(GridLayout): args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=True, - cls=ListItem) + cls=ListItemButton) selected_fruits_list_view = \ ListView(adapter=selected_fruits_list_adapter, size_hint=(.2, 1.0)) diff --git a/kivy/adapters/adapter.py b/kivy/adapters/adapter.py index c335d600a..7604dd8ac 100644 --- a/kivy/adapters/adapter.py +++ b/kivy/adapters/adapter.py @@ -25,6 +25,7 @@ Notes: from kivy.event import EventDispatcher from kivy.properties import ObjectProperty from kivy.lang import Builder +from kivy.adapters.util import list_item_args_converter class Adapter(EventDispatcher): @@ -37,6 +38,10 @@ class Adapter(EventDispatcher): args_converter = ObjectProperty(None) def __init__(self, **kwargs): + if hasattr(kwargs, 'args_converter'): + self.args_converter = kwargs['args_converter'] + else: + self.args_converter = list_item_args_converter super(Adapter, self).__init__(**kwargs) if self.cls is None and self.template is None: raise Exception('A cls or template must be defined') diff --git a/kivy/adapters/util.py b/kivy/adapters/util.py new file mode 100644 index 000000000..b5ab73a14 --- /dev/null +++ b/kivy/adapters/util.py @@ -0,0 +1,11 @@ +''' +The default list item args converter for list adapters is this simple function +that takes a string and returns the string as the text argument in a dict, +along with two properties suited for simple text items with height of 25. + +[TODO] Might there be other useful converters to put here, with descriptive +names? +''' +list_item_args_converter = lambda x: {'text': x, + 'size_hint_y': None, + 'height': 25} diff --git a/kivy/tests/test_adapters.py b/kivy/tests/test_adapters.py index 734c8fef7..cadb4f030 100644 --- a/kivy/tests/test_adapters.py +++ b/kivy/tests/test_adapters.py @@ -7,6 +7,7 @@ import unittest from kivy.uix.widget import Widget from kivy.uix.button import Button +from kivy.uix.listview import ListView, ListItemButton from kivy.properties import NumericProperty, ListProperty from kivy.adapters.mixins.selection import SelectionObserver, SelectableItem from kivy.adapters.listadapter import ListAdapter @@ -101,26 +102,6 @@ for row in raw_fruit_data: **dict(zip(descriptors_and_units.keys(), row['data']))) -class FruitListItem(SelectableItem, Button): - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = None - - def __init__(self, **kwargs): - super(FruitListItem, self).__init__(**kwargs) - - # Set deselected_color to be default Button bg color. - self.deselected_color = self.background_color - - def select(self, *args): - self.background_color = self.selected_color - - def deselect(self, *args): - self.background_color = self.deselected_color - - def __repr__(self): - return self.text - - class AdaptersTestCase(unittest.TestCase): def setUp(self): @@ -134,9 +115,9 @@ class AdaptersTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='none', allow_empty_selection=True, - cls=FruitListItem) + cls=ListItemButton) - self.assertEqual(list_adapter.cls, FruitListItem) + self.assertEqual(list_adapter.cls, ListItemButton) self.assertEqual(list_adapter.args_converter, self.args_converter) self.assertEqual(list_adapter.template, None) @@ -144,4 +125,4 @@ class AdaptersTestCase(unittest.TestCase): self.assertTrue(isinstance(apple_data_item, str)) apple_view = list_adapter.get_view(0) - self.assertTrue(isinstance(apple_view, FruitListItem)) + self.assertTrue(isinstance(apple_view, ListItemButton)) diff --git a/kivy/tests/test_selection.py b/kivy/tests/test_selection.py index 4112b2a2e..1dc6bc0ab 100644 --- a/kivy/tests/test_selection.py +++ b/kivy/tests/test_selection.py @@ -7,7 +7,7 @@ import unittest from kivy.uix.widget import Widget from kivy.uix.button import Button -from kivy.uix.listview import ListView +from kivy.uix.listview import ListView, ListItemButton from kivy.properties import NumericProperty, ListProperty, StringProperty from kivy.adapters.mixins.selection import SelectionObserver, SelectableItem from kivy.adapters.listadapter import ListAdapter, SelectableListsAdapter, \ @@ -103,26 +103,6 @@ for row in raw_fruit_data: **dict(zip(descriptors_and_units.keys(), row['data']))) -class FruitListItem(SelectableItem, Button): - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = None - - def __init__(self, **kwargs): - super(FruitListItem, self).__init__(**kwargs) - - # Set deselected_color to be default Button bg color. - self.deselected_color = self.background_color - - def select(self, *args): - self.background_color = self.selected_color - - def deselect(self, *args): - self.background_color = self.deselected_color - - def __repr__(self): - return self.text - - class FruitSelectionObserver(SelectionObserver, Widget): fruit_name = StringProperty('') call_count = NumericProperty(0) @@ -147,7 +127,7 @@ class ListAdapterTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='none', allow_empty_selection=True, - cls=FruitListItem) + cls=ListItemButton) self.assertEqual(len(list_adapter.selection), 0) list_adapter.check_for_empty_selection() @@ -158,7 +138,7 @@ class ListAdapterTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='single', allow_empty_selection=True, - cls=FruitListItem) + cls=ListItemButton) self.assertEqual(len(list_adapter.selection), 0) list_adapter.check_for_empty_selection() @@ -173,7 +153,7 @@ class ListAdapterTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='single', allow_empty_selection=False, - cls=FruitListItem) + cls=ListItemButton) self.assertEqual(len(list_adapter.selection), 1) list_adapter.check_for_empty_selection() @@ -184,7 +164,7 @@ class ListAdapterTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='multiple', allow_empty_selection=False, - cls=FruitListItem) + cls=ListItemButton) self.assertEqual(len(list_adapter.selection), 1) list_adapter.handle_selection(list_adapter.get_view(1)) @@ -197,7 +177,7 @@ class ListAdapterTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='single', allow_empty_selection=False, - cls=FruitListItem) + cls=ListItemButton) selection_observer = FruitSelectionObserver( observed_list_adapter=list_adapter) @@ -230,7 +210,7 @@ class SelectableListsAdapterTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='single', allow_empty_selection=False, - cls=FruitListItem) + cls=ListItemButton) fruit_categories_list_view = \ ListView(adapter=fruit_categories_l_a, @@ -251,7 +231,7 @@ class SelectableListsAdapterTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='single', allow_empty_selection=False, - cls=FruitListItem) + cls=ListItemButton) fruits_list_view = ListView(adapter=fruits_l_a, size_hint=(.2, 1.0)) diff --git a/kivy/tests/test_uix_listview.py b/kivy/tests/test_uix_listview.py index 410d907d4..06002e76c 100644 --- a/kivy/tests/test_uix_listview.py +++ b/kivy/tests/test_uix_listview.py @@ -7,7 +7,7 @@ import unittest from kivy.uix.button import Button from kivy.properties import ListProperty, StringProperty -from kivy.uix.listview import ListView +from kivy.uix.listview import ListView, ListItemButton from kivy.adapters.mixins.selection import SelectionObserver, SelectableItem from kivy.adapters.listadapter import ListAdapter @@ -104,32 +104,6 @@ for row in raw_fruit_data: **dict(zip(descriptors_and_units.keys(), row['data']))) -class FruitListItem(SelectableItem, Button): - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = None - - def __init__(self, list_adapter, **kwargs): - self.list_adapter = list_adapter - super(FruitListItem, self).__init__(**kwargs) - - # Set deselected_color to be default Button bg color. - self.deselected_color = self.background_color - - self.bind(on_release=self.handle_selection) - - def handle_selection(self, button): - self.list_adapter.handle_selection(self) - - def select(self, *args): - self.background_color = self.selected_color - - def deselect(self, *args): - self.background_color = self.deselected_color - - def __repr__(self): - return self.text - - class AdaptersTestCase(unittest.TestCase): def setUp(self): @@ -143,7 +117,7 @@ class AdaptersTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='none', allow_empty_selection=True, - cls=FruitListItem) + cls=ListItemButton) self.assertEqual(len(list_adapter.selection), 0) list_adapter.check_for_empty_selection() @@ -161,7 +135,7 @@ class AdaptersTestCase(unittest.TestCase): args_converter=self.args_converter, selection_mode='multiple', allow_empty_selection=True, - cls=FruitListItem) + cls=ListItemButton) self.assertEqual(len(list_adapter.selection), 0) list_adapter.check_for_empty_selection() diff --git a/kivy/uix/listview.py b/kivy/uix/listview.py index ace9af648..af3cb9054 100644 --- a/kivy/uix/listview.py +++ b/kivy/uix/listview.py @@ -1,3 +1,7 @@ +''' + +''' + ''' List View =========== @@ -6,68 +10,173 @@ List View The :class:`ListView` widget provides a scrollable/pannable viewport that is clipped at the scrollview's bounding box, which contains a list of -item_view_instances. +list item view instances. -[TODO]: +:class:`ListView` implements AbstractView as a vertical scrollable list. +From AbstractView we have these properties and methods: - - Initial selection is apparently working in the associated ListAdapter, - but the list view display does not show the initial selection (red, in - example code). After the list view has been clicked for the first manual - selection, the updating of selected items (in red) works. - - Explain why multiple levels of abstraction are needed. (Adapter, - ListAdapter, AbstractView, ListView) -- Tie discussion to inspiration - for Adapter and related classes: + - adapter (an instance of ListAdapter or one of its subclasses here) - http://developer.android.com/reference/android/\ - widget/Adapter.html#getView(int,%20android/\ - .view.View,%20android.view.ViewGroup) + - item_view_instances, a dict with indices as keys to the list item view + instances created and held in the ListAdapter - - Divider isn't used (yet). - - Consider adding an associated SortableItem mixin, to be used by list - item classes in a manner similar to the SelectableItem mixin. - - Consider a sort_by property. Review the use of the items property. - (Presently items is a list of strings -- are these just the - strings representing the item_view_instances, which are instances of - the provided cls input argument?). If so, formalize and document. - - Work on item_view_instances marked [TODO] in the code. + - set_item_view() and get_item_view() methods to list item view instances - Examples (in examples/widgets): +Basic Example +------------- - - Improve examples: - - Add fruit images. - - Add an example where selection doesn't just change background color - or font, but animates. +The list items you put in a list view can be totally custom, but to have a +simple button, we may use the :class:`ListItem` class. - Other Possibilities: +Here we make a list view with 100 items. - - Consider a horizontally scrolling variant. - - Is it possible to have dynamic item_view height, for use in a - master-detail list view in this manner? + from kivy.adapters.list_adapter import ListAdapter + from kivy.uix.listview import ListItem, ListView - http://www.zkoss.org/zkdemo/grid/master_detail + item_strings = ["Item {0}".format(index) for index in xrange(100)] - (Would this be a new widget called MasterDetailListView, or would the - listview widget having a facility for use in this way?) + # A list view needs a list adapter to provide a mediating service to the + # data, in the case here our item_strings list, passed as the first + # argument. We choose single selection mode. We may also set this for + # allowing multiple selection. We set allow_empty_selection to False so + # that there is always an item selected if at least one is in the list. + # Setting this to true will make the list useful for display only. + # Finally, we pass ListItem as the class (cls) to be instantiated by the + # list adapter for each list item. ListItem is based on Button. When an + # item is selected, its background color will change to red. + list_adapter = ListAdapter(data=item_strings, + selection_mode='single', + allow_empty_selection=False, + cls=ListItem) - (See the list_disclosure.py file as a start.) + # The list view is a simple component. Just give it the list adapter that + # will provide list item views based on its data. + list_view = ListView(adapter=list_adapter) + +Custom Composite Example +------------------------ + +Let's say you would like to make a list view with composite list item views +consisting of a button on the left, a label in the middle, and a second button +on the right. Perhaps the buttons could be made as toggle buttons for two +separate properties pertaining to the label. First, we need to make a custom +list item, which we will base on the required SelectableItem mixin and our +choice of layout, BoxLayout: + + from kivy.adapters.list_adapter import ListAdapter + from kivy.adapters.mixins.selection import SelectableItem + from kivy.uix.listview import ListItemButton, ListItemLabel, ListView + + + class CompositeListItem(SelectableItem, BoxLayout): + # ListItem sublasses Button, which has background_color. + # For a composite list item, we must add this property. + background_color = ListProperty([1, 1, 1, 1]) + + selected_color = ListProperty([1., 0., 0., 1]) + deselected_color = ListProperty([.33, .33, .33, 1]) + + left_button = ObjectProperty(None) + middle_label = ObjectProperty(None) + right_button = ObjectProperty(None) + + def __init__(self, **kwargs): + super(CompositeListItem, self).__init__(**kwargs) + + # For the component list items in our composite list item, we want + # their individual selection to select the composite. Here we pass + # an argument called selection_target, setting it to self (this + # composite list item). + self.left_button = ListItemButton(selection_target=self) + self.middle_label = ListItemLabel(selection_target=self) + self.right_button = ListItemButton(selection_target=self) + + self.add_widget(self.left_button) + self.add_widget(self.middle_label) + self.add_widget(self.right_button) + + def select(self, *args): + self.background_color = self.selected_color + + def deselect(self, *args): + self.background_color = self.deselected_color + + def __repr__(self): + return self.middle_label.text + + # Here we create a list adapter with some item strings, passing our + # CompositeListItem as the list item view class, and then we create a + # list view using this adapter: + item_strings = ["Item {0}".format(index) for index in xrange(100)] + list_adapter = ListAdapter(data=item_strings, + selection_mode='single', + allow_empty_selection=False, + cls=CompositeListItem) + list_view = ListView(adapter=list_adapter) + + +Using With kv +------------- + +[TODO] + +Cascading Selection Between Lists +--------------------------------- + +[TODO] - - Make a separate master-detail example that works like an iphone-style - animated "source list" that has "disclosure" buttons per item_view, on - the right, that when clicked will expand to fill the entire list view - area (useful on mobile devices especially). Similar question as above -- - would listview be given expanded functionality or would this become - another kind of "master-detail" widget?) ''' __all__ = ('ListView', ) from kivy.clock import Clock from kivy.uix.widget import Widget +from kivy.uix.button import Button +from kivy.uix.label import Label +from kivy.adapters.mixins.selection import SelectableItem +from kivy.adapters.util import list_item_args_converter from kivy.uix.abstractview import AbstractView -from kivy.properties import ObjectProperty, DictProperty, NumericProperty +from kivy.properties import ObjectProperty, DictProperty, \ + NumericProperty, ListProperty from kivy.lang import Builder from math import ceil, floor + +class ListItemButton(SelectableItem, Button): + selected_color = ListProperty([1., 0., 0., 1]) + deselected_color = None + + def __init__(self, **kwargs): + super(ListItemButton, self).__init__(**kwargs) + + # Set deselected_color to be default Button bg color. + self.deselected_color = self.background_color + + def select(self, *args): + self.background_color = self.selected_color + + def deselect(self, *args): + self.background_color = self.deselected_color + + def __repr__(self): + return self.text + + +class ListItemLabel(SelectableItem, Label): + + def __init__(self, **kwargs): + super(ListItemLabel, self).__init__(**kwargs) + + def select(self, *args): + self.bold = True + + def deselect(self, *args): + self.bold = False + + def __repr__(self): + return self.text + + Builder.load_string(''' : container: container @@ -83,16 +192,6 @@ Builder.load_string(''' class ListView(AbstractView): - '''Implementation of an Abstract View as a vertical scrollable list. - - From AbstractView we have these properties and methods: - - - adapter (in usage here, a ListAdapter) - - item_view_instances, a dictionary with data item indices as keys - to the item view instances created and held in the ListAdapter - - set_item_view() and get_item_view() methods to item view instances - - ''' divider = ObjectProperty(None) '''[TODO] Not used. @@ -105,10 +204,10 @@ class ListView(AbstractView): container = ObjectProperty(None) '''The container is a GridLayout widget held within a ScrollView widget. (See the associated kv block in the Builder.load_string() setup). Item - view instances managed in the ListAdapter are added to this container. The - container is cleared with a call to contain.clear_widgets() when the list - is rebuilt in the populate() method. A padding Widget instance is also - added as needed, depending on the row height calculations. + view instances managed and provided by the ListAdapter are added to this + container. The container is cleared with a call to clear_widgets() when + the list is rebuilt by the populate() method. A padding Widget instance + is also added as needed, depending on the row height calculations. ''' row_height = NumericProperty(None) @@ -198,7 +297,6 @@ class ListView(AbstractView): continue sizes[index] = item_view.height container.add_widget(item_view) - else: available_height = self.height real_height = 0