This is the second day of my participation in the August More text Challenge. For details, see:August is more challenging

Start with the nonsense: Navigation bar often is commonly used within the app UI functional modules, whether independent encapsulation or reference to a third party is best to have some of the personal development experience, this will improve the ability of individual thinking, today just tidy it up how to use half a day to encapsulate a navigation bar linkage effect, someone may ask, is old, why again on what questions right, The implementation principles are the same even for the old stuff, but there are more and more open programming ideas that can be found in different language features. A beginner’s study and summary

Step one, general overview of knowledge

1. Use the Notification class to realize the child component to bubble the message to the parent component.

2. Use EventBus singleton to realize that the parent component sends notification messages to the child component.

3, familiar with ScrollController implementation record scrollView scrolling state.

Familiar with viewPage components.

Step 2. UI effect display and structure analysis

First, the renderings:

You can see that the top custom navigation bar interacts with the viewPage below and slides to keep the final width visible.

Structural analysis:

Set the scroll direction to horizontal and set the Controller to monitor the scroll status and set the offset

New SingleChildScrollView(scrollDirection: axy. horizontal,// Set scrollController: _scrollController,// Set scrollController Child: new Row( mainAxisAlignment: MainAxisAlignment.start, children: _getBarItems(context), ), )Copy the code

The items inside need to be wrapped separately so that they can hold more information, such as their own width, for scrolling within the visual range when switching navigation bars.

Class GenneralChannelItem extends StatelessWidget {String name; int index; int currentSelectIndex; double left; double right; double width = 0; / / record width GenneralChannelItem ({required this. The name, the required this. The index, the required enclosing currentSelectIndex, enclosing left = 16,this.right = 16}); @override Widget build(BuildContext context) { TextStyle style = SystemFont.blodFontSizeAndColor(this.index == this.currentSelectIndex ? 17.0:17.0, this.index == this.currentselectIndex? Colors.red : Colors.black); This.width = SystemFont. GetTextSize (this.name, style).width + this.left + this.right; WSLGetWidgetWithNotification(width: this.width).dispatch(context); return new Container( padding: EdgeInsets.only(left: this.left,right: this.right), child: new Text(this.name,style: style,) ); }}Copy the code

The SystemFont class calculates the width of the code:

static Size getTextSize(String text, TextStyle style) { TextPainter painter = TextPainter( text: TextSpan(text: text, style: style), textDirection: TextDirection.ltr, maxLines: 1, ellipsis: '... ', a); painter.layout(); return painter.size; }Copy the code

Create a class that records the navigation bar item location information

class ChannelFrame{ double left; // Double width; {this.left = 0,this.width = 0}){}}Copy the code

Create a collection of navigation items and save the related information

List<Widget> _getBarItems(BuildContext context){ this.channelFrameList = []; this._maxScrollViewWidth = 0; / / titleList for navigation item title set return. This widget. The titleList. The map ((e) {/ / initializes the navigation item GenneralChannelItem GenneralChannelItem = new GenneralChannelItem( name: e, index: this.widget.titleList.indexOf(e), currentSelectIndex: this.widget.selectIndex, left: this._left, right: this._right, ); return new NotificationListener<WSLGetWidgetWithNotification>( onNotification: (notification){// Create a navigation layout record class to record the distance and width of the current item to the left. ChannelFrame channelFrame = ChannelFrame(left:this._maxScrollViewWidth,width: genneralChannelItem.width); / / save all ChannelFrame value, so that when the index value of the modified external viewPage or click on the item when modified scrollview offset this. ChannelFrameList. Add (ChannelFrame); this._maxScrollViewWidth += genneralChannelItem.width; Return false; // Return false; }, child: new GestureDetector( child: genneralChannelItem, onTap: (){ setState(() { this.widget.selectIndex = this.widget.titleList.indexOf(e); // Send a bubble message to switch the viewPage at the bottom of the outer layer. After receiving the message, the outside world uses the viewPage's Control to change the offset of the current viewPage. WSLCustomTabbarNotification(index: this.widget.selectIndex).dispatch(context); }); })); } ).toList(); }Copy the code

2. The bottom of the home page is viewPage. Because it is personal internal business logic, there is no too much detailed description here.

You need to change the state of the upper navigation item after the viewPage has been swiped. You need to set the event at creation time.

new PageView( controller: this.pageController! OnPageChanged: (index){setState(() {// Modify the navigation flag selectIndex = index; / / send the EventBus event, inform the navigation bar for selected item within visual range scroll EventBusUtils, getInstance (). The fire (WSLChannelScrollViewNeedScrollEvent ((index))); }); },)Copy the code

Step 3. How to perform the scrolling calculation of the navigation bar beyond the part?

Direct code

/ / declare an eventBus news listening object var _needChangeScrollviewEvent; / / initialize evenBus news listening @ override void initState () {_needChangeScrollviewEvent = EventBusUtils.getInstance().on<WSLChannelScrollViewNeedScrollEvent>().listen((event) { ChannelFrame channelFrame = this.channelFrameList[event.index]; Double centerX = channelframe.left + channelframe.width / 2.0; double centerX = channelframe.left + channelframe.width / 2.0; Double needScrollView = 0; double needScrollView = 0; If (centerx-_scrollController.offset < this.widget.width / 2.0) {needScrollView = (this.widget.width / 2.0-centerx + _scrollController.offset); If (_scrollController.offset > 0) { If (_scrollController.offset < needScrollView) {needScrollView = _scrollController.offset; } / / offset animation rolling _scrollController. AnimateTo (_scrollController. Offset - needScrollView, duration: the duration (milliseconds: 100), curve: Curves.linear); NeedScrollView = (centerx-_scrollController.offset - this.widget.width / 2.0);} else {needScrollView = (centerX-_scrollController.offset - this.widget.width / 2.0); If (_maxScrollViewWidth -_scrollController.offset -this.widget.width > 0) { Set to roll to the maximum position if(_maxScrollViewWidth - _scrollController.offset - this.widget.width < needScrollView) {needScrollView = _maxScrollViewWidth - _scrollController.offset - this.widget.width; } _scrollController.animateTo(_scrollController.offset + needScrollView, duration: Duration(milliseconds: 100), curve: Curves.linear); }}}); } @ override void the dispose () {/ / destroyed cancel evenBus listening _needChangeScrollviewEvent. Cancel (); super.dispose(); }Copy the code

Step 4. Send an EventBus event when clicking the navigation item or after sliding the viewPage, and move the navigation item to display within the visual scope.

EvenBus singleton class

class EventBusUtils { static EventBus _instance = new EventBus(); static EventBus getInstance() { return _instance; }}Copy the code

Register the message object class for EventBus

class WSLChannelScrollViewNeedScrollEvent {
  int index = 0;
  WSLChannelScrollViewNeedScrollEvent(this.index);
}
Copy the code

The evenBus event was sent

The index value is the index value of the currently selected navigation item, triggering a visible scrolling event. EventBusUtils.getInstance().fire(WSLChannelScrollViewNeedScrollEvent((index)));Copy the code

Well, simple navigation bar linkage function is finished, code is poor, god do not spray, if it is helpful to everyone, it is very pleased.