Sideband/sbapp/kivymd/uix/refreshlayout/refreshlayout.py

330 lines
9.4 KiB
Python
Executable File

"""
Components/RefreshLayout
========================
Example
-------
.. code-block:: python
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.properties import StringProperty
from kivymd.app import MDApp
from kivymd.uix.button import MDIconButton
from kivymd.icon_definitions import md_icons
from kivymd.uix.list import ILeftBodyTouch, OneLineIconListItem
from kivymd.theming import ThemeManager
from kivymd.utils import asynckivy
Builder.load_string('''
<ItemForList>
text: root.text
IconLeftSampleWidget:
icon: root.icon
<Example@MDFloatLayout>
MDBoxLayout:
orientation: 'vertical'
MDTopAppBar:
title: app.title
md_bg_color: app.theme_cls.primary_color
background_palette: 'Primary'
elevation: 4
left_action_items: [['menu', lambda x: x]]
MDScrollViewRefreshLayout:
id: refresh_layout
refresh_callback: app.refresh_callback
root_layout: root
spinner_color: "brown"
circle_color: "white"
MDGridLayout:
id: box
adaptive_height: True
cols: 1
''')
class IconLeftSampleWidget(ILeftBodyTouch, MDIconButton):
pass
class ItemForList(OneLineIconListItem):
icon = StringProperty()
class Example(MDApp):
title = 'Example Refresh Layout'
screen = None
x = 0
y = 15
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
self.screen = Factory.Example()
self.set_list()
return self.screen
def set_list(self):
async def set_list():
names_icons_list = list(md_icons.keys())[self.x:self.y]
for name_icon in names_icons_list:
await asynckivy.sleep(0)
self.screen.ids.box.add_widget(
ItemForList(icon=name_icon, text=name_icon))
asynckivy.start(set_list())
def refresh_callback(self, *args):
'''
A method that updates the state of your application
while the spinner remains on the screen.
'''
def refresh_callback(interval):
self.screen.ids.box.clear_widgets()
if self.x == 0:
self.x, self.y = 15, 30
else:
self.x, self.y = 0, 15
self.set_list()
self.screen.ids.refresh_layout.refresh_done()
self.tick = 0
Clock.schedule_once(refresh_callback, 1)
Example().run()
"""
__all__ = ("MDScrollViewRefreshLayout",)
import os
from typing import Union
from kivy.animation import Animation
from kivy.core.window import Window
from kivy.effects.dampedscroll import DampedScrollEffect
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
ColorProperty,
NumericProperty,
ObjectProperty,
StringProperty,
)
from kivy.uix.floatlayout import FloatLayout
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.scrollview import MDScrollView
with open(
os.path.join(uix_path, "refreshlayout", "refreshlayout.kv"),
encoding="utf-8",
) as kv_file:
Builder.load_string(kv_file.read())
class _RefreshScrollEffect(DampedScrollEffect):
"""
This class is simply based on DampedScrollEffect.
If you need any documentation please look at
:class:`~kivy.effects.dampedscrolleffect`.
"""
min_scroll_to_reload = NumericProperty("-100dp")
"""
Minimum overscroll value to reload.
:attr:`min_scroll_to_reload` is a :class:`~kivy.properties.NumericProperty`
and defaults to `'-100dp'`.
"""
def on_overscroll(
self, instance_refresh_scroll_effect, overscroll: Union[int, float]
) -> bool:
if overscroll < self.min_scroll_to_reload:
scroll_view = self.target_widget.parent
scroll_view._did_overscroll = True
return True
else:
return False
class MDScrollViewRefreshLayout(ThemableBehavior, MDScrollView):
"""
Refresh layout class.
For more information, see in the
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivymd.uix.scrollview.MDScrollView`
class documentation.
"""
root_layout = ObjectProperty()
"""
The spinner will be attached to this layout.
:attr:`root_layout` is a :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
refresh_callback = ObjectProperty()
"""
The method that will be called at the on_touch_up event,
provided that the overscroll of the list has been registered.
:attr:`refresh_callback` is a :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
spinner_color = ColorProperty([1, 1, 1, 1])
"""
Color of the spinner in (r, g, b, a) or string format.
.. versionadded:: 1.2.0
:attr:`spinner_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[1, 1, 1, 1]`.
"""
circle_color = ColorProperty(None)
"""
Color of the ellipse around the spinner in (r, g, b, a) or string format.
.. versionadded:: 1.2.0
:attr:`circle_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
show_transition = StringProperty("out_elastic")
"""
Transition of the spinner's opening.
.. versionadded:: 1.2.0
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_elastic'`.
"""
show_duration = NumericProperty(0.8)
"""
Duration of the spinner display.
.. versionadded:: 1.2.0
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.8`.
"""
hide_transition = StringProperty("out_elastic")
"""
Transition of hiding the spinner.
.. versionadded:: 1.2.0
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_elastic'`.
"""
hide_duration = NumericProperty(0.8)
"""
Duration of hiding the spinner.
.. versionadded:: 1.2.0
:attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.8`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.circle_color:
self.circle_color = self.theme_cls.primary_dark
self.effect_cls = _RefreshScrollEffect
self._work_spinner = False
self._did_overscroll = False
self.refresh_spinner = None
def on_touch_up(self, *args):
if self._did_overscroll and not self._work_spinner:
if self.refresh_callback:
self.refresh_callback()
if not self.refresh_spinner:
self.refresh_spinner = RefreshSpinner(
_refresh_layout=self,
spinner_color=self.spinner_color,
circle_color=self.circle_color,
show_transition=self.show_transition,
show_duration=self.show_duration,
hide_transition=self.hide_transition,
hide_duration=self.hide_duration,
)
self.root_layout.add_widget(self.refresh_spinner)
self.refresh_spinner.start_anim_spinner()
self._work_spinner = True
self._did_overscroll = False
return True
return super().on_touch_up(*args)
def refresh_done(self) -> None:
if self.refresh_spinner:
self.refresh_spinner.hide_anim_spinner()
class RefreshSpinner(ThemableBehavior, FloatLayout):
# Color of the spinner in (r, g, b, a) or string format.
spinner_color = ColorProperty([1, 1, 1, 1])
# Color of the ellipse around the spinner in (r, g, b, a) or string format.
circle_color = ColorProperty()
# Transition of the spinner's opening.
show_transition = StringProperty()
# The duration of the spinner display.
show_duration = NumericProperty(0.8)
# Transition of hiding the spinner.
hide_transition = StringProperty()
# Duration of hiding the spinner.
hide_duration = NumericProperty(0.8)
# kivymd.refreshlayout.MDScrollViewRefreshLayout object
_refresh_layout = ObjectProperty()
def start_anim_spinner(self) -> None:
spinner = self.ids.body_spinner
Animation(
y=spinner.y - self.theme_cls.standard_increment * 2 + dp(10),
d=self.show_duration,
t=self.show_transition,
).start(spinner)
def hide_anim_spinner(self) -> None:
spinner = self.ids.body_spinner
anim = Animation(
y=Window.height, d=self.hide_duration, t=self.hide_transition
)
anim.bind(on_complete=self.set_spinner)
anim.start(spinner)
def set_spinner(self, *args) -> None:
body_spinner = self.ids.body_spinner
body_spinner.size = (dp(46), dp(46))
body_spinner.y = Window.height
body_spinner.opacity = 1
spinner = self.ids.spinner
spinner.size = (dp(30), dp(30))
spinner.opacity = 1
self._refresh_layout._work_spinner = False
self._refresh_layout._did_overscroll = False