In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
In this article, the editor introduces in detail "how to use Flutter to achieve KuGou's smooth Tabbar effect". The content is detailed, the steps are clear, and the details are handled properly. I hope this "how to use Flutter to achieve KuGou's smooth Tabbar effect" article can help you solve your doubts.
Analysis effect
By studying the animation of KuGou Tabbar, we can find that the dot is displayed at the center of the current Tab by default, and the effect of sliding is divided into two parts:
Translate from the center of a single Tab A to the center of Tab B according to the X axis
The length of the indicator changes from the dot to the dot and then shortens to the dot. The maximum length is variable, which is related to the size and distance of the two Tab.
Although the indicator relies on size and offset of Tab to transform, it is basically rendered at the same time as Tab, and the whole process is very smooth.
In general, KuGou's effect is to change the indicator of the rendered animation.
Development ideas
From the above analysis, it is clear that the sliding effect of the indicator must be related to the size and offset of each Tab. In Flutter, when we get the rendering information, we can immediately think of GlobalKey. We can get the Rander information through the currentContext object of GlobalKey, but this can only be obtained after the view rendering, that is, the Tab rendering can start to calculate and render the indicator. It is obvious that it does not meet the experience requirements, and frequent use of GlobalKey can lead to poor performance.
To change the way of thinking, we need to constantly send information to the indicator during Tab rendering, and then update the indicator, which naturally reminds us of CustomPainter. In Tab updateWidget, constantly passing the Rander information to the brush Painter, and then updating the paint, this makes perfect sense in theory.
Flutter Tabbar parsing source code
To test my thinking, I began to study how the official Tabbar was written:
Go to the TabBar class and look directly at the build method. You can see that Globalkey is added to each Tab, and then the indicator is drawn with CustomPaint.
Widget build (BuildContext context) {/ /... Omit some of the code here. Final List wrappedTabs = List.generate (widget.tabs.length, (int index) {const double verticalAdjustment = (_ kTextAndIconTabHeight-_ kTabHeight) / 2.0; EdgeInsetsGeometry? AdjustedPadding; / / add Globalkey for tab to obtain Tab rendering information if (widget.tabs [index] is PreferredSizeWidget) {final PreferredSizeWidget tab = widget.tabs [index] as PreferredSizeWidget; if (widget.tabHasTextAndIcon & & tab.preferredSize.height = = _ kTabHeight) {if (widget.labelPadding! = null | | tabBarTheme.labelPadding! = null) {adjustedPadding = (widget.labelPadding??) TabBarTheme.labelPadding!) .add (const EdgeInsets.symmetric (vertical: verticalAdjustment));} else {adjustedPadding = const EdgeInsets.symmetric (vertical: verticalAdjustment, horizontal: 16.0);} / / Omitting some of the code here. / / you can see that the indicator is the CustomPaint object Widget tabBar = CustomPaint (painter: _ indicatorPainter, child: _ TabStyle (animation: kAlwaysDismissedAnimation, selected: false, labelColor: widget.labelColor, unselectedLabelColor: widget.unselectedLabelColor, labelStyle: widget.labelStyle, unselectedLabelStyle: widget.unselectedLabelStyle Child: _ TabLabelBar (onPerformLayout: _ saveTabOffsets, children: wrappedTabs,),)
The drawing indicator with CustomPaint is consistent with our expectations, so how to transfer the drawn size and offset into it. Let's see that _ TabLabelBar inherits from Flex, and Flex inherits from MultiChildRenderObjectWidget, overriding its createRenderObject method
Class _ TabLabelBar extends Flex {_ TabLabelBar ({Key? Key, List children = const [], required this.onPerformLayout,}): super (key: key, children: children, direction: Axis.horizontal, mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, verticalDirection: VerticalDirection.down,); final _ LayoutCallback onPerformLayout @ override RenderFlex createRenderObject (BuildContext context) {/ / check out _ TabLabelBarRenderer return _ TabLabelBarRenderer (direction: direction, mainAxisAlignment: mainAxisAlignment, mainAxisSize: mainAxisSize, crossAxisAlignment: crossAxisAlignment, textDirection: getEffectiveTextDirection (context)!, verticalDirection: verticalDirection, onPerformLayout: onPerformLayout,);} @ override void updateRenderObject (BuildContext context, _ TabLabelBarRenderer renderObject) {super.updateRenderObject (context, renderObject); renderObject.onPerformLayout = onPerformLayout;}}
View the real rendered object: _ TabLabelBarRenderer, return the rendered size and offset in performLayout, and save it to _ indicatorPainter through the _ saveTabOffsets method passed in by TabBar. _ saveTabOffsets is particularly important to notify Painter of the rendering displacement of Tabbar, so that Painter can easily calculate the width difference between tab.
Class _ TabLabelBarRenderer extends RenderFlex {_ TabLabelBarRenderer ({List? Children, required Axis direction, required MainAxisSize mainAxisSize, required MainAxisAlignment mainAxisAlignment, required CrossAxisAlignment crossAxisAlignment, required TextDirection textDirection, required VerticalDirection verticalDirection, required this.onPerformLayout,}): assert (onPerformLayout! = null), assert (textDirection! = null), super (children: children, direction: direction, mainAxisSize: mainAxisSize, mainAxisAlignment: mainAxisAlignment, crossAxisAlignment: crossAxisAlignment, textDirection: textDirection, verticalDirection: verticalDirection ) _ LayoutCallback onPerformLayout; @ override void performLayout () {super.performLayout (); / / xOffsets will contain childCount+1 values, giving the offsets of the / / leading edge of the first tab as the first value, of the leading edge of / / the each subsequent tab as each subsequent value, and of the trailing / / edge of the last tab as the last value. RenderBox? Child = firstChild; final List xOffsets = []; while (child! = null) {final FlexParentData childParentData = child.parentData! As FlexParentData; xOffsets.add (childParentData.offset.dx); assert (child.parentData = = childParentData); child = childParentData.nextSibling;} assert (textDirection! = null); switch (textDirection!) {case TextDirection.rtl: xOffsets.insert (0, size.width); break; case TextDirection.ltr: xOffsets.add (size.width); break } onPerformLayout (xOffsets, textDirectionals, size.width);}}
Update indicators through the didChangeDependencies and didUpdateWidget lifecycles in Tabbar
@ overridevoid didChangeDependencies () {super.didChangeDependencies (); assert (debugCheckHasMaterial (context)); final TabBarTheme tabBarTheme = TabBarTheme.of (context); _ updateTabController (); _ initIndicatorPainter (adjustedPadding, tabBarTheme);} @ overridevoid didUpdateWidget (KuGouTabBar oldWidget) {super.didUpdateWidget (oldWidget); final TabBarTheme tabBarTheme = TabBarTheme.of (context); if (widget.controller! = oldWidget.controller) {_ updateTabController (); _ initIndicatorPainter (adjustedPadding, tabBarTheme) } else if (widget.indicatorColor! = oldWidget.indicatorColor | | widget.indicatorWeight! = oldWidget.indicatorWeight | | widget.indicatorSize! = oldWidget.indicatorSize | | widget.indicator! = oldWidget.indicator) {_ initIndicatorPainter (adjustedPadding, tabBarTheme);} if (widget.tabs.length > oldWidget.tabs.length) {final int delta = widget.tabs.length-oldWidget.tabs.length; _ tabKeys.addAll (List.generate (delta, (int n) = > GlobalKey ());} else if (widget.tabs.length)
< oldWidget.tabs.length) { _tabKeys.removeRange(widget.tabs.length, oldWidget.tabs.length); }} 然后重点就在指示器_IndicatorPainter如何进行绘制了。 实现步骤 通过理解Flutter Tabbar的实现思路,大体跟我们预想的差不多。不过官方继承了Flex来计算Offset和size,实现起来很优雅。所以我也不班门弄斧了,直接改动官方的Tabbar就可以了。 创建KuGouTabbar,复制官方代码,修改引用,删除无关的类,只保留Tabbar相关的代码。 2. 重点修改_IndicatorPainter,根据我们的需求来绘制指示器。在painter方法中,我们可以通过controller拿到当前tab的index以及animation!.value, 我们模拟下切换的过程,当tab从第0个移到第1个,动画的值从0变成1,然后动画走到0.5时,tab的index会从0突然变为1,指示器应该是先变长,然后在动画走到0.5时,再变短。因此动画0.5之前,我们用动画的value-index作为指示器缩放的倍数,指示器不断增大;动画0.5之后,用index-value作为缩放倍数,不断缩小。 final double index = controller.index.toDouble();final double value = controller.animation!.value;/// 改动 ltr为false,表示索引还是0,动画执行未超过50%;ltr为true,表示索引变为1,动画执行超过50%final bool ltr = index >Value;final int from = (ltr? Value.floor (): value.ceil (). Clamp (0, maxTabIndex); final int to = (ltr? From + 1: from-1). Clamp (0, maxTabIndex); / / change determines whether to zoom in or out through ltr. The formula can be obtained: ltr? Index-value): (value-index) final Rect fromRect = indicatorRect (size, from, ltr? (index-value): (value-index)); / / change final Rect toRect = indicatorRect (size, to, ltr? (index-value): (value-index)); _ currentRect = Rect.lerp (fromRect, toRect, (value-from) .abs ())
On the premise that the indicator receives the zoom multiple, the maximum width of the indicator needs to be calculated, and the indicator should reach the maximum width according to 0.5 of the animation, that is, when it is moved to the middle of the animation. So the maximum width of the indicator requires ✖️ 2. Take a look at the following code:
Class _ IndicatorPainter extends CustomPainter {. Some of the code is omitted here. Void saveTabOffsets (List? TabOffsets, TextDirection? TextDirection) {_ currentTabOffsets = tabOffsets; _ currentTextDirection = textDirection;} / / _ currentTabOffsets [is the offset of the start edge of the tab at index], and / / _ currentTabOffsets [_ currentTabOffsets.length] is the end edge of the last tab. Int get maxTabIndex = > _ currentTabOffsets.length-2; double centerOf (int tabIndex) {assert (_ currentTabOffsets! = null); assert (_ currentTabOffsetsroom.isNotEmpty); assert (tabIndex > = 0); assert (tabIndex = 0); assert (tabIndex minWidth? Res: minWidth, tabBarSize.height); if (! (rect.size > = insets.collapsedSize)) {throw FlutterError ('indicatorPadding insets should be less than Tab Size\ n' 'Rect Size: ${rect.size}, Insets: ${insets.toString ()}',);} return insets.deflateRect (rect);}}
As mentioned above, the width of the indicator is converted according to the index and animation values when controller is switched, and the width is changed. The minimum and maximum values of Offset are the central points of the two Tab before and after switching, respectively. The corresponding restrictions should be made here, and then passed to the Rect.fromLTWH.
Due to the problem of time and energy, I didn't do this step, and the relationship between animation and sliding logic on KuGou's side needs a specific formula given by UI in order to restore it 100%. ]
Finally, add one more parameter to let the business side pass in the minimum width of the indicator.
/ minimum width of indicator final double indicatorMinWidth; service usage
We have finished the simple animation above, and then pass in the fillet indicator and the minimum width indicatorMinWidth, which can be used normally.
Round corner indicator, I go directly to the source code
Import 'package:flutter/material.dart';class RRecTabIndicator extends Decoration {const RRecTabIndicator ({this.borderSide = const BorderSide (width: 2.0, color: Colors.white), this.insets = EdgeInsets.zero, this.radius = 0, this.color = Colors.white}); final double radius; final Color color; final BorderSide borderSide; final EdgeInsetsGeometry insets; @ override Decoration? LerpFrom (Decoration? A, double t) {if (an is RRecTabIndicator) {return RRecTabIndicator (borderSide: BorderSide.lerp (a.borderSide, borderSide, t), insets: EdgeInsetsGeometry.lerp (a.insets, insets, t)!,);} return super.lerpFrom (a, t);} @ override Decoration? LerpTo (Decoration? B, double t) {if (b is RRecTabIndicator) {return RRecTabIndicator (borderSide: BorderSide.lerp (borderSide, b.borderSide, t), insets: EdgeInsetsGeometry.lerp (insets, b.insets, t)!,);} return super.lerpTo (b, t);} @ override _ UnderlinePainter createBoxPainter ([VoidCallback? OnChanged]) {return _ UnderlinePainter (this, onChanged);} Rect _ indicatorRectFor (Rect rect, TextDirection textDirection) {final Rect indicator = insets.resolve (textDirection) .originateRect (rect); return Rect.fromLTWH (indicator.left, indicator.bottom-borderSide.width, indicator.width, borderSide.width,);} @ override Path getClipPath (Rect rect, TextDirection textDirection) {return Path (). AddRect (_ indicatorRectFor (rect, textDirection)) }} class _ UnderlinePainter extends BoxPainter {_ UnderlinePainter (this.decoration, VoidCallback? OnChanged): super (onChanged); final RRecTabIndicator decoration; @ override void paint (Canvas canvas, Offset offset, ImageConfiguration configuration) {final Rect rect = offset & organization.sizeboxes; final TextDirection textDirection = organization.textDirectionals; final Rect indicator = decoration._indicatorRectFor (rect, textDirection); final Paint paint = decoration.borderSide.toPaint ().. strokeCap = StrokeCap.square. Color = decoration.color; final RRect rRect = RRect.fromRectAndRadius (indicator, Radius.circular (decoration.radius)) Canvas.drawRRect (rRect, paint);}}
The call is very simple, exactly the same as the original official code.
Scaffold (appBar: AppBar (/ / Here we take the value from the MyHomePage object that was created by / / the App.build method, and use it to set our appbar title. Title: Text (widget.title), bottom: KuGouTabBar (tabs: const [Tab (text: "Music"), Tab (text: "dynamic"), Tab (text: "language")], / / labelPadding: EdgeInsets.symmetric (horizontal: 8), controller: _ tabController, / / indicatorSize: TabBarIndicatorSize.label, / / isScrollable: true, padding: EdgeInsets.zero, indicator: const RRecTabIndicator (radius: 4 Insets: EdgeInsets.only (bottom: 5), indicatorMinWidth: 6,) After reading this, the article "how to use Flutter to achieve KuGou's fluent Tabbar effect" has been introduced. If you want to master the knowledge of this article, you still need to practice and use it before you can understand it. If you want to know more about related articles, welcome to follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.