kivymd / KivyMD

KivyMD is a collection of Material Design compliant widgets for use with Kivy, a framework for cross-platform, touch-enabled graphical applications. https://youtube.com/c/KivyMD https://twitter.com/KivyMD https://habr.com/ru/users/kivymd https://stackoverflow.com/tags/kivymd
https://kivymd.readthedocs.io
MIT License
2.22k stars 670 forks source link

Other ExpansionPanel's bugs. Panel shakes #616

Closed recordit closed 3 years ago

recordit commented 3 years ago

Description of the Bug

1) When i close and open panel, it shakes. And other part of screen that located in panel's container(for our example, that's Scrollview) also shakes. Video: https://yadi.sk/i/gIZp_sk4CnG7hw Code:

from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelThreeLine
from kivymd.uix.button import MDRaisedButton

KV = '''
<Content>
    adaptive_height: True

ScrollView:

    MDGridLayout:
        id: box
        cols: 1
        adaptive_height: True
'''

class Content(MDBoxLayout):
    '''Custom content.'''

class Test(MDApp):
    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        self.root.ids.box.add_widget(MDRaisedButton(text='bug'))
        self.root.ids.box.add_widget(MDRaisedButton(text='bug'))
         # even if add after second button some space(for ex  self.root.ids.box.add_widget(Widget(size_hint_y=None, height=200))), # whole buttons will anyway shake
        self.root.ids.box.add_widget(MDRaisedButton(text='bug'))

        self.root.ids.box.add_widget(
            MDExpansionPanel(
                icon=f"devices",
                content=Content(),
                panel_cls=MDExpansionPanelThreeLine(
                    text="Text",
                    secondary_text="Secondary text",
                    tertiary_text="Tertiary text",
                )
            )
        )

Test().run()

2) When panel is open and i add any item to the panel, panel's container(for our example that's ScrollView) "jumps" to the bottom. Video: https://yadi.sk/i/Eksswx426d_AOQ

from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelThreeLine
from kivymd.uix.list import TwoLineIconListItem

KV = '''
<Content>
    adaptive_height: True
    orientation: 'vertical'

<Item>:
    text: "(050)-123-45-67"
    secondary_text: "Mobile"

    IconLeftWidget:
        icon: 'phone'
Screen:
    BoxLayout:
        orientation: 'vertical'
        MDRaisedButton:
            text: 'add_into_panel'
            pos_hint: {'center_y':.5, 'center_x':.5}
            on_release: app.add()
        Widget:
            size_hint_y: None 
            height: 100

        ScrollView:

            MDGridLayout:
                id: box
                cols: 1
                adaptive_height: True
'''

class Item(TwoLineIconListItem):
    pass

class Content(MDBoxLayout):
    '''Custom content.'''

class Test(MDApp):
    def build(self):
        return Builder.load_string(KV)

    def add(self):
        item = Item()
        self.panel.content.add_widget(item)
        if self.panel_is_open and len(self.panel.content.children) > 1:
            self.panel.height += item.height
        elif self.panel_is_open and len(self.panel.content.children) == 1:
            self.panel.height -= (self.panel.height - item.height) - self.panel.panel_cls.height

    def on_start(self):
        self.panel = MDExpansionPanel(
            icon=f"youtube",
            content=Content(),
            panel_cls=MDExpansionPanelThreeLine(
                text="Text",
                secondary_text="Secondary text",
                tertiary_text="Tertiary text",
            )
        )
        self.panel_is_open = False
        self.panel.bind(on_open=self.panel_open, on_close=self.panel_close)
        self.root.ids.box.add_widget(self.panel)
    def panel_open(self, *args):
        self.panel_is_open = True
    def panel_close(self, *args):
        self.panel_is_open = False

Test().run()

Versions

allexeyj commented 3 years ago

@HeaTTheatR Is the second part of my report also bug?

HeaTTheatR commented 3 years ago

@alexcode4u No.

HeaTTheatR commented 3 years ago

The problem is this:

If the GridLayout's height is set to minimum_height, then the shakes effect cannot be avoided if all the elements fit on the screen and cannot be scrolled:

Shakes effect

from kivy.animation import Animation
from kivy.lang import Builder
from kivy.app import App

KV = '''
ScrollView:

    GridLayout:
        id: box
        cols: 1
        size_hint_y: None
        height: self.minimum_height

        Button:
            text: "TEXT"
            size_hint_y: None
            height: 100
            on_press: app.anim()

        Button:
            id: button
            text: "TEXT"
            size_hint_y: None
            height: 100
'''

class Test(App):
    def build(self):
        self.flag = 0
        return Builder.load_string(KV)

    def anim(self):
        if not self.flag:
            self.flag = True
            Animation(height=200, d=0.2).start(self.root.ids.button)
        else:
            self.flag = False
            Animation(height=100, d=0.2).start(self.root.ids.button)

Test().run()

The automatic calculation of the GridLayout's height results in the shakes effect. This effect can be avoided if you know for sure that there will be one or two elements in the GridLayout, and they all fit on the screen. In this case, you shouldn't use the minimum_height for the GridLayout. There is no shakes effect if the GridLayout is set to minimum_height and items can be scrolled.

Without shakes effect

from kivy.animation import Animation
from kivy.lang import Builder
from kivy.app import App

KV = '''
ScrollView:

    GridLayout:
        id: box
        cols: 1

        Button:
            text: "TEXT"
            size_hint_y: None
            height: 100
            on_press: app.anim()

        Button:
            id: button
            text: "TEXT"
            size_hint_y: None
            height: 100
'''

class Test(App):
    def build(self):
        self.flag = 0
        return Builder.load_string(KV)

    def anim(self):
        if not self.flag:
            self.flag = True
            Animation(height=200, d=0.2).start(self.root.ids.button)
        else:
            self.flag = False
            Animation(height=100, d=0.2).start(self.root.ids.button)

Test().run()

The analysis of the problem has been completed, the reasons have been identified, so I believe that the problem can be closed.