Project preview

Source making

gif gif gif

Dart knowledge

  • Uncommon function
import 'dart:convert'; //var new_key=key.split('').reversed.join(); var new_key = String.fromCharCodes(key.codeUnits.reversed); // String inversion String pubKey = '010001'; var dec = int.parse(pubKey, radix: 16); Var hex = 255. ToRadixString (16); Print (int. Parse ('AA',radix: 16).toradixString (2)); Var ASCII = string.fromCharcode (97); Ascii2 = String. FromCharCodes ([97,98]); Var encoded = utf8.encode('qq'); var decoded = utf8.decode(encoded); Utf8.decode (stringdata.runes.tolist ()), 'aa'. CodeUnits // String to ASCII, array result to 'a'. CodeUnitAt (0) // Single character to ASCIICopy the code
  • String to byte
'qq'.codeUnits
'qq'.runes.toList()
utf8.encode('qq')
Copy the code
  • Viewing variable types
'a'.runtimeType
'a' is String
Copy the code
  • Random Chinese characters
  static String getChinese() {

    Random random = new Random();
    int max= 0x5000 - 0x4e00 + 1;
    int base= 0x4e00 ;
    int r = base+random.nextInt(max);
    return String.fromCharCode(r);
  }
Copy the code
  • Asynchronous traversal
await Future.forEach(list, (element) => null); Stream<FileSystemEntity> fileList = directory.list(followLinks: false); ForEach ((e) => null);Copy the code

Flutter related

Have something to say

Affected by the front-end thinking, I advise all the front end personnel to learn flutter by abandoning the front-end thinking

  1. In flutter, the overall length and width are fixed, i.e. the screen length and width (width= mediaquery.of (context).sie.width).
  2. There is no overflow auto-scroll, so when you heap components, be careful that the maximum height is equal to the screen height, beyond which an error will be reported, or use the scroll component SingleChildScrollView as the root element.
  3. There is no percentage length or width. The width and height of the container can be left unset. the values must be specified. If you want percentages, there are two ways to do it:

(1) Screen width height * percentage, (2) Use row or column components, where Expanded component has a parameter Flex, similar to the previous end, to achieve proportional allocation.

Check whether it is debug or release

 static const bool isProduction = const bool.fromEnvironment("dart.vm.product");
Copy the code

Thermal failure

If you drag and drop the file path in IDEA, congratulations on the hot loading failure of the file. The reason is that the file path becomes an absolute path, like C:\Users… , this path cannot be recognized by hot loading, so it needs to be changed to package:…

Clicking blank is invalid

(1) Set the behavior of the GestureDetector to hittestBehavior.opaque. (2) Use the InkWell component

Keyboard overflow

By default, a flutter will push the page up when the keyboard bounses. This may cause an overflow error. There are two solutions:

  1. Changing the Default Values
Scaffold (appBar: appBar (title: new Text (" home page "),), resizeToAvoidBottomPadding: False, // The default value is true to indicate that the page is pushed up, false to indicate that the page is not pushed up, in which case the keyboard may cover the page, similar to the stack effect);Copy the code
  1. Using scroll components

Use SingleChildScrollView or a listview component as the root element, at this point, do not have to set up resizeToAvoidBottomPadding to false, otherwise we may not get pushed on a page

A problem appears at the bottom of the showModalBottomSheet

  • At the top of the rounded corners
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
Copy the code
  • Highly restricted

The default height is half screen. Set isScrollControlled to true for full screen. If you do not want full screen, use the BoxConstraints component to limit the maximum height

  • Keyboard overflow

The first to useSingleChildScrollViewAs the root component it can scroll and then get the keyboard heightMediaQuery.of(context).viewInsets.bottomAs apaddingthebottomBecause it’s not herescaffoldThe Flutter does not push up the interface for us while working.

  • Status updates

showModalBottomSheetOther Dialog components, for example, are equivalent to jumping to a new routing page on this pagesetState(() {});Updating the status of the previous page does not work.

There are many solutions, one of which is to useBuilderComponent wraps the component to be updated and is called when it is updated(context as Element).markNeedsBuild();

TextField content is not centered vertically

ContentPadding: EdgeInsets. All (0.0),Copy the code

Water ripple removal

By default, a scrollable component slides to the top and bottom to create a water ripple effect, as shown in the picture. How to remove it? Delete the global as follows:

MaterialApp( builder: (context, child) { child= ScrollConfiguration( child: child, behavior: RefreshScrollBehavior(), ); return child; },)Copy the code
class RefreshScrollBehavior extends ScrollBehavior { @override Widget buildViewportChrome( BuildContext context, Widget child, AxisDirection axisDirection) { switch (getPlatform(context)) { case TargetPlatform.iOS: return child; case TargetPlatform.macOS: case TargetPlatform.android: return GlowingOverscrollIndicator( child: child, showLeading: False, // Does the top water ripple show showTrailing: false, // does the bottom water ripple show axisDirection: axisDirection, notificationPredicate: (notification) {if (notification. The depth = = 0) {/ / whether cross-border water ripple if (notification. The metrics. OutOfRange) {return false. } return true; } return false; }, color: Theme.of(context).primaryColor, ); case TargetPlatform.fuchsia: } return null; }}Copy the code

The gradient appbar

By setting flexibleSpace properties of AppBar

      flexibleSpace: Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.cyan, Colors.blue, Colors.blueAccent],
            ),
          ),
        ),
Copy the code

Dynamic gradient appBar

Use NotificationListener to listen for page scrolling and dynamically change the appBar transparency value.

   body: NotificationListener(
          onNotification: (scrollNotification) {
            if (scrollNotification is ScrollUpdateNotification) {
              if (scrollNotification.metrics.axis == Axis.vertical) _onScroll(scrollNotification.metrics.pixels);
            }
            return false;
          },
    
Copy the code
  _onScroll(offset) {
    //print(offset);
    if (offset > 200) return;
    double alpha = offset / 200;
    if (alpha < 0) {
      alpha = 0;
    } else if (alpha > 1) {
      alpha = 1;
    }
    setState(() {
      appBarAlpha = alpha;
    });
  }
Copy the code

Adaptive width and height

Use the FittedBox component to automatically adjust the content, and automatically adjust the font size when you exceed the width and height

Customize bottom navigation

As shown in the picture, this kind of navigation bar is not provided officially, we have to customize. BottomAppBarItem is a custom method that generates navigation buttons. The red dots are positioned in a stack with a play progress button in the middle, which is similar to Himalayan. Idea is CircularProgressIndicator components as a progress bar, shape of the Container component is specified as round shape: BoxShape. Circle, subcomponents are images, then relative positioning in CircularProgressIndicator

bottomNavigationBar: BottomAppBar( child: Consumer<IMNoticeProvider>( builder: (context,_imNotice,child){return Row(children: [bottomAppBarItem(0, Icons. Home, 'home ', badge: Badge1), bottomAppBarItem(1, Icons. Email, 'message ', badge: _imNotice. UnreadMsgCount), bottomAppBarItem(-1, Icons. Store, 'store ', badge: Badge1), bottomAppBarItem(2, Icons. Store, 'store ', badge: 101), bottomAppBarItem(3, Icons. Person,' my ', badge: 1, type: 'q')], mainAxisAlignment: mainAxisAlignment. SpaceAround, divided the navigation bar at the bottom of the transverse space mainAxisSize: / / mainAxisSize. Max,); },))Copy the code

PopupMenu popupMenu slides off

The official pop-up menu, need to click blank to close, how can slide the screen to close? Refer to wechat long press chat session. Official did not provide, we have to customize. Copy the source code of the showMenu function to the project folder and rename it to customShowMenu. At about line 770, add the GestureDetector component and we handle the sliding events ourselves.

 return MediaQuery.removePadding(
        context: context,
        removeTop: true,
        removeBottom: true,
        removeLeft: true,
        removeRight: true,
        child: GestureDetector(
          behavior: HitTestBehavior.translucent,
          onPanStart: (DragStartDetails details) {
            Navigator.of(context).maybePop();
          },
          child: Builder(
            builder: (BuildContext context) {
              return CustomSingleChildLayout(
                  delegate: _PopupMenuRouteLayout(
Copy the code

Tabbar Save location

By default, tabbar switches and the previous scrolling position is destroyed

var _tab1 = PageStorageKey('_tab1');
Copy the code

Custom search

As shown in the figure, the official search component showSearch needs to implement a SearchDelegate. In order to implement the bottom tabbar, we need to modify the source code. Copy the SearchDelegate source code into our project folder and rename it myShowSearchGoods and MySearchDelegateGoods. This is an abstract class and we’ll implement it later.

GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () => myShowSearchGoods(context: context, delegate: GoodsSearchBarDelegate()),
                  child: Container(
Copy the code
class GoodsSearchBarDelegate extends MySearchDelegateGoods<String> { List recentSuggest = List.from(MySheetSearch.getData().reversed.toList()); int id = 0; //List tabTitle = [' singles ', 'albums ',' singles ', 'singles ']; List songList = []; List albumList = []; List artistList = []; List sheetList = []; int page1,page2,page3,page4=0; The List tabTitle = [{" name ":" single ", "type" : 1}, {" name ":" album ", "type" : 10}, {" name ":" singer ", "type" : 100}, {" name ": ", "type": 1000},]; String oldQuery; RefreshController _controllerR1 =RefreshController(initialRefresh: false); RefreshController _controllerR2 =RefreshController(initialRefresh: false); RefreshController _controllerR3 =RefreshController(initialRefresh: false); RefreshController _controllerR4 =RefreshController(initialRefresh: false); GoodsSearchBarDelegate(); @override String get searchFieldLabel => 'search for something '; @override loadData(BuildContext context) async {// loadData if(query.isempty){utils.showtoast (' please enter the search content '); return false; } if (oldQuery ! = query) { oldQuery = query; songList = []; albumList = []; artistList = []; sheetList = []; page1=0; page2=0; page3=0; page4=0; } else showResults(context); if (tabController.index == 0 && (songList==null || songList.isNotEmpty)) return false; else if (tabController.index == 1 && (albumList==null || albumList.isNotEmpty)) return false; else if (tabController.index == 2 && (artistList==null || artistList.isNotEmpty)) return false; else if (tabController.index == 3 && (sheetList==null || sheetList.isNotEmpty)) return false; var cancel = Utils.showLoading(); List data = await GoodsSearch().getSearchRes(query, type: tabTitle[tabController.index]['type']); cancel(); if (tabController.index == 0) songList = data; else if (tabController.index == 1) albumList = data; else if (tabController.index == 2) artistList = data; else if (tabController.index == 3) sheetList = data; showResults(context); } loadMoreData(int page) async{ // var cancel = Utils.showLoading(); List data = await GoodsSearch().getSearchRes(query, type: tabTitle[tabController.index]['type'],page: page); // cancel(); return data; } @override Widget buildAppBarBottom(BuildContext context) { //tabbar return PreferredSize( preferredSize: Size. FromHeight (40.0), child: height (40), child: TabBar(controller: tabController, indicatorSize: TabBarIndicatorSize.label, labelColor: Theme.of(context).primaryColor, tabs: List.generate( tabTitle.length, (index) => Tab( text: tabTitle[index]['name'], )), ))); Dart Line 347, maintainState is set to true. LoadData is added on line 118. Some data rewrites like buildAppBarBottom are used for tabbar, 296 lines set historyIndex to delete the history update page, and 477 556 lines are used for carriage return to load data */Copy the code

Parent and child components call methods to each other

  • The child component calls the parent component

If there are few methods, pass the method name directly to the child component and the child component can call it. For more methods, use abstract classes. The parent component implements the abstract class and passes this as an argument to the child component.

abstract class BottomInputBarDelegate {
  String  userIdOrGroupId;
  int chatType=0;

  void insertNewMessage(EMMessage msg);
  void scrollBottom();
}
Copy the code
Void scrollBottom() {if (_msgListController.offset! = 0.0). / / _msgListController jumpTo (0.0); _msgListController. AnimateTo (0.0, the curve: Curves. EaseInOut, duration: const duration (milliseconds: 200), a); }... ChatBottomInputTool( this, childController: childController, )Copy the code
Class ChatBottomInputTool extends StatefulWidget{// Final ChildPageController childController; final BottomInputBarDelegate delegate; ChatBottomInputTool(this.delegate,{@required this.childController}); @override State<StatefulWidget> createState() { // TODO: implement createState return _ChatBottomInputToolState(childController); }}... widget.delegate.scrollBottom();Copy the code
  • The parent component calls the child component

With the aid of Controller Controller idea to achieve.

class ChildPageController{
  bool Function() closeBottom;
}
Copy the code
Class ChatBottomInputTool extends StatefulWidget{// Final ChildPageController childController; final BottomInputBarDelegate delegate; ChatBottomInputTool(this.delegate,{@required this.childController}); @override State<StatefulWidget> createState() { // TODO: implement createState return _ChatBottomInputToolState(childController); } } class _ChatBottomInputToolState extends State<ChatBottomInputTool> with WidgetsBindingObserver,TickerProviderStateMixin{ _ChatBottomInputToolState( ChildPageController _childController){ _childController.closeBottom=closeBottom; } bool closeBottom() { bool update = false; faceH = 0; MoreToolH = 0.0; showFaceA = true; showToolA = true; if (showMoreTool) { update = true; } if (showFace) { update = true; } if (_focusNode.hasFocus && curKeyboardH > 0) { _focusNode.unfocus(); SystemChannels.textInput.invokeMethod('TextInput.hide'); return true; } if (update) { setState(() {}); Future.delayed(Duration(milliseconds: 100)).then((value) { showFace = false; showMoreTool = false; }); return true; } return false; }Copy the code
class _ChatRoomPageState extends State<ChatRoomPage> with TickerProviderStateMixin implements EMMessageListener, BottomInputBarDelegate {/ / parent component final ChildPageController childController = ChildPageController (); . ChatBottomInputTool( this, childController: childController, ) ... childController.closeBottom();Copy the code

How do I make the components as high as the keyboard

The sample refers to more tools of wechat, that is, the + sign in the chat interface. Click to open it as high as the keyboard. I happened to find that wechat’s emoticon interface and tool interface are as high as the keyboard, which caught my attention. This makes the transition between the input box and emoticon very smooth. So how did wechat know the height of the keyboard without opening the input method? I checked some information on the Internet, but there is no relevant API, and the keyboard height can only be obtained when it is opened. Some people say that the height is obtained when you log in wechat, after measurement is not so. In the first time you login WeChat (clear the cache), open the chat box plus, there will be a default height, until you open the keyboard, WeChat height will be preserved, and reset the more the height of the tool interface, and WeChat in every time they open the keyboard, will check whether the height is same as last time, because the height of the input method can be adjustable. The above is only my personal speculation!

@override void didChangeMetrics() { final renderObject = context.findRenderObject(); final renderBox = renderObject as RenderBox; final offset = renderBox.localToGlobal(Offset.zero); final widgetRect = Rect.fromLTWH( offset.dx, offset.dy, renderBox.size.width, renderBox.size.height, ); final keyboardTopPixels = window.physicalSize.height - window.viewInsets.bottom; final keyboardTopPoints = keyboardTopPixels / window.devicePixelRatio; double keyH = widgetRect.bottom - keyboardTopPoints; Print (' get keyboard height $keyH'); }Copy the code