Flutter Boost 混合开发框架

目录
  • 一、Flutter Boost简介
  • 二、Flutter Boost集成
    • 2.1 Android集成
    • 2.2 iOS集成
  • 三、Flutter Boost架构
  • 四、FlutterBoost3.0更新
    • 4.1 不入侵引擎
    • 4.2 不区分Androidx和Support分支
    • 4.3 双端设计统一,接口统一
    • 4.4 支持 【打开flutter页面不再打开容器】 场景
    • 4.5 生命周期的精准通知
    • 4.6 其他Issue

一、Flutter Boost简介

众所周知,Flutter是一个由C++实现的Flutter Engine和由Dart实现的Framework组成的跨平台技术框架。其中,Flutter Engine负责线程管理、Dart VM状态管理以及Dart代码加载等工作,而Dart代码所实现的Framework则负责上层业务开发,如Flutter提供的组件等概念就是Framework的范畴。

随着Flutter的发展,国内越来越多的App开始接入Flutter。为了降低风险,大部分App采用渐进式方式引入Flutter,在App里选几个页面用Flutter来编写,但都碰到了相同的问题,在原生页面和Flutter页面共存的情况下,如何管理路由,以及原生页面与Flutter页面之间的切换和通信都是混合开发中需要解决的问题。然而,官方没有提供明确的解决方案,只是在混合开发时,官方建议开发者,应该使用同一个引擎支持多窗口绘制的能力,至少在逻辑上做到FlutterViewController是共享同一个引擎里面的资源。换句话说,官方希望所有的绘制窗口共享同一个主Isolate,而不是出现多个主Isolate的情况。不过,对于现在已经出现的多引擎模式问题,Flutter官方也没有提供好的解决方案。除了内存消耗严重外,多引擎模式还会带来如下一些问题

  • 冗余资源问题。多引擎模式下每个引擎的Isolate是相互独立的,虽然在逻辑上这并没有什么坏处,但是每个引擎底层都维护了一套图片缓存等比较消耗内存的对象,因此设备的内存消耗是非常严重的。
  • 插件注册问题。在Flutter插件中,消息传递需要依赖Messenger,而Messenger是由FlutterViewController去实现的。如果一个应用中同时存在多个FlutterViewController,那么插件的注册和通信将会变得混乱且难以维护。
  • Flutter组件和原生页面的差异化问题。通常,Flutter页面是由组件构成的,原生页面则是由ViewController或者Activity构成的。逻辑上来说,我们希望消除Flutter页面与原生页面的差异,否则在进行页面埋点和其它一些操作时增加一些额外的工作量。
  • 增加页面通信的复杂度。如果所有的Dart代码都运行在同一个引擎实例中,那么它们会共享同一个Isolate,可以用统一的框架完成组件之间的通信,但是如果存在多个引擎实例会让Isolate的管理变得更加复杂。

如果不解决多引擎问题,那么混合项目的导航栈如下图所示。

目前,对于原生工程混编Flutter工程出现的多引擎模式问题,国内主要有两种解决方案,一种是字节跳动的修改Flutter Engine源码方案,另一种是闲鱼开源的FlutterBoost。由于字节跳动的混合开发的方案没有开源,所以现在能使用的就剩下FlutterBoost方案。

FlutterBoost是闲鱼技术团队开发的一个可复用页面的插件,旨在把Flutter容器做成类似于浏览器的加载方案。为此,闲鱼技术团队为希望FlutterBoost能完成如下的基本功能:

  • 可复用的通用型混合开发方案。
  • 支持更加复杂的混合模式,比如支持Tab切换的场景。
  • 无侵入性方案,使用时不再依赖修改Flutter的方案。
  • 支持对页面生命周期进行统一的管理。
  • 具有统一明确的设计概念。

并且,最近Flutter Boost升级了3.0版本,并带来了如下的一些更新:

  • 不侵入引擎,兼容Flutter的各种版本,Flutter sdk的升级不需要再升级FlutterBoost,极大降低升级成本。
  • 不区分Androidx和Support分支。
  • 简化架构和接口,和FlutterBoost2.0比,代码减少了一半。
  • 双端统一,包括接口和设计上的统一。
  • 支持打开Flutter页面,不再打开容器场景。
  • 页面生命周期变化通知更方便业务使用。
  • 解决了2.0中的遗留问题,例如,Fragment接入困难、页面关闭后不能传递数据、dispose不执行,内存占用过高等。

二、Flutter Boost集成

在原生项目中集成Flutter Boost只需要将Flutter Boost看成是一个插件工程即可。和其他Flutter插件的集成方式一样,使用FlutterBoost之前需要先添加依赖。使用Android Studio打开混合工程的Flutter工程,在pubspec.yaml中添加FlutterBoost依赖插件,如下所示。

flutter_boost:
    git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: 'v3.0-hotfixes'

需要说明的是,此处的所依赖的FlutterBoost的版本与Flutter的版本是对应的,如果不对应使用过程中会出现版本不匹配的错误。然后,使用flutter packages get命令将FlutterBoost插件拉取到本地。

2.1 Android集成

使用Android Studio打开新建的原生Android工程,在原生Android工程的settings.gradle文件中添加如下代码。

setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir.parentFile,
  'flutter_library/.android/include_flutter.groovy'))

然后,打开原生Android工程app目录下的build.gradle文件,继续添加如下依赖脚本。

dependencies {
  implementation project(':flutter_boost')
  implementation project(':flutter')
}

重新编译构建原生Android工程,如果没有任何错误则说明Android成功了集成FlutterBoost。使用Flutter Boost 之前,需要先执行初始化。打开原生Android工程,新建一个继承FlutterApplication的Application,然后在onCreate()方法中初始化FlutterBoost,代码如下。

public class MyApplication extends FlutterApplication {

    @Override
    public void onCreate() {
        super.onCreate();

        FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {

            @Override
            public void pushNativeRoute(String pageName, HashMap<String, String> arguments) {
                Intent intent = new Intent(FlutterBoost.instance().currentActivity(), NativePageActivity.class);
                FlutterBoost.instance().currentActivity().startActivity(intent);
            }

            @Override
            public void pushFlutterRoute(String pageName, HashMap<String, String> arguments) {
                Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class, FlutterBoost.ENGINE_ID)
                        .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
                        .destroyEngineWithActivity(false)
                        .url(pageName)
                        .urlParams(arguments)
                        .build(FlutterBoost.instance().currentActivity());
                FlutterBoost.instance().currentActivity().startActivity(intent);
            }

        },engine->{
            engine.getPlugins();
        } );
    }
}

然后,打开原生Android工程下的AndroidManifest.xml文件,将Application替换成自定义的MyApplication,如下所示。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          package="com.idlefish.flutterboost.example">

    <application
        android:name="com.idlefish.flutterboost.example.MyApplication"
        android:label="flutter_boost_example"
        android:icon="@mipmap/ic_launcher">

        <activity
            android:name="com.idlefish.flutterboost.containers.FlutterBoostActivity"
            android:theme="@style/Theme.AppCompat"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize" >
            <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background"/>

        </activity>
        <meta-data android:name="flutterEmbedding"
                   android:value="2">
        </meta-data>
    </application>
</manifest>

由于Flutter Boost 是以插件的方式集成到原生Android项目的,所以我们可以在Native 打开和关闭Flutter模块的页面。

FlutterBoost.instance().open("flutterPage",params);
FlutterBoost.instance().close("uniqueId");

而Flutter Dart的使用如下。首先,我们可以在main.dart文件的程序入口main()方法中进行初始化。

void main() {
  runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
   static Map<String, FlutterBoostRouteFactory>
       routerMap = {
    '/': (settings, uniqueId) {
      return PageRouteBuilder<dynamic>(
          settings: settings, pageBuilder: (_, __, ___)
          => Container());
    },
    'embedded': (settings, uniqueId) {
      return PageRouteBuilder<dynamic>(
          settings: settings,
          pageBuilder: (_, __, ___) =>
          EmbeddedFirstRouteWidget());
    },
    'presentFlutterPage': (settings, uniqueId) {
      return PageRouteBuilder<dynamic>(
          settings: settings,
          pageBuilder: (_, __, ___) =>
          FlutterRouteWidget(
                params: settings.arguments,
                uniqueId: uniqueId,
              ));
    }};
   Route<dynamic> routeFactory(RouteSettings settings, String uniqueId) {
    FlutterBoostRouteFactory func =routerMap[settings.name];
    if (func == null) {
      return null;
    }
    return func(settings, uniqueId);
  }

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

  @override
  Widget build(BuildContext context) {
    return FlutterBoostApp(
      routeFactory
    );
  }

当然,还可以监听页面的生命周期,如下所示。

class SimpleWidget extends StatefulWidget {
  final Map params;
  final String messages;
  final String uniqueId;

  const SimpleWidget(this.uniqueId, this.params, this.messages);

  @override
  _SimpleWidgetState createState() => _SimpleWidgetState();
}

class _SimpleWidgetState extends State<SimpleWidget>
    with PageVisibilityObserver {
  static const String _kTag = 'xlog';
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('$_kTag#didChangeDependencies, ${widget.uniqueId}, $this');

  }

  @override
  void initState() {
    super.initState();
   PageVisibilityBinding.instance.addObserver(this, ModalRoute.of(context));
   print('$_kTag#initState, ${widget.uniqueId}, $this');
  }

  @override
  void dispose() {
    PageVisibilityBinding.instance.removeObserver(this);
    print('$_kTag#dispose, ${widget.uniqueId}, $this');
    super.dispose();
  }

  @override
  void onForeground() {
    print('$_kTag#onForeground, ${widget.uniqueId}, $this');
  }

  @override
  void onBackground() {
    print('$_kTag#onBackground, ${widget.uniqueId}, $this');
  }

  @override
  void onAppear(ChangeReason reason) {
    print('$_kTag#onAppear, ${widget.uniqueId}, $reason, $this');
  }

  void onDisappear(ChangeReason reason) {
    print('$_kTag#onDisappear, ${widget.uniqueId}, $reason, $this');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('tab_example'),
      ),
      body: SingleChildScrollView(
          physics: BouncingScrollPhysics(),
          child: Container(
              child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Container(
                margin: const EdgeInsets.only(top: 80.0),
                child: Text(
                  widget.messages,
                  style: TextStyle(fontSize: 28.0, color: Colors.blue),
                ),
                alignment: AlignmentDirectional.center,
              ),
              Container(
                margin: const EdgeInsets.only(top: 32.0),
                child: Text(
                  widget.uniqueId,
                  style: TextStyle(fontSize: 22.0, color: Colors.red),
                ),
                alignment: AlignmentDirectional.center,
              ),
              InkWell(
                child: Container(
                    padding: const EdgeInsets.all(8.0),
                    margin: const EdgeInsets.all(30.0),
                    color: Colors.yellow,
                    child: Text(
                      'open flutter page',
                      style: TextStyle(fontSize: 22.0, color: Colors.black),
                    )),
                onTap: () => BoostNavigator.of().push("flutterPage",
                    arguments: <String, String>{'from': widget.uniqueId}),
              )
              Container(
                height: 300,
                width: 200,
                child: Text(
                  '',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                ),
              )
            ],
          ))),
    );
  }
}

然后,运行项目,就可以从原生页面跳转到Flutter页面,如下图所示效果。

2.2 iOS集成

和Android的集成步骤一样,使用Xcode打开原生iOS工程,然后在iOS的AppDelegate文件中初始化Flutter Boost ,如下所示。

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  MyFlutterBoostDelegate* delegate=[[MyFlutterBoostDelegate alloc ] init];
    [[FlutterBoost instance] setup:application delegate:delegate callback:^(FlutterEngine *engine) {
    } ];

    return YES;
}
@end

下面是自定义的FlutterBoostDelegate的代码,如下所示。

@interface MyFlutterBoostDelegate : NSObject<FlutterBoostDelegate>
@property (nonatomic,strong) UINavigationController *navigationController;
@end

@implementation MyFlutterBoostDelegate

- (void) pushNativeRoute:(FBCommonParams*) params{
    BOOL animated = [params.arguments[@"animated"] boolValue];
    BOOL present= [params.arguments[@"present"] boolValue];
    UIViewControllerDemo *nvc = [[UIViewControllerDemo alloc] initWithNibName:@"UIViewControllerDemo" bundle:[NSBundle mainBundle]];
    if(present){
        [self.navigationController presentViewController:nvc animated:animated completion:^{
        }];
    }else{
        [self.navigationController pushViewController:nvc animated:animated];
    }
}

- (void) pushFlutterRoute:(FBCommonParams*)params {

    FlutterEngine* engine =  [[FlutterBoost instance ] getEngine];
    engine.viewController = nil;

    FBFlutterViewContainer *vc = FBFlutterViewContainer.new ;

    [vc setName:params.pageName params:params.arguments];

    BOOL animated = [params.arguments[@"animated"] boolValue];
    BOOL present= [params.arguments[@"present"] boolValue];
    if(present){
        [self.navigationController presentViewController:vc animated:animated completion:^{
        }];
    }else{
        [self.navigationController pushViewController:vc animated:animated];

    }
}

- (void) popRoute:(FBCommonParams*)params
         result:(NSDictionary *)result{

    FBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;

    if([vc isKindOfClass:FBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: params.uniqueId]){
        [vc dismissViewControllerAnimated:YES completion:^{}];
    }else{
        [self.navigationController popViewControllerAnimated:YES];
    }

}

@end

如果要在原生iOS代码中打开或关闭Flutter页面,可以使用下面的方式。

[[FlutterBoost instance] open:@"flutterPage" arguments:@{@"animated":@(YES)}  ];
[[FlutterBoost instance] open:@"secondStateful" arguments:@{@"present":@(YES)}];

三、Flutter Boost架构

对于混合工程来说,原生端和Flutter端对于页面的定义是不一样的。对于原生端而言,页面通常指的是一个ViewController或者Activity,而对于Flutter来说,页面通常指的是Flutter组件。FlutterBoost框架所要做的就是统一混合工程中页面的概念,或者说弱化Flutter组件对应容器页面的概念。换句话说,当有一个原生页面存在的时候,FlutteBoost就能保证一定有一个对应的Flutter的容器页面存在。

FlutterBoost框架其实就是由原生容器通过消息驱动Flutter页面容器,从而达到原生容器与Flutter容器同步的目的,而Flutter渲染的内容是由原生容器去驱动的,下面是Flutter Boost 给的一个Flutter Boost 的架构示意图。

可以看到,Flutter Boost插件分为平台和Dart两端,中间通过Message Channel连接。平台侧提供了Flutter引擎的配置和管理、Native容器的创建/销毁、页面可见性变化通知,以及Flutter页面的打开/关闭接口等。而Dart侧除了提供类似原生Navigator的页面导航接口的能力外,还负责Flutter页面的路由管理。

总的来说,正是基于共享同一个引擎的方案,使得FlutterBoost框架有效的解决了多引擎的问题。简单来说,FlutterBoost在Dart端引入了容器的概念,当存在多个Flutter页面时,FlutterBoost不需要再用栈的结构去维护现有页面,而是使用扁平化键值对映射的形式去维护当前所有的页面,并且每个页面拥有一个唯一的id

四、FlutterBoost3.0更新

4.1 不入侵引擎

为了解决官方引擎复用引起的问题,FlutterBoost2.0拷贝了Flutter引擎Embedding层的一些代码进行改造,这使得后期的升级成本极高。而FlutterBoost3.0采用继承的方式扩展FlutterActivity/FlutterFragment等组件的能力,并且通过在适当时机给Dart侧发送appIsResumed消息解决引擎复用时生命周期事件错乱导致的页面卡死问题,并且,FlutterBoost 3.0 也兼容最新的官方发布的 Flutter 2.0。

4.2 不区分Androidx和Support分支

FlutterBoost2.0通过自己实现FlutterActivityAndFragmentDelegate.Host接口来扩展FlutterActivity和FlutterFragment的能力,而getLifecycle是必须实现的接口,这就导致对androidx的依赖。这也是为什么FlutterBoostView的实现没有被放入FlutterBoost3.0插件中的原因。而FlutterBoost3.0通过继承的方式扩展FlutterActivity/FlutterFragment的能力的额外收益就是,可以做到不依赖androidx。

4.3 双端设计统一,接口统一

很多Flutter开发者只会一端,只会Android 或者只会IOS,但他需要接入双端,所以双端统一能降低他的 学习成本和接入成本。FlutterBoost3.0,在设计上 Android和IOS都做了对齐,特别接口上做到了参数级的对齐。

4.4 支持 【打开flutter页面不再打开容器】 场景

在Flutter模块内部,Flutter 页面跳转Flutter 页面是可以不需要再打开Flutter容器的,不打开容器,能节省内存开销。在FlutterBoost3.0上,打开容器和不打开容器的区别表现在用户接口上仅仅是withContainer参数是否为true就好。

InkWell(
  child: Container(
      color: Colors.yellow,
      child: Text(
        '打开外部路由',
        style: TextStyle(fontSize: 22.0, color: Colors.black),
      )),
  onTap: () => BoostNavigator.of().push("flutterPage",
      arguments: <String, String>{'from': widget.uniqueId}),
),
InkWell(
  child: Container(
      color: Colors.yellow,
      child: Text(
        '打开内部路由',
        style: TextStyle(fontSize: 22.0, color: Colors.black),
      )),
  onTap: () => BoostNavigator.of().push("flutterPage",
      withContainer: true,
      arguments: <String, String>{'from': widget.uniqueId}),
)

4.5 生命周期的精准通知

在FlutterBoost2.0上,每个页面都会收到页面生命周期通知,而FlutterBoost3.0只会通知页面可见性实际发生了变化的页面,接口也更符合flutter的设计。

4.6 其他Issue

除了上面的一些特性外,Flutter Boost 3.0版本还解决了如下一些问题:

  • 页面关闭后参数的传递,之前只有iOS支持,android不支持,目前在dart侧实现,Ios 和Android 都支持。
  • 解决了Android 状态栏字体和颜色问题。
  • 解决了页面回退willpopscope不起作用问题。
  • 解决了不在栈顶的页面也收到生命周期回调的问题
  • 解决了多次setState耗性能问题。
  • 提供了Framgent 多种接入方式的Demo,方便tab 场景的接入。
  • 生命周期的回调代码,可以用户代码里面with的方式接入,使用更简单。
  • 全面简化了,接入成本,包括 dart侧,android侧和ios
  • 丰富了demo,包含了基本场景,方便用户接入 和测试回归

到此这篇关于Flutter Boost 混合开发框架的文章就介绍到这了,更多相关Flutter Boost内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!,希望大家以后多多支持我们!

时间: 2021-08-23

Flutter listview如何实现下拉刷新上拉加载更多功能

目录 下拉刷新 RefreshIndicator 上拉加载更多 总结: 下拉刷新 在Flutter中系统已经为我们提供了google material design的刷新功能 , 样式与原生Android一样. 我们可以使用RefreshIndicator组件来实现Flutter中的下拉刷新,下面们还是先来看下如何使用吧 RefreshIndicator 构造方法: const RefreshIndicator({ Key key, @required this.child, this.disp

MacBook M1 Flutter环境搭建的实现步骤

目录 一.基础环境搭建 git: Flutter SDK: CocoaPods: 二.安装IDE IDEA: Xcode: AndroidStudio: 三.跑一个app试试 最近入手了Apple M1,MacBook Air,由于之前未使用苹果系列产品,并且Flutter官方和各项配套的软件环境也还没有成熟,导致搭建环境时碰到了不少坑,这里总结记录一波,来看文档的同学,有不懂的地方直接发评论或者消息就好 一.基础环境搭建 git: 我是装完homebrew,git就装好了,homebrew的安

Flutter多选按钮组件Checkbox使用方法详解

Flutter 中的多选按钮组件有两种,供大家参考,具体内容如下 1. Checkbox 多选按钮,一般用来表现一些简单的信息. 常用属性如下: (1). value  多选的值: (2). onChanged 选择改变触发的事件: (3). activeColor 选中时的颜色: (4). checkColor 选中后对号的颜色: 2. CheckboxListTile 包含更多信息的多选项,提供多种配置信息的属性,可以表现更丰富的信息. 常用的属性如下: (1). value  多选的值:

Flutter源码分析之自定义控件(RenderBox)指南

目录 前言 RenderObject 类继承层级解析 RenderBox 叶节点与父节点 控件的测量与布局 performResize 和 performLayout relayoutBoundary 叶节点 父节点 ParentData ParentData BoxParentData ContainerBoxParentData ContainerParentDataMixin 测量 child 大小 布局 child 控件的绘制 绘制自身内容 绘制 child repaintBoundar

flutter 动手撸一个城市选择citypicker功能

城市选择器在项目开发中一般都会用到,基于flutter版本的也有一个city_pickers但是已经很久没有人维护了,项目中之前也用的是这个,最近升级到flutter1.17.x后,发现有一定的概率闪退,无奈之下,只能自动动手撸一个了 demo下载地址:https://github.com/qqcc1388/city_picker CityPickerView能够实现以下功能 显示省市区地址,市或者区可以为空白数据 省市区数据支持自定义,但是格式要按照city.json中个格式来,如果需要外部传

Flutter 剪裁组件的使用

目录 效果展示 剪裁 Widget ClipRRect(圆角矩形剪裁) 其他属性 其他形状剪裁 ClipOval(椭圆剪裁) 其他属性 ClipRect(矩形剪裁) ClipPath(路径剪裁) 做个优化 源码仓库 参考链接 效果展示 在实际项目当中我们经常看到如下各种剪裁形状的效果,Flutter 为我们提供了非常方便的 Widget 很轻松就可以实现,下面我们来一起看看吧 剪裁 Widget ClipRRect(圆角矩形剪裁) 这里我们通过 borderRadius 属性就可以很方便的设置圆

Android 集成Flutter

目录 Android 集成Flutter 1, Hello Flutter 2, 引入 Flutter 模块 3,使用Flutter 3.1 添加依赖 3.2 运行Flutter页面 3.2.1 添加Flutter页面 4,Flutter APK 解析 5,踩过的坑 Android 集成Flutter Flutter 作为 Google 开源的新一代跨平台.高性能 UI 框架,旨在帮助开发者高效地构建出跨平台的.UI 与交互体验一致的精美应用,推出后一直倍受开发者的青睐. 当需要开发一个全新的应

Flutter给控件实现钻石般的微光特效

效果图 使用方法 Shimmer( baseColor: const Color(0x08ffffff), // 背景颜色 highlightColor: Colors.white, // 高光的颜色 loop: 2, // 闪烁循环次数,不传默认一直循环 child: Image.asset('assets/images/watermelon.png',width: 325, height: 260, fit: BoxFit.contain), ) 实现原理 ① 特效控件分为两层:底层显示调用

Android控件系列之Button以及Android监听器使用介绍

学习目的: 1.掌握在Android中如何建立Button 2.掌握Button的常用属性 3.掌握Button按钮的点击事件(监听器) Button是各种UI中最常用的控件之一,它同样也是Android开发中最受欢迎的控件之一,用户可以通过触摸它来触发一系列事件,要知道一个没有点击事件的Button是没有任何意义的,因为使用者的固定思维是见到它就想去点! 先看下Android中普通Button的样子: 以及点中Button后的样子: 我在Android控件系列之XML静态资源中已经强调了布局和

Android RecyclerView艺术般的控件使用完全解析

RecyclerView出现已经有一段时间了,相信大家肯定不陌生了,大家可以通过导入support-v7对其进行使用. 据官方的介绍,该控件用于在有限的窗口中展示大量数据集,其实这样功能的控件我们并不陌生,例如:ListView.GridView. 那么有了ListView.GridView为什么还需要RecyclerView这样的控件呢?整体上看RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecorati

Flutter 日期时间DatePicker控件及国际化

最近在学习Flutter,今天正好看到一篇文章收藏下来做个笔记,也分享给大家. 注意:无特殊说明,Flutter版本及Dart版本如下: Flutter版本: 1.12.13+hotfix.5 Dart版本: 2.7.0 DatePicker Flutter并没有DatePicker这个控件,需要使用showDatePicker方法弹出日期选择控件,基本用法如下: RaisedButton( onPressed: () async { var result = await showDatePic

Flutter 分页功能表格控件详细解析

前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析. PaginatedDataTable PaginatedDataTable是一个带分页功能的DataTable,生成一批数据,项目中此一般通过服务器获取,定义model类: class User { User(this.name, this.age, this.sex); final String name; final int age; final String sex; } 生成数据: List<User> _d

ASP.NET中验证控件的使用方法

对于这些常用的控件有效性验证,在Asp.Net中有单独的验证控件可供使用.他们可以满足一般的,诸如非空,范围.比较等的验证,为用户登录页面添加输入数据验证功能和验证码功能. 验证控件: Asp.Net中内置的验证控件有:RequiredFieldValidation.RangeValidation.RegularExpressValidation.CompareValidation.CustomValidation和ValidationSummary等六种.其中用户自定义验证控件,由于并不非常常

jQuery实现ajax无刷新分页页码控件

这段时间在做公司项目的时候需要用到ajax进行分页(点击页码也是无刷新的显示上一页或下一页的内容,不是一般的选择跳转),但是在网上找了一下,大部分分页插件都是直接刷新跳转的,感觉和我的需求差距比较大,就自己动手写了一个可以实现自己需要的功能的js来实现这个控件,实现的效果如下: 因为业务需要,我需要在一个页面创建多个这样的页码控件(有些是在ajax中生成的),显然为每一个控件写一个专属的js是不现实的,所以,我将公共部分提取出来,然后通过传入的选择器不同,对不同的控件进行相应方法的调用. 下面是

asp.net 打印控件使用方法

打印的效果及控制性虽然不是很好,但是也能勉强使用,应付一般的打印还是 可以的了.代码如下所示: 代码 复制代码 代码如下: //调用PrintControl.ExecWB(?,?)实现直接打印和打印预览功能.(直接用系统提供的print()方法打印无法隐藏某些区域) //preview:是否显示预览.null/false:不显示,true:显示 function printPage(preview) { try { var content=window.document.body.innerHT

javascript利用控件对windows的操作实现原理与应用

复制代码 代码如下: //windows 98 <script language=javascript> //var wsh = new ActiveXObject("WScript.Shell"); //wsh.Run("rundll32.exe user.exe,exitWindows"); </script> //win2000 or xp <script language=javascript> var wsh = new

C#中改变DataGridView控件边框颜色的方法

DataGridView是Visual Studio中一个最重要的数据控件.它可以应用在大多数场合,功能强大,使用灵活.本文要重点介绍一下,如果设置DataGridView的边框颜色. 比尔盖次说"Apple机上没有哪一个软件我是觉得应该是微软首创的",这说明盖次对微软软件功能强大的自信心.而乔布斯而说,微软的软件毫无艺术感可言!这说明什么,说明微软的东西--丑! 乔帮主不愧是乔帮主,真是入木三分,直中要害!是的,默认情况下的DataGridView,真是丑!尤其是那个黑色的边框,不是

android中webview控件和javascript交互实例

当我们要实现丰富的图文混排效果的时候,我们一般会使用webview,这是一个功能十分强大的的控件,来看看官方的解释: 复制代码 代码如下: A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit