1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
flutter-hy-composition.md 27 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

В предыдущей статье "Интеграция Android PlatformView и проблемы с клавиатурой" были рассмотрены реализация и проблемы использования PlatformView в гибридной разработке на платформе Flutter. Для интеграции таких компонентов, как WebView, MapView, ранее использовался подход с VirtualDisplays.

С версией Yöntem 1.20 официальное сообщество начало экспериментировать с новым режимом «гибридного состава», аналогичным тому, что используется на iOS. В этой статье мы сравним и объясним использование нового режима «гибридного состава».

Помните, это версия 1.20, а не 1.2!

1. Старый метод VirtualDisplay

До версии 1.20 в Flutter содержимое, которое должно было быть отображено через AndroidView, рисовалось в VirtualDisplays. Затем изображение в памяти можно было получить через Surface этого VirtualDisplay.

VirtualDisplay работает как виртуальная область отображения, которая обычно используется вместе с DisplayManager. Обычно он применяется при работе со вторым экраном или записи экрана. VirtualDisplay рисует содержимое виртуальной области отображения на поверхности (Surface).

image1

Как показано на приведённой выше диаграмме, VirtualDisplay создаёт виртуальную область отображения, которая затем передаётся в SurfaceTexture, который в свою очередь передаётся в SurfaceView. SurfaceView отображает изображение на устройстве.

// Создание VirtualDisplay
VirtualDisplay virtualDisplay = mMediaProjection.createVirtualDisplay(
    "My App Name",
    width,
    height,
    density,
    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
    surface, // Surface полученный из SurfaceTexture
    null);

// Запись экрана
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setVideoEncodingBitRate(512 * 1000);
mediaRecorder.setVideoFrameRate(frameRate);
mediaRecorder.setVideoSize(width, height);
mediaRecorder.setOutputFile(outputPath);
mediaRecorder.setPreviewDisplay(surface); // Surface полученный из SurfaceTexture
mediaRecorder.prepare();
mediaRecorder.start();

// Закрытие VirtualDisplay после завершения записи
virtualDisplay.release();

Как это работает?

VirtualDisplay создает виртуальную область отображения, которая затем передается в SurfaceTexture. Этот SurfaceTexture передается в SurfaceView, который отображает изображение на устройстве. Таким образом, VirtualDisplay позволяет нам использовать вторые экраны или записывать экран устройства.


2. Новый метод Hybrid Composition

С версией 1.20 Flutter начал использовать новый метод «гибридного состава». Этот метод использует HybridComposition вместо VirtualDisplay.

HybridComposition позволяет Flutter использовать нативные Android UI компоненты, такие как WebView, MapView, и другие, прямо внутри Flutter приложения. Это делает интеграцию этих компонентов более простой и эффективной.

image2

На приведенной выше диаграмме видно, что HybridComposition позволяет Flutter использовать нативные Android UI компоненты, такие как WebView, MapView, и другие, прямо внутри Flutter приложения. Это делает интеграцию этих компонентов более простой и эффективной.

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Flutter Hybrid Composition Example')),
        body: Column(
          children: <Widget>[
            AndroidView(
              viewType: 'com.example.myapp',
              onPlatformViewCreated: (int id) {},
            ),
            WebView(
              initialUrl: 'https://flutter.dev/',
              javascriptMode: JavascriptMode.unrestricted,
            ),
          ],
        ),
      ),
    );
  }
}

Преимущества нового метода

Новый метод «гибридного состава» предлагает несколько преимуществ:

  • Улучшенная производительность: Используя нативные Android UI компоненты, Flutter может обеспечивать лучшую производительность.
  • Упрощенная интеграция: Интеграция нативных Android UI компонентов теперь стала проще благодаря новому методу.
  • Более легкая работа с клавиатурой: Новый метод также помогает решить некоторые проблемы, связанные с работой клавиатуры.

Заключение

В данной статье мы рассмотрели старый метод VirtualDisplay и новый метод «гибридного состава» в Flutter. Новый метод предоставляет множество преимуществ, особенно когда дело доходит до интеграции нативных Android UI компонентов.## 2. Интеграция гибридного компонента

Благодаря усилиям команды разработчиков и сообщества, начиная с версии 1.20, был представлен новый способ реализации PlatformView — «гибридный компонент». Этот подход решает большинство проблем, связанных с PlatformView на Android, такие как неожиданное исчезновение веб-интерфейса после открытия клавиатуры на устройствах Huawei. Для использования Hybrid Composition требуется использовать объекты PlatformViewLink, AndroidViewSurface и PlatformViewsService. Сначала нам нужно создать Dart-компонент:

  • Через viewType в PlatformViewLink регистрируется имя, соответствующее нативному слою, что аналогично регистрации PlatformView;
  • Затем в surfaceFactory возвращается AndroidViewSurface, который используется для отрисовки и принятия событий сенсора;
  • Наконец, метод onCreatePlatformView использует PlatformViewsService для инициализации AndroidViewSurface и необходимых параметров, а также запускает отображение нативного слоя через Engine.
Widget build(BuildContext context) {
  // Это используется на стороне платформы для регистрации представления.
  final String viewType = 'hybrid-view-type';
  // Передаются параметры на сторону платформы.
  final Map<String, dynamic> creationParams = <String, dynamic>{};
```  return PlatformViewLink(
    viewType: viewType, 
    surfaceFactory: 
        (BuildContext context, PlatformViewController controller) {
      return AndroidViewSurface(
        controller: controller,
        gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>[],
        hitTestBehavior: PlatformViewHitTestBehavior.opaque,
      );
    },
    onCreatePlatformView: 
        (PlatformViewCreationParams params) {
      return PlatformViewsService.initSurfaceAndroidView(
        id: params.id,
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: StandardMessageCodec(),
      )..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)..create();
    },
  );
}Перейдём к нативной части на Android. В нативной части мы создаём компонент, расширяющий `PlatformView`, и возвращаем нужный элемент управления через метод `getView`.

```dart
package dev.flutter.example;

import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.platform.PlatformView;

Класс NativeView реализует интерфейс PlatformView.

class NativeView implements PlatformView {
   @NonNull private final TextView textView;

    NativeView(@NonNull Context context, int id, @Nullable Map<String, Object> creationParams) {
        textView = new TextView(context);
        textView.setTextSize(72);
        textView.setBackgroundColor(Color.rgb(255, 255, 255));
        textView.setText("Рenders on native Android view (id: " + id + ")");
    }

    @NonNull
    @Override
    public View getView() {
        return textView;
    }

    @Override
    public void dispose() {}
}

Затем следует создание экземпляра PlatformViewFactory, который будет использоваться методом create для загрузки и инициализации PlatformView.

package dev.flutter.example;

import android.content.Context;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
import java.util.Map;

class NativeViewFactory extends PlatformViewFactory {
  @NonNull private final BinaryMessenger messenger;
  @NonNull private final View containerView;

  NativeViewFactory(@NonNull BinaryMessenger messenger, @NonNull View containerView) {
    super(StandardMessageCodec.INSTANCE);
    this.messenger = messenger;
    this.containerView = containerView;
  }

  @NonNull
  @Override
  public PlatformView create(@NonNull Context context, int id, @Nullable Object args) {
    final Map<String, Object> creationParams = (Map<String, Object>) args;
    return new NativeView(context, id, creationParams);
  }
}
```Наконец, в `MainActivity` через `flutterEngine` метод `getPlatformViewsController` регистрируется `NativeViewFactory`.

```dart
package dev.flutter.example;

import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;

public class MainActivity extends FlutterActivity {
    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        flutterEngine
            .getPlatformViewsController()
            .getRegistry()
            .registerViewFactory("hybrid-view-type", new NativeViewFactory(null, null));
    }
}

Конечно, если требуется включить Hybrid Composition на Android, необходимо добавить следующий код в файл AndroidManifest.xml для активации конфигурации:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="dev.flutter.example">
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="hybrid"
        android:icon="@mipmap/ic_launcher">
        <!-- ... -->
        <!-- Hybrid composition -->
        <meta-data
            android:name="io.flutter.embedded_views_preview"
            android:value="true" />
    </application>
</manifest>

Кроме того, официальные представители сообщили, что производительность Hybrid Composition на устройствах с Android 10 и выше достаточно хорошая, а на версиях ниже Android 10 скорость отображения Flutter-интерфейса на экране замедляется. Это замедление вызвано необходимостью синхронизации кадров Flutter с системой представлений Android.

Чтобы минимизировать это влияние, следует избегать отображения нативных компонентов во время выполнения анимаций на Dart. Например, можно использовать placeholder скриншот нативного компонента и отображать его вместо реального компонента во время анимации.## 3. Характеристики и принцип реализации Hybrid Composition

Для объяснения реализации Hybrid Composition стоит рассмотреть новый объект, добавленный в этот выпуск — FlutterImageView.

FlutterImageView не является обычным ImageView.

На самом деле, смешивание нативных компонентов в Hybrid Composition осуществляется через FlutterImageView. Сам FlutterImageView представляет собой обычный нативный View, который реализует интерфейс RenderSurface для предоставления части возможностей FlutterSurfaceView.

Внутри FlutterImageView используются три основных класса: ImageReader, Image и Bitmap. В частности:

  • ImageReader можно рассматривать как объект, способный хранить данные Image, предоставляющий Surface для рисования данных Image из нативных слоёв.
  • Image представляет собой пиксельные данные, содержащие ByteBuffers, обычно используемые в нативной среде, например, при работе с камерой.
  • Bitmap преобразует Image в битмап, которую затем можно отрисовать внутри FlutterImageView с помощью Canvas.

Как видно, FlutterImageView может предоставлять Surface, получать данные Image с Surface и отрисовывать их с использованием Bitmap. В FlutterImageView предоставляются два типа SurfaceKind: background и overlay. В частности:

  • background используется для режима отрисовки FlutterView по умолчанию, то есть это значение по умолчанию для рендера основного приложения Flutter. Таким образом, FlutterView имеет три типа RenderMode: surface, texture и image.- overlay предназначен для использования в режиме Hybrid Composition для совместной работы с PlatformView.

Кроме того, можно заметить, что в PlatformViewsController присутствуют методы createAndroidViewForPlatformView и createVirtualDisplayForPlatformView. Это также является способом, которым команда Flutter обеспечивает совместимость с VirtualDisplay по умолчанию вместе с предоставлением Hybrid Composition.

В случае Hybrid Composition слой Dart вызывает метод create канала PlatformViewsChannel через PlatformViewsService, после чего отправляет запрос PlatformViewCreationRequest, который проверяет наличие usesHybridComposition. Если значение равно true, то следующий шаг будет использовать метод createAndroidViewForPlatformView.

Как работает FlutterImageView в режиме Hybrid Composition?

Сначала запустите пример из второго раздела, открыв границы макета на Android-устройстве. Вы увидите белый прямоугольник со значком Re по центру экрана. Анализируя границы макета, можно понять, что этот белый прямоугольник — это нативный компонент.

Пример

Затем используйте ту же самую кодировку для создания ещё одного белого прямоугольника с значком Re в другом месте. Вы увидите, что теперь на правом верхнем углу экрана появился ещё один белый прямоугольник с значком Re и границами макета. Это показывает, что PlatformView в режиме Hybrid Composition отображается с помощью некоторого нативного компонента.Пример

Но возникает вопрос: что особенного в том, чтобы размещать нативные компоненты в Flutter? Это просто слойовой состав? Добавьте два белых прямоугольника с значком Re рядом друг с другом, а затем вместо использования PlatformView создайте синюю надпись Re с помощью стандартного Text.

Пример

Получили ли вы это? Без использования PlatformView надпись Text синего цвета Re действительно отображается поверх белого непрозрачного нативного прямоугольника Re!> Возможно, некоторые из вас скажут, что в этом нет ничего особенного? Но те, кто знает основные принципы работы с Flutter, должны понимать, что по умолчанию Flutter на уровне нативной реализации представляет собой SurfaceView. Далее движок Engine отрисовывает все элементы управления на этот Surface.

Однако что вы видите сейчас? Наш текстовый элемент Text на уровне Dart с синей надписью "Re" действительно отображается поверх белого маленького квадрата "Re". Это указывает на то, что Hybrid Composition не сводится к простому расположению нативных компонентов поверх Flutter.Затем мы заметили ещё одну странную проблему — синий текст "Re", созданный с помощью стандартного Text в Flutter, также имеет границы нативного компонента? Поэтому мы добавили желтый текст "Re" и красный текст "Re" с использованием стандартного Text, и можно заметить, что границы компонента отображаются только там, где текст пересекается с PlatformView.

При перемещении желтого текста "Re" вниз, границы этого текста тоже исчезают, поэтому можно сделать вывод, что Hybrid Composition позволяет компонентам Dart отображаться поверх нативных компонентов благодаря тому, что при пересечении с PlatformView происходит перерисовка через какой-то нативный компонент.

Используя Layout Inspector, можно заметить, что перекрывающиеся компоненты Text отображаются через слой FlutterImageView.

Кроме того, есть ещё одна интересная особенность — когда несколько стандартных компонентов Flutter отображаются одновременно в одном PlatformView, они используют один и тот же FlutterImageView.

Если эти компоненты находятся в разных областях, каждый из них использует свой собственный FlutterImageView. Также стоит отметить, что стандартный PlatformView, используемый в Hybrid Composition, является FlutterMutatorView.

На самом деле FlutterMutatorView используется для изменения положения и матрицы нативных компонентов, чтобы они корректно располагались внутри FlutterView. Общая структура взаимодействия PlatformView в режиме Hybrid Composition следующая:**Таким образом, PlatformView добавляется в FlutterView через FlutterMutatorView, а затем с помощью возможностей FlutterImageView производятся манипуляции со слоями.**Как Flutter определяет, что компоненту требуется использовать FlutterImageView?

На самом деле можно заметить, что при отправке кадра (SubmitFrame) в Engine, используется current_frame_view_count для планирования каждого представления, после чего проверяется необходимость вызова функции CreateSurfaceIfNeeded. В конечном итоге это приводит к запуску метода создания FlutterImageViewcreateOverlaySurface.

for (const SkRect& overlay_rect : overlay_layers.at(view_id)) {
  std::unique_ptr<SurfaceFrame> frame = CreateSurfaceIfNeeded(
    context,
    view_id,
    pictures.at(view_id),
    overlay_rect
  );
  if (should_submit_current_frame) {
    frame->Submit();
  }
}

Что касается уровня Dart, то PlatformViewSurface добавляется через PlatformViewRenderBox как PlatformViewLayer, а затем через вызов addPlatformView в ui.SceneBuilder передается информация о слое в Engine.

(Дополнительные детали можно найти в статье "Полное понимание рендеринга экрана в Flutter")

Кроме того, есть ещё много реализационных деталей, которые не были рассмотрены:

  • Метод onDisplayPlatformView, который при отображении PlatformView вызывает flutterView.convertToImageView для перехода renderSurface в flutterImageView;
  • В методе initializePlatformViewIfNeeded повторная инициализация уже инициализированных PlatformViews не происходит;
  • При создании ImageReader и обновлении текущего битмапа в FlutterImageView, на устройствах Android 10 возможно использование аппаратного ускорения GPU, что объясняет хорошую производительность Hybrid Composition на этих устройствах.Из-за ограничений объёма материала остальные подробности не рассматриваются. Однако Hybrid Composition доступна в стабильной версии 1.20, что помогло решить некоторые проблемы с клавиатурой. Останется ли она эффективной — покажет время, ведь каждый шаг может быть испытанием.

Рекомендованные ресурсы

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master