fletでドロップダウンコンテナを作ってみた


投稿時点で、fletにはアコーディオン的な、折り畳みが可能なWidget(control)がなかったので、
flutterでいうところの`ExpansionTile`を作ってみた。

```python:dropdownContainer.py
from typing import List, Optional
from flet import *
from flet_core.control import Control, OptionalNumber
from flet_core.types import AnimationValue,BorderRadiusValue,MarginValue,PaddingValue

class DropDownContainer(UserControl):
    def __init__(
            self,
            leading:Optional[Control] = None,
            title:Optional[Control] = None,
            inner_contents:Optional[List[Control]] = None,
            inner_content_height:OptionalNumber = 25,
            action_button_icon:Optional[str] = icons.ARROW_DROP_DOWN_CIRCLE_ROUNDED,
            action_button_icon_size:OptionalNumber = 20,
            height:OptionalNumber = 70,
            width:OptionalNumber = 265,
            padding: PaddingValue = None,
            margin: MarginValue = None,
            bgcolor: Optional[str] = None,
            border: Optional[Border] = None,
            border_radius: BorderRadiusValue = None,
            animate: AnimationValue = animation.Animation(400,"decelerate"),
            disabled: Optional[bool] = None,
            clip_behavior: Optional[ClipBehavior] = ClipBehavior.HARD_EDGE):
        self.leading = leading
        self.title = title
        self.inner_contents = inner_contents
        self.inner_content_height = inner_content_height
        self.inner_contents_height = len(self.inner_contents) * self.inner_content_height
        self.action_button_icon = action_button_icon
        self.action_button_icon_size = action_button_icon_size
        
        self.dd_height = height
        self.dd_width = width
        self.dd_padding = padding
        self.dd_margin = margin
        self.dd_bgcolor = bgcolor
        self.dd_border = border
        self.dd_border_radius = border_radius
        self.dd_animate = animate
        self.dd_disabled = disabled
        self.dd_clip_behavior = clip_behavior
        self.full_width = (width+padding.left+padding.right) if padding is not None else width
        self.full_height = (height+padding.top+padding.bottom) if padding is not None else height
        super().__init__()
        

    def ExpandContainer(self,e):
        openHeight = self.full_height + self.inner_contents_height

        if self.controls[0].height != openHeight:
            self.controls[0].height = openHeight
            self.controls[0].update()
        else:
            self.controls[0].height = self.full_height
            self.controls[0].update()


    def TitleContainer(self):
        return Container(
            width=self.dd_width,
            height=self.dd_height,
            content=Row(
                alignment=MainAxisAlignment.SPACE_BETWEEN,
                vertical_alignment=CrossAxisAlignment.CENTER,
                controls=[
                    self.leading,
                    self.title,
                    IconButton(
                        icon=self.action_button_icon,
                        icon_size=self.action_button_icon_size,
                        on_click=lambda e:self.ExpandContainer(e),
                    )
                ]
            )
        )
    
    def InnerContainer(self):
        return Container(
            width=self.dd_width,
            height=self.inner_contents_height,
            content=Column(
                alignment=MainAxisAlignment.START,
                spacing=0,
                controls=self.inner_contents
            )

        )
    
    def build(self):
        return Container(
            width = self.full_width,
            height = self.full_height,
            bgcolor = self.dd_bgcolor,
            border = self.dd_border,
            border_radius = self.dd_border_radius,
            animate = self.dd_animate,
            disabled = self.disabled,
            padding = self.dd_padding,
            clip_behavior = self.dd_clip_behavior,
            content=Column(
                spacing=0,
                alignment=MainAxisAlignment.START,
                horizontal_alignment=CrossAxisAlignment.CENTER,
                controls=[
                    self.TitleContainer(),
                    self.InnerContainer(),
                ]
            )
        )
```


使用例
```python:myDropdownContainer.py
from flet import *
from dropDownContainer import DropDownContainer

def myDropDownContainer(title:str,sub_title:str = None,inner_contents:list =[]):
    def _getInnerData(inner_contents):
        l = []

        for item in inner_contents:
            l.append(
                Container(
                    padding=padding.only(left=20),
                    content=Row(
                        height=25,
                        spacing=0,
                        controls=[
                            Container(
                                padding=padding.only(right=10),
                                content=Icon(icons.FOLDER,size=14),
                            ),
                            Text(item,size=9,weight="bold",color="white54")
                        ]
                    )
                )
            )
        
        return l
    
    sub_title_widget = Text(sub_title,size=9,color="white54")

    title_container = Container(
            height=70,
            content=Column(
                spacing=1,
                alignment=MainAxisAlignment.CENTER,
                controls=[
                    Text(title,size=11),
                ]
            )
        )
    if sub_title is not None :
        title_container.content.controls.append(sub_title_widget)
    
    return DropDownContainer(
        leading=Icon(icons.FOLDER),
        title=title_container,
        inner_contents=_getInnerData(inner_contents),
        inner_content_height=25,
        bgcolor="white54",
        border_radius=11,
        height=70,
        width=265,
        padding=padding.only(left=10,right=10),
    )
```
```python:main.py
import flet
from flet import *

from myDropDownContainer import myDropDownContainer

def main(page:Page):
    page.title = 'expandingcontainer'
    main_container = Container(
        width=400,
        height=600,
        padding=20,
        content = Column(
            scroll="hidden",
            controls=[
                myDropDownContainer(
                    title='MyDropDownContainer',
                    sub_title="doropdown_container",
                    inner_contents=["hoge_folder1","hoge_folder2","hoge_folder3"]
                    ),
                myDropDownContainer(
                    title='MyDropDownContainer',
                    inner_contents=["hoge_folder1","hoge_folder2","hoge_folder3"]
                    ),
            ]
        )
    )
    page.add(main_container)
    page.update()

if __name__ == '__main__':
    flet.app(target=main)
```

大満足です！