Android fluent hybrid development of high imitation large factory app

Time:2021-10-25

Android fluent hybrid development of high imitation large factory app

Since the last chapterSummary of flutter’s 10 day high imitation large factory app and tips accumulationThe sequel, this time is full of dry goods.

This article will outlineAndroid component architectureandFlutterandAndroidHow to mix development(only the home page of the whole app is completed with native Android, and other pages are flutter pages made before the introduction), the main host program is built by Android, using a component-based architectureApp, different services correspond to different module projects, and interface communication is adopted between services(ARouter), which is mixed into the flutter in the form of moduleMethodChannelandFlutterAnd these functions realize the open source of the source code. Interested partners can move toGitHub


The following blog post will be divided into four parts:

  • Function preview of project completion
  • Project component structure analysis
  • Detailed overview of project functions (knowledge points used)
  • Android fluent hybrid development

Function preview of project completion

First, we will use a video to quickly preview the functions and operation effects of the project, as shown below

You can, tooClick to watch the video (click gear — > more playback settings to hide the black edge)

After watching the video, in fact, most of the functions are the same as beforePure fluent projectThe functions are the same, but the home page has added four tab recommendation pages and Ctrip’s second floor and layout changes.

You can also scan and install the experience:

Android fluent hybrid development of high imitation large factory app

Project component structure analysis

Project structure chart Preview

Secondly, analyze and sort out the project structure. The project structure is roughly shown in the figure, and some details are not reflected in the figure:

Android fluent hybrid development of high imitation large factory app

Project structure analysis

Business Engineering

Split the specific and independent businesses into separate modules to reduce the maintenance pressure of the project

  • ft_ Home: home page module. In fact, this module can continue to be split. You can divide four tabs(selected, nearby, scenic spots and delicious food)All pages are split into modules. I haven’t split them here for the time being. They will be completed later
  • ft_ Destination: the destination module is not established because it directly introduces the previously prepared fluent page
  • ft_ Travel: the travel shooting module also uses the shuttle page
  • Shuttle: Shuttle module. This module is from shuttle_ Automatically generated in module, which will be described later

Foundation library project

The specific functions are encapsulated into independent libraries for business modules to reduce the maintenance cost of the project and the coupling between codes

  • lib_ Network: network library, usingokhttpThe plug-in is encapsulated twice, and the business layer can simply call it
  • lib_ WebView: open the WebView Library of the web page and useagentwebThe plug-in is encapsulated twice, and the business layer only needs one sentence of code to complete the jump of the web page
  • lib_ image_ Loader: image loading library, usingglideThe plug-in is encapsulated twice, and the business layer can load pictures with different parameters with only one sentence of code
  • lib_ ASR: Baidu AI voice library is integrated through Android for use on the flutter end
  • lib_ common_ UI: public UI library, centralized management of reusable pages
  • lib_ Base: basic library, throughARouterThe service function exposes the interface to provide services to the business layer. Of course, the business layer can also expose the interface here for external use

Some of the plug-ins used here are not reflected in the project structure diagram (the space of the structure diagram is limited).

plug-in unit

The plug-ins used in the project are listed here for your reference:

  • magicindicatorPowerful, customizable and easy to expand viewpager indicator framework, with 4 tabs on the home page(selected, nearby, scenic spots and delicious food)This is how it is implemented.
  • immersionbarOne sentence of code can easily realize the immersive management of status bar and navigation bar
  • pagerBottomTabStripThe navigation bar at the bottom and side of the page, home page, destination, travel photography and my page switching are realized by this.
  • rxjava/rxandroidAsynchronous and chained programming
  • butterknifeView injection plug-in, used in conjunction with Android plug-in, can quickly and automatically generate init view code without writing a sentencefindViewByIdCode for.
  • gsonJSON parsing, used with Android plug-ins, can quickly generate entity classes
  • smartRefreshLayoutIntelligent pull-down refresh framework, Ctrip second floor and pull-down refresh loading are more realized by this
  • eventbusPublish / subscribe event bus to gracefully complete the communication between components
  • arouterDependency injection, route jump and service registration can gracefully complete the communication between modules
  • okhttpNetwork request plug-in
  • agentwebWebView framework, simple secondary packaging, can gracefully jump to the web page
  • glideHigh performance and extensible picture loading plug-in
  • bannerPicture rotation control

That’s basically all. There should be no leakage. For detailed use of plug-ins, please enter the GitHub home page of each plug-in.

Here, I introduce the plug-in of my project into the of code and version managementgradleThe code is posted as follows:

Plug in import Code:

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 indicator
    implementation rootProject.depsLibs.magicindicator
    //Immersive
    implementation rootProject.depsLibs.immersionbar
    //Navigation bar
    implementation rootProject.depsLibs.pagerBottomTabStrip
    //rxjava
    implementation rootProject.depsLibs.rxjava
    //rxandroid
    implementation rootProject.depsLibs.rxandroid
    //View injection
    implementation rootProject.depsLibs.butterknife
    //View injection
    annotationProcessor rootProject.depsLibs.butterknifeCompiler
    //gson
    implementation rootProject.depsLibs.gson
    //banner
    implementation rootProject.depsLibs.banner
    //Drop down refresh on smartrefreshlayout
    implementation rootProject.depsLibs.smartRefreshLayout
    implementation rootProject.depsLibs.refreshHeader
    implementation rootProject.depsLibs.refreshHeaderTwoLevel
    implementation rootProject.depsLibs.refreshFooter
    //eventbus
    implementation rootProject.depsLibs.eventbus
    //Arouter Library
    implementation(rootProject.depsLibs.arouterapi) {
        exclude group: 'com.android.support'
    }
    annotationProcessor rootProject.depsLibs.aroutercompiler

    //Introducing home module
    implementation project(':ft_home')
    //Import picture loading Library
    implementation project(':lib_image_loader')
    //Import Network Library
    implementation project(':lib_network')
    //webview
    implementation project(':lib_webview')
    //Import basic UI Library
    implementation project(':lib_common_ui')
    //Base library
    implementation project(':lib_base')
    //Introduction of fluent module
    implementation project(':flutter')
    //Introduction of Baidu AI voice library
    implementation project(':lib_asr')
}

Version management code(unified management version number) :

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}",
    ]
}

Detailed overview of project functions (knowledge points used)

Here is an overview of the functions and knowledge points of the home page. Since other pages introduce the previous fluent page, the specific functions are shown inSummary of flutter’s 10 day high imitation large factory app and tips accumulationIt has been introduced and will not be described here.

The home page focuses on the implementation of the following functions:

  • Second floor, Ctrip
  • Search AppBar
  • Gradient grid navigation
  • Banner component
  • Multi state tab indicator(roll fixed top)

Second floor, Ctrip

First, take a look at the specific renderings, as shown in the figure:

Android fluent hybrid development of high imitation large factory app

Pull down refresh and Ctrip second floor are usedsmartRefreshLayoutThe plug-in is completed, and the implementation code is as follows:

private void initRefreshMore() {
    homeHeader.setRefreshHeader(new ClassicsHeader(getContext()), -1, (int) Utils.dp2px(76)); // Set the height of the drop-down refresh and the header on the second floor
    homeHeader.setFloorRate(1.6f); // Set the trigger ratio on the second floor
    homeRefreshContainer.setPrimaryColorsId(R.color.colorPrimary, R.color.white); // Set the drop-down refresh and second floor prompt text color
    homeRefreshContainer.setOnMultiListener(new SimpleMultiListener() {
        @Override
        public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
            loadMore(refreshLayout); // Load more
        }

        @Override
        public void onRefresh(@NonNull RefreshLayout refreshLayout) {
            refreshLayout.finishRefresh(1600); // Set pull-down refresh delay
        }

        @Override
        public void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight) {
            homeSecondFloorImg.setVisibility(View.VISIBLE);  // Hide second floor background
            homeSearchBarContainer.setAlpha(1 - Math.min(percent, 1)); // Change searchBar transparency
        }

        @Override
        public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
            If (oldstate = = refreshstate. Releasetotwolevel) {// going to the second floor for processing
                homeSecondFloorImg.setVisibility(View.GONE);
                homeHeaderContent.animate().alpha(1).setDuration(666);
            }Else if (newstate = = refreshstate. Pulldowncancelled) {// drop down cancels status processing
                homeHeaderContent.animate().alpha(0).setDuration(666);
            }Else if (newstate = = refreshstate. Refreshing) {// refreshing state processing
                homeHeaderContent.animate().alpha(0).setDuration(666);
            }Else if (oldstate = = refreshstate. Twolevelreleased) {// you are going to the second floor to complete the status processing. Open WebView here
                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) {// processing after reaching the second floor
                homeCustomScrollView.setVisibility(View.GONE);
                homeHeaderContent.animate().alpha(0).setDuration(666);
            }Else if (oldstate = = refreshstate. Twolevelfinish) {// the second floor completes the status processing
                homeCustomScrollView.setVisibility(View.VISIBLE);
                homeCustomScrollView.animate().alpha(1).setDuration(666);
            }
        }

    });

}

XMLThe page layout file code is as follows:

<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>

The specific implementation details can be moved step by stepGitHubView the source code.

Search AppBar

The scrolling placeholder text of the search bar is usedbannerPlug-in implementation, click the search box to jump to the search page(search page written by fluent), after jumping to the page, you can bring the placeholder text to the fluent search page.

The effect is shown in the figure:

Android fluent hybrid development of high imitation large factory app

The implementation code of scrolling placeholder text is as follows(the implementation of the search box is no longer shown here. It is all XML layout code)

homeSearchBarPlaceholder
                . setadapter (New homesearchbarplaceholderadapter (homedata. Getsearchplaceholderlist()) // set the adapter
                . setorientation (banner. Vertical) // set the scroll direction
                . setdelaytime (3600) // set the interval
                .setOnBannerListener(new OnBannerListener() {
                    @Override
                    Public void onbannerclick (object data, int position) {// click to open the filter search page
                        ARouter.getInstance()
                                .build("/home/search")
                                .withString("placeHolder", ((Home.SearchPlaceHolderListBean) data).getText())
                                .navigation();
                    }
                });
    }

The specific functions of searchBar are not elaborated, which is consistent with previous projects.

Gradient grid navigation

Gradient grid navigation is basically someXMLPage layout code, but I encapsulated it into a separate component, the effect is shown in the figure

Android fluent hybrid development of high imitation large factory app

The introduction after encapsulation is very simple. The code is as follows:

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

    <!--  Grid navigation -- >
    <net.lishaoy.ft_home.GridNavView
        android:id="@+id/home_grid_nav_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    ...

</LinearLayout>

The specific implementation details can be moved step by stepGitHubView the source code.

Banner component

The banner component is also usedbannerThe plug-in is implemented, as shown in the figure

Android fluent hybrid development of high imitation large factory app

The implementation code is as follows:

private void initBanner() {
    homeBanner.addBannerLifecycleObserver(this)
            . setadapter (New homebanneradapter (homedata. Getbannerlist()) // set the adapter
            . setindicator (New ellipseindicator (getcontext()) // set the indicator. The indicator shown in the figure is not provided in my customized plug-in
            . setindicatorselectedcolorres (r.color. White) // set indicator color
            . setindicatorspace ((int) bannerutils.dp2px (10)) // set spacing
            .setBannerRound(BannerUtils.dp2px(6));                      // Set fillet

}

Multi state tab indicator

The implementation of multi state tab indicator needs to pay attention to many details, because it is on the front pagefragmentofScrollViewEmbedded inviewPaperFirstly, you will find that the viewpaper does not display, and secondly, the scrolling is not smooth. My solutions to these two problems are:

  • Problems not shown in viewpaper: use customViewPagerrewriteonMeasureMethod, recalculate the height
  • Problem of poor scrolling: use customScrollView, rewritecomputeScrollandonScrollChangedRetrieve the rolling distance

The implementation effect is shown in the figure:

Android fluent hybrid development of high imitation large factory app

There are too many function implementation codes to be displayed here. The specific implementation details can be movedGitHubView the source code.

Android fluent hybrid development

Only the home page of this project is implemented natively by Android, and other pages are implemented by fluent, as beforePure flutter project

The following steps are required for Android to introduce fluent for hybrid development

  • Create a fluent module
  • Writing fluent code(create a shuttle route)
  • Flutter and Android communicate with each other

The following is an overview of how these parts are implemented.

Create a fluent module

This should not be described too much. Everyone can see the basic operation of file — > New — > new module, as shown in the figure:

Android fluent hybrid development of high imitation large factory app

After the creation is completed, Android studio will automatically generate the configuration code into the gradle configuration file, and generate a library module of fluent.

Tips:
When creating a new project, you’d better put the shuttle module and Android project under the same level directory;
The new version of Android studio will automatically generate the gradle configuration code. It seems that the old version needs to be configured manually

For example, if the gradle configuration code is not generated, you need to be in the root projectsettings.gradleManually add the following configuration into the file:

setBinding(new Binding([gradle: this]))
evaluate(new File(
  Settingsdir, // set the root path and configure it according to the path of the specific shuttle module
  'flutter_module/.android/include_flutter.groovy'
))

include ':flutter_module'

It also needs to be in the host project(if you don’t change your name, it’s all apps)ofbuild.gradleThe introduction of FLUENT is as follows:

dependencies {
    ...
    //Introduction of fluent module
    implementation project(':flutter')
    ...
}

Writing fluent code

Write the fluent code in the fluent module according to the normal fluent development process.(the code of fluent in my project is written in previous projects. Copy it, change the introduction of the package, and you can run it.)

It should be noted that there is only one entrance to the shuttle, that ismain()Function, we need to deal with the jump of the fluent page here.

On the Android side, create a fluent page with the following code:

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

Flutter.createViewThree parameters are requiredactivitylifecycleroute, this route is to be passed to the shuttle side. Of course, it is of string type. We can freely pass ordinary strings or JSON strings.

We can also create a shuttle page in other ways, such as:Flutter.createFragment()FlutterActivity.withNewEngine()FlutterFragment.createDefault()Wait.

For specific use, you can go toOfficial flutter documentationConsult.

Then, how to receive this route parameter at the fluent end is throughwindow.defaultRouteName, the routing code of the management shuttle end in this project is as follows:

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), // receive parameters from Android through window. Defaultroutename
    );
  }
}

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

    Switch (defaultroutename) {// returns the corresponding page according to the parameters
        ...
        case 'destination/search':
            return DestinationSearchPage(
                hideLeft: false,
        );
        ...
        default:
            return Center(
                child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                    Text('not found $defaultRouteName',
                        textDirection: TextDirection.ltr),
                ],
                ),
            );
    }
}

In fact, there is another way for the fluent end to receive this route parameter, that is, throughonGenerateRoute, it is a method in materialapp.

The code is as follows:

Ongeneraterote: (settings) {// get the parameters from Android through settings.name
    return _widgetRoute(settings.name);
},

Flutter and Android communicate with each other

The fluent end can call Android methods and how to transfer data to each other. The fluent official provides three methods to implement, namely:

  • Eventchannel: one-way continuous communication, such as network changes, sensors, etc.
  • Methodchannel: one time communication, generally applicable to method calls.
  • Basic message channel: continuous two-way communication.

Used in this projectMethodChannelMethods for communication, such as calling AI intelligent voice method on Android side by fluent side and opening Android side page by fluent sideMethodChannelImplemented.

The code of the AI intelligent voice method called by the fluent end to the Android end is as follows:

class AsrManager {
  static const MethodChannel _channel = const MethodChannel('lib_asr');
  //Start recording
  static Future<String> start({Map params}) async {
    return await _channel.invokeMethod('start', params ?? {});
  }
  //Stop recording
    ...
  //Cancel recording
    ...
  //Destroy
    ...
}

The code of the Android page opened by FLUENT is as follows:

class MethodChannelPlugin {

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

  static Future<void> gotoDestinationSearchPage() async {
    try {
      await methodChannel.invokeMethod('gotoDestinationSearchPage'); // The gotodestinationsearchpage parameter will be passed to the Android side
    } on PlatformException {
      print('Failed go to gotoDestinationSearchPage');
    }
  }
    ...
}

Android is also received throughMethodChannel, the specific implementation code is as follows:

public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {

    private static MethodChannel methodChannel;
    private Activity activity;

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

    //The caller registers the fluent page through registerwith
    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") {// carry out the specific operation after receiving the message
            EventBus.getDefault().post(new GotoDestinationSearchPageEvent());
            result.success(200);
        } 
        ...
        else {
            result.notImplemented();
        }
    }
}

The mixed development of Android shuttle is basically these three steps. Please refer to other details and specific processesGitHubProject source code.

Finally, attach the project address and blog address:

Project address:https://github.com/persilee/android_ctrip
Blog address:https://h.lishaoy.net/androidctrip