Desenvolvimento de aplicativos móveis em Python. Biblioteca KivyMD


Saudações! Hoje falaremos novamente sobre a biblioteca KivyMD - um conjunto de widgets para desenvolvimento de plataforma cruzada em Python no estilo de Design de materiais. Neste artigo, não analisarei os widgets do KivyMD, como em um artigo recente , mas sim sobre o posicionamento de widgets. Algo como um tutorial sobre o desenvolvimento de aplicativos móveis em Python para iniciantes não estará aqui. Portanto, se você ouvir pela primeira vez sobre a estrutura do Kivy, é improvável que esteja interessado em tudo isso. Bem, nós dirigimos sob o corte!

Outro dia, baixei o aplicativo de demonstração Flutter UIKit do Google Play:


E agora vamos tentar repetir uma tela a partir desta aplicação. Vejamos os resultados imediatamente: Flutter à esquerda, Kivy e KivyMD à direita.

Alguns elementos da interface do usuário são diferentes, não devido a alguns recursos técnicos, pelos quais era impossível obter um resultado idêntico, mas eu apenas pensei que seria mais orgânico (por exemplo, a Barra de Ferramentas preta, na minha opinião, não parece nada).

Então! O que é surpreendente ao olhar para a tela que vamos tocar? Layout frontal de fundo transparente. No Kivy, essa opção é fornecida pelo FloatLayout, que permite colocar widgets e controles um acima do outro, da seguinte maneira:


Esquematicamente, nossa tela ficará assim:


O layout desta tela é bastante simples:


Por que estou falando do FloatLayout se nossa tela é herdada da Screen?

<ProductScreen@Screen>: ... 

Só porque Tela -> RelativeLayout -> FloatLayout.

Todos os widgets no FloatLayout são posicionados no canto inferior esquerdo, ou seja, na tela eles recebem a posição automaticamente (0, 0). Na marcação, não é difícil rastrear a ordem de adicionar elementos à tela de cima para baixo:


Se alguém prestou atenção, indicamos a posição para apenas um widget:

 MDToolbar: ... pos_hint: {"top": 1} 

Além das coordenadas específicas (x, y), cada widget no Kivy pode receber uma dica de posição:

 pos_hint: {"top": 1} #    pos_hint: {"bottom": 1} #    pos_hint: {"right": 1} #    pos_hint: {"center_y": .5} #     pos_hint: {"center_x": .2} #   20 %       ... ... 

Então, a imagem de fundo inferior ...

  BoxLayout: size_hint_y: None height: root.height - toolbar.height FitImage: source: "smokestackheather.jpeg" 

... graças ao widget FitImage (biblioteca KivyMD), ele é estendido automaticamente para todo o espaço alocado, mantendo as proporções da imagem:



Por padrão, cada widget e layout no Kivy recebe 100% do espaço, a menos que seja especificado de outra forma. Por exemplo, se você deseja adicionar um botão à tela, obviamente fará o seguinte:

 from kivy.app import App from kivy.lang import Builder KV = """ Button: text: "Button" """ class MyApp(App): def build(self): return Builder.load_string(KV) MyApp().run() 

E obtenha o resultado:


O botão ocupou 100% do espaço. Para colocar um botão no centro da tela, primeiro é necessário fornecer o tamanho necessário e, em segundo lugar, indicar onde será localizado:

 from kivy.app import App from kivy.lang import Builder KV = """ Button: text: "Button" size_hint: None, None size: 100, 50 pos_hint: {"center_y": .5, "center_x": .5} """ class MyApp(App): def build(self): return Builder.load_string(KV) MyApp().run() 

Agora a imagem mudou:


Você também pode especificar a propriedade size_hint , de 0 a 1 (equivalente a 0-100%), ou seja, a dica de tamanho:

 from kivy.app import App from kivy.lang import Builder KV = """ BoxLayout: Button: text: "Button" size_hint_y: .2 Button: text: "Button" size_hint_y: .1 Button: text: "Button" """ class MyApp(App): def build(self): return Builder.load_string(KV) MyApp().run() 


Ou a mesma coisa, mas a dica de largura ( size_hint_x ):

 from kivy.app import App from kivy.lang import Builder KV = """ BoxLayout: Button: text: "Button" size_hint_x: .2 Button: text: "Button" size_hint_x: .1 Button: text: "Button" """ class MyApp(App): def build(self): return Builder.load_string(KV) MyApp().run() 


O MDToolbar tem uma altura de 56dp, não pode ocupar todo o espaço e, se você não informar que o local está no topo, ele ficará automaticamente na parte inferior da tela:


A lista de cartões, OrderProductLayout (falaremos sobre isso a seguir), é um ScrollView com elementos MDCard e ocupa toda a altura da tela, mas graças ao preenchimento (valores de indentação em pontos baixos) parece estar localizado um pouco acima do centro da tela. Bem, por padrão, o MDBottomAppBar lança âncora na borda inferior da tela. Portanto, apenas o MDToolbar indicamos onde está o seu lugar.

Agora vamos ver o que é o widget OrderProductLayout:


Como você pode ver, essas são quatro placas incorporadas no ScrillView. Diferentemente da tela pai, herdada do FloatLayout, aqui todos os widgets são lidos de cima para baixo.


Isso é muito conveniente, pois existe uma hierarquia clara de widgets, uma estrutura em árvore e, à primeira vista, fica claro qual widget / controle pertence a qual layout. No Kivy, o layout usado mais comum é o BoxLayout - uma caixa que permite colocar widgets dentro de si vertical ou horizontalmente (por padrão - o último):


Isso pode ser visto com mais clareza no diagrama a seguir, onde a orientação horizontal BoxLayout é usada:


Proibimos o BoxLayout de usar 100% do espaço - size_hint_y: None e disse - sua altura será exatamente a mesma que a altura do elemento mais alto aninhado em você - height: self.minimum_height .

Lista de Imagens:


Se quiséssemos usar a rolagem vertical da lista, precisaríamos alterar o GridLayout da seguinte maneira:

  ScrollView: GridLayout: size_hint_y: None height: self.minimum_height cols: 1 

Substitua linhas (colunas) por colunas (colunas) e indique no mínimo não a largura, mas a altura:

 from kivy.app import App from kivy.lang import Builder from kivy.metrics import dp from kivy.uix.button import Button KV = """ ScrollView: GridLayout: id: box size_hint_y: None height: self.minimum_height spacing: "5dp" cols: 1 """ class MyApp(App): def build(self): return Builder.load_string(KV) def on_start(self): for i in range(20): self.root.ids.box.add_widget( Button( text=f"Label {i}", size_hint_y=None, height=dp(40), ) ) MyApp().run() 



Os seguintes cartões são a escolha da cor e tamanho (eles são quase idênticos):


Um recurso distintivo da linguagem de marcação da Kv Language não é apenas a estrutura clara dos widgets, mas também o fato de que essa linguagem suporta alguns recursos da linguagem Python. A saber: chamar métodos, criar / alterar variáveis, operações lógicas, E / S e matemáticas ...


Calculando o valor declarado no Label ...

  Label: value: 0 text: str(self.value) 

... acontece diretamente na própria marcação:

  MDIconButton: on_release: label_value.value -= 1 if label_value.value > 0 else 0 

E eu nunca vou acreditar que isso (código Flutter) ...



... mais lógico e legível que o código da linguagem Kv:


Ontem me perguntaram como o Kivy está indo com o ambiente de desenvolvimento. Existem autocomplits, hotloads e outras comodidades? Com o preenchimento automático, tudo fica bem se você usar o PyCharm:


Quanto ao hotload ... Python é uma linguagem interpretada. O Kivy usa Python. Portanto, para ver o resultado, você não precisa compilar o código, executá-lo - consulte / teste. Como eu disse, o Kivy não usa APIs nativas para renderizar a interface do usuário; portanto, você pode emular vários modelos de dispositivos e plataformas usando o módulo de tela . É suficiente executar seu projeto com os parâmetros necessários para que a janela do aplicativo testado seja aberta no computador como se estivesse sendo executado em um dispositivo real. Parece estranho, mas como o Kivy abstrai da plataforma na renderização da interface do usuário, isso elimina a necessidade de emuladores pesados ​​e lentos para testes. Isso se aplica apenas à interface do usuário. Por exemplo, o aplicativo de teste descrito neste artigo foi testado com as opções de tela -m: droid2, portrait, scale = .75 .

À esquerda - executando em um dispositivo móvel, à direita - em um computador:

Lista completa de parâmetros do módulo de tela:
 devices = { # device: (name, width, height, dpi, density) 'onex': ('HTC One X', 1280, 720, 312, 2), 'one': ('HTC One', 1920, 1080, 468, 3), 'onesv': ('HTC One SV', 800, 480, 216, 1.5), 's3': ('Galaxy SIII', 1280, 720, 306, 2), 'note2': ('Galaxy Note II', 1280, 720, 267, 2), 'droid2': ('Motorola Droid 2', 854, 480, 240, 1.5), 'xoom': ('Motorola Xoom', 1280, 800, 149, 1), 'ipad': ('iPad (1 and 2)', 1024, 768, 132, 1), 'ipad3': ('iPad 3', 2048, 1536, 264, 2), 'iphone4': ('iPhone 4', 960, 640, 326, 2), 'iphone5': ('iPhone 5', 1136, 640, 326, 2), 'xperiae': ('Xperia E', 480, 320, 166, 1), 'nexus4': ('Nexus 4', 1280, 768, 320, 2), 'nexus7': ('Nexus 7 (2012 version)', 1280, 800, 216, 1.325), 'nexus7.2': ('Nexus 7 (2013 version)', 1920, 1200, 323, 2), # taken from design.google.com/devices # please consider using another data instead of # a dict for autocompletion to work # these are all in landscape 'phone_android_one': ('Android One', 854, 480, 218, 1.5), 'phone_htc_one_m8': ('HTC One M8', 1920, 1080, 432, 3.0), 'phone_htc_one_m9': ('HTC One M9', 1920, 1080, 432, 3.0), 'phone_iphone': ('iPhone', 480, 320, 168, 1.0), 'phone_iphone_4': ('iPhone 4', 960, 640, 320, 2.0), 'phone_iphone_5': ('iPhone 5', 1136, 640, 320, 2.0), 'phone_iphone_6': ('iPhone 6', 1334, 750, 326, 2.0), 'phone_iphone_6_plus': ('iPhone 6 Plus', 1920, 1080, 400, 3.0), 'phone_lg_g2': ('LG G2', 1920, 1080, 432, 3.0), 'phone_lg_g3': ('LG G3', 2560, 1440, 533, 3.0), 'phone_moto_g': ('Moto G', 1280, 720, 327, 2.0), 'phone_moto_x': ('Moto X', 1280, 720, 313, 2.0), 'phone_moto_x_2nd_gen': ('Moto X 2nd Gen', 1920, 1080, 432, 3.0), 'phone_nexus_4': ('Nexus 4', 1280, 768, 240, 2.0), 'phone_nexus_5': ('Nexus 5', 1920, 1080, 450, 3.0), 'phone_nexus_5x': ('Nexus 5X', 1920, 1080, 432, 2.6), 'phone_nexus_6': ('Nexus 6', 2560, 1440, 496, 3.5), 'phone_nexus_6p': ('Nexus 6P', 2560, 1440, 514, 3.5), 'phone_samsung_galaxy_note_4': ('Samsung Galaxy Note 4', 2560, 1440, 514, 3.0), 'phone_samsung_galaxy_s5': ('Samsung Galaxy S5', 1920, 1080, 372, 3.0), 'phone_samsung_galaxy_s6': ('Samsung Galaxy S6', 2560, 1440, 576, 4.0), 'phone_sony_xperia_c4': ('Sony Xperia C4', 1920, 1080, 400, 2.0), 'phone_sony_xperia_z_ultra': ('Sony Xperia Z Ultra', 1920, 1080, 348, 2.0), 'phone_sony_xperia_z1_compact': ('Sony Xperia Z1 Compact', 1280, 720, 342, 2.0), 'phone_sony_xperia_z2z3': ('Sony Xperia Z2/Z3', 1920, 1080, 432, 3.0), 'phone_sony_xperia_z3_compact': ('Sony Xperia Z3 Compact', 1280, 720, 313, 2.0), 'tablet_dell_venue_8': ('Dell Venue 8', 2560, 1600, 355, 2.0), 'tablet_ipad': ('iPad', 1024, 768, 132, 1.0), 'tablet_ipad_mini': ('iPad Mini', 1024, 768, 163, 1.0), 'tablet_ipad_mini_retina': ('iPad Mini Retina', 2048, 1536, 326, 2.0), 'tablet_ipad_pro': ('iPad Pro', 2732, 2048, 265, 2.0), 'tablet_ipad_retina': ('iPad Retina', 2048, 1536, 264, 2.0), 'tablet_nexus_10': ('Nexus 10', 2560, 1600, 297, 2.0), 'tablet_nexus_7_12': ('Nexus 7 12', 1280, 800, 216, 1.3), 'tablet_nexus_7_13': ('Nexus 7 13', 1920, 1200, 324, 2.0), 'tablet_nexus_9': ('Nexus 9', 2048, 1536, 288, 2.0), 'tablet_samsung_galaxy_tab_10': ('Samsung Galaxy Tab 10', 1280, 800, 148, 1.0), 'tablet_sony_xperia_z3_tablet': ('Sony Xperia Z3 Tablet', 1920, 1200, 282, 2.0), 'tablet_sony_xperia_z4_tablet': ('Sony Xperia Z4 Tablet', 2560, 1600, 297, 2.0)TodoList() app.run() } 


Bem, e finalmente, o resultado final é o lançamento em um dispositivo móvel ...


A única coisa que incomoda é a velocidade de lançamento. No mesmo Flutter, ela é simplesmente fenomenal!

Espero ter sido útil para alguém, até breve!

Source: https://habr.com/ru/post/pt480018/


All Articles