Flutter 移动端适配方案(原生+Channel)

864 阅读2分钟

目前流行的通过 MediaQueryData.fromView(View.of(context))获取屏幕宽高的适配方案,当app从后台唤醒的时候,在安卓机型上会出现适配丢失的场景。

通过MediaQuery.sizeOf(context)获取屏幕宽高的适配方案,横屏竖屏切换的时候,会引起整个app重构。

这里给出结合原生层面的方案,解决上面的问题。

那如何解决适配丢失的问题?

从原生层面获取屏幕信息(屏幕宽高、dpr以及状态栏高度)。

代码如下

安卓+channel代码

class MainActivity: FlutterActivity() {

    private val CHANNEL = "app.channel.notification"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
            // This method is invoked on the main thread.
            call, result ->
            if (call.method == "getScreenSize") {
                // get statusBarHeight
                val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
                statusBarHeight = if (resourceId > 0) {
                    resources.getDimensionPixelSize(resourceId)
                } else {
                    0
                }

                // Get the screen width and height
                val displayMetrics = resources.displayMetrics
                screenWidth = displayMetrics.widthPixels
                screenHeight = displayMetrics.heightPixels
                
                // Get the device pixel ratio (DPR)
                dpr = displayMetrics.density
                
                // Return the screen width and height to Flutter
                val resultMap = mapOf(
                        "width" to screenWidth/dpr,
                        "height" to screenHeight/dpr,
                        "statusBarHeight" to statusBarHeight/dpr
                )
                result.success(resultMap)
            } else{
                result.notImplemented()
            }
        }

    }

}

苹果+channel代码

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      
      let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
          let appChannel = FlutterMethodChannel(name: "app.channel.notification",
                                                    binaryMessenger: controller.binaryMessenger)
          appChannel.setMethodCallHandler({
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
              // This method is invoked on the UI thread.
              if call.method == "getScreenSize" {
                  let statusBarHeight = UIApplication.shared.statusBarFrame.size.height
                  let screenSize = UIScreen.main.bounds.size
                  let screenWidth = screenSize.width
                  let screenHeight = screenSize.height
                  let dpr = UIScreen.main.scale
                  let resultMap: [String: Any] = [
                      "width": screenWidth,
                      "height": screenHeight,
                      "statusBarHeight": statusBarHeight
                  ]
                  result(resultMap)
              }else {
                  result(FlutterMethodNotImplemented)
              }
          })
      
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

flutter main.dart

Future<void> main() async{
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();

  //获取屏幕尺寸信息
  const platform = MethodChannel('app.channel.notification');
  var screenSize = await platform.invokeMethod('getScreenSize');

  Constants.initializeScreenConstants(screenSize);
  HYSizeFit.initialize(screenSize:screenSize);

  runApp(const MyApp());
}

constants.dart

import 'package:flutter/material.dart';

class Constants {

  static late double stateHeight;

  static late double screenHeight;

  static late double screenWidth;

  static void initializeScreenConstants(Map screenSize){

    stateHeight = screenSize["statusBarHeight"];
    screenWidth = screenSize["width"];
    screenHeight = screenSize["height"];

  }
}

size_fit.dart

import 'package:flutter/material.dart';

class HYSizeFit {
  static late double screenWidth;
  static late double screenHeight;

  static late double rpx;
  static late double px;

  static void initialize({required Map screenSize,double standardWidth = 750}) {

    screenWidth = screenSize["width"];
    screenHeight = screenSize["height"];
    // 计算rpx 和px大小
    rpx = screenWidth / standardWidth;
    px = screenWidth / standardWidth * 2;  // 必须是乘以2,因为是以iphone6(750)为标准
  }

  // 按照像素来设置
  static double setPx(double size) {
    return px * size ;
  }

  // 按照rpx来设置(如果给的设计稿是物理像素用rpx)
  static double setRpx(double size) {
    return rpx * size;
  }
}

int_extension.dart

import './size_fit.dart';

extension IntFit on int {
  double get px {
    // 因为我的工具size_fit自己规定了rpx与px都必须是double类型,所以这里也必须要转换一下
    return HYSizeFit.setPx(toDouble());
  }

  double get rpx {
// 因为我的工具size_fit自己规定了rpx与px都必须是double类型,所以这里也必须要转换一下
    return HYSizeFit.setRpx(toDouble());
  }
}

double_extension.dart

import './size_fit.dart';

extension DoubleFit on double {
  double get px {
    //这里的this是数字200.0, 相当于使用时 width: 200.0.px 里面的200.0 ,相当于是它调用方法
    return HYSizeFit.setPx(this);
  }

  double get rpx {
    //这里和上面都是用的get ,所以外面使用的时候是:width:200.5.rpx
    return HYSizeFit.setRpx(this);
  }
}

总结

本适配方案从根源解决适配问题。