Flutter 内嵌原生组件 for android

前言:

前面做过 原生内嵌 flutter,

今天尝试下 flutter 内嵌原生组件 ImageView

效果如下: flutter按钮 控制原生组件切换显示网络图片

第一步创建 Flutter Plugins 工程

这个步骤没啥说的;估计 AndroidStuido 版本不同 界面会有差异;

当然你也可以 命令行 进行创建

工程创建完毕后,下面的目录大致如下:

 

你可以直接运行这个工程,就能把这个 flutter plugins 模板工程跑起来,

第二步编写 插件脚本  lib 目录

flutter_plugins.dart 是系统自动帮你创建的我们不用管

我们创建 native_image.dart,  native_image_controller.dart 这两个脚本

 native_image.dart   用于UI展示

 这里注册了一个 切换图片的事件

void initState() {
  // TODO: implement initState
  super.initState();
  
  // 执行的切换图命令, 参数就是 图片的url
  widget.controller.addListener(() {
    // print(
    //     '-----------------------------imageUrl:${widget.controller.imageUrl}');
    _channel
        .invokeListMethod("changeImage", {'url': widget.controller.imageUrl});
  });
}

这里是实际创建原生界面的容器 根据不同系统进行的创建 (ios部分在下一章我们会讲到)

_loadNativeView() {
  if (Platform.isAndroid) {
    return AndroidView(
      viewType: 'imageView', //视图标识符 要和原生 保持一致 要不然加载不到视图
      onPlatformViewCreated: onPlatformViewCreated, //原生视图创建成功的回调
      creationParams: <String, dynamic>{
        'initUrl': widget.initUrl
      }, //给原生传递初始化参数 就是上面定义的初始化参数
      // 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
      // 如果存在 creationParams,则该值不能为null
      creationParamsCodec: const StandardMessageCodec(),
    );
  } else if (Platform.isIOS) {
    return UiKitView(viewType: 'imageView');
  }
}

完整的脚本

import 'dart:io';

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

class NativeImage extends StatefulWidget {
  final NativeImageController controller;
  final String initUrl;
  const NativeImage({Key? key, required this.initUrl, required this.controller})
      : super(key: key);
  @override
  _NativeImageState createState() => _NativeImageState();
}

class _NativeImageState extends State<NativeImage> {
  static const MethodChannel _channel =
      MethodChannel('flutter_plugins_native_image');
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    
    // 执行的切换图命令, 参数就是 图片的url
    widget.controller.addListener(() {
      // print(
      //     '-----------------------------imageUrl:${widget.controller.imageUrl}');
      _channel
          .invokeListMethod("changeImage", {'url': widget.controller.imageUrl});
    });
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    widget.controller.removeListener(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      width: MediaQuery.of(context).size.width,
      height: 200,
      child: _loadNativeView(),
    );
  }

  Future<void> onPlatformViewCreated(int id) async {
    // return widget.onCreated( new FlutterPlugins().)
    print('-----创建成功');
  }

  _loadNativeView() {
    if (Platform.isAndroid) {
      return AndroidView(
        viewType: 'imageView', //视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated: onPlatformViewCreated, //原生视图创建成功的回调
        creationParams: <String, dynamic>{
          'initUrl': widget.initUrl
        }, //给原生传递初始化参数 就是上面定义的初始化参数
        // 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        // 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    } else if (Platform.isIOS) {
      return UiKitView(viewType: 'imageView');
    }
  }
}

native_image_controller.dart  用于原生的ImageView 显示图片的控制

 代码很简单

import 'package:flutter/cupertino.dart';

class NativeImageController extends ChangeNotifier {
  late String imageUrl;
  changeImage(String url) {
    imageUrl = url;
    notifyListeners();
  }
}

第三步编写 原生脚本  Android 目录

点击右上角  Open for Editing in Android Studio

  

其中: FlutterPluginsPlugin.java 系统生成 不用管

我们 新建 NativeViewPluginsView.java  和 NativeViewPluginsFactory.java

NativeViewPluginsFactory  是我们的一个工厂方法 承接创建的 原生页面固定写法
package com.john.flutter_plugins;

import android.content.Context;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MessageCodec;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;

// https://blog.csdn.net/WangQingLei0307/article/details/122680290
public class NativeViewPluginsFactory extends PlatformViewFactory {
    private BinaryMessenger binaryMessenger = null;
    public NativeViewPluginsFactory(BinaryMessenger messenger){
        super(StandardMessageCodec.INSTANCE);
        this.binaryMessenger = messenger;
    }
    /**
     * @param createArgsCodec the codec used to decode the args parameter of {@link #create}.
     */
    public NativeViewPluginsFactory(MessageCodec<Object> createArgsCodec) {
        super(createArgsCodec);

    }

    @Override
    public PlatformView create(Context context, int viewId, Object args) {
        return new NativeViewPluginsView(context,viewId,args,this.binaryMessenger);
    }
}

NativeViewPluginsView 继承 ImageView 是我们真正用进行页面操作的地方

 这个方法中我们主要进行 原生页面的设置以及事件的注册

public NativeViewPluginsView(Context context, int viewId, Object args, BinaryMessenger binaryMessenger) {
    super(context);
    this.context = context;

    Toast.makeText(context, "原生插件启用成功id:"+((Map)args).isEmpty(), Toast.LENGTH_LONG).show();
    // ImageView 相关设置
    setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
    setBackgroundColor(Color.argb(255,75,79,79));  //0完全透明  255不透明
    
    // 设置默认图片 参数取自默认传参方式
    imageUrl =((Map<String, String>) args).get("initUrl");
    new WorkThread().start();


    /// 注册方法  用于切换显示的图片
    methodChannel = new MethodChannel(binaryMessenger, "flutter_plugins_native_image");
    methodChannel.setMethodCallHandler(this);
}

完整脚本

package com.john.flutter_plugins;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.annotation.NonNull;


import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

import io.flutter.Log;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.platform.PlatformView;

public class NativeViewPluginsView extends ImageView implements PlatformView, MethodChannel.MethodCallHandler, TextureView.SurfaceTextureListener {
    public Context context;
    private  MethodChannel methodChannel = null;
    private  String imageUrl = "https://img.gbm001.com/media/catalog/product/pic/x/PCVJYJD1.png";

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.i("---------------------","获得图片");
            setImageBitmap( (Bitmap)msg.obj);
        }
    };

public NativeViewPluginsView(Context context, int viewId, Object args, BinaryMessenger binaryMessenger) {
    super(context);
    this.context = context;

    Toast.makeText(context, "原生插件启用成功id:"+((Map)args).isEmpty(), Toast.LENGTH_LONG).show();
    // ImageView 相关设置
    setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
    setBackgroundColor(Color.argb(255,75,79,79));  //0完全透明  255不透明
    
    // 设置默认图片 参数取自默认传参方式
    imageUrl =((Map<String, String>) args).get("initUrl");
    new WorkThread().start();


    /// 注册方法  用于切换显示的图片
    methodChannel = new MethodChannel(binaryMessenger, "flutter_plugins_native_image");
    methodChannel.setMethodCallHandler(this);
}

    @Override
    public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {

    }

    @Override
    public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {

    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        handleCall(call, result);
    }
    // 事件回调
    private void handleCall(MethodCall methodCall, MethodChannel.Result result){
        //Toast.makeText(context, "收到 Flutter 调用", Toast.LENGTH_LONG).show();
        if (methodCall.method.equals("changeImage")){
          //Log.i("url",  methodCall.argument("url"));
            imageUrl = methodCall.argument("url");
            new WorkThread().start();
        }

    }

    @Override
    public View getView() {
        return this;
    }

    @Override
    public void dispose() {

    }


    //工作线程
    private class WorkThread extends Thread {
        @Override
        public void run() {
            //......处理比较耗时的操作
            Message msg = new Message();
            Bitmap bitmap = getHttpBitmap(imageUrl);
            msg.obj = bitmap;
            handler.sendMessage(msg);
        }
        public Bitmap getHttpBitmap(String url){

            URL myFileURL;
            Bitmap bitmap=null;
            try{
                myFileURL = new URL(url);
                //获得连接
                HttpURLConnection conn=(HttpURLConnection)myFileURL.openConnection();
                //设置超时时间为6000毫秒,conn.setConnectionTiem(0);表示没有时间限制
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(6000);
                //连接设置获得数据流
                conn.setDoInput(true);
                //不使用缓存
                conn.setUseCaches(false);
                //这句可有可无,没有影响
                //conn.connect();
                //得到数据流
                InputStream is = conn.getInputStream();
                //解析得到图片
                bitmap = BitmapFactory.decodeStream(is);
                //关闭数据流
                is.close();
            }catch(Exception e){
                e.printStackTrace();
                //Toast.makeText(context, "图片出错", Toast.LENGTH_LONG).show();
                Log.i("-----------图片出错",e.toString());

            }
            return bitmap;
        }
    }


}

这里有个地方要注意下;

  • 因为是网络图片 需要的权限等等是要加上的;
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 允许写手机存储(必须) -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
  • 而且这个地方下载图片的过程中使用多线程;多线程不能直接访问UI线程;所以用到 Handler 进行数据传递; 我在想这里换成异步网络请求应该会优雅一些

第四步 在Flutter 工程中使用我们的插件

马上就要大功告成 了! 我们返回到 Flutter 工程中,在 lib 文件夹中 编辑我们的 main.dart

直接使用 我们插件中写的 NativeImage

完整脚本

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_plugins/flutter_plugins.dart';
import 'package:flutter_plugins/native_image.dart';
import 'package:flutter_plugins/native_image_controller.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  final NativeImageController controller = NativeImageController();
  final List<String> images = [
    'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fgss0.baidu.com%2F-Po3dSag_xI4khGko9WTAnF6hhy%2Fzhidao%2Fpic%2Fitem%2F5366d0160924ab188c376d623efae6cd7b890b45.jpg&refer=http%3A%2F%2Fgss0.baidu.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652700915&t=cee2b6aad854f8f859d23986be137e30',
    'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F201610%2F31%2F20161031211042_FfTLG.thumb.1000_0.png&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652701005&t=829ecac7047856867820e2c4fb0afcc5',
    'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.js.xinhuanet.com%2Ftitlepic%2F111724%2F1117245314_1448349306603_title0h.jpg&refer=http%3A%2F%2Fwww.js.xinhuanet.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652701166&t=44b77ec0737766b99958f4510acad99f',
    'https://img1.baidu.com/it/u=3294782826,883543847&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=334',
    'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fa.zdmimg.com%2F202110%2F26%2F6177cd2a57db88955.jpg_e1080.jpg&refer=http%3A%2F%2Fa.zdmimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652701313&t=03f121950c96459d40adc812de7b81b4',
    'https://img0.baidu.com/it/u=2007762294,622825342&fm=253&fmt=auto&app=120&f=JPEG?w=960&h=600',
  ];

  int currentIndex = 0;

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion =
          await FlutterPlugins.platformVersion ?? 'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: [
              NativeImage(
                initUrl:
                    "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1113%2F0F420110430%2F200F4110430-6-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1658406221&t=4fcb0dc9d8c5d7a123ae4aff0d18e588",
                controller: controller,
              ),
              Text('Running on: $_platformVersion\n'),
              Spacer(),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  OutlinedButton(
                    onPressed: () {
                      if (currentIndex < 0) {
                        currentIndex = images.length - 1;
                      }
                      if (currentIndex >= images.length) {
                        currentIndex = 0;
                      }
                      controller.changeImage(images[currentIndex]);
                      currentIndex--;
                    },
                    child: Text("上一张"),
                  ),
                  OutlinedButton(
                    onPressed: () {
                      if (currentIndex >= images.length) {
                        currentIndex = 0;
                      }
                      if (currentIndex < 0) {
                        currentIndex = images.length - 1;
                      }
                      controller.changeImage(images[currentIndex]);
                      currentIndex++;
                    },
                    child: Text("下一张"),
                  ),
                  // TextField(controller: ,)
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

到此为止都OK了

 工程链接icon-default.png?t=M5H6https://download.csdn.net/download/nicepainkiller/85733897

猜你喜欢

转载自blog.csdn.net/nicepainkiller/article/details/125403744