Pull to refresh

Comments 113

Как по мне, то, как строится UI во Flutter — это самое настоящее извращение. Придумать это мог только больной на голову человек.

Видимо, я еще более больной на голову человек, потому что считаю, что система построения UI во флаттере – это едва ли не лучшее решение на рынке: статическая типизация, автодополнения от IDE, все фишки языка + не надо отдельно изучать язык разметки, composition over inheritance. Ну и примеры для флаттера и для киви у Вас все-таки разные.

Даже если не принимать во внимание такие вещи, как размер и цвет шрифта (которые Вы не указываете в примере с киви), код на флаттере можно безболезненно сократить, например, до такого:


import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

main() => runApp(MaterialApp(home: Scaffold(body: Counter())));

class Counter extends StatefulWidget {
  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) => Center(
        child: GestureDetector(
          onTap: () => setState(() => _counter++),
          child: Container(
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: Color(0xFF4eaabe),
            ),
            width: 80.0,
            child: Center(
              child: Text(
                _counter.toString(),
                style: TextStyle(fontSize: 30.0, color: Colors.white),
              ),
            ),
          ),
        ),
      );
}

Мое мнение останется неизменным в любом случае — это невозможно читать!

Ок. Вот только подписывать всех людей с мнением, отличным от Вашего, под больных – так себе занятие.

Это не только мое мнение. Код на Flutter абсолютно не читаем. Умные люди не зря придумали DSL языки разметки UI, а пихать все в код — это удел не совсем умных людей.

Ну, и да — указал размер шрифта:


from kivy.lang import Builder
from kivymd.app import MDApp

KV = """
#:import get_color_from_hex kivy.utils.get_color_from_hex

Screen:
    MDCard:

    MDLabel:
        value: 0
        text: str(self.value)
        halign: "center"
        on_touch_down: self.value += 1
        font_size: "30sp"

        canvas.before:
            Color:
                rgba: get_color_from_hex("#4eaabe")
            Ellipse:
                pos: self.center[0] - dp(25), self.center[1] - dp(25)
                size: dp(50), dp(50)
"""

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

HelloWorld().run()
pos: self.center[0] — dp(25), self.center[1] — dp(25)

Фу, это не про это случайно была статья на хабре про "ошибка в проектировании за которую мне стыдно"


напоминает мне как я был в первом классе и рисовал в "Лого"

глянул на это дело и запахло лиспом :)

Если от скобочек начинает рябить в глазах, то это хороший показатель, что надо выносить код в отдельные виджеты/функции. Этим и хороши разметка в коде и общий принцип composition over inheritance во флаттере – вы просто применяете те же самые принципы, что и в остальном коде. Например (утрированный пример, но сам принцип такой):


Код
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

main() => runApp(MaterialApp(home: Scaffold(body: Counter())));

class Counter extends StatefulWidget {
  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) => Button(
        onTap: () => setState(() => _counter++),
        child: ButtonText(text: _counter.toString()),
      );
}

class Button extends StatelessWidget {
  const Button({Key key, this.onTap, this.child}) : super(key: key);

  final VoidCallback onTap;
  final Widget child;

  @override
  Widget build(BuildContext context) => GestureDetector(
        onTap: onTap,
        child: Container(
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: Color(0xFF4eaabe),
          ),
          width: 80.0,
          child: child,
        ),
      );
}

class ButtonText extends StatelessWidget {
  const ButtonText({Key key, this.text}) : super(key: key);

  final String text;

  @override
  Widget build(BuildContext context) => Center(
        child: Text(
          text,
          style: TextStyle(fontSize: 30.0, color: Colors.white),
        ),
      );
}

Button и ButtonText при этом можно вынести в отдельные файлы, и вот у вас готовы переиспользуемые компоненты. А можно их просто сделать внутренней функцией, как в моем примере ниже. Такой гибкости очень сложно добиться в подходе с отдельным языком для разметки (либо сам этот язык становится слишком сложным, и возникает вопрос – а зачем писать приложение сразу на 2 языках программирования). И много компаний идут именно в этом направлении – iOS и SwiftUI, Android и Jetpack Compose (и что-то похожее было до этого у JetBrains с Anko Layout), тот же React и JSX.

тот же React и JSX

JSX это всё-таки смесь ежа с ужом кода и разметки, в отличии от Flutter, где только код.

Ну как сказать – это ж просто синтаксический сахар, чтобы оно выглядело похоже на html: It is called JSX, and it is a syntax extension to JavaScript.

В целом хорошо, если было бы {} вместо () было бы вообще привычно почти как какой ни будь жсон. У меня когда то давно первое время после знакомства с питоном были проблемы с возвращением на языки с си подобными скобочками. Со временем понимаешь что это все не важно. К тому же без подсветки синтаксиса ваш пример не так хорошо смотрится как мог бы. К тому же есть и плагины для разноцветных скобочек, и для отображения древовидной структуры. Лучше скринте свое IDE.

Хотелось бы увидеть приложение и для ios так как и прошлая статья направлена на андроид… Попробуйте собрать приложение под ios на flutter и на kivy…

Скрины можно? То что это скомпилировалось это уже хорошо. Но как при этом будет выглядеть приложение на ios?

Я не занимаюсь этим по одной простой причине — у меня нет iOS. А гонять все это в эмуляторе в X-Code смысла не вижу.

Но как при этом будет выглядеть приложение на ios?

Точно так же как на Андроид, если вы юзаете KivyMD.

Проблема именно в этом нативные виджеты android и ios визуально отличаются и flutter очень хорошо это сглаживает...

Также хотелось бы увидеть более живой пример, например с отображением геолокации на карте на флаттер и на киви. Вот это был бы реальный пример...

Правильно я понял, что и не flexbox-контейнеры и не grid, а что то своё? Есть ли пересечение в подходе с общепринятыми методами позиционирования?

Как будет выглядеть описание «типичного» элемента списка в KivyMD?
image

То есть, привести разметку данной карточки?

Да, если не трудно. Можно даже не всей, а например «шапки» — ширина 100%, внутри два блока прижатые к краям, левый из двух горизонтальных элементов, правый из двух вертикальных. Без стилей, просто разметка.

Это, как вы выразились, не "типичный" элемент в KivyMD! Но, хорошо, я покажу...

Имеется в виду «типичный» не применительно к конкретному фреймворку, а к реальному приложению, такие элементы списка довольно часто встречаются в мобильных приложениях.

У нас нет набора "типичных" карточек. Они все разные и индивидуальные для любого приложения. У нас есть базовый класс MDCard и набор классов MDCardPost:


image


Конкретно такой карты, как привели вы — нет.

Если брать Flutter, то такая карточка довольно просто описывается (схематично):

Column(
  children: [
    Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Row(
          children: [
            Icon,
            Text
          ]
        ),
        Column(
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Text,
            Text
          ]
        ),
      ]
    ),
    Text,
    Text,
    Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Row(
          children: [ Chip, Chip, Chip ]
        ),
        Row(
          children: [
            Icon,
            Text
          ]
        ),
      ]
    ),
  ]
)

Да чего уж там, приведите полный пример этой карты. Я в свою очередь сейчас размечу данную карту...

Вы в том плане, что он будет плохо читаем? Я не буду спорить, код будет многострочный, от количества закрывающих скобочек будет кружиться голова. Но есть декомпозиция и т.п.

Но не суть, я не про красоту, простоту и читаемость, в данный момент мне интересно, как на KivyMD сделать разметку с главной и поперечной осями.

Готово! Так пойдет или стили меток выдержать?


image

Ох, ёшки-матрёшки, я ж вас не на слабо беру, типа «так не получится». Но за то что сделали, респект.
Просто покажите, как выглядит код разметки, а я буду смотреть и думать, сложно-не-сложно для меня, сравнивать, думать о том, стоит ли возвращаться к Kivy, в своё время совсем не зашёл, может появление KivyMD всё изменило.

image


from kivy.lang import Builder
from kivymd.app import MDApp

KV = """
<MDLabel>:
    size_hint_y: None
    height: self.texture_size[1]
    pos_hint: {"center_y": .5}

<MDChip>:
    pos_hint: {"center_y": .5} 

Screen:

    MDCard:
        orientation: "vertical"
        padding: "5dp"
        spacing: "10dp"
        size_hint_y: None
        height: self.minimum_height
        pos_hint: {"center_y": .5}

        BoxLayout:
            size_hint_y: None
            height: self.minimum_height
            spacing: "10dp"
            padding: "10dp"

            Image:
                size_hint: None, None
                size: "40dp", "40dp"
                source: "data/logo/kivy-icon-128.png"

            MDLabel:
                text: "User 123"
                font_style: "H6" 
                bold: True               

            Widget:

            BoxLayout:
                orientation: "vertical"
                spacing: "5dp"

                MDLabel:
                    text: "2019-12.08 20:55"
                    font_style: "Button"
                    halign: "right"

                MDLabel:
                    text: "Development"
                    font_style: "H6"
                    halign: "right"
                    bold: True 

        MDLabel:
            text: "Как перенести Linux из embedded"
            font_style: "H4"
            bold: True 

        MDLabel:
            text:
                "Да, если не трудно. Можно даже не всей, а например «шапки» — " \
                "ширина 100%, внутри два блока прижатые к краям, левый из " \
                "двух горизонтальных элементов, правый из двух вертикальных. " \
                "Без стилей, просто разметка."

        BoxLayout:
            size_hint_y: None
            height: self.minimum_height
            spacing: "5dp"

            MDChip:
                label: "arm"
                icon: ''

            MDChip:
                label: "linux"
                icon: ''

            MDChip:
                label: "qume"
                icon: ''

            Widget:

            BoxLayout:
                size_hint: None, None
                size: self.minimum_size
                padding: "10dp"

                MDIconButton:
                    user_font_size: "18sp"
                    icon: "mail"

                Label:
                    text: "13"
                    color: 0, 0, 0, 1
                    size_hint: None, None
                    size: self.texture_size
                    pos_hint: {"center_y": .5}
"""

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

Card().run()
Вот посмотрел я на ваш код, скажите пожалуйста, как обстоят дела с автокомплитом и поддержкой IDE? По сути это все один большой комментарий для меня на данный момент, было бы логичней делать что-то вроде шаблонов в Django/Jinja2 где файл шаблона лежит отдельно и наполняется контекстом. Сильно не пинайте, если говорю глупости. Просто как это рефакторить в случае чего, я не представляю.

Для pycharm есть плагин для подсветки синтаксиса kv

kv-файлы и должны лежать отдельным файлом.

В VScode есть плагин для kv файлов, в которые можно разметку вынести.

Только самодостаточный: скомпилировал — запустил!

Как вариант:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

main() => runApp(MaterialApp(home: Scaffold(body: MyCard())));

class MyCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Center(
        child: Card(
          child: Padding(
            padding: const EdgeInsets.all(8),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                _buildHeader(),
                _buildTitle(context),
                _buildContent(),
                _buildFooter(),
              ],
            ),
          ),
        ),
      );

  Widget _buildHeader() => Row(
        children: <Widget>[
          Icon(Icons.person, size: 48),
          Expanded(child: Text('User 123')),
          Column(
            crossAxisAlignment: CrossAxisAlignment.end,
            children: <Widget>[
              Text('2019-12-08 20:55'),
              Text('User 123'),
            ],
          ),
        ],
      );

  Widget _buildTitle(BuildContext context) => Padding(
        padding: const EdgeInsets.symmetric(vertical: 8),
        child: Text(
          'Как перенести Linux из embedded',
          style: Theme.of(context).textTheme.title,
        ),
      );

  Widget _buildContent() => Padding(
        padding: const EdgeInsets.only(bottom: 16),
        child: Text(text),
      );

  Widget _buildFooter() => Row(
        children: <Widget>[
          Expanded(
            child: Wrap(
              spacing: 5,
              children: <Widget>[
                Chip(label: Text('arm')),
                Chip(label: Text('linux')),
                Chip(label: Text('qemu')),
              ],
            ),
          ),
          Icon(Icons.comment),
          Text('13'),
        ],
      );

  String get text =>
      'Да, если не трудно. Можно даже не всей, а например «шапки» — '
      'ширина 100%, внутри два блока прижатые к краям, левый из двух '
      'горизонтальных элементов, правый из двух вертикальных. '
      'Без стилей, просто разметка.';
}

Это просто вырвиглазной пи… ец!

Это очень аргументированное мнение!

Даже у Java все логично построено в их xml-разметке и спорить с этим бесполезно, у Flutter просто ужасный код!

С релизом Jetpack Compose там будет так же, как и на флаттере

В Java взрослые люди стараются писать UI декларативно, а не заниматься извращегиями с XML.

А можно более развернутую аргументацию? Я не особо силен в питоне и код на флаттере для меня значительно читаемее выглядит.

Даже те, кто используют Flutter, согласны, что код этого фреймворка "паравозный" и выгрвиглазной. Но вы, видимо, очередной троллейбус, так что ваше сообщение остается без ответа!

С вашей точки зрения существует только два мнения — ваше и не правильное?
UFO just landed and posted this here

Я не работал с Docker да и времени, если честно, в этом разбираться как бы нету.

1) Пример на Flutter не работает :/
2) Если смотреть по коду, то не особо то и больше получается на Flutter (Если не считать строки со скобками)
3) Давайте этот же пример, но вместо счётчика будем рандомить цвет круга и менять его с анимацией длительностью 1 секунда и кривой easeOut (Мне интересно, как обстоят дела с анимацией)
Выглядит красиво и надо будет поковырять это самостоятельно. Несколько предложений на будущее:
— разметку вынести в отдельные файлы. В этом случае можно уже создать плагины для IDE, которые бы подсвечивали синтаксис разметки, позволяли автокомплит и рефакторинг и т.п. Ну и в целом удобнее, в этом случае версткой и кодом могли бы заниматься разные люди.
— создать докер-образ для сборки под андроид. Тогда действительно можно будет билдить одной командой, не ставя зависимостей.

Кстати, как с поддержкой нескольких активитей/окон приложения? Редкий проект ограничивается одним окном…
Вот да, DSL для гуя это мечта, а описывать это кодом это мягко говоря странно.
Это отлично. Работают все рефакторинги и автокомплиты, отличная гибкость, можно использовать те же способы декомпозиции что и в коде обычном. Сколько боли в той же нативной разработке под андроид из за использования xml? Жуть.
Это называется костыль — вместо того чтобы допилить инструментарий для поддержки читамоего и удобного DSL, делать всё кодом и считать что это хорошо.
А зачем? Если описание UI во flatter, jetpack compose, swift ui, все равно выглядит достаточно декларативно, но дает при желании гибкость которая многим DSL и не снилась.
Если выглядит, то да, но в этом и суть проблемы.

KV Language поддерживает код Python и гибок настолько, насколько гибок мягкий пластилин. В коде должна быть только логика и ничего более!

Просветите пожалуйста, меня несмышленного, смогу ли при помощи Kivy написать приложение на ТСД, которое будет получать доступ к встроенному 2D-сканеру, терминала?

Kivy == Python. Почти все библиотеки, которые поддерживаются Python смогут работать и на мобильном устройстве. На десктопе — все!

Как раз kivy изучаю в данный момент, только ради приложения для себя. И тут как раз эта статья, спасибо вам.

Установите Android Debug Bridge. Подключите устройство к компьютеру USB кабелем. Откройте терминал и введите команду:


adb logcat | grep python

Запустите приложение на устройстве и смотрите ошибку в терминале.

Да я забыл kivymd подключить просто)
А за способ отладки спасибо пригодится!!!
До недавнего времени сборка apk отлично собирались, но появилась ошибка File "/usr/local/lib/python3.6/dist-packages/buildozer-0.40.dev0-py3.6.egg/buildozer/targets/android.py", line 816, in get_dist_dir
matching_dirs = glob.glob(join(self._build_dir, 'dist', '{}*'.format(dist_name)))
AttributeError: 'function' object has no attribute 'glob'

По этой инструкции все делали?

Да, только не на виртуалке, а на настоящей убунте. Причем буквально два дня назад apk собиралась без проблем. Видимо это связано с апдейтом python-for-android.

Сейчас попробую на своей машине...

image


Все прекрасно собирается в виртуальной машине!

Ошибка была у меня в коде: откатился до предыдущей версии и всё заработало. Хотя если честно не понятно почему buidozer валился

Да, код с ошибкой не будет построен.

Хотя на десктопе всё запускалось без ошибок. Или же buidozer анализирует все файлы в проекте на наличие ошибок?
Попробовал прям с примером из статьи, apk собралось. Запускаю на двух смартфонах, появляется Loading… и вылетает. Вечером попробую с Android Debug Bridge разобраться, может станет понятно в чём дело.

Лог покажите. Что можно ответить на "Тоже самое, вылетает"?

Символично, буквально недавно узнал про kivy и сделал неделю назад простенькую игрушку для андроида на нем)
play.google.com/store/apps/details?id=org.corgi.corgi
Приятный инструмент, здорово, что продолжаете над ним работу.
UFO just landed and posted this here
Переборщил, знаю. Я не знаю, как сжимать изображения, там все элементы отрисованы для 4к разрешения и в png формате, поэтому получилось, что каждая кнопка весит по 1 Мб. Я пробовал преобразовать их в gif формат, они сразу становились раза в три легче, но и сильно теряли в качестве и получались мыльными\
UFO just landed and posted this here
Спасибо)
Качество все же падает от этого сжатия тоже, но на экране телефона этого почти не заметно. Обновил версию, теперь весит 35 Мб)

А как дела обстоят с KivyMD Studio?
Вроде выглядит приятно, очень хочется пощупать

Этот проект развивается очень медленно, так как я работаю над ним в одиночку. А поскольку это не основной мой проект, плюс основная работа занимает львиную часть времени, то о каких-либо сроках говорить бессмысленно.

Я извиняюсь. А «loading» на несколько секунд для «Hello world» необходимая стадия? Или это особенность работы именно вашего фреймворка?
Хамить совершенно не обязательно. Вроде бы на серьезном ресурсе, а не на базаре.

Так не надо тут троллинг разводить, если не на базаре.

Я опечален тем фактом, что мой вопрос был воспринят как троллинг. Никакого негатива я в него не вкладывал и интересовался совершенно искренне т.к. просто не имел ранее дела с подобными фреймворками.

А Flutter, типа натив?! Вам бы пойти почитать, как работает движок Flutter, а не позорится глупыми фразами!

Смотря что считать нативом. Релизная версия компилится в нативный бинарь для целевой архитектуры. А вот дебаг версию которая запускается в dartVM действительно нативом не назовешь.

Серьезно? В нативный? И все элементы UI нативные?

С т.з. бинарного кода — да. С т.з. «нативной» разработки — нет. Но наверно для вас не будет сюрпризом, но в той же нативной разработке под андроид часто используется AppCompat/androidx в которых по сути содержатся аналогичные нативным вьюшки но переписанные для единообразной работы на разных версиях. Т.е. по сути «нативные» разработчики очень часто используют «ненативные» вью которых нет в SDK. Это даже если про кастомные вью забыть и библиотечные которых нет опять же в SDK, например как в бибилиотеке material.

С этой точки зрения — пакет с приложением Kivy также компилируется в натив, поскольку все сторонние библиотеки собираются под архитектуру мобильных процессоров. Поэтому очень раздражают люди, которые не понимают сути вопроса, но пишут, что Flutter — это натив.

Флаттер это таки натив. Возможно даже больший натив чем та же java под андроид (до появления ART). Потому что исполняется бинарь нативный для процессора и нет никакого интерпретируемого кода на скриптовом языке. Или хотите сказать что у вас python aot компилируется в нативный бинарь для ARM?
Возможно даже больший натив чем та же java под андроид

На этой ноте диалог с вами окончен!

Flutter компилируется в нативный код устройства, но ни в каком виде не использует нативные виджеты, т.к. для реализации GUI используется движок Skia, производящий отрисовку графики на холсте. Но я полностью согласен, что ассемблерный код, генерируемый компилятором C++, является нативным для устройства, в отличие от виртуальной машины Java.
Ну, в последних версиях таки в общем dex тоже art компилируется aot в нативный для процессора бинарь с учетом профилей исполнения (пусть для первых запусков и может использоваться интерпретация байткода с jit). Это раньше оно далвиком исполнялось и только jit был. Так что оговорка что под андроид нативные приложения не совсем нативные — оно в целом только про старые версии))
Еще хотел бы добавить, что фреймворк Kivy работает по тому же принципу, что и Flutter: исходный код транслируется в C++ (благодаря тому, что Kivy написан на CPython и C/C++) для упрощения распространения, а рендеринг GUI приложения происходит при помощи OpenGL.
Странно, если оно таки компилится в нативный бинарь — чего оно загружается долго?

Исходный код Kivy приложений не транслируется в С++. При компиляции пакета вся ресурсоемкая часть движка Kivy компилируется в натив, так как фреймворк Kivy написан на Cython, а уже все это обернуто в вызовы и API из чистого Python.

Извиняюсь, был не прав. Действительно, в нативный код компилируются не все модули готового приложения.
Здравствуйте. Я вот залил один из ваших примеров на телефон. Чёт работает как-то не плавно. И такое ощущение, что притормаживает. Это так всегда?

Аналогично и в примерах на Flutter! Некоторые — просто жуткий стоп кран! В следующей статье я покажу, какой Flutter тормоз. А вам могу ответить только одно — примеры в студию!

Это очень сильное заявление. Какой именно пример на флаттере и на каком телефоне у Вас тормозит?


И да, жду с нетерпением заявленную статью.

Может вы его в debug mode запускали? Это две большие разницы
Sign up to leave a comment.

Articles