GVKun编程网logo

android – Flutter App与背景音乐(外部)(flutter音乐播放插件)

16

在本文中,我们将为您详细介绍android–FlutterApp与背景音乐(外部)的相关知识,并且为您解答关于flutter音乐播放插件的疑问,此外,我们还会提供一些关于AndroidAPP打开flu

在本文中,我们将为您详细介绍android – Flutter App与背景音乐(外部)的相关知识,并且为您解答关于flutter音乐播放插件的疑问,此外,我们还会提供一些关于Android APP 打开 flutter app 并传递参数、Android Flutter 混合开发高仿大厂App、Android Flutter实现"斑马纹"背景的示例代码、android – Flutter是Instant App可用吗?的有用信息。

本文目录一览:

android – Flutter App与背景音乐(外部)(flutter音乐播放插件)

android – Flutter App与背景音乐(外部)(flutter音乐播放插件)

在后台运行的应用程序(即Google Play音乐,Apple Music,Spotify等)上播放音乐时,如果您在颤动应用中播放任何音频,则背景音乐会停止/暂停,并且在音频完成后不会重新启动播放.

我已经尝试使用iOS上的TTS库和AudioPlayer库.

关于如何在我的音频完成后恢复播放背景音频的任何提示(这实际上只是一个2/3秒的短语)?

尚不确定这是否会影响Android.

注意:在此示例中使用TTS,但它发生在我尝试过的每个库中.

Pubspec.yaml

tts: "^1.0.1"

码:

import 'package:Flutter/material.dart';
import 'package:tts/tts.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.red,
      ),
      home: new AudioExample(),
    );
  }
}

class AudioExample extends StatefulWidget {
  @override
  _AudioExampleState createState() => _AudioExampleState();
}

class _AudioExampleState extends State<AudioExample> {

  speak() async {
    Tts.speak('Hello World');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: new AppBar(
        title: new Text('Audio example app'),
      ),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        decoration: Boxdecoration(
          color: Colors.grey[300],
        ),
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            IconButton(
              icon: Icon(Icons.play_arrow),
              onpressed: speak,
              color: Colors.red,
              iconSize: 100.0,
            ),
            new Padding(
              padding: const EdgeInsets.all(20.0),
              child: Text(
                  'Will say hello world once clicked and pause background audio on iOS - how to restart background audio after playing has finished?',
                textAlign: TextAlign.center,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

解决方法:

你可以在Java文件中添加音乐而不是颤动并添加循环转到android / app / src / main / java / com / example / ProjectName / MainActivity.java

并添加

  mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.MusicName);
  mediaPlayer.setLooping(true);
  mediaPlayer.start();
  mediaPlayer.setLooping(true);

在onCreate方法中,转到名为res的文件夹,在其中创建一个名为raw的文件夹,并在其中输入您的音乐

Android APP 打开 flutter app 并传递参数

Android APP 打开 flutter app 并传递参数

1 flutter Android 端配置

1.1 manifest 配置修改

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTop"
    android:theme="@style/LaunchTheme"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">
    <!-- Specifies an Android theme to apply to this Activity as soon as
         the Android process has started. This theme is visible to the user
         while the Flutter UI initializes. After that, this theme continues
         to determine the Window background behind the Flutter UI. -->
    <meta-data
      android:name="io.flutter.embedding.android.NormalTheme"
      android:resource="@style/NormalTheme"
      />
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
        <!-- 添加如下代码 -->
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

1.2 在 Android 源代码的 mainActivity 为如下代码

package com.swan.flutter_framework

import android.os.Bundle
import io.flutter.plugin.common.MethodChannel
import io.flutter.embedding.android.FlutterActivity

class MainActivity : FlutterActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        //FlutterMain.startInitialization(this);
        super.onCreate(savedInstanceState)

       // GeneratedPluginRegistrant.registerWith(FlutterEngine(this)) // here is the error: Type mismatch. Required: FlutterEngine! Found: MainActivity
        //接收第三方app调运参数并且传递给flutter
        MethodChannel(flutterEngine?.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == InvokeMethod) {
                val greetings = successNativeCode()
                result.success(greetings)
            }
        }
    }

    private fun successNativeCode(): String {
        return "我是android原生跑过来的数据=>${intent.getStringExtra("test")}"
    }

    companion object {
        private const val CHANNEL = "com.swan.shareData"
        private const val InvokeMethod = "shareData"
    }
}

1.3 在要接受回传内容的 dart 文件中这样写

getSharedText() async {
  const String CHANNEL = "com.swan.shareData"; //这儿要与MethodChannel(flutterEngine?.dartExecutor, CHANNEL)中CHANNEL名称一致
  const String invokeMethod = "shareData"; //这儿要与 call.method == invokeMethod中invokeMethod名称一致
  var channel = const MethodChannel(CHANNEL);
  var result = await channel.invokeMethod(invokeMethod);
  ToastUtils.showToast("message=>$result");
  print(''message=>$result'');
}

1.4 源代码

  • android 端
package com.swan.flutter_framework

import android.os.Bundle
import io.flutter.plugin.common.MethodChannel
import io.flutter.embedding.android.FlutterActivity

class MainActivity : FlutterActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        //FlutterMain.startInitialization(this);
        super.onCreate(savedInstanceState)

       // GeneratedPluginRegistrant.registerWith(FlutterEngine(this)) // here is the error: Type mismatch. Required: FlutterEngine! Found: MainActivity
        //接收第三方app调运参数并且传递给flutter
        MethodChannel(flutterEngine?.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == InvokeMethod) {
                val greetings = successNativeCode()
                result.success(greetings)
            }
        }
    }

    private fun successNativeCode(): String {
        return "我是android原生跑过来的数据=>${intent.getStringExtra("test")}"
    }

    companion object {
        private const val CHANNEL = "com.swan.shareData"
        private const val InvokeMethod = "shareData"
    }
}
  • dart
import ''package:flutter/material.dart'';
import ''package:flutter/services.dart'';
import ''package:flutter_framework/app/navigator/my_bottom_app_bar.dart'';
import ''package:flutter_framework/app/pages/first_guild_page.dart'';

/// @Description 应用启动闪屏页
/// @Author swan
/// @Date 2021/12/30 9:37 上午
///
class IndexPage extends StatefulWidget {
  const IndexPage({Key? key}) : super(key: key);

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

class _IndexPageState extends State<IndexPage> with ProtocolModel,CheckUpdateUtil{
    getSharedText() async {
      const String CHANNEL = "com.swan.shareData"; //这儿要与MethodChannel(flutterEngine?.dartExecutor, CHANNEL)中CHANNEL名称一致
      const String invokeMethod = "shareData"; //这儿要与 call.method == invokeMethod中invokeMethod名称一致
      var channel = const MethodChannel(CHANNEL);
      var result = await channel.invokeMethod(invokeMethod);
      ToastUtils.showToast("message=>$result");
      print(''message=>$result'');
    }
  // 页面的初始化函数
  @override
  void initState() {
    super.initState();
    // 1 检查权限
    WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
      // 2 用户权限和隐私政策
      initDataNext();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Image.asset(Res.welcome),
      ),
    );
  }

  /// 初始化工具类
  Future<void> initDataNext() async {
    getSharedText();
  }
}

2 打开 APP 调用代码

Intent intent = new Intent();
//参数:包名,要打开的类名
intent.setClassName("com.swan.flutter_framework","com.swan.fluter_framework.MainActivity");
intent.putExtra("test","test");
startActivity(intent);

Android Flutter 混合开发高仿大厂App

Android Flutter 混合开发高仿大厂App

自上篇 Flutter 10天高仿大厂App及小技巧积累总结 的续篇,这次更是干货满满。

这篇文章将概述 Android组件化的架构搭建FlutterAndroid 如何混合开发 (整个App只有首页是用原生Android完成,其他页面都是引入之前的做好的Flutter页面) ,主宿主程序由 Android 搭建,采用了组件化的架构搭建整个 App ,不同业务,对应不同的 module 工程,业务之间采用接口通信 (ARouter) ,以 module 的形式混入 Flutter,通过 MethodChannelFlutter 端进行数据通信等,且这些功能实现源码开源,感兴趣的小伙伴可以移步至 GitHub。


以下博文会分为4个部分概述:

  • 项目完成的功能预览
  • 项目组件化结构分析
  • 项目功能详细概述(所用知识点)
  • Android Flutter 混合开发

项目完成的功能预览

首先,我们还是通过一个视频来快速预览下项目完成的功能和运行效果,如下

大家也可以 点击观看视频 (点击齿轮 --> 更多播放设置,可以隐藏黑边)

看完视频后,其实大部分功能和之前的 纯flutter项目 功能相同,只是首页新增了4个tab推荐页面及携程二楼和布局改变。

大家也可扫描,安装体验:

手机扫描二维码安装

项目组件化结构分析

项目结构图预览

其次,分析梳理下项目结构,项目的结构大致如图,还有一些细枝末节的没有体现在图里:

project structure

项目结构分析

业务工程

把具体独立的业务都拆分成单独的 module 减小项目的维护压力

  • ft_home: 首页模块,这个模块其实还可以继续拆分,可把4个 tab (精选、附近、景点、美食) 页都拆成模块,这里我暂时没有拆分,后续会完成
  • ft_destination: 目的地模块,其实并没有建立这个模块,因为直接引入了之前做好的 flutter 页面
  • ft_travel: 旅拍模块,同样也使用了 flutter 页面
  • flutter: flutter模块,这个模块是从 flutter_module 中自动生成的,后面介绍到

基础库工程

把具体的功能都封装成独立的库供业务模块使用,降低项目的维护成本及代码之间耦合性

  • lib_network: 网络库,使用 okhttp 插件二次封装,业务层简单的调用即可
  • lib_webview: 打开网页的webview库,使用了 agentweb 插件二次封装,业务层只需要一句代码即可完成网页的跳转
  • lib_image_loader: 图片加载库,使用了 glide 插件二次封装,业务层只需一句代码即可加载不同参数的图片
  • lib_asr: 百度AI语音库,通过 Android 集成好供 Flutter 端使用
  • lib_common_ui: 公共UI库,重复多次使用的页面集中管理
  • lib_base: 基础库,通过 ARouter 的 service 功能暴露接口提供服务给业务层,当然业务层也可以在这里暴露接口供外界使用

这里有一些使用的插件并没有在项目结构图里体现出来(结构图空间有限)。

插件

在这里把项目使用的插件整理列举出来供大家参考:

  • magicindicator 强大、可定制、易扩展的 ViewPager 指示器框架,首页的4个 tab (精选、附近、景点、美食) 就是用这个实现的。
  • immersionbar 一句代码轻松实现状态栏、导航栏沉浸式管理
  • pagerBottomTabStrip 页面底部和侧边的导航栏,首页、目的地、旅拍、我的页面切换就是用这个实现的。
  • rxjava/rxandroid 异步和链式编程
  • butterknife view注入插件,配合Android插件使用,可快速自动生成 init view的代码,不用写一句 findViewById 的代码。
  • gson json解析,配合Android插件使用,可快速生成实体类
  • smartRefreshLayout 智能下拉刷新框架,携程二楼及下拉刷新加载更多就是用这个实现的
  • eventbus 发布/订阅事件总线,优雅的完成组件之间通信
  • arouter 依赖注入、路由跳转、注册service,优雅的完成模块之间的通信
  • okhttp 网络请求插件
  • agentweb webview框架,进行简单的二次封装可优雅的进行网页跳转
  • glide 高性能、可扩展的图片加载插件
  • banner 图片轮播控件

基本就是这些了,应该没有漏的,插件的详细使用,请进入各插件的 GitHub 主页。

在此,把我项目的插件引入代码及版本管理的 gradle 代码贴出来,如下:

插件引入代码:

dependencies {
    implementation fileTree(dir: ''libs'', include: [''*.jar''])

    implementation rootProject.depsLibs.appcompat
    implementation rootProject.depsLibs.legacy
    implementation rootProject.depsLibs.recyclerview
    implementation rootProject.depsLibs.constraintlayout
    implementation rootProject.depsLibs.cardview

    //tab指示器
    implementation rootProject.depsLibs.magicindicator
    //沉浸式
    implementation rootProject.depsLibs.immersionbar
    //导航栏
    implementation rootProject.depsLibs.pagerBottomTabStrip
    //rxjava
    implementation rootProject.depsLibs.rxjava
    //rxandroid
    implementation rootProject.depsLibs.rxandroid
    //view 注入
    implementation rootProject.depsLibs.butterknife
    //view 注入
    annotationProcessor rootProject.depsLibs.butterknifeCompiler
    //gson
    implementation rootProject.depsLibs.gson
    //banner
    implementation rootProject.depsLibs.banner
    //smartRefreshLayout 上下拉刷新
    implementation rootProject.depsLibs.smartRefreshLayout
    implementation rootProject.depsLibs.refreshHeader
    implementation rootProject.depsLibs.refreshHeaderTwoLevel
    implementation rootProject.depsLibs.refreshFooter
    //eventbus
    implementation rootProject.depsLibs.eventbus
    //arouter库
    implementation(rootProject.depsLibs.arouterapi) {
        exclude group: ''com.android.support''
    }
    annotationProcessor rootProject.depsLibs.aroutercompiler

    //引入home模块
    implementation project('':ft_home'')
    //引入图片加载库
    implementation project('':lib_image_loader'')
    //引入网络库
    implementation project('':lib_network'')
    //webview
    implementation project('':lib_webview'')
    //引入基础ui库
    implementation project('':lib_common_ui'')
    //base库
    implementation project('':lib_base'')
    //引入flutter模块
    implementation project('':flutter'')
    //引入百度AI语音库
    implementation project('':lib_asr'')
}

版本管理代码 (统一管理版本号) :

ext {
    android = [
            compileSdkVersion: 29,
            buildToolsVersion: "29.0.0",
            minSdkVersion    : 19,
            targetSdkVersion : 29,
            applicationId    : ''net.lishaoy.android_ctrip'',
            versionCode      : 1,
            versionName      : ''1.0'',
            multiDexEnabled  : true,
    ]

    depsVersion = [
            appcompat            : ''1.1.0'',
            legacy               : ''1.0.0'',
            recyclerview         : ''1.0.0'',
            constraintlayout     : ''1.1.3'',
            cardview             : ''1.0.0'',
            magicindicator       : ''1.5.0'',
            immersionbar         : ''3.0.0'',
            pagerBottomTabStrip  : ''2.3.0X'',
            glide                : ''4.11.0'',
            glidecompiler        : ''4.11.0'',
            butterknife          : ''10.2.1'',
            butterknifeCompiler  : ''10.2.1'',
            rxjava               : ''3.0.0'',
            rxandroid            : ''3.0.0'',
            okhttp               : ''4.7.2'',
            okhttpLogging        : ''4.7.2'',
            gson                 : ''2.8.6'',
            banner               : ''2.0.10'',
            smartRefreshLayout   : ''2.0.1'',
            refreshHeader        : ''2.0.1'',
            refreshFooter        : ''2.0.1'',
            refreshHeaderTwoLevel: ''2.0.1'',
            eventbus             : ''3.2.0'',
            agentweb             : ''4.1.3'',
            arouterapi           : ''1.5.0'',
            aroutercompiler      : ''1.2.2'',

    ]

    depsLibs = [
            appcompat            : "androidx.appcompat:appcompat:${depsVersion.appcompat}",
            legacy               : "androidx.legacy:legacy-support-v4:${depsVersion.legacy}",
            recyclerview         : "androidx.recyclerview:recyclerview:${depsVersion.recyclerview}",
            constraintlayout     : "androidx.constraintlayout:constraintlayout:${depsVersion.constraintlayout}",
            cardview             : "androidx.cardview:cardview:${depsVersion.cardview}",
            magicindicator       : "com.github.hackware1993:MagicIndicator:${depsVersion.magicindicator}",
            immersionbar         : "com.gyf.immersionbar:immersionbar:${depsVersion.immersionbar}",
            pagerBottomTabStrip  : "me.majiajie:pager-bottom-tab-strip:${depsVersion.pagerBottomTabStrip}",
            glide                : "com.github.bumptech.glide:glide:${depsVersion.glide}",
            glidecompiler        : "com.github.bumptech.glide:compiler:${depsVersion.glidecompiler}",
            butterknife          : "com.jakewharton:butterknife:${depsVersion.butterknife}",
            butterknifeCompiler  : "com.jakewharton:butterknife-compiler:${depsVersion.butterknifeCompiler}",
            rxjava               : "io.reactivex.rxjava3:rxjava:${depsVersion.rxjava}",
            rxandroid            : "io.reactivex.rxjava3:rxandroid:${depsVersion.rxandroid}",
            okhttp               : "com.squareup.okhttp3:okhttp:${depsVersion.okhttp}",
            okhttpLogging        : "com.squareup.okhttp3:logging-interceptor:${depsVersion.okhttpLogging}",
            gson                 : "com.google.code.gson:gson:${depsVersion.gson}",
            banner               : "com.youth.banner:banner:${depsVersion.banner}",
            smartRefreshLayout   : "com.scwang.smart:refresh-layout-kernel:${depsVersion.smartRefreshLayout}",
            refreshHeader        : "com.scwang.smart:refresh-header-classics:${depsVersion.refreshHeader}",
            refreshHeaderTwoLevel: "com.scwang.smart:refresh-header-two-level:${depsVersion.refreshHeader}",
            refreshFooter        : "com.scwang.smart:refresh-footer-classics:${depsVersion.refreshFooter}",
            eventbus             : "org.greenrobot:eventbus:${depsVersion.eventbus}",
            agentweb             : "com.just.agentweb:agentweb:${depsVersion.agentweb}",
            arouterapi           : "com.alibaba:arouter-api:${depsVersion.arouterapi}",
            aroutercompiler      : "com.alibaba:arouter-compiler:${depsVersion.aroutercompiler}",
    ]
}

项目功能详细概述(所用知识点)

这里主要对首页功能及知识点进行概述,由于其他页面是引入了之前的 Flutter 页面, 具体功能在 Flutter 10天高仿大厂App及小技巧积累总结 已经介绍过了,在这就不再阐述。

首页重点概述以下功能的实现:

  • 下拉刷新、携程二楼
  • 搜索appBar
  • 渐变色网格导航
  • banner组件
  • 多状态的tab指示器 (滚动固定顶部)

下拉刷新、携程二楼

首先,看看具体的效果图,如图:

second floor

下拉刷新和携程二楼是使用 smartRefreshLayout 插件完成的,实现代码如下:

private void initRefreshMore() {
    homeHeader.setRefreshHeader(new ClassicsHeader(getContext()), -1, (int) Utils.dp2px(76)); //设置下拉刷新及二楼header的高度
    homeHeader.setFloorRate(1.6f); //设置二楼触发比率
    homeRefreshContainer.setPrimaryColorsId(R.color.colorPrimary, R.color.white); //设置下拉刷新及二楼提示文字颜色
    homeRefreshContainer.setOnMultiListener(new SimpleMultiListener() {
        @Override
        public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
            loadMore(refreshLayout); //加载更多
        }

        @Override
        public void onRefresh(@NonNull RefreshLayout refreshLayout) {
            refreshLayout.finishRefresh(1600); //设置下拉刷新延迟
        }

        @Override
        public void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight) {
            homeSecondFloorImg.setVisibility(View.VISIBLE);  //隐藏二楼背景图
            homeSearchBarContainer.setAlpha(1 - Math.min(percent, 1)); //改变searchBar透明度
        }

        @Override
        public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
            if (oldState == RefreshState.ReleaseToTwoLevel) {  //即将去往二楼状态处理
                homeSecondFloorImg.setVisibility(View.GONE);
                homeHeaderContent.animate().alpha(1).setDuration(666);
            } else if (newState == RefreshState.PullDownCanceled) { //下拉取消状态处理
                homeHeaderContent.animate().alpha(0).setDuration(666);
            } else if (newState == RefreshState.Refreshing) { //正在刷新状态处理
                homeHeaderContent.animate().alpha(0).setDuration(666);
            } else if (oldState == RefreshState.TwoLevelReleased) { // 准备去往二楼完成状态处理,这里打开webview
                WebViewImpl.getInstance().gotoWebView("https://m.ctrip.com/webapp/you/tsnap/secondFloorIndex.html?isHideNavBar=YES&s_guid=feb780be-c55a-4f92-a6cd-2d81e04d3241", true);
                homeHeader.finishTwoLevel();
            } else if (oldState == RefreshState.TwoLevel) { //到达二楼状态处理
                homeCustomScrollView.setVisibility(View.GONE);
                homeHeaderContent.animate().alpha(0).setDuration(666);
            } else if (oldState == RefreshState.TwoLevelFinish) { //二楼完成状态处理
                homeCustomScrollView.setVisibility(View.VISIBLE);
                homeCustomScrollView.animate().alpha(1).setDuration(666);
            }
        }

    });

}

XML 页面布局文件代码如下:

<com.scwang.smart.refresh.layout.SmartRefreshLayout
    android:id="@+id/home_refresh_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    app:srlAccentColor="@color/colorPrimary"
    app:srlPrimaryColor="@color/colorPrimary">
    <com.scwang.smart.refresh.header.TwoLevelHeader
        android:id="@+id/home_header"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="top">

        <ImageView
            android:id="@+id/home_second_floor_img"
            android:layout_width="match_parent"
            android:layout_height="460dp"
            android:layout_alignTop="@+id/home_header"
            android:scaleType="fitXY"
            android:src="@drawable/second_floor"
            android:visibility="gone"/>
        <FrameLayout
            android:id="@+id/home_header_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:alpha="0">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:src="@drawable/second_floor" />
        </FrameLayout>

    </com.scwang.smart.refresh.header.TwoLevelHeader>
    
    ...

    <com.scwang.smart.refresh.footer.ClassicsFooter
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</com.scwang.smart.refresh.layout.SmartRefreshLayout>

具体实现详情,可移步 GitHub 查看源码。

搜索appBar

搜索栏的滚动的 placeholder 文字是使用 banner 插件实现的,点击搜索框可跳转到搜索页面 (flutter写的搜索页面) ,跳转页面后可以把 placeholder 文字带到 flutter 搜索页面。

效果如图:

search bar

滚动的placeholder文字实现代码如下 (搜索框的实现就不再这里展示都是一些XML布局代码)

        homeSearchBarPlaceholder
                .setAdapter(new HomeSearchBarPlaceHolderAdapter(homeData.getSearchPlaceHolderList())) // 设置适配器
                .setOrientation(Banner.VERTICAL) // 设置滚动方向
                .setDelayTime(3600) // 设置间隔时间
                .setOnBannerListener(new OnBannerListener() {
                    @Override
                    public void OnBannerClick(Object data, int position) {  //点击打开 flutter 搜索页面
                        ARouter.getInstance()
                                .build("/home/search")
                                .withString("placeHolder", ((Home.SearchPlaceHolderListBean) data).getText())
                                .navigation();
                    }
                });
    }

searchBar的具体功能不过多阐述,和之前的项目一致。

渐变色网格导航

渐变色网格导航基本都是一些 XML 页面布局代码,只是我把它封装成了单独的组件,效果如图

GridNav

封装之后的引入就非常简单,代码如下:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@color/white">

    <!-- 网格导航 -->
    <net.lishaoy.ft_home.GridNavView
        android:id="@+id/home_grid_nav_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    ...

</LinearLayout>

具体实现详情,可移步 GitHub 查看源码。

banner组件

banner组件也是用 banner 插件实现的,如图

banner

实现代码如下:

private void initBanner() {
    homeBanner.addBannerLifecycleObserver(this)
            .setAdapter(new HomeBannerAdapter(homeData.getBannerList())) //设置适配器
            .setIndicator(new EllipseIndicator(getContext()))           //设置指示器,如图的指示器是我自定义的插件里并没有提供
            .setIndicatorSelectedColorRes(R.color.white)                //设置指示器颜色
            .setIndicatorSpace((int) BannerUtils.dp2px(10))             //设置间距
            .setBannerRound(BannerUtils.dp2px(6));                      //设置圆角

}

多状态的tab指示器

多状态的tab指示器的实现需要注意很多细节,因为它是在首页的 fragmentScrollView 里嵌入 viewPaper,首先你会发现 viewPaper 不显示的问题,其次是滚动不流畅的问题,这两个问题我的解决方案是:

  • viewPaper 不显示的问题:使用自定义的 ViewPager 重写 onMeasure 方法,重新计算高度
  • 滚动不流畅的问题:使用自定义的 ScrollView,重写 computeScrollonScrollChanged 重新获取滚动距离

实现效果如图:

tab page

这个功能实现代码过多不便在这里展示,具体实现详情,可移步 GitHub 查看源码。

Android Flutter 混合开发

这个项目的实现只有首页是用 Android 原生实现,其他的页面均是 Flutter 实现的,之前 纯Flutter项目。

Android 引入 Flutter 进行混合开发,需要以下几个步骤

  • 建立一个flutter module
  • 编写flutter代码 (创建 flutter 路由)
  • flutter 和 android 之间相互通信

下面依次概述这几部分是如何操作实现的。

建立一个flutter module

这个应该不用过多描述,基本操作大家都会 File --> New --> New Module 如图:

no-shadow

新建完成之后,android studio 会自动生成配置代码到 gradle 配置文件里,且生成一个 flutter 的 library 模块。

Tips:
新建的时候最好 flutter module 和 android 项目放到同级目录下;
新版的 android studio 才会自动生成 gradle 配置代码,老版本貌似需要手动配置

如,没有生成 gradle 配置代码,你需要在根项目的 settings.gradle 文件里手动加入如下配置:

setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir, //设置根路径,根据具体flutter module路径配置
  ''flutter_module/.android/include_flutter.groovy''
))

include '':flutter_module''

还需在宿主工程 (没改名的话都是app)build.gradle 引入 flutter, 如下:

dependencies {
    ...
    //引入flutter模块
    implementation project('':flutter'')
    ...
}

编写flutter代码

编写flutter代码,在 flutter module 里按照正常 flutter 开发流程编写 flutter 代码即可。 (我项目里的 flutter 的代码是之前项目都写好的,复制过来,改改包的引入问题,就可以运行了。)

这里需要注意的是,flutter 有且只有一个入口,就是 main() 函数,我们需要在这里处理好 flutter 页面的跳转问题。

在 android 端,创建 flutter 页面,代码如下:

    Flutter.createView(getActivity(),getLifecycle(),"destination");

Flutter.createView 需要3个参数 activitylifecycleroute ,这个 route 就是要传递到 flutter 端的,当然,它是 String 类型的,我们可以自由发挥传递普通字符串或 json 字符串等。

我们也可以通过其他的方式创建 flutter 页面,如: Flutter.createFragment()FlutterActivity.withNewEngine()FlutterFragment.createDefault() 等。

具体的使用,可前往 Flutter官方文档 查阅。

那么,flutter 端如何接收这个 route 参数,是通过 window.defaultRouteName,此项目里管理 flutter 端路由代码如下:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: ''Flutter model'',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        fontFamily: ''PingFang'',
      ),
      home: _widgetRoute(window.defaultRouteName), // 通过 window.defaultRouteName 接收 android 端传来的参数
    );
  }
}

Widget _widgetRoute(String defaultRouteName) {
    Map<String, dynamic> params = convert.jsonDecode(defaultRouteName); //解析参数
    defaultRouteName = params[''routeName''];
    placeHolder = params[''placeHolder''];

    switch (defaultRouteName) { // 根据参数返回对应的页面
        ...
        case ''destination/search'':
            return DestinationSearchPage(
                hideLeft: false,
        );
        ...
        default:
            return Center(
                child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                    Text(''not found $defaultRouteName'',
                        textDirection: TextDirection.ltr),
                ],
                ),
            );
    }
}

其实,flutter 端接收这个 route 参数,还有一种方法,就是通过 onGenerateRoute,它是 MaterialApp 里的一个方法。

代码如下:

onGenerateRoute: (settings){ //通过 settings.name 获取android端传来的参数
    return _widgetRoute(settings.name);
},

flutter 和 android 之间相互通信

flutter 端可以调用 android 端的方法及相互传递数据是如何实现的,flutter 官方提供了3个方法可以实现,分别是:

  • EventChannel:单向的持续通信,如:网络变化、传感器等。
  • MethodChannel:一次性通信,一般适用如方法的调用。
  • BasicMessageChannel:持续的双向通信。

此项目里采用了 MethodChannel 方法进行通信,如:flutter 端调用 android 端的AI智能语音方法以及 flutter 打开 android 端页面就是用 MethodChannel 实现的。

flutter 端调用 android 端的AI智能语音方法代码如下:

class AsrManager {
  static const MethodChannel _channel = const MethodChannel(''lib_asr'');
  //开始录音
  static Future<String> start({Map params}) async {
    return await _channel.invokeMethod(''start'', params ?? {});
  }
  //停止录音
    ...
  //取消录音
    ...
  //销毁
    ...
}

flutter 打开 android 端页面代码如下:

class MethodChannelPlugin {

  static const MethodChannel methodChannel = MethodChannel(''MethodChannelPlugin'');

  static Future<void> gotoDestinationSearchPage() async {
    try {
      await methodChannel.invokeMethod(''gotoDestinationSearchPage''); //gotoDestinationSearchPage 参数会传到android端
    } on PlatformException {
      print(''Failed go to gotoDestinationSearchPage'');
    }
  }
    ...
}

android 接收也是通过 MethodChannel ,具体实现代码如下:

public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {

    private static MethodChannel methodChannel;
    private Activity activity;

    private MethodChannelPlugin(Activity activity) {
        this.activity = activity;
    }

    //调用方通过 registerWith 来注册flutter页面
    public static void registerWith(FlutterView flutterView) {
        methodChannel = new MethodChannel(flutterView, "MethodChannelPlugin");
        MethodChannelPlugin instance = new MethodChannelPlugin((Activity) flutterView.getContext());
        methodChannel.setMethodCallHandler(instance);
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        if (methodCall.method.equals("gotoDestinationSearchPage")) { // 收到消息进行具体操作
            EventBus.getDefault().post(new GotoDestinationSearchPageEvent());
            result.success(200);
        } 
        ...
        else {
            result.notImplemented();
        }
    }
}

android flutter 混合开发基本就是这3个步骤,其他一些细节及具体的流程请参考 GitHub 项目源码。

最后附上项目地址和博客地址:

项目地址:https://github.com/persilee/android_ctrip
博客地址:https://h.lishaoy.net/androidctrip

Android Flutter实现

Android Flutter实现"斑马纹"背景的示例代码

由于工作中项目需求,需要将H5转换为Flutter代码。

其中的斑马纹背景需要根据接口返回的颜色来渲染,所以不能只是图片形式,无法通过decoration属性配置图片背景板。

楼主这边想到的方法就是通过 实现一个canvas绘制斑马纹类。使用Stack布局,将斑马纹放在下方作为背景板,需要展示的内容在上方。实现 “斑马纹”背景(需要变换颜色)

文章主要分为 效果图、实现思维、代码、计算过程解释。希望对大家有所帮助

最终效果图

实现思维

斑马纹(45°角,向左倾斜)

使用CustomPaint(size: Size(width, height), painter: 画笔)

CustomPaint(
   size: Size(widget.width, widget.height),
   painter: 画笔,
)

画笔

继承 CustomPainter类,实现paint(Canvas canvas, Size size)方法,根据 宽度、高度、画笔宽度、间距 计算出各个点位。使用canvas. drawLine方法 绘制出斑马纹。

@override
void paint(Canvas canvas, Size size) {
  …
  canvas. drawLine();
}

斑马纹坐标位置计算

2.82 = 2倍根号2

1.41 = 根号二

  • 填充个数= 最大高度 / (画笔宽度1.41+间距) 向上取整。(从0, 0开始,左侧会露出空位,所以需要填充)
  • 条纹个数 = 宽度/(画笔宽度1.41+间距) 向上取整。
  • (x轴y轴) 偏移量 =画笔宽度 / 2.82 (画笔起始点、结束点会露出一小节,需要计算x,y偏移量。将左上角x,y减去偏移量,右下角x,y加上偏移量,补充此部分)
  • 起点坐标 =((画笔宽度1.41+间距) * 条纹index – 偏移量,– 偏移量)
  • 终点坐标 =((画笔宽度1.41+间距) * 条纹index - 偏移量+高度, 高度+偏移量)

圆角裁剪(如果需要)

由于画笔绘制的是直角的,所以作为背景板会超出,需要裁剪掉四个角。使用

ClipRRect(
   borderRadius: BorderRadius.all(Radius.circular(10)),
   child: xxx
)

作为背景

使用Stack布局,实现斑马纹在下方作为背景板,需要展示的内容在上方

Stack(
  children: [
  	buildZebraBack(…), 
  	需要展示的内容
  ]
)

代码

使用处 main_page.dart

Stack(
  children: [
    Positioned(
      child: ZebraStripesBack(
          width: 335,
          height: 44,
          lineWidth: 10,
          spacing: 10,
          borderRaduis: 10,
          lineColor: Colors.blue),
      top: 0,
      left: 0,
    ),
    Container(
      width: 335,
      height: 44,
      alignment: Alignment.center,
      padding: EdgeInsets.only(
          top: 10,
          left: 12,
          bottom: 10,
          right: 12),
      child: Text(
              "英语",
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 14.sp,
                fontWeight: FontWeight.w500),
            )
      )
  ]
)

斑马纹具体实现类 zebra_stripes_back.dart

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

// 斑马纹具体实现类
class ZebraStripesBack extends StatefulWidget {
  ZebraStripesBack({
    this.width: 0,
    this.height: 0,
    this.spacing: 4,
    this.lineWidth: 4,
    this.lineColor: Colors.transparent,
    this.borderRaduis: 0,
  });

  final double width; // 容器宽度
  final double height; // 容器高度 
  final double lineWidth; // 斑马纹宽度
  final double spacing; // 间距
  final double borderRaduis; // 容器圆角
  final Color lineColor; // 斑马纹颜色

  @override
  State<StatefulWidget> createState() => _ZebraStripesBackState();
}

class _ZebraStripesBackState extends State<ZebraStripesBack> {
  @override
  void initState() {
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
        borderRadius: BorderRadius.all(Radius.circular(widget.borderRaduis)),
        child: CustomPaint(
          size: Size(widget.width, widget.height),
          painter: _ZebraStripesBackPainter(
            maxWidth: widget.width,
            maxHeight: widget.height,
            spacing: widget.spacing,
            lineWidth: widget.lineWidth,
            lineColor: widget.lineColor,
            borderRaduis: widget.borderRaduis,
          ),
        ));
  }
}

class _ZebraStripesBackPainter extends CustomPainter {
  _ZebraStripesBackPainter({
    this.maxWidth: 0,
    this.maxHeight: 0,
    this.spacing: 4,
    this.lineWidth: 4,
    this.lineColor: Colors.black12,
    this.borderRaduis: 0,
  });

  final double maxWidth;
  final double maxHeight;
  final double spacing;
  final double lineWidth;
  final Color lineColor;
  final double borderRaduis;

  @override
  void paint(Canvas canvas, Size size) {

    var paint = Paint()
      ..isAntiAlias = true
      ..style = PaintingStyle.fill
      ..color = lineColor
      ..strokeWidth = lineWidth;

    int number = 0; // 个数
    int fillNumber = 0; // 填充个数
    double lineAndSpace = lineWidth *1.41 + spacing; // 单个条纹宽 + 间距宽
    if (lineWidth > 0) {
      number = (maxWidth / lineAndSpace).ceil();
      fillNumber = (maxHeight / lineAndSpace).ceil(); // 填充个数
    }

    double deviation = lineWidth / 2.82; // x y轴偏移量 = width / 2倍根号2
    for (int i = -fillNumber; i < number; i++) {
      var left = lineAndSpace * i - deviation;
      double dx = left;
      double dy = -deviation;
      double dx1 = left + maxHeight;
      double dy1 = maxHeight + deviation;
      canvas.drawLine(
        Offset(dx, dy),
        Offset(dx1, dy1),
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

计算过程解释

偏移量计算过程

填充个数计算过程

为什么画笔宽度需要乘 根号二?

缺少-填充

缺少-偏移量

以上就是Android Flutter实现"斑马纹"背景的示例代码的详细内容,更多关于Flutte斑马纹背景的资料请关注其它相关文章!

您可能感兴趣的文章:
  • Android Flutter绘制有趣的 loading加载动画
  • Android Flutter利用CustomPaint绘制基本图形详解
  • Android Flutter制作交错动画的示例代码
  • Android Flutter表格组件Table的使用详解
  • Android Flutter实现GIF动画效果的方法详解
  • Android利用Flutter实现立体旋转效果
  • Android Flutter实现弹幕效果
  • Android Flutter实现有趣的页面滚动效果
  • Android使用Flutter实现录音插件

android – Flutter是Instant App可用吗?

android – Flutter是Instant App可用吗?

我对Flutter很新,在学习Flutter是否适用于Instant App之前我必须先了解.
谢谢.

解决方法

Flutter目前无法使用即时应用.实际上没有办法拆分功能,但Flutter也会让你的应用更大,正如你在本次讨论中看到的那样: https://groups.google.com/d/msg/flutter-dev/JpoMCJsPF-4/lzbUdLigAQAJ

“一个简单的Java应用程序不附带任何图形引擎或小部件,一切都由Android运行时提供,甚至大多数标准Java库.
一个Flutter应用程序必须附带几乎所有东西:引擎,小部件,标准库等…“

我并不完全确定有多大(这个例子说明安装大小约为7MB),但由于Instant Apps必须低于4MB,我认为Instant Apps不会很快到达Flutter.

今天关于android – Flutter App与背景音乐(外部)flutter音乐播放插件的分享就到这里,希望大家有所收获,若想了解更多关于Android APP 打开 flutter app 并传递参数、Android Flutter 混合开发高仿大厂App、Android Flutter实现"斑马纹"背景的示例代码、android – Flutter是Instant App可用吗?等相关知识,可以在本站进行查询。

本文标签: