From the first part Summary of App and tips accumulation of flutter's 10 day high imitation large factory This time, we are full of dry goods.
This article will outline the building of Android component architecture and how to mix the development of Flutter and Android (only the first page of the whole App is completed with native Android, and other pages are introduced with the previous Flutter pages). The main host program is built by Android, and the component architecture is used to build the whole App. Different businesses correspond to different modules Projects and businesses adopt the form of interface communication (ARouter), which is mixed in the form of module into the Flutter, and data communication is carried out through the method channel and the Flutter side, and these functions realize the source code open source, and interested partners can move to GitHub.
The following blog will be divided into four parts:
- Function preview of project completion
- Analysis of project component structure
- Detailed overview of project functions (knowledge points used)
- Android Flutter hybrid development
Function preview of project completion
First of all, we will use a video to quickly preview the functions and running effects of the project, as follows
You can Click 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 those before Pure shuttle project The functions are the same, but the home page has added 4 tab s, and Ctrip's second floor and layout have changed.
You can also scan and install the experience:
Analysis of project component structure
Preview of project structure chart
Secondly, analyze and sort out the project structure. The structure of the project is roughly as shown in the figure, and some details are not shown in the figure:
Project structure analysis
Business Engineering
Separate specific and independent businesses into separate module s to reduce the maintenance pressure of the project
- ft_home: the homepage module, which can be divided into four tab s (featured, nearby, scenic spot and delicious food) can be divided into modules. I haven't split it here for the time being, and it will be completed later
- ft_destination: the destination module has not been set up, because it directly introduces the previously completed flitter page
- ft_travel: travel photography module also uses the shuttle page
- Boutter: the boutter module. This module is from boutter_ Automatically generated in module
Foundation base project
Encapsulate specific functions into independent libraries for business modules to reduce maintenance costs and code coupling
- lib_network: network library, using okhttp Second encapsulation of plug-in, simple call of business layer
- lib_webview: open the webview Library of the web page, and use the agentweb Second encapsulation of plug-ins, the business layer only needs one code to complete the jump of web pages
- lib_image_loader: image loading library, used glide The second encapsulation of plug-ins enables the business layer to load pictures with different parameters in one sentence of code
- lib_asr: Baidu AI voice library, integrated with Android for the use of the Fletter
- lib_common_ui: public UI library, centralized management of reused pages
- lib_base: basic library, through ARouter The service function exposes the interface to provide services to the business layer, which can also expose the interface for external use
Some of the plug-ins used here are not reflected in the project structure diagram (structure diagram space is limited).
plug-in unit
Here, the plug-ins used in the project are listed for your reference:
- magicindicator Powerful, customizable and easy to expand ViewPager indicator framework, the four tab s (select, nearby, scenic spot, food) of the home page are realized by this.
- immersionbar One line of code easily realizes immersive management of status bar and navigation bar
- pagerBottomTabStrip The navigation bar at the bottom and side of the page, home page, destination, travel photo and my page switching are realized by this.
- rxjava/rxandroid Asynchronous and chained programming
- butterknife View injection plug-in, used with Android plug-in, can quickly and automatically generate init view code without writing a findViewById code.
- gson json parsing, used with Android plug-ins, can quickly generate entity classes
- smartRefreshLayout Intelligent pull-down refresh framework, Ctrip second floor and pull-down refresh load are more realized with this
- eventbus Publish / subscribe event bus, elegant communication between components
- arouter Rely on injection, route jump and register service to complete communication between modules gracefully
- okhttp Network request plug in
- agentweb webview framework, simple secondary encapsulation, elegant web page Jump
- glide High performance and extensible image loading plug-in
- banner Picture carousel control
Basically, there should be no missing. For the detailed use of plug-ins, please go to the GitHub homepage of each plug-in.
Here, the plug-in code of my project and the gradle code of version management are pasted 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 //smartRefreshLayout up and down refresh 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 //Introduce home module implementation project(':ft_home') //Import image loading Library implementation project(':lib_image_loader') //Introduce network library implementation project(':lib_network') //webview implementation project(':lib_webview') //Introduce 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 No.):
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. As other pages introduce the previous Flutter page, the specific functions are as follows: Summary of App and tips accumulation of flutter's 10 day high imitation large factory It has been introduced, and will not be elaborated here.
The home page focuses on the realization of the following functions:
- Pull down to refresh the second floor of Ctrip
- Search appBar
- Gradient grid navigation
- banner components
- Multi state tab indicator (scrolling fixed top)
Pull down to refresh the second floor of Ctrip
First, look at the specific renderings, as shown in the figure:
Pull down refresh and Ctrip second floor are used smartRefreshLayout The implementation code of the plug-in is as follows:
private void initRefreshMore() { homeHeader.setRefreshHeader(new ClassicsHeader(getContext()), -1, (int) Utils.dp2px(76)); //Set the height of pull-down refresh and second floor header homeHeader.setFloorRate(1.6f); //Set the second floor trigger ratio homeRefreshContainer.setPrimaryColorsId(R.color.colorPrimary, R.color.white); //Set dropdown 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 dropdown 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 homeSecondFloorImg.setVisibility(View.GONE); homeHeaderContent.animate().alpha(1).setDuration(666); } else if (newState == RefreshState.PullDownCanceled) { //Drop down cancel 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) { // Ready to go to the second floor to complete the status processing, here open 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) { //Arrive at the second floor for status processing homeCustomScrollView.setVisibility(View.GONE); homeHeaderContent.animate().alpha(0).setDuration(666); } else if (oldState == RefreshState.TwoLevelFinish) { //Second floor completion status processing homeCustomScrollView.setVisibility(View.VISIBLE); homeCustomScrollView.animate().alpha(1).setDuration(666); } } }); }
The XML 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>
Detailed implementation details can be moved GitHub Check the source code.
Search appBar
The scrolling placeholder text for the search bar uses the banner For plug-in implementation, click the search box to jump to the search page (the search page written by flitter), and then you can bring the placeholder text to the flitter search page.
The effect is as follows:
The implementation code of the scrolling placeholder text is as follows (the implementation of the search box is no longer shown here, but some XML layout codes):
homeSearchBarPlaceholder .setAdapter(new HomeSearchBarPlaceHolderAdapter(homeData.getSearchPlaceHolderList())) // Set adapter .setOrientation(Banner.VERTICAL) // Set scrolling direction .setDelayTime(3600) // Set interval .setOnBannerListener(new OnBannerListener() { @Override public void OnBannerClick(Object data, int position) { //Click to open the shuttle 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 the previous projects.
Gradient grid navigation
Gradient grid navigation is basically some XML page layout code, but I encapsulated it as a separate component, as shown in the figure
After encapsulation, the introduction 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>
Detailed implementation details can be moved GitHub Check the source code.
banner components
banner components are also used banner Implemented by plug-in, as shown in the figure
The implementation code is as follows:
private void initBanner() { homeBanner.addBannerLifecycleObserver(this) .setAdapter(new HomeBannerAdapter(homeData.getBannerList())) //Set adapter .setIndicator(new EllipseIndicator(getContext())) //Set the indicator, as shown in the figure, which 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 embeds viewPaper in the ScrollView of the fragment on the home page. First, you will find the problem that viewPaper does not display, and second, the problem that scrolling is not smooth. My solutions to these two problems are:
- Problems with viewPaper not displaying: overriding the onMeasure method with a custom ViewPager and recalculating the height
- The problem of rolling is not smooth: using the custom ScrollView, rewrite computeScroll and onScrollChanged to get the rolling distance again
The realization effect is as shown in the figure:
Too many codes for this function are inconvenient to display here. For details, please move GitHub Check the source code.
Android Flutter hybrid development
Only the home page of this project is implemented by Android, and other pages are implemented by Flutter Pure shuttle project.
The following steps are required for Android to introduce Flutter for hybrid development
- Create a fluent module
- Write fluent code (create fluent route)
- Communication between flitter and android
The following is an overview of how these parts are implemented.
Create a fluent module
This should not be described too much. For basic operations, you will see File > New > new module as shown in the figure below:
After the creation, android studio will automatically generate the configuration code into the gradle configuration file, and generate a library module of the fluent.
Tips:
When creating a new one, it is better to put the flitter module and android project in the same directory;
The new version of android studio will automatically generate the gradle configuration code. It seems that the old version needs to be manually configured
For example, there is no gradle configuration code generated, you need to settings.gradle Manually add the following configuration to 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 fluent module 'flutter_module/.android/include_flutter.groovy' )) include ':flutter_module'
It also needs to be in the host project (app if it's not renamed) build.gradle Introduce the fluent as follows:
dependencies { ... //Introduction of fluent module implementation project(':flutter') ... }
Write fluent code
Write the fluent code. In the fluent module, write the fluent code according to the normal fluent development process. (the code of the fluent in my project is written in the previous project. Copy it, change the introduction of the package, and then it can be run. )
It should be noted here that there is only one entry for flutter, which is the main() function. We need to deal with the jump problem of flutter page here.
On the android side, create the flitter page with the following code:
Flutter.createView(getActivity(),getLifecycle(),"destination");
Flutter.createView Three parameters, activity, lifecycle and route, are required. This route is to be passed to the flitter end. Of course, it is of String type. We can freely pass ordinary strings or json strings.
We can also create fluent pages in other ways, such as: Flutter.createFragment() , FlutterActivity.withNewEngine(), FlutterFragment.createDefault() etc.
For specific use, you can go to Official documentation of Flutter Check.
Then, how to receive the route parameter from the router is through window.defaultRouteName In this project, the routing code of the managed router 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), // Through window.defaultRouteName Receive parameters from android ); } } Widget _widgetRoute(String defaultRouteName) { Map<String, dynamic> params = convert.jsonDecode(defaultRouteName); //Analytic parameters defaultRouteName = params['routeName']; placeHolder = params['placeHolder']; switch (defaultRouteName) { // Return 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 to receive this route parameter on the fluent side, that is, through onGenerateRoute, which is a method in MaterialApp.
The code is as follows:
onGenerateRoute: (settings){ //Through settings.name Get parameters from android return _widgetRoute(settings.name); },
Communication between flitter and android
flutter can call android's methods and how to transfer data to each other. flutter officially provides three methods to implement, which are:
- EventChannel: one way continuous communication, such as network changes, sensors, etc.
- Method channel: one time communication, generally applicable to the call of methods.
- BasicMessageChannel: continuous two-way communication.
In this project, MethodChannel method is used for communication, such as: the flutter calls the AI intelligent voice method of android and the flutter opens the android page with MethodChannel.
The AI intelligent voice method code of the android terminal called by the fluent terminal 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 ... //Destruction ... }
The code of opening android page by FLUENT is as follows:
class MethodChannelPlugin { static const MethodChannel methodChannel = MethodChannel('MethodChannelPlugin'); static Future<void> gotoDestinationSearchPage() async { try { await methodChannel.invokeMethod('gotoDestinationSearchPage'); //gotoDestinationSearchPage parameter will be passed to android side } on PlatformException { print('Failed go to gotoDestinationSearchPage'); } } ... }
android is also received through MethodChannel. 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")) { // Receive the message for specific operation EventBus.getDefault().post(new GotoDestinationSearchPageEvent()); result.success(200); } ... else { result.notImplemented(); } } }
These three steps are the basic steps of Android flitter hybrid development. For other details and specific processes, please refer to GitHub Project 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