330 lines
9.4 KiB
Python
330 lines
9.4 KiB
Python
|
"""
|
||
|
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
|