diff --git a/lib/views/components/flutter/no_div_expansion_tile.dart b/lib/views/components/flutter/no_div_expansion_tile.dart new file mode 100644 index 0000000..4cb93ff --- /dev/null +++ b/lib/views/components/flutter/no_div_expansion_tile.dart @@ -0,0 +1,205 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + + +const Duration _kExpand = Duration(milliseconds: 200); + +/// A single-line [ListTile] with a trailing button that expands or collapses +/// the tile to reveal or hide the [children]. +/// +/// This widget is typically used with [ListView] to create an +/// "expand / collapse" list entry. When used with scrolling widgets like +/// [ListView], a unique [PageStorageKey] must be specified to enable the +/// [NoBorderExpansionTile] to save and restore its expanded state when it is scrolled +/// in and out of view. +/// +/// See also: +/// +/// * [ListTile], useful for creating expansion tile [children] when the +/// expansion tile represents a sublist. +/// * The "Expand/collapse" section of +/// . +class NoBorderExpansionTile extends StatefulWidget { + /// Creates a single-line [ListTile] with a trailing button that expands or collapses + /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must + /// be non-null. + const NoBorderExpansionTile({ + Key key, + this.leading, + @required this.title, + this.subtitle, + this.backgroundColor, + this.onExpansionChanged, + this.children = const [], + this.trailing, + this.initiallyExpanded = false, + }) : assert(initiallyExpanded != null), + super(key: key); + + /// A widget to display before the title. + /// + /// Typically a [CircleAvatar] widget. + final Widget leading; + + /// The primary content of the list item. + /// + /// Typically a [Text] widget. + final Widget title; + + /// Additional content displayed below the title. + /// + /// Typically a [Text] widget. + final Widget subtitle; + + /// Called when the tile expands or collapses. + /// + /// When the tile starts expanding, this function is called with the value + /// true. When the tile starts collapsing, this function is called with + /// the value false. + final ValueChanged onExpansionChanged; + + /// The widgets that are displayed when the tile expands. + /// + /// Typically [ListTile] widgets. + final List children; + + /// The color to display behind the sublist when expanded. + final Color backgroundColor; + + /// A widget to display instead of a rotating arrow icon. + final Widget trailing; + + /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default). + final bool initiallyExpanded; + + @override + _NoBorderExpansionTileState createState() => _NoBorderExpansionTileState(); +} + +class _NoBorderExpansionTileState extends State with SingleTickerProviderStateMixin { + static final Animatable _easeOutTween = CurveTween(curve: Curves.easeOut); + static final Animatable _easeInTween = CurveTween(curve: Curves.easeIn); + static final Animatable _halfTween = Tween(begin: 0.0, end: 0.5); + + final ColorTween _borderColorTween = ColorTween(); + final ColorTween _headerColorTween = ColorTween(); + final ColorTween _iconColorTween = ColorTween(); + final ColorTween _backgroundColorTween = ColorTween(); + + AnimationController _controller; + Animation _iconTurns; + Animation _heightFactor; + Animation _borderColor; + Animation _headerColor; + Animation _iconColor; + Animation _backgroundColor; + + bool _isExpanded = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController(duration: _kExpand, vsync: this); + _heightFactor = _controller.drive(_easeInTween); + _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); + _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween)); + _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween)); + _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); + _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween)); + + _isExpanded = PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded; + if (_isExpanded) + _controller.value = 1.0; + } + + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _handleTap() { + setState(() { + _isExpanded = !_isExpanded; + if (_isExpanded) { + _controller.forward(); + } else { + _controller.reverse().then((void value) { + if (!mounted) + return; + setState(() { + // Rebuild without widget.children. + }); + }); + } + PageStorage.of(context)?.writeState(context, _isExpanded); + }); + if (widget.onExpansionChanged != null) + widget.onExpansionChanged(_isExpanded); + } + + Widget _buildChildren(BuildContext context, Widget child) { + + return Container( + color: _backgroundColor.value ?? Colors.transparent, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTileTheme.merge( + iconColor: _iconColor.value, + textColor: _headerColor.value, + child: ListTile( + onTap: _handleTap, + leading: widget.leading, + title: widget.title, + subtitle: widget.subtitle, + trailing: widget.trailing ?? RotationTransition( + turns: _iconTurns, + child: const Icon(Icons.expand_more), + ), + ), + ), + ClipRect( + child: Align( +// alignment: Alignment.topCenter, + heightFactor: _heightFactor.value, + child: child, + ), + ), + ], + ), + ); + } + + @override + void didChangeDependencies() { + final ThemeData theme = Theme.of(context); + _borderColorTween + ..end = theme.dividerColor; + _headerColorTween + ..begin = theme.textTheme.subhead.color + ..end = theme.accentColor; + _iconColorTween + ..begin = theme.unselectedWidgetColor + ..end = theme.accentColor; + _backgroundColorTween + ..end = widget.backgroundColor; + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + final bool closed = !_isExpanded && _controller.isDismissed; + return AnimatedBuilder( + animation: _controller.view, + builder: _buildChildren, + child: closed ? null : Column(children: widget.children), + ); + + } +} diff --git a/lib/views/components/flutter/no_shadow_tab_bar.dart b/lib/views/components/flutter/no_shadow_tab_bar.dart new file mode 100644 index 0000000..a1ae1ae --- /dev/null +++ b/lib/views/components/flutter/no_shadow_tab_bar.dart @@ -0,0 +1,1345 @@ +/// create by 张风捷特烈 on 2020-03-16 +/// contact me by email 1981462002@qq.com +/// 说明: + +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:ui' show lerpDouble; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; + + +const double _kTabHeight = 46.0; +const double _kTextAndIconTabHeight = 72.0; + +/// Defines how the bounds of the selected tab indicator are computed. +/// +/// See also: +/// +/// * [TabBar], which displays a row of tabs. +/// * [TabBarView], which displays a widget for the currently selected tab. +/// * [TabBar.indicator], which defines the appearance of the selected tab +/// indicator relative to the tab's bounds. +enum TabBarIndicatorSize { + /// The tab indicator's bounds are as wide as the space occupied by the tab + /// in the tab bar: from the right edge of the previous tab to the left edge + /// of the next tab. + tab, + + /// The tab's bounds are only as wide as the (centered) tab widget itself. + /// + /// This value is used to align the tab's label, typically a [Tab] + /// widget's text or icon, with the selected tab indicator. + label, +} + +class _TabStyle extends AnimatedWidget { + const _TabStyle({ + Key key, + Animation animation, + this.selected, + this.labelColor, + this.unselectedLabelColor, + this.labelStyle, + this.unselectedLabelStyle, + @required this.child, + }) : super(key: key, listenable: animation); + + final TextStyle labelStyle; + final TextStyle unselectedLabelStyle; + final bool selected; + final Color labelColor; + final Color unselectedLabelColor; + final Widget child; + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final TabBarTheme tabBarTheme = TabBarTheme.of(context); + final Animation animation = listenable; + + // To enable TextStyle.lerp(style1, style2, value), both styles must have + // the same value of inherit. Force that to be inherit=true here. + final TextStyle defaultStyle = (labelStyle + ?? tabBarTheme.labelStyle + ?? themeData.primaryTextTheme.body2 + ).copyWith(inherit: true); + final TextStyle defaultUnselectedStyle = (unselectedLabelStyle + ?? tabBarTheme.unselectedLabelStyle + ?? labelStyle + ?? themeData.primaryTextTheme.body2 + ).copyWith(inherit: true); + final TextStyle textStyle = selected + ? TextStyle.lerp(defaultStyle, defaultUnselectedStyle, animation.value) + : TextStyle.lerp(defaultUnselectedStyle, defaultStyle, animation.value); + + final Color selectedColor = labelColor + ?? tabBarTheme.labelColor + ?? themeData.primaryTextTheme.body2.color; + final Color unselectedColor = unselectedLabelColor + ?? tabBarTheme.unselectedLabelColor + ?? selectedColor.withAlpha(0xB2); // 70% alpha + final Color color = selected + ? Color.lerp(selectedColor, unselectedColor, animation.value) + : Color.lerp(unselectedColor, selectedColor, animation.value); + + return DefaultTextStyle( + style: textStyle.copyWith(color: color), + child: IconTheme.merge( + data: IconThemeData( + size: 24.0, + color: color, + ), + child: child, + ), + ); + } +} + +typedef _LayoutCallback = void Function(List xOffsets, TextDirection textDirection, double width); + +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; + 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, textDirection, size.width); + } +} + +// This class and its renderer class only exist to report the widths of the tabs +// upon layout. The tab widths are only used at paint time (see _IndicatorPainter) +// or in response to input. +class _TabLabelBar extends Flex { + _TabLabelBar({ + Key key, + List children = const [], + 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) { + 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; + } +} + +double _indexChangeProgress(TabController controller) { + final double controllerValue = controller.animation.value; + final double previousIndex = controller.previousIndex.toDouble(); + final double currentIndex = controller.index.toDouble(); + + // The controller's offset is changing because the user is dragging the + // TabBarView's PageView to the left or right. + if (!controller.indexIsChanging) + return (currentIndex - controllerValue).abs().clamp(0.0, 1.0); + + // The TabController animation's value is changing from previousIndex to currentIndex. + return (controllerValue - currentIndex).abs() / (currentIndex - previousIndex).abs(); +} + +class _IndicatorPainter extends CustomPainter { + _IndicatorPainter({ + @required this.controller, + @required this.indicator, + @required this.indicatorSize, + @required this.tabKeys, + _IndicatorPainter old, + }) : assert(controller != null), + assert(indicator != null), + super(repaint: controller.animation) { + if (old != null) + saveTabOffsets(old._currentTabOffsets, old._currentTextDirection); + } + + final TabController controller; + final Decoration indicator; + final TabBarIndicatorSize indicatorSize; + final List tabKeys; + + List _currentTabOffsets; + TextDirection _currentTextDirection; + Rect _currentRect; + BoxPainter _painter; + bool _needsPaint = false; + void markNeedsPaint() { + _needsPaint = true; + } + + void dispose() { + _painter?.dispose(); + } + + void saveTabOffsets(List tabOffsets, TextDirection textDirection) { + _currentTabOffsets = tabOffsets; + _currentTextDirection = textDirection; + } + + // _currentTabOffsets[index] 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(_currentTabOffsets.isNotEmpty); + assert(tabIndex >= 0); + assert(tabIndex <= maxTabIndex); + return (_currentTabOffsets[tabIndex] + _currentTabOffsets[tabIndex + 1]) / 2.0; + } + + Rect indicatorRect(Size tabBarSize, int tabIndex) { + assert(_currentTabOffsets != null); + assert(_currentTextDirection != null); + assert(_currentTabOffsets.isNotEmpty); + assert(tabIndex >= 0); + assert(tabIndex <= maxTabIndex); + double tabLeft, tabRight; + switch (_currentTextDirection) { + case TextDirection.rtl: + tabLeft = _currentTabOffsets[tabIndex + 1]; + tabRight = _currentTabOffsets[tabIndex]; + break; + case TextDirection.ltr: + tabLeft = _currentTabOffsets[tabIndex]; + tabRight = _currentTabOffsets[tabIndex + 1]; + break; + } + + if (indicatorSize == TabBarIndicatorSize.label) { + final double tabWidth = tabKeys[tabIndex].currentContext.size.width; + final double delta = ((tabRight - tabLeft) - tabWidth) / 2.0; + tabLeft += delta; + tabRight -= delta; + } + + return Rect.fromLTWH(tabLeft, 0.0, tabRight - tabLeft, tabBarSize.height); + } + + @override + void paint(Canvas canvas, Size size) { + _needsPaint = false; + _painter ??= indicator.createBoxPainter(markNeedsPaint); + + if (controller.indexIsChanging) { + // The user tapped on a tab, the tab controller's animation is running. + final Rect targetRect = indicatorRect(size, controller.index); + _currentRect = Rect.lerp(targetRect, _currentRect ?? targetRect, _indexChangeProgress(controller)); + } else { + // The user is dragging the TabBarView's PageView left or right. + final int currentIndex = controller.index; + final Rect previous = currentIndex > 0 ? indicatorRect(size, currentIndex - 1) : null; + final Rect middle = indicatorRect(size, currentIndex); + final Rect next = currentIndex < maxTabIndex ? indicatorRect(size, currentIndex + 1) : null; + final double index = controller.index.toDouble(); + final double value = controller.animation.value; + if (value == index - 1.0) + _currentRect = previous ?? middle; + else if (value == index + 1.0) + _currentRect = next ?? middle; + else if (value == index) + _currentRect = middle; + else if (value < index) + _currentRect = previous == null ? middle : Rect.lerp(middle, previous, index - value); + else + _currentRect = next == null ? middle : Rect.lerp(middle, next, value - index); + } + assert(_currentRect != null); + + final ImageConfiguration configuration = ImageConfiguration( + size: _currentRect.size, + textDirection: _currentTextDirection, + ); + _painter.paint(canvas, _currentRect.topLeft, configuration); + } + + static bool _tabOffsetsEqual(List a, List b) { + // TODO(shihaohong): The following null check should be replaced when a fix + // for https://github.com/flutter/flutter/issues/40014 is available. + if (a == null || b == null || a.length != b.length) + return false; + for (int i = 0; i < a.length; i += 1) { + if (a[i] != b[i]) + return false; + } + return true; + } + + @override + bool shouldRepaint(_IndicatorPainter old) { + return _needsPaint + || controller != old.controller + || indicator != old.indicator + || tabKeys.length != old.tabKeys.length + || (!_tabOffsetsEqual(_currentTabOffsets, old._currentTabOffsets)) + || _currentTextDirection != old._currentTextDirection; + } +} + +class _ChangeAnimation extends Animation with AnimationWithParentMixin { + _ChangeAnimation(this.controller); + + final TabController controller; + + @override + Animation get parent => controller.animation; + + @override + void removeStatusListener(AnimationStatusListener listener) { + if (parent != null) + super.removeStatusListener(listener); + } + + @override + void removeListener(VoidCallback listener) { + if (parent != null) + super.removeListener(listener); + } + + @override + double get value => _indexChangeProgress(controller); +} + +class _DragAnimation extends Animation with AnimationWithParentMixin { + _DragAnimation(this.controller, this.index); + + final TabController controller; + final int index; + + @override + Animation get parent => controller.animation; + + @override + void removeStatusListener(AnimationStatusListener listener) { + if (parent != null) + super.removeStatusListener(listener); + } + + @override + void removeListener(VoidCallback listener) { + if (parent != null) + super.removeListener(listener); + } + + @override + double get value { + assert(!controller.indexIsChanging); + return (controller.animation.value - index.toDouble()).abs().clamp(0.0, 1.0); + } +} + +// This class, and TabBarScrollController, only exist to handle the case +// where a scrollable TabBar has a non-zero initialIndex. In that case we can +// only compute the scroll position's initial scroll offset (the "correct" +// pixels value) after the TabBar viewport width and scroll limits are known. +class _TabBarScrollPosition extends ScrollPositionWithSingleContext { + _TabBarScrollPosition({ + ScrollPhysics physics, + ScrollContext context, + ScrollPosition oldPosition, + this.tabBar, + }) : super( + physics: physics, + context: context, + initialPixels: null, + oldPosition: oldPosition, + ); + + final _TabBarState tabBar; + + bool _initialViewportDimensionWasZero; + + @override + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { + bool result = true; + if (_initialViewportDimensionWasZero != true) { + // If the viewport never had a non-zero dimension, we just want to jump + // to the initial scroll position to avoid strange scrolling effects in + // release mode: In release mode, the viewport temporarily may have a + // dimension of zero before the actual dimension is calculated. In that + // scenario, setting the actual dimension would cause a strange scroll + // effect without this guard because the super call below would starts a + // ballistic scroll activity. + assert(viewportDimension != null); + _initialViewportDimensionWasZero = viewportDimension != 0.0; + correctPixels(tabBar._initialScrollOffset(viewportDimension, minScrollExtent, maxScrollExtent)); + result = false; + } + return super.applyContentDimensions(minScrollExtent, maxScrollExtent) && result; + } +} + +// This class, and TabBarScrollPosition, only exist to handle the case +// where a scrollable TabBar has a non-zero initialIndex. +class _TabBarScrollController extends ScrollController { + _TabBarScrollController(this.tabBar); + + final _TabBarState tabBar; + + @override + ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition oldPosition) { + return _TabBarScrollPosition( + physics: physics, + context: context, + oldPosition: oldPosition, + tabBar: tabBar, + ); + } +} + +/// A material design widget that displays a horizontal row of tabs. +/// +/// Typically created as the [AppBar.bottom] part of an [AppBar] and in +/// conjunction with a [TabBarView]. +/// +/// If a [TabController] is not provided, then a [DefaultTabController] ancestor +/// must be provided instead. The tab controller's [TabController.length] must +/// equal the length of the [tabs] list and the length of the +/// [TabBarView.children] list. +/// +/// Requires one of its ancestors to be a [Material] widget. +/// +/// Uses values from [TabBarTheme] if it is set in the current context. +/// +/// To see a sample implementation, visit the [TabController] documentation. +/// +/// See also: +/// +/// * [TabBarView], which displays page views that correspond to each tab. +class NoShadowTabBar extends StatefulWidget implements PreferredSizeWidget { + /// Creates a material design tab bar. + /// + /// The [tabs] argument must not be null and its length must match the [controller]'s + /// [TabController.length]. + /// + /// If a [TabController] is not provided, then there must be a + /// [DefaultTabController] ancestor. + /// + /// The [indicatorWeight] parameter defaults to 2, and must not be null. + /// + /// The [indicatorPadding] parameter defaults to [EdgeInsets.zero], and must not be null. + /// + /// If [indicator] is not null, then [indicatorWeight], [indicatorPadding], and + /// [indicatorColor] are ignored. + const NoShadowTabBar({ + Key key, + @required this.tabs, + this.controller, + this.isScrollable = false, + this.indicatorColor, + this.indicatorWeight = 2.0, + this.indicatorPadding = EdgeInsets.zero, + this.indicator, + this.indicatorSize, + this.labelColor, + this.labelStyle, + this.labelPadding, + this.unselectedLabelColor, + this.unselectedLabelStyle, + this.dragStartBehavior = DragStartBehavior.start, + this.onTap, + }) : assert(tabs != null), + assert(isScrollable != null), + assert(dragStartBehavior != null), + assert(indicator != null || (indicatorWeight != null && indicatorWeight > 0.0)), + assert(indicator != null || (indicatorPadding != null)), + super(key: key); + + /// Typically a list of two or more [Tab] widgets. + /// + /// The length of this list must match the [controller]'s [TabController.length] + /// and the length of the [TabBarView.children] list. + final List tabs; + + /// This widget's selection and animation state. + /// + /// If [TabController] is not provided, then the value of [DefaultTabController.of] + /// will be used. + final TabController controller; + + /// Whether this tab bar can be scrolled horizontally. + /// + /// If [isScrollable] is true, then each tab is as wide as needed for its label + /// and the entire [TabBar] is scrollable. Otherwise each tab gets an equal + /// share of the available space. + final bool isScrollable; + + /// The color of the line that appears below the selected tab. + /// + /// If this parameter is null, then the value of the Theme's indicatorColor + /// property is used. + /// + /// If [indicator] is specified, this property is ignored. + final Color indicatorColor; + + /// The thickness of the line that appears below the selected tab. + /// + /// The value of this parameter must be greater than zero and its default + /// value is 2.0. + /// + /// If [indicator] is specified, this property is ignored. + final double indicatorWeight; + + /// The horizontal padding for the line that appears below the selected tab. + /// + /// For [isScrollable] tab bars, specifying [kTabLabelPadding] will align + /// the indicator with the tab's text for [Tab] widgets and all but the + /// shortest [Tab.text] values. + /// + /// The [EdgeInsets.top] and [EdgeInsets.bottom] values of the + /// [indicatorPadding] are ignored. + /// + /// The default value of [indicatorPadding] is [EdgeInsets.zero]. + /// + /// If [indicator] is specified, this property is ignored. + final EdgeInsetsGeometry indicatorPadding; + + /// Defines the appearance of the selected tab indicator. + /// + /// If [indicator] is specified, the [indicatorColor], [indicatorWeight], + /// and [indicatorPadding] properties are ignored. + /// + /// The default, underline-style, selected tab indicator can be defined with + /// [UnderlineTabIndicator]. + /// + /// The indicator's size is based on the tab's bounds. If [indicatorSize] + /// is [TabBarIndicatorSize.tab] the tab's bounds are as wide as the space + /// occupied by the tab in the tab bar. If [indicatorSize] is + /// [TabBarIndicatorSize.label], then the tab's bounds are only as wide as + /// the tab widget itself. + final Decoration indicator; + + /// Defines how the selected tab indicator's size is computed. + /// + /// The size of the selected tab indicator is defined relative to the + /// tab's overall bounds if [indicatorSize] is [TabBarIndicatorSize.tab] + /// (the default) or relative to the bounds of the tab's widget if + /// [indicatorSize] is [TabBarIndicatorSize.label]. + /// + /// The selected tab's location appearance can be refined further with + /// the [indicatorColor], [indicatorWeight], [indicatorPadding], and + /// [indicator] properties. + final TabBarIndicatorSize indicatorSize; + + /// The color of selected tab labels. + /// + /// Unselected tab labels are rendered with the same color rendered at 70% + /// opacity unless [unselectedLabelColor] is non-null. + /// + /// If this parameter is null, then the color of the [ThemeData.primaryTextTheme]'s + /// body2 text color is used. + final Color labelColor; + + /// The color of unselected tab labels. + /// + /// If this property is null, unselected tab labels are rendered with the + /// [labelColor] with 70% opacity. + final Color unselectedLabelColor; + + /// The text style of the selected tab labels. + /// + /// If [unselectedLabelStyle] is null, then this text style will be used for + /// both selected and unselected label styles. + /// + /// If this property is null, then the text style of the + /// [ThemeData.primaryTextTheme]'s body2 definition is used. + final TextStyle labelStyle; + + /// The padding added to each of the tab labels. + /// + /// If this property is null, then kTabLabelPadding is used. + final EdgeInsetsGeometry labelPadding; + + /// The text style of the unselected tab labels + /// + /// If this property is null, then the [labelStyle] value is used. If [labelStyle] + /// is null, then the text style of the [ThemeData.primaryTextTheme]'s + /// body2 definition is used. + final TextStyle unselectedLabelStyle; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// An optional callback that's called when the [TabBar] is tapped. + /// + /// The callback is applied to the index of the tab where the tap occurred. + /// + /// This callback has no effect on the default handling of taps. It's for + /// applications that want to do a little extra work when a tab is tapped, + /// even if the tap doesn't change the TabController's index. TabBar [onTap] + /// callbacks should not make changes to the TabController since that would + /// interfere with the default tap handler. + final ValueChanged onTap; + + /// A size whose height depends on if the tabs have both icons and text. + /// + /// [AppBar] uses this size to compute its own preferred size. + @override + Size get preferredSize { + for (Widget item in tabs) { + if (item is Tab) { + final Tab tab = item; + if (tab.text != null && tab.icon != null) + return Size.fromHeight(_kTextAndIconTabHeight + indicatorWeight); + } + } + return Size.fromHeight(_kTabHeight + indicatorWeight); + } + + @override + _TabBarState createState() => _TabBarState(); +} + +class _TabBarState extends State { + ScrollController _scrollController; + TabController _controller; + _IndicatorPainter _indicatorPainter; + int _currentIndex; + double _tabStripWidth; + List _tabKeys; + + @override + void initState() { + super.initState(); + // If indicatorSize is TabIndicatorSize.label, _tabKeys[i] is used to find + // the width of tab widget i. See _IndicatorPainter.indicatorRect(). + _tabKeys = widget.tabs.map((Widget tab) => GlobalKey()).toList(); + } + + Decoration get _indicator { + if (widget.indicator != null) + return widget.indicator; + final TabBarTheme tabBarTheme = TabBarTheme.of(context); + if (tabBarTheme.indicator != null) + return tabBarTheme.indicator; + + Color color = widget.indicatorColor ?? Theme.of(context).indicatorColor; + // ThemeData tries to avoid this by having indicatorColor avoid being the + // primaryColor. However, it's possible that the tab bar is on a + // Material that isn't the primaryColor. In that case, if the indicator + // color ends up matching the material's color, then this overrides it. + // When that happens, automatic transitions of the theme will likely look + // ugly as the indicator color suddenly snaps to white at one end, but it's + // not clear how to avoid that any further. + // + // The material's color might be null (if it's a transparency). In that case + // there's no good way for us to find out what the color is so we don't. + if (color.value == Material.of(context).color?.value) + color = Colors.white; + + return UnderlineTabIndicator( + insets: widget.indicatorPadding, + borderSide: BorderSide( + width: widget.indicatorWeight, + color: color, + ), + ); + } + + // If the TabBar is rebuilt with a new tab controller, the caller should + // dispose the old one. In that case the old controller's animation will be + // null and should not be accessed. + bool get _controllerIsValid => _controller?.animation != null; + + void _updateTabController() { + final TabController newController = widget.controller ?? DefaultTabController.of(context); + assert(() { + if (newController == null) { + throw FlutterError( + 'No TabController for ${widget.runtimeType}.\n' + 'When creating a ${widget.runtimeType}, you must either provide an explicit ' + 'TabController using the "controller" property, or you must ensure that there ' + 'is a DefaultTabController above the ${widget.runtimeType}.\n' + 'In this case, there was neither an explicit controller nor a default controller.' + ); + } + return true; + }()); + + if (newController == _controller) + return; + + if (_controllerIsValid) { + _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller.removeListener(_handleTabControllerTick); + } + _controller = newController; + if (_controller != null) { + _controller.animation.addListener(_handleTabControllerAnimationTick); + _controller.addListener(_handleTabControllerTick); + _currentIndex = _controller.index; + } + } + + void _initIndicatorPainter() { + _indicatorPainter = !_controllerIsValid ? null : _IndicatorPainter( + controller: _controller, + indicator: _indicator, + indicatorSize: widget.indicatorSize ?? TabBarTheme.of(context).indicatorSize, + tabKeys: _tabKeys, + old: _indicatorPainter, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + assert(debugCheckHasMaterial(context)); + _updateTabController(); + _initIndicatorPainter(); + } + + @override + void didUpdateWidget(NoShadowTabBar oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _updateTabController(); + _initIndicatorPainter(); + } else if (widget.indicatorColor != oldWidget.indicatorColor || + widget.indicatorWeight != oldWidget.indicatorWeight || + widget.indicatorSize != oldWidget.indicatorSize || + widget.indicator != oldWidget.indicator) { + _initIndicatorPainter(); + } + + 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); + } + } + + @override + void dispose() { + _indicatorPainter.dispose(); + if (_controllerIsValid) { + _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller.removeListener(_handleTabControllerTick); + } + _controller = null; + // We don't own the _controller Animation, so it's not disposed here. + super.dispose(); + } + + int get maxTabIndex => _indicatorPainter.maxTabIndex; + + double _tabScrollOffset(int index, double viewportWidth, double minExtent, double maxExtent) { + if (!widget.isScrollable) + return 0.0; + double tabCenter = _indicatorPainter.centerOf(index); + switch (Directionality.of(context)) { + case TextDirection.rtl: + tabCenter = _tabStripWidth - tabCenter; + break; + case TextDirection.ltr: + break; + } + return (tabCenter - viewportWidth / 2.0).clamp(minExtent, maxExtent); + } + + double _tabCenteredScrollOffset(int index) { + final ScrollPosition position = _scrollController.position; + return _tabScrollOffset(index, position.viewportDimension, position.minScrollExtent, position.maxScrollExtent); + } + + double _initialScrollOffset(double viewportWidth, double minExtent, double maxExtent) { + return _tabScrollOffset(_currentIndex, viewportWidth, minExtent, maxExtent); + } + + void _scrollToCurrentIndex() { + final double offset = _tabCenteredScrollOffset(_currentIndex); + _scrollController.animateTo(offset, duration: kTabScrollDuration, curve: Curves.ease); + } + + void _scrollToControllerValue() { + final double leadingPosition = _currentIndex > 0 ? _tabCenteredScrollOffset(_currentIndex - 1) : null; + final double middlePosition = _tabCenteredScrollOffset(_currentIndex); + final double trailingPosition = _currentIndex < maxTabIndex ? _tabCenteredScrollOffset(_currentIndex + 1) : null; + + final double index = _controller.index.toDouble(); + final double value = _controller.animation.value; + double offset; + if (value == index - 1.0) + offset = leadingPosition ?? middlePosition; + else if (value == index + 1.0) + offset = trailingPosition ?? middlePosition; + else if (value == index) + offset = middlePosition; + else if (value < index) + offset = leadingPosition == null ? middlePosition : lerpDouble(middlePosition, leadingPosition, index - value); + else + offset = trailingPosition == null ? middlePosition : lerpDouble(middlePosition, trailingPosition, value - index); + + _scrollController.jumpTo(offset); + } + + void _handleTabControllerAnimationTick() { + assert(mounted); + if (!_controller.indexIsChanging && widget.isScrollable) { + // Sync the TabBar's scroll position with the TabBarView's PageView. + _currentIndex = _controller.index; + _scrollToControllerValue(); + } + } + + void _handleTabControllerTick() { + if (_controller.index != _currentIndex) { + _currentIndex = _controller.index; + if (widget.isScrollable) + _scrollToCurrentIndex(); + } + setState(() { + // Rebuild the tabs after a (potentially animated) index change + // has completed. + }); + } + + // Called each time layout completes. + void _saveTabOffsets(List tabOffsets, TextDirection textDirection, double width) { + _tabStripWidth = width; + _indicatorPainter?.saveTabOffsets(tabOffsets, textDirection); + } + + void _handleTap(int index) { + assert(index >= 0 && index < widget.tabs.length); + _controller.animateTo(index); + if (widget.onTap != null) { + widget.onTap(index); + } + } + + Widget _buildStyledTab(Widget child, bool selected, Animation animation) { + return _TabStyle( + animation: animation, + selected: selected, + labelColor: widget.labelColor, + unselectedLabelColor: widget.unselectedLabelColor, + labelStyle: widget.labelStyle, + unselectedLabelStyle: widget.unselectedLabelStyle, + child: child, + ); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterialLocalizations(context)); + assert(() { + if (_controller.length != widget.tabs.length) { + throw FlutterError( + 'Controller\'s length property (${_controller.length}) does not match the \n' + 'number of tabs (${widget.tabs.length}) present in TabBar\'s tabs property.' + ); + } + return true; + }()); + final MaterialLocalizations localizations = MaterialLocalizations.of(context); + if (_controller.length == 0) { + return Container( + height: _kTabHeight + widget.indicatorWeight, + ); + } + + final TabBarTheme tabBarTheme = TabBarTheme.of(context); + + final List wrappedTabs = List(widget.tabs.length); + for (int i = 0; i < widget.tabs.length; i += 1) { + wrappedTabs[i] = Center( + heightFactor: 1.0, + child: Padding( + padding: widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding, + child: KeyedSubtree( + key: _tabKeys[i], + child: widget.tabs[i], + ), + ), + ); + + } + + // If the controller was provided by DefaultTabController and we're part + // of a Hero (typically the AppBar), then we will not be able to find the + // controller during a Hero transition. See https://github.com/flutter/flutter/issues/213. + if (_controller != null) { + final int previousIndex = _controller.previousIndex; + + if (_controller.indexIsChanging) { + // The user tapped on a tab, the tab controller's animation is running. + assert(_currentIndex != previousIndex); + final Animation animation = _ChangeAnimation(_controller); + wrappedTabs[_currentIndex] = _buildStyledTab(wrappedTabs[_currentIndex], true, animation); + wrappedTabs[previousIndex] = _buildStyledTab(wrappedTabs[previousIndex], false, animation); + } else { + // The user is dragging the TabBarView's PageView left or right. + final int tabIndex = _currentIndex; + final Animation centerAnimation = _DragAnimation(_controller, tabIndex); + wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation); + if (_currentIndex > 0) { + final int tabIndex = _currentIndex - 1; + final Animation previousAnimation = ReverseAnimation(_DragAnimation(_controller, tabIndex)); + wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, previousAnimation); + } + if (_currentIndex < widget.tabs.length - 1) { + final int tabIndex = _currentIndex + 1; + final Animation nextAnimation = ReverseAnimation(_DragAnimation(_controller, tabIndex)); + wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, nextAnimation); + } + } + } + + // Add the tap handler to each tab. If the tab bar is not scrollable, + // then give all of the tabs equal flexibility so that they each occupy + // the same share of the tab bar's overall width. + final int tabCount = widget.tabs.length; + for (int index = 0; index < tabCount; index += 1) { + wrappedTabs[index] = GestureDetector( + onTap: () { _handleTap(index); }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(bottom: widget.indicatorWeight), + child: Stack( + children: [ + wrappedTabs[index], + Semantics( + selected: index == _currentIndex, + label: localizations.tabLabel(tabIndex: index + 1, tabCount: tabCount), + ), + ], + ), + ), + ); + if (!widget.isScrollable) + wrappedTabs[index] = Expanded(child: wrappedTabs[index]); + } + + 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, + ), + ), + ); + + if (widget.isScrollable) { + _scrollController ??= _TabBarScrollController(this); + tabBar = SingleChildScrollView( + dragStartBehavior: widget.dragStartBehavior, + scrollDirection: Axis.horizontal, + controller: _scrollController, + child: tabBar, + ); + } + + return tabBar; + } +} + + +final PageScrollPhysics _kTabBarViewPhysics = const PageScrollPhysics().applyTo(const ClampingScrollPhysics()); + +class _TabBarViewState extends State { + TabController _controller; + PageController _pageController; + List _children; + List _childrenWithKey; + int _currentIndex; + int _warpUnderwayCount = 0; + + // If the TabBarView is rebuilt with a new tab controller, the caller should + // dispose the old one. In that case the old controller's animation will be + // null and should not be accessed. + bool get _controllerIsValid => _controller?.animation != null; + + void _updateTabController() { + final TabController newController = widget.controller ?? DefaultTabController.of(context); + assert(() { + if (newController == null) { + throw FlutterError( + 'No TabController for ${widget.runtimeType}.\n' + 'When creating a ${widget.runtimeType}, you must either provide an explicit ' + 'TabController using the "controller" property, or you must ensure that there ' + 'is a DefaultTabController above the ${widget.runtimeType}.\n' + 'In this case, there was neither an explicit controller nor a default controller.' + ); + } + return true; + }()); + + if (newController == _controller) + return; + + if (_controllerIsValid) + _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller = newController; + if (_controller != null) + _controller.animation.addListener(_handleTabControllerAnimationTick); + } + + @override + void initState() { + super.initState(); + _updateChildren(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _updateTabController(); + _currentIndex = _controller?.index; + _pageController = PageController(initialPage: _currentIndex ?? 0); + } + + @override + void didUpdateWidget(TabBarView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) + _updateTabController(); + if (widget.children != oldWidget.children && _warpUnderwayCount == 0) + _updateChildren(); + } + + @override + void dispose() { + if (_controllerIsValid) + _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller = null; + // We don't own the _controller Animation, so it's not disposed here. + super.dispose(); + } + + void _updateChildren() { + _children = widget.children; + _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children); + } + + void _handleTabControllerAnimationTick() { + if (_warpUnderwayCount > 0 || !_controller.indexIsChanging) + return; // This widget is driving the controller's animation. + + if (_controller.index != _currentIndex) { + _currentIndex = _controller.index; + _warpToCurrentIndex(); + } + } + + Future _warpToCurrentIndex() async { + if (!mounted) + return Future.value(); + + if (_pageController.page == _currentIndex.toDouble()) + return Future.value(); + + final int previousIndex = _controller.previousIndex; + if ((_currentIndex - previousIndex).abs() == 1) + return _pageController.animateToPage(_currentIndex, duration: kTabScrollDuration, curve: Curves.ease); + + assert((_currentIndex - previousIndex).abs() > 1); + final int initialPage = _currentIndex > previousIndex + ? _currentIndex - 1 + : _currentIndex + 1; + final List originalChildren = _childrenWithKey; + setState(() { + _warpUnderwayCount += 1; + + _childrenWithKey = List.from(_childrenWithKey, growable: false); + final Widget temp = _childrenWithKey[initialPage]; + _childrenWithKey[initialPage] = _childrenWithKey[previousIndex]; + _childrenWithKey[previousIndex] = temp; + }); + _pageController.jumpToPage(initialPage); + + await _pageController.animateToPage(_currentIndex, duration: kTabScrollDuration, curve: Curves.ease); + if (!mounted) + return Future.value(); + setState(() { + _warpUnderwayCount -= 1; + if (widget.children != _children) { + _updateChildren(); + } else { + _childrenWithKey = originalChildren; + } + }); + } + + // Called when the PageView scrolls + bool _handleScrollNotification(ScrollNotification notification) { + if (_warpUnderwayCount > 0) + return false; + + if (notification.depth != 0) + return false; + + _warpUnderwayCount += 1; + if (notification is ScrollUpdateNotification && !_controller.indexIsChanging) { + if ((_pageController.page - _controller.index).abs() > 1.0) { + _controller.index = _pageController.page.floor(); + _currentIndex =_controller.index; + } + _controller.offset = (_pageController.page - _controller.index).clamp(-1.0, 1.0); + } else if (notification is ScrollEndNotification) { + _controller.index = _pageController.page.round(); + _currentIndex = _controller.index; + } + _warpUnderwayCount -= 1; + + return false; + } + + @override + Widget build(BuildContext context) { + assert(() { + if (_controller.length != widget.children.length) { + throw FlutterError( + 'Controller\'s length property (${_controller.length}) does not match the \n' + 'number of tabs (${widget.children.length}) present in TabBar\'s tabs property.' + ); + } + return true; + }()); + return NotificationListener( + onNotification: _handleScrollNotification, + child: PageView( + dragStartBehavior: widget.dragStartBehavior, + controller: _pageController, + physics: widget.physics == null ? _kTabBarViewPhysics : _kTabBarViewPhysics.applyTo(widget.physics), + children: _childrenWithKey, + ), + ); + } +} + +/// Displays a single circle with the specified border and background colors. +/// +/// Used by [TabPageSelector] to indicate the selected page. +class TabPageSelectorIndicator extends StatelessWidget { + /// Creates an indicator used by [TabPageSelector]. + /// + /// The [backgroundColor], [borderColor], and [size] parameters must not be null. + const TabPageSelectorIndicator({ + Key key, + @required this.backgroundColor, + @required this.borderColor, + @required this.size, + }) : assert(backgroundColor != null), + assert(borderColor != null), + assert(size != null), + super(key: key); + + /// The indicator circle's background color. + final Color backgroundColor; + + /// The indicator circle's border color. + final Color borderColor; + + /// The indicator circle's diameter. + final double size; + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + margin: const EdgeInsets.all(4.0), + decoration: BoxDecoration( + color: backgroundColor, + border: Border.all(color: borderColor), + shape: BoxShape.circle, + ), + ); + } +} + +/// Displays a row of small circular indicators, one per tab. +/// +/// The selected tab's indicator is highlighted. Often used in conjunction with +/// a [TabBarView]. +/// +/// If a [TabController] is not provided, then there must be a +/// [DefaultTabController] ancestor. +class TabPageSelector extends StatelessWidget { + /// Creates a compact widget that indicates which tab has been selected. + const TabPageSelector({ + Key key, + this.controller, + this.indicatorSize = 12.0, + this.color, + this.selectedColor, + }) : assert(indicatorSize != null && indicatorSize > 0.0), + super(key: key); + + /// This widget's selection and animation state. + /// + /// If [TabController] is not provided, then the value of + /// [DefaultTabController.of] will be used. + final TabController controller; + + /// The indicator circle's diameter (the default value is 12.0). + final double indicatorSize; + + /// The indicator circle's fill color for unselected pages. + /// + /// If this parameter is null, then the indicator is filled with [Colors.transparent]. + final Color color; + + /// The indicator circle's fill color for selected pages and border color + /// for all indicator circles. + /// + /// If this parameter is null, then the indicator is filled with the theme's + /// accent color, [ThemeData.accentColor]. + final Color selectedColor; + + Widget _buildTabIndicator( + int tabIndex, + TabController tabController, + ColorTween selectedColorTween, + ColorTween previousColorTween, + ) { + Color background; + if (tabController.indexIsChanging) { + // The selection's animation is animating from previousValue to value. + final double t = 1.0 - _indexChangeProgress(tabController); + if (tabController.index == tabIndex) + background = selectedColorTween.lerp(t); + else if (tabController.previousIndex == tabIndex) + background = previousColorTween.lerp(t); + else + background = selectedColorTween.begin; + } else { + // The selection's offset reflects how far the TabBarView has / been dragged + // to the previous page (-1.0 to 0.0) or the next page (0.0 to 1.0). + final double offset = tabController.offset; + if (tabController.index == tabIndex) { + background = selectedColorTween.lerp(1.0 - offset.abs()); + } else if (tabController.index == tabIndex - 1 && offset > 0.0) { + background = selectedColorTween.lerp(offset); + } else if (tabController.index == tabIndex + 1 && offset < 0.0) { + background = selectedColorTween.lerp(-offset); + } else { + background = selectedColorTween.begin; + } + } + return TabPageSelectorIndicator( + backgroundColor: background, + borderColor: selectedColorTween.end, + size: indicatorSize, + ); + } + + @override + Widget build(BuildContext context) { + final Color fixColor = color ?? Colors.transparent; + final Color fixSelectedColor = selectedColor ?? Theme.of(context).accentColor; + final ColorTween selectedColorTween = ColorTween(begin: fixColor, end: fixSelectedColor); + final ColorTween previousColorTween = ColorTween(begin: fixSelectedColor, end: fixColor); + final TabController tabController = controller ?? DefaultTabController.of(context); + assert(() { + if (tabController == null) { + throw FlutterError( + 'No TabController for $runtimeType.\n' + 'When creating a $runtimeType, you must either provide an explicit TabController ' + 'using the "controller" property, or you must ensure that there is a ' + 'DefaultTabController above the $runtimeType.\n' + 'In this case, there was neither an explicit controller nor a default controller.' + ); + } + return true; + }()); + final Animation animation = CurvedAnimation( + parent: tabController.animation, + curve: Curves.fastOutSlowIn, + ); + return AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget child) { + return Semantics( + label: 'Page ${tabController.index + 1} of ${tabController.length}', + child: Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(tabController.length, (int tabIndex) { + return _buildTabIndicator(tabIndex, tabController, selectedColorTween, previousColorTween); + }).toList(), + ), + ); + }, + ); + } +} diff --git a/lib/views/components/permanent/animated_text.dart b/lib/views/components/permanent/animated_text.dart new file mode 100644 index 0000000..c2edcba --- /dev/null +++ b/lib/views/components/permanent/animated_text.dart @@ -0,0 +1,67 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class AnimatedText extends StatefulWidget { + final String text; + final int delayInMilliseconds; + final int durationInMilliseconds; + final TextStyle textStyle; + + AnimatedText(this.text, this.delayInMilliseconds, + {this.durationInMilliseconds: 2500, this.textStyle}); + + @override createState() => AnimatedTextState(); +} + +class AnimatedTextState extends State + with SingleTickerProviderStateMixin { + + String currentText = ''; + + AnimationController _controller; + + List get textRunes=> widget.text.runes.toList(); + int get value => _controller.value.toInt(); + + int curIndex = 0; + + + @override + void initState() { + super.initState(); + currentText = String.fromCharCode(textRunes[0]); + + _controller = AnimationController( + vsync: this, + value: 0.0, + lowerBound: 0.0, + upperBound: textRunes.length.toDouble(), + duration: Duration(milliseconds: widget.durationInMilliseconds)); + + _controller..addListener(_updateText)..forward(); + + } + + _updateText(){ + if (value > curIndex && value < textRunes.length) { + setState(() { + curIndex = value; + currentText += String.fromCharCode(textRunes[curIndex]); + }); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Text(currentText, textAlign: TextAlign.left, + style: widget.textStyle ?? + TextStyle(fontWeight: FontWeight.w600, fontSize: 20.0),); + } +} \ No newline at end of file diff --git a/lib/views/components/permanent/circle.dart b/lib/views/components/permanent/circle.dart new file mode 100644 index 0000000..e50a58f --- /dev/null +++ b/lib/views/components/permanent/circle.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class Circle extends StatelessWidget { + final Color color; + final double radius; + final bool showShadow; + final Widget child; + + const Circle({this.color=Colors.blue, this.radius=6,this.showShadow=true,this.child}); + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + child: child==null?Container():child, + width: 2*radius, + height: 2*radius, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + boxShadow: [ + if (showShadow) + BoxShadow( + color: Colors.grey, + offset: Offset(.5,.5), + blurRadius: .5, + )] + ), + ); + } +} diff --git a/lib/views/components/permanent/circle_image.dart b/lib/views/components/permanent/circle_image.dart new file mode 100644 index 0000000..724bd11 --- /dev/null +++ b/lib/views/components/permanent/circle_image.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class CircleImage extends StatelessWidget { + CircleImage( + {Key key, + this.borderSize =3, + @required this.image, + this.size = 70, + this.shadowColor, + this.roundColor}) + : super(key: key); + final ImageProvider image; //图片 + final double size; //大小 + final Color shadowColor; //阴影颜色 + final Color roundColor; //边框颜色 + final double borderSize; + @override + Widget build(BuildContext context) { + var headIcon = Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, //圆形装饰线 + color: roundColor ?? Colors.white, + boxShadow: [ + BoxShadow( + //阴影 + color: shadowColor ?? Colors.grey.withOpacity(0.3), + offset: Offset(0.0, 0.0), blurRadius: 3.0, spreadRadius: 0.0, + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(borderSize), + child: + CircleAvatar( + backgroundImage: image, + ), + ), + ); + return headIcon; + } +} diff --git a/lib/views/components/permanent/circle_text.dart b/lib/views/components/permanent/circle_text.dart new file mode 100644 index 0000000..8918a9e --- /dev/null +++ b/lib/views/components/permanent/circle_text.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +class CircleText extends StatelessWidget { + CircleText( + {Key key, + @required this.text, + this.size = 70, + this.fontSize = 24, + this.color = Colors.white, + this.shadowColor, + this.backgroundColor, + this.roundColor}) + : super(key: key); + final String text; //图片 + final double size; //大小 + final double fontSize; //大小 + final Color shadowColor; //阴影颜色 + final Color color; //阴影颜色 + final Color roundColor; //边框颜色 + final Color backgroundColor; //边框颜色 + @override + Widget build(BuildContext context) { + var headIcon = Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, //圆形装饰线 + color: roundColor ?? Colors.white, + boxShadow: [ + BoxShadow( + //阴影 + color: shadowColor ?? Colors.grey.withOpacity(0.3), + offset: Offset(0.0, 0.0), blurRadius: 3.0, spreadRadius: 0.0, + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(3), + child: Container( + alignment: Alignment.center, + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, //圆形装饰线 + color: backgroundColor??Color(0xffD8F5FF), + ), + child: Text( + text.length>2?text.substring(0, 2):text, + style: TextStyle( + fontSize: fontSize, + color: color, + fontWeight: FontWeight.bold, + shadows: [ + Shadow( + //阴影 + color: Colors.grey, + offset: Offset(1.0, 1.0), blurRadius: 1.0, + ) + ], + ), + )), + ), + ); + return headIcon; + } +} \ No newline at end of file diff --git a/lib/views/components/permanent/code/back/code_panel.dart b/lib/views/components/permanent/code/back/code_panel.dart new file mode 100755 index 0000000..f56cd38 --- /dev/null +++ b/lib/views/components/permanent/code/back/code_panel.dart @@ -0,0 +1,66 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../high_light_code.dart'; +import '../highlighter_style.dart'; + + +class CodeWidget extends StatelessWidget { + CodeWidget({ + Key key, + @required this.code, + this.style, + this.fontSize = 13, + this.fontFamily, + }) : super(key: key); + + final String code; + final HighlighterStyle style; + final double fontSize; + final String fontFamily; + @override + Widget build(BuildContext context) { + if (code == null) { + return Container(); + } + + Widget _codeWidget = FutureBuilder( + future: compute(_hightlight, _HightlightArgs(style, code)), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Text(code); + } + if (snapshot.hasData) + return RichText( + text: TextSpan( + children: [snapshot.data], + ), + ); + // computing + return SizedBox.shrink(); + }, + ); + + return SingleChildScrollView( + child: Container( + child: DefaultTextStyle( + child: _codeWidget, + style: TextStyle(fontSize: fontSize, fontFamily: fontFamily), + ), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: style.backgroundColor ?? Color(0xffF6F8FA), + borderRadius: BorderRadius.all(Radius.circular(5.0))), + ), + ); + } +} + +class _HightlightArgs { + final HighlighterStyle style; + final String code; + _HightlightArgs(this.style, this.code); +} + +TextSpan _hightlight(_HightlightArgs args) => + DartHighlighter(args.style).format(args.code); diff --git a/lib/views/components/permanent/code/code_widget.dart b/lib/views/components/permanent/code/code_widget.dart new file mode 100644 index 0000000..be8d647 --- /dev/null +++ b/lib/views/components/permanent/code/code_widget.dart @@ -0,0 +1,49 @@ + +/// create by 张风捷特烈 on 2020-04-15 +/// contact me by email 1981462002@qq.com +/// 说明: + +import 'package:flutter/material.dart'; + +import 'high_light_code.dart'; +import 'highlighter_style.dart'; + +class CodeWidget extends StatelessWidget { + CodeWidget({Key key, @required this.code, this.style, this.fontSize = 13,this.fontFamily}) + : super(key: key); + + final String code; + final HighlighterStyle style; + final double fontSize; + final String fontFamily; + + @override + Widget build(BuildContext context) { + Widget body; + if (code == null) { + return Container(); + } else { + Widget _codeWidget; + try { + _codeWidget = RichText( + text: TextSpan( + style: TextStyle(fontSize: fontSize,fontFamily: fontFamily), + children: [DartHighlighter(style).format(code)], + ), + ); + } catch (err) { + _codeWidget = Text(code); + } + body = SingleChildScrollView( + child: Container( + child: _codeWidget, + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: style.backgroundColor ?? Color(0xffF6F8FA), + borderRadius: BorderRadius.all(Radius.circular(5.0))), + ), + ); + } + return body; + } +} \ No newline at end of file diff --git a/lib/views/components/permanent/code/high_light_code.dart b/lib/views/components/permanent/code/high_light_code.dart new file mode 100644 index 0000000..e3987e1 --- /dev/null +++ b/lib/views/components/permanent/code/high_light_code.dart @@ -0,0 +1,319 @@ +/// create by 张风捷特烈 on 2020-04-15 +/// contact me by email 1981462002@qq.com +/// 说明: + +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:string_scanner/string_scanner.dart'; + +import 'highlighter_style.dart'; + +/// final SyntaxHighlighterStyle style = SyntaxHighlighterStyle.lightThemeStyle(); +/// DartSyntaxHighlighter(style).format(source) + + +abstract class Highlighter { // ignore: one_member_abstracts + TextSpan format(String src); +} + +//暗黑模式下的高亮样式 +class DartHighlighter extends Highlighter { + DartHighlighter([this._style]) { + _spans = <_HighlightSpan>[]; + _style ??= HighlighterStyle.fromColors(HighlighterStyle.lightColor); + } + + HighlighterStyle _style; + + static const List _keywords = [ + 'abstract', 'as', 'assert', 'async', 'await', 'break', 'case', 'catch', + 'class', 'const', 'continue', 'default', 'deferred', 'do', 'dynamic', 'else', + 'enum', 'export', 'external', 'extends', 'factory', 'false', 'final', + 'finally', 'for', 'get', 'if', 'implements', 'import', 'in', 'is', 'library', + 'new', 'null', 'operator', 'part', 'rethrow', 'return', 'set', 'static', + 'super', 'switch', 'sync', 'this', 'throw', 'true', 'try', 'typedef', 'var', + 'void', 'while', 'with', 'yield' + ]; + + static const List _builtInTypes = [ + 'int', 'double', 'num', 'bool' + ]; + + String _src; + StringScanner _scanner; + + List<_HighlightSpan> _spans; + + @override + TextSpan format(String src) { + _src = src; + _scanner = StringScanner(_src); + + if (_generateSpans()) { + // Successfully parsed the code + final List formattedText = []; + int currentPosition = 0; + + for (_HighlightSpan span in _spans) { + if (currentPosition != span.start) + formattedText.add(TextSpan(text: _src.substring(currentPosition, span.start))); + + formattedText.add(TextSpan(style: span.textStyle(_style), text: span.textForSpan(_src))); + + currentPosition = span.end; + } + + if (currentPosition != _src.length) + formattedText.add(TextSpan(text: _src.substring(currentPosition, _src.length))); + + return TextSpan(style: _style.baseStyle, children: formattedText); + } else { + // Parsing failed, return with only basic formatting + return TextSpan(style: _style.baseStyle, text: src); + } + } + + bool _generateSpans() { + int lastLoopPosition = _scanner.position; + + while (!_scanner.isDone) { + // Skip White space + _scanner.scan(RegExp(r'\s+')); + + // Block comments + if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) { + _spans.add(_HighlightSpan( + _HighlightType.comment, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // Line comments + if (_scanner.scan('//')) { + final int startComment = _scanner.lastMatch.start; + + bool eof = false; + int endComment; + if (_scanner.scan(RegExp(r'.*\n'))) { + endComment = _scanner.lastMatch.end - 1; + } else { + eof = true; + endComment = _src.length; + } + + _spans.add(_HighlightSpan( + _HighlightType.comment, + startComment, + endComment + )); + + if (eof) + break; + + continue; + } + + // Raw r"String" + if (_scanner.scan(RegExp(r'r".*"'))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // Raw r'String' + if (_scanner.scan(RegExp(r"r'.*'"))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // Multiline """String""" + if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // Multiline '''String''' + if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // "String" + if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // 'String' + if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // Double + if (_scanner.scan(RegExp(r'\d+\.\d+'))) { + _spans.add(_HighlightSpan( + _HighlightType.number, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // Integer + if (_scanner.scan(RegExp(r'\d+'))) { + _spans.add(_HighlightSpan( + _HighlightType.number, + _scanner.lastMatch.start, + _scanner.lastMatch.end) + ); + continue; + } + + // Punctuation + if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) { + _spans.add(_HighlightSpan( + _HighlightType.punctuation, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // Meta data + if (_scanner.scan(RegExp(r'@\w+'))) { + _spans.add(_HighlightSpan( + _HighlightType.keyword, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + continue; + } + + // Words + if (_scanner.scan(RegExp(r'\w+'))) { + _HighlightType type; + + String word = _scanner.lastMatch[0]; + if (word.startsWith('_')) + word = word.substring(1); + + if (_keywords.contains(word)) + type = _HighlightType.keyword; + else if (_builtInTypes.contains(word)) + type = _HighlightType.keyword; + else if (_firstLetterIsUpperCase(word)) + type = _HighlightType.klass; + else if (word.length >= 2 && word.startsWith('k') && _firstLetterIsUpperCase(word.substring(1))) + type = _HighlightType.constant; + + if (type != null) { + _spans.add(_HighlightSpan( + type, + _scanner.lastMatch.start, + _scanner.lastMatch.end + )); + } + } + + // Check if this loop did anything + if (lastLoopPosition == _scanner.position) { + // Failed to parse this file, abort gracefully + return false; + } + lastLoopPosition = _scanner.position; + } + + _simplify(); + return true; + } + + void _simplify() { + for (int i = _spans.length - 2; i >= 0; i -= 1) { + if (_spans[i].type == _spans[i + 1].type && _spans[i].end == _spans[i + 1].start) { + _spans[i] = _HighlightSpan( + _spans[i].type, + _spans[i].start, + _spans[i + 1].end + ); + _spans.removeAt(i + 1); + } + } + } + + bool _firstLetterIsUpperCase(String str) { + if (str.isNotEmpty) { + final String first = str.substring(0, 1); + return first == first.toUpperCase(); + } + return false; + } +} + +enum _HighlightType { + number, + comment, + keyword, + string, + punctuation, + klass, + constant +} + +class _HighlightSpan { + _HighlightSpan(this.type, this.start, this.end); + final _HighlightType type; + final int start; + final int end; + + String textForSpan(String src) { + return src.substring(start, end); + } + + TextStyle textStyle(HighlighterStyle style) { + if (type == _HighlightType.number) + return style.numberStyle; + else if (type == _HighlightType.comment) + return style.commentStyle; + else if (type == _HighlightType.keyword) + return style.keywordStyle; + else if (type == _HighlightType.string) + return style.stringStyle; + else if (type == _HighlightType.punctuation) + return style.punctuationStyle; + else if (type == _HighlightType.klass) + return style.classStyle; + else if (type == _HighlightType.constant) + return style.constantStyle; + else + return style.baseStyle; + } +} \ No newline at end of file diff --git a/lib/views/components/permanent/code/highlighter_style.dart b/lib/views/components/permanent/code/highlighter_style.dart new file mode 100644 index 0000000..3980bfc --- /dev/null +++ b/lib/views/components/permanent/code/highlighter_style.dart @@ -0,0 +1,139 @@ +/// create by 张风捷特烈 on 2020-04-15 +/// contact me by email 1981462002@qq.com +/// 说明: + +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-04-11 +/// contact me by email 1981462002@qq.com +/// 说明: + +class HighlighterStyle { + //句法高亮样式 + const HighlighterStyle( + { //构造函数 + this.baseStyle, //基础样式 + this.numberStyle, //数字的样式 + this.commentStyle, //注释样式 + this.keywordStyle, //关键字样式 + this.stringStyle, //字符串样式 + this.punctuationStyle, //标点符号样式 + this.classStyle, //类名 + this.backgroundColor, + this.constantStyle}); + + static List get lightColor => [ + 0xFF000000, //基础 + 0xFF00b0e8, //数字 + 0xFF9E9E9E, //注释 + 0xFF9C27B0, //关键 + 0xFF43A047, //字符串 + 0xFF000000, //标点符号 + 0xFF3D62F5, //类名 + 0xFF795548, //常量 + 0xffF6F8FA, //背景 + ]; + + static List get darkColor => [ + 0xFFFFFFFF, //基础 + 0xFFDF935F, //数字 + 0xFF9E9E9E, //注释 + 0xFF80CBC4, //关键字 + 0xFFB9CA4A, //字符串 + 0xFFFFFFFF, //标点符号 + 0xFF7AA6DA, //类名 + 0xFF795548, //常量 + 0xFF1D1F21, //背景 + ]; + + static List get gitHub => + [ + 0xFF333333, //基础 + 0xFF008081, //数字 + 0xFF9D9D8D, //注释 + 0xFF009999, //关键字 + 0xFFDD1045, //字符串 + 0xFF333333, //标点符号 + 0xFF6F42C1, //类名 + 0xFF795548, //常量 + 0xFFF8F8F8, //背景 + ]; + + static List get zenburn => [ + 0xFFDCDCDC, //普通字 + 0xFF87C5C8, //数字 + 0xFF8F8F8F, //注释 + 0xFFE4CEAB, //关键字 + 0xFFCC9493, //字符串 + 0xFFDCDCDC, //标点符号 + 0xFFEFEF90, //类名 + 0xFFEF5350, //常量 + 0xFF3F3F3F, //背景 + ]; + + static List get mf =>[ + 0xFF707D95, //基础 + 0xFF6897BB, //数字 + 0xFF629755, //注释 + 0xFFCC7832, //关键 + 0xFFF14E9F, //字符串 + 0xFFFFBB33, //标点符号 + 0xFF66CCFF, //类名 + 0xFF9876AA, //常量 + 0xFF2B2B2B //背景 + ]; + + static List get solarized =>[ + 0xFF657B83, // 普通字 + 0xFFD33682, // 数字 + 0xFF93A1A1, // 注释 + 0xFF859900, // 关键字 + 0xFF2AA198, // 字符串 + 0xFF859900, // 标点符号 + 0xFF268BD2, // 类名 + 0xFF268BD2, //常量 + 0xFFDDD6C1, // 背景 + ]; + + factory HighlighterStyle.fromColors(List colors) => HighlighterStyle( + baseStyle: TextStyle( + color: Color(colors[0]), + ), + numberStyle: TextStyle( + color: Color(colors[1]), + ), + commentStyle: TextStyle( + color: Color( + colors[2], + ), + fontStyle: FontStyle.italic), + keywordStyle: TextStyle( + fontWeight: FontWeight.bold, + color: Color( + colors[3], + ), + ), + stringStyle: TextStyle( + color: Color(colors[4]), + ), + punctuationStyle: TextStyle( + color: Color(colors[5]), + ), + classStyle: TextStyle( + color: Color(colors[6]), + ), + constantStyle: TextStyle( + color: Color(colors[7]), + ), + backgroundColor: Color(colors[8]), + ); + final TextStyle baseStyle; + final TextStyle numberStyle; + final TextStyle commentStyle; + final TextStyle keywordStyle; + final TextStyle stringStyle; + final TextStyle punctuationStyle; + final TextStyle classStyle; + final TextStyle constantStyle; + final Color backgroundColor; +} \ No newline at end of file diff --git a/lib/views/components/permanent/edit_panel.dart b/lib/views/components/permanent/edit_panel.dart new file mode 100644 index 0000000..7a554d8 --- /dev/null +++ b/lib/views/components/permanent/edit_panel.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; + +typedef ChangeCallback = void Function(String str); + +///输入面板 +class EditPanel extends StatefulWidget { + EditPanel( + {Key key, + this.backgroundColor = Colors.white, + this.color = Colors.lightBlue, + this.minLines = 4, + this.maxLines = 15, + this.fontSize = 14, + this.submitClear = true, + this.defaultText = "", + this.onChange, + this.hint = "写点什么..."}) + : super(key: key); + + final Color color; //字颜色 + final Color backgroundColor; //背景色颜色 + final int minLines; //最小行数 + final int maxLines; //最大行数 + final double fontSize; //字号 + final String hint; //提示字 + final bool submitClear; //提交是否清空文字 + final ChangeCallback onChange; //提交监听 + final String defaultText; //提交监听 + + @override + _EditPanelState createState() => _EditPanelState(); +} + +class _EditPanelState extends State { + var _radius; //边角半径 + + TextEditingController _controller; + + @override + void initState() { + _radius = Radius.circular(widget.fontSize * 0.618); + _controller = TextEditingController(text: widget.defaultText??''); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var panel = TextField( + controller: _controller, + //输入控制器 + keyboardType: TextInputType.text, + //键盘类型 + textAlign: TextAlign.start, + //文字居左 + cursorColor: Colors.black, + //游标颜色 + minLines: widget.minLines, + //最小行数 + maxLines: widget.maxLines, + //最大行数 + style: TextStyle( + //文字样式 + fontSize: widget.fontSize, + color: widget.color, + backgroundColor: Colors.white), + decoration: InputDecoration( + //装饰线 + filled: true, + //是否填充 + fillColor: widget.backgroundColor, + //填充色 + hintText: widget.hint, + //提示文字 + hintStyle: TextStyle(color: Colors.black26, fontSize: widget.fontSize), + //提示文字样式 + focusedBorder: UnderlineInputBorder( + //聚焦时边线 + borderSide: BorderSide(color: widget.backgroundColor), + borderRadius: BorderRadius.all(_radius), + ), + enabledBorder: UnderlineInputBorder( + //非聚焦时边线 + borderSide: BorderSide(color: widget.backgroundColor), + borderRadius: BorderRadius.all(_radius), + ), + ), + onChanged: (str) { + //文字变化监听 + if (widget.onChange != null) widget.onChange(str); + }, + onSubmitted: (str) { + //提交监听 + FocusScope.of(context).requestFocus(FocusNode()); //收起键盘 + if (widget.submitClear) { + setState(() { + _controller.clear(); + }); + } + }, + ); + return panel; + } +} diff --git a/lib/views/components/permanent/feedback_widget.dart b/lib/views/components/permanent/feedback_widget.dart new file mode 100644 index 0000000..36a148e --- /dev/null +++ b/lib/views/components/permanent/feedback_widget.dart @@ -0,0 +1,82 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-04-10 +/// contact me by email 1981462002@qq.com +/// 说明: + +enum FeedMode { + scale, + fade, + rotate, +} + +class FeedbackWidget extends StatefulWidget { + final Widget child; + final FeedMode mode; + final Duration duration; + final Function() onPressed; + final a; + + FeedbackWidget( + {@required this.child, + this.mode = FeedMode.scale, + this.a = 0.9, + this.duration = const Duration(milliseconds: 150), + @required this.onPressed}); + + @override + _FeedBackState createState() => _FeedBackState(); +} + +class _FeedBackState extends State + with SingleTickerProviderStateMixin { + AnimationController _controller; + var rate = 1.0; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this, duration: widget.duration) + ..addListener(() { + setState(() => rate = (widget.a - 1) * _controller.value + 1); + }) + ..addStatusListener((s) { + if (s == AnimationStatus.completed) { + _controller.reverse(); + } + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + _controller.forward(); + if(widget.onPressed!=null){ + widget.onPressed(); + } + }, + child: _buildByMode(widget.child, widget.mode), + ); + } + + Widget _buildByMode(Widget child, FeedMode mode) { + switch (mode) { + case FeedMode.scale: + return Transform.scale(scale: rate, child: widget.child); + case FeedMode.fade: + return Opacity(opacity: rate, child: widget.child); + case FeedMode.rotate: + return Transform.rotate(angle: rate * pi * 2, child: widget.child); + } + return Container(); + } +} diff --git a/lib/views/components/permanent/input_button.dart b/lib/views/components/permanent/input_button.dart new file mode 100644 index 0000000..81f6b51 --- /dev/null +++ b/lib/views/components/permanent/input_button.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; + +typedef SubmitCallback = void Function(String str); + +class InputButtonConfig { + final double height; //高度 + final IconData iconData; //图标 + final String hint; //提示文字 + final double fontSize; //文字大小 + final Widget front; //前面图标 + final bool submitClear; //是否提交清空 + + + const InputButtonConfig( + {this.height = 36, + + this.iconData = Icons.add, + this.fontSize = 14, + this.submitClear = true, + this.front, + this.hint = "I want to say..."}); +} + +class InputButton extends StatefulWidget { + final SubmitCallback onSubmit; + final ValueChanged onChanged; + final VoidCallback onTap; + final InputButtonConfig config; + final String defaultText; + + InputButton( + {Key key, + this.onSubmit, + this.onChanged, + this.defaultText, + this.onTap, + this.config = const InputButtonConfig()}) + : super(key: key); + + @override + _InputButtonState createState() => _InputButtonState(); +} + +class _InputButtonState extends State { + var _height; + var _fontSize; + var _radius; + + TextEditingController _controller; + + @override + void initState() { + super.initState(); + _height = widget.config.height; + _fontSize = widget.config.fontSize; + _radius = Radius.circular(_height / 3.6); + _controller = TextEditingController(text: widget.defaultText??''); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var textField = TextField( + controller: _controller, + maxLines: 1, + style: TextStyle( + fontSize: _fontSize, + color: Colors.lightBlue, + backgroundColor: Colors.white), + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: widget.config.hint, + hintStyle: TextStyle(color: Colors.black26, fontSize: _fontSize), + contentPadding: EdgeInsets.only(left: 14.0, top: -_fontSize), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white), + borderRadius: + BorderRadius.only(topLeft: _radius, bottomLeft: _radius), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white), + borderRadius: + BorderRadius.only(topLeft: _radius, bottomLeft: _radius), + ), + ), + onChanged: (str) { + if (widget.onChanged != null) widget.onChanged(str); + }, + onTap: widget.onTap, + ); + var btn = RaisedButton( + + elevation: 0, + child: Icon(widget.config.iconData,color: Theme.of(context).primaryColor,), + color: Color(0x99E0E0E0), + padding: EdgeInsets.zero, + onPressed: () { + FocusScope.of(context).requestFocus(FocusNode()); //收起键盘 + if (widget.onSubmit != null) widget.onSubmit(_controller.text); + if (widget.config.submitClear) { + setState(() { + _controller.clear(); + }); + } + }, + ); + var inputBtn = Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + child: textField, + height: _height, + ), + ), + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.zero, + bottomLeft: Radius.zero, + topRight: _radius, + bottomRight: _radius), + child: Container( + child: btn, + width: _height, + height: _height, + ), + ), + ], + ); + return inputBtn; + } +} diff --git a/lib/views/components/permanent/panel.dart b/lib/views/components/permanent/panel.dart new file mode 100644 index 0000000..a0481e0 --- /dev/null +++ b/lib/views/components/permanent/panel.dart @@ -0,0 +1,24 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + + +class Panel extends StatelessWidget { + final double radius; + final Color color; + final Widget child; + + Panel({this.radius = 5.0, this.color, this.child}); + + @override + Widget build(BuildContext context) { + return Container( + child: child, + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: color ?? Color(0xffF6F8FA), + borderRadius: BorderRadius.all(Radius.circular(radius))), + ); + } +} + + diff --git a/lib/views/components/permanent/tag.dart b/lib/views/components/permanent/tag.dart new file mode 100644 index 0000000..957d3c7 --- /dev/null +++ b/lib/views/components/permanent/tag.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +class Tag extends StatelessWidget { + final Size size; + final double shadowHeight; + final double tranRate; + final Color color; + + const Tag({this.size = const Size(100, 150),this.shadowHeight=9.0,this.tranRate=0.25,this.color=Colors.red}); + + @override + Widget build(BuildContext context) { + return Container( + width: size.width, + height: size.height, + child: CustomPaint( + painter: _TagPaint( + color: color, + shadowHeight: shadowHeight, + tranRate: tranRate, + ), + ), + ); + } +} + +class _TagPaint extends CustomPainter { + Path path = Path(); + Path shadowPath = Path(); + Paint _paint; + final tranRate; + final double shadowHeight; + final Color color; + + final rate = 0.5; + + _TagPaint({this.tranRate, this.color ,this.shadowHeight}) + : _paint = Paint()..color = color; + + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Offset.zero & size); + + path.moveTo(0, 0); + path.relativeLineTo(size.width-shadowHeight*rate, 0); + path.relativeLineTo(0, size.height); + path.relativeLineTo(-(size.width-shadowHeight*rate) / 2, -tranRate * size.height); + path.relativeLineTo(-(size.width-shadowHeight*rate) / 2, tranRate * size.height); + path.close(); + canvas.drawPath(path, _paint..color = color); + + shadowPath.moveTo(size.width-shadowHeight*rate, 0); + shadowPath.relativeLineTo(0, shadowHeight); + shadowPath.relativeLineTo(shadowHeight*rate, 0); + shadowPath.close(); + canvas.drawPath(shadowPath, _paint..color=color.withAlpha(88)); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/views/components/project/color_chooser.dart b/lib/views/components/project/color_chooser.dart new file mode 100644 index 0000000..f31abbc --- /dev/null +++ b/lib/views/components/project/color_chooser.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_unit/views/components/permanent/circle.dart'; +import 'package:flutter_unit/views/components/permanent/feedback_widget.dart'; + + +typedef CheckCallback = void Function(T color); + +class ColorChooser extends StatefulWidget { + ColorChooser( + {Key key, + this.defaultIndex=0, + this.radius = 10, + @required this.colors, + @required this.onChecked}) + : super(key: key); + final double radius; + final List colors; + final Function(Color) onChecked; + final int defaultIndex; + + @override + _ColorChooserState createState() => _ColorChooserState(); +} + +class _ColorChooserState extends State { + List _checkLi; + int _perPosition = 0; + + @override + void initState() { + _perPosition=widget.defaultIndex; + _checkLi = List.generate(widget.colors.length, (_) => false); + _checkLi[_perPosition] = true; + super.initState(); + } + + @override + Widget build(BuildContext context) { + var li = []; + for (var i = 0; i < widget.colors.length; i++) { + li.add(FeedbackWidget( + a: 0.8, + onPressed: () { + _checkLi[_perPosition] = false; + _perPosition = i; + _checkLi[i] = true; + if (widget.onChecked != null) widget.onChecked(widget.colors[i]); + setState(() {}); + }, + child: Circle( + color: widget.colors[i], + radius: widget.radius, + child: _checkLi[i] + ? Icon( + Icons.star, + size: 15, + color: Colors.white, + ) + : null, +// checked: _checkLi[i] + ))); + } + return Wrap(spacing: 10, runSpacing: 10, children: li); + } +} + +class IconChooser extends StatefulWidget { + IconChooser( + {Key key, + this.radius = 20, + @required this.icons, + @required this.onChecked, + this.initialIndex = 0}) + : super(key: key); + final double radius; + final List icons; + final int initialIndex; + final CheckCallback onChecked; + + @override + _IconChooserState createState() => _IconChooserState(); +} + +class _IconChooserState extends State { + List _checkLi; + int _perPosition = 0; + + @override + void initState() { + _checkLi = List.generate(widget.icons.length, (_) => false); + _checkLi[widget.initialIndex] = true; + super.initState(); + } + + @override + Widget build(BuildContext context) { + var li = []; + for (var i = 0; i < widget.icons.length; i++) { + li.add(GestureDetector( + onTap: () { + _checkLi[_perPosition] = false; + _perPosition = i; + _checkLi[i] = true; + if (widget.onChecked != null) widget.onChecked(i); + setState(() {}); + }, + child: buildIcon(checked: _checkLi[i], icon: widget.icons[i]))); + } + return Wrap( + alignment: WrapAlignment.center, + runSpacing: 10, + spacing: 25, + children: li); + } + + buildIcon({bool checked, IconData icon}) { + var defaultColor = Colors.black26; + var activeColor = Colors.blue; + return Icon( + icon, + color: checked ? activeColor : defaultColor, + size: 35, + ); + } +} diff --git a/lib/views/components/project/widget_node_panel.dart b/lib/views/components/project/widget_node_panel.dart new file mode 100644 index 0000000..02d57e4 --- /dev/null +++ b/lib/views/components/project/widget_node_panel.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:flutter_unit/app/res/toly_icon.dart'; +import 'package:flutter_unit/app/utils/Toast.dart'; +import 'package:flutter_unit/views/components/permanent/circle.dart'; +import 'package:flutter_unit/views/components/permanent/code/back/code_panel.dart'; +import 'package:flutter_unit/views/components/permanent/panel.dart'; + +import 'package:share/share.dart'; +import 'package:toggle_rotate/toggle_rotate.dart'; + +import '../permanent/feedback_widget.dart'; +import '../permanent/code/highlighter_style.dart'; + +/// create by 张风捷特烈 on 2020-04-13 +/// contact me by email 1981462002@qq.com +/// 说明: 一个Widget的知识点对应的界面 + +class WidgetNodePanel extends StatefulWidget { + final String text; + final String subText; + final String code; + final Widget show; + final HighlighterStyle codeStyle; + final String codeFamily; + + WidgetNodePanel( + {this.text, + this.subText, + this.code, + this.show, + this.codeStyle, + this.codeFamily}); + + @override + _WidgetNodePanelState createState() => _WidgetNodePanelState(); +} + +class _WidgetNodePanelState extends State { + var _crossFadeState = CrossFadeState.showFirst; + + bool get isFirst => _crossFadeState == CrossFadeState.showFirst; + + Color get themeColor => Theme.of(context).primaryColor; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + buildNodeTitle(), + SizedBox( + height: 20, + ), + _buildCode(context), + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 20), + child: widget.show, + ), + _buildNodeInfo(), + Divider(), + ], + ), + ); + } + + Widget buildNodeTitle() => Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Circle( + color: themeColor, + radius: 5, + ), + ), + Expanded( + child: Text( + '${widget.text}', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + _buildShareButton(), + _buildCodeButton() + ], + ); + + Widget _buildNodeInfo() => Container( + width: double.infinity, + child: Panel( + child: Text( + '${widget.subText}', + style: TextStyle(fontSize: 14), + )), + ); + + Widget _buildCodeButton() => Padding( + padding: const EdgeInsets.only(right: 10.0), + child: ToggleRotate( + durationMs: 300, + child: Icon( + TolyIcon.icon_code, + color: themeColor, + ), + onTap: _toggleCodePanel, + ), + ); + + Widget _buildShareButton() => FeedbackWidget( + mode: FeedMode.fade, + a: 0.4, + onPressed: _doCopy, + child: Padding( + padding: const EdgeInsets.only( + right: 10, + ), + child: Icon( + Icons.content_copy, + size: 20, + color: themeColor, + ), + ), + ); + + Widget _buildCode(BuildContext context) => AnimatedCrossFade( + firstCurve: Curves.easeInCirc, + secondCurve: Curves.easeInToLinear, + firstChild: Container(), + secondChild: Container( + width: MediaQuery.of(context).size.width, + child: CodeWidget( + fontFamily: widget.codeFamily, + code: isFirst?'':widget.code, + style: widget.codeStyle ?? + HighlighterStyle.fromColors(HighlighterStyle.lightColor), + ), + ), + duration: Duration(milliseconds: 200), + crossFadeState: _crossFadeState, + ); + + //执行分享 + _doCopy() async{ + await Clipboard.setData(ClipboardData(text: widget.code)); + Toast.toast(context, '复制成功!',duration: Duration(seconds: 1)); +// Share.share(widget.code); + } + + // 折叠代码面板 + _toggleCodePanel() { + setState(() { + _crossFadeState = + !isFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond; + }); + } +} diff --git a/lib/views/pages/app/bloc_wrapper.dart b/lib/views/pages/app/bloc_wrapper.dart new file mode 100644 index 0000000..8e1e162 --- /dev/null +++ b/lib/views/pages/app/bloc_wrapper.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_unit/app/enums.dart'; +import 'package:flutter_unit/blocs/bloc_exp.dart'; +import 'package:flutter_unit/repositories/impl/catagory_db_repository.dart'; +import 'package:flutter_unit/repositories/impl/widget_db_repository.dart'; +import 'package:flutter_unit/repositories/impl/widget_innner_repository.dart'; +import 'package:flutter_unit/storage/app_storage.dart'; + +/// create by 张风捷特烈 on 2020/4/28 +/// contact me by email 1981462002@qq.com +/// 说明: + +final storage = AppStorage(); + +class BlocWrapper extends StatelessWidget { + final Widget child; + + BlocWrapper({this.child}); + + final repository = WidgetInnerRepository(storage); + final categoryRepo = CategoryDbRepository(storage); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider(//使用MultiBlocProvider包裹 + providers: [ + //Bloc提供器 + BlocProvider( + create: (_) => GlobalBloc(storage)..add(EventInitApp())), + + BlocProvider( + create: (_) => HomeBloc(repository: repository) + ..add(EventTabTap(WidgetFamily.statelessWidget))), + + BlocProvider( + create: (_) => DetailBloc(repository: repository)), + BlocProvider( + create: (_) => + CategoryBloc(repository: categoryRepo)..add(EventLoadCategory())), + + BlocProvider( + create: (_) => + CollectBloc(repository: repository)..add(EventSetCollectData())), + + BlocProvider( + create: (_) => SearchBloc(repository: repository)), + ], child: child); + } +} \ No newline at end of file diff --git a/lib/views/pages/app/flutter_app.dart b/lib/views/pages/app/flutter_app.dart new file mode 100644 index 0000000..12738fc --- /dev/null +++ b/lib/views/pages/app/flutter_app.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_unit/app/router.dart'; +import 'package:flutter_unit/blocs/bloc_exp.dart'; +import 'package:flutter_unit/views/pages/app/splash/unit_splash.dart'; + +/// create by 张风捷特烈 on 2020/4/28 +/// contact me by email 1981462002@qq.com +/// 说明: + +class FlutterApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (_, state) { + return BlocProvider( + create: (_) => CategoryWidgetBloc( + categoryBloc: BlocProvider.of(context)), + child: MaterialApp( + title: 'Flutter Unit', + debugShowCheckedModeBanner: false, + onGenerateRoute: UnitRouter.generateRoute, + theme: ThemeData( + visualDensity: VisualDensity.adaptivePlatformDensity, + primarySwatch: state.themeColor, + fontFamily: state.fontFamily, + ), + home: UnitSplash()), + ); + }); + } +} diff --git a/lib/views/pages/app/navigation/unit_bottom_bar.dart b/lib/views/pages/app/navigation/unit_bottom_bar.dart new file mode 100644 index 0000000..328fac2 --- /dev/null +++ b/lib/views/pages/app/navigation/unit_bottom_bar.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-04-11 +/// contact me by email 1981462002@qq.com +/// 说明: + +class UnitBottomBar extends StatefulWidget { + final Color color; + final Map itemData; + final Function(int) onItemClick; + + UnitBottomBar( + {this.color = Colors.blue, + @required this.itemData, + @required this.onItemClick}); + + @override + _UnitBottomBarState createState() => _UnitBottomBarState(); +} + +class _UnitBottomBarState extends State { + int _position = 0; + + @override + Widget build(BuildContext context) { + return BottomAppBar( + elevation: 0, + shape: const CircularNotchedRectangle(), + notchMargin: 5, + color: widget.color, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: info + .map((e) => _buildChild(context, info.indexOf(e), widget.color)) + .toList(), + )); + } + + List get info => widget.itemData.keys.toList(); + + final borderTR = const BorderRadius.only(topRight: Radius.circular(10)); + final borderTL = const BorderRadius.only(topLeft: Radius.circular(10)); + final paddingTR = const EdgeInsets.only(top: 2, right: 2); + final paddingTL = const EdgeInsets.only(top: 2, left: 2); + + Widget _buildChild(BuildContext context, int i, Color color) { + var active = i == _position; + bool left = i == 0; + + return GestureDetector( + onTap: () => _tapTab(i), + onLongPress: () => _onLongPress(context, i), + child: Material( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: left ? borderTR : borderTL), + child: Container( + margin: left ? paddingTR : paddingTL, + alignment: Alignment.center, + decoration: BoxDecoration( + color: color.withAlpha(88), + borderRadius: left ? borderTR : borderTL), + height: 45, + width: 100, + child: Icon( + widget.itemData[info[i]], + color: active ? color : Colors.white, + size: active ? 28 : 24, + )), + ), + ); + } + + _tapTab(int i) { + setState(() { + _position = i; + if (widget.onItemClick != null) { + widget.onItemClick(_position); + } + }); + } + + _onLongPress(BuildContext context, int i) { + if (i == 0) { + Scaffold.of(context).openDrawer(); + } + if (i == 1) { + Scaffold.of(context).openEndDrawer(); + } + } +} diff --git a/lib/views/pages/app/navigation/unit_navigation.dart b/lib/views/pages/app/navigation/unit_navigation.dart new file mode 100644 index 0000000..594b074 --- /dev/null +++ b/lib/views/pages/app/navigation/unit_navigation.dart @@ -0,0 +1,285 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_unit/app/res/cons.dart'; +import 'package:flutter_unit/app/router.dart'; +import 'package:flutter_unit/app/res/toly_icon.dart'; +import 'package:flutter_unit/blocs/bloc_exp.dart'; +import 'package:flutter_unit/views/components/permanent/circle_image.dart'; +import 'package:flutter_unit/views/components/permanent/feedback_widget.dart'; +import 'package:flutter_unit/views/pages/category/home_right_drawer.dart'; +import 'package:flutter_unit/views/pages/home/home_drawer.dart'; +import 'package:flutter_unit/views/pages/home/home_page.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class UnitNavigation extends StatefulWidget { + @override + _UnitNavigationState createState() => _UnitNavigationState(); +} + +class _UnitNavigationState extends State { + PageController _controller; //页面控制器,初始0 + + @override + void initState() { + _controller = PageController(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); //释放控制器 + super.dispose(); + } + final tempText = Text('这里是一片未知的领域\n等待着勇者的探寻...',style: TextStyle(fontSize: 24,color: Colors.white),); + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (_, state) => Scaffold( + drawer: HomeDrawer(color: state.homeColor), + //左滑页 + endDrawer: HomeRightDrawer( + color: state.homeColor, + ), + //右滑页 + floatingActionButton: _buildSearchButton(state.homeColor), + body: Row( + children: [ + _buildLeftNav(), + Expanded( + child: Container( + child: PageView( + physics: const NeverScrollableScrollPhysics(), + //使用PageView实现页面的切换 + controller: _controller, + children: [ + HomePage(), + Container(color: Colors.red,alignment: Alignment.center,child:tempText,), + Container(color: Colors.blue,alignment: Alignment.center,child: tempText), + Container(color: Colors.purpleAccent,alignment: Alignment.center,child: tempText), + Container(color: Colors.green,alignment: Alignment.center,child: tempText), + +// CollectPage(), +// PaintUnitPage(), +// LayoutUnitPage(), +// BugUnitPage(), + ], + ), + ), + ), + ], + ), + )); + } + + Widget _buildSearchButton(Color color) { + return FloatingActionButton( + elevation: 2, + backgroundColor: color, + child: const Icon(Icons.search), + onPressed: () => Navigator.of(context).pushNamed(UnitRouter.search), + ); + } + + _onTapNav(int index) { + _controller.jumpToPage(index); + if (index == 1) { + BlocProvider.of(context).add(EventSetCollectData()); + } + } + + Widget _buildLeftNav() { + return Container( + padding: EdgeInsets.only(top: 20), + alignment: Alignment.topCenter, + margin: EdgeInsets.only(right: 1), + width: 120, + decoration: BoxDecoration(color: Color(0xff2C3036), boxShadow: [ + BoxShadow(color: Colors.grey, offset: Offset(1, 0), blurRadius: 2) + ]), + child: Column( + children: [ + Wrap( + direction: Axis.vertical, + spacing: 10, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + CircleImage( + image: AssetImage('assets/images/icon_head.png'), + size: 60, + ), + Text( + '张风捷特烈', + style: TextStyle(color: Colors.white70), + ) + ], + ), + buildIcons(), + Divider( + color: Colors.white, + height: 1, + endIndent: 20, + ), +// SizedBox(height: 60,), + Expanded( + flex: 5, + child: Center( + child: RightNavBar( + itemData: Cons.ICONS_MAP, + onItemClick: _onTapNav, + color: Theme.of(context).primaryColor, + ), + ), + ), + + Expanded( + child: Container(), + flex: 1, + ), + Divider( + indent: 20, + color: Colors.white, + height: 1, + ), + Builder( + builder: (ctx) => FeedbackWidget( + onPressed: () => Scaffold.of(ctx).openDrawer(), + child: Padding( + padding: const EdgeInsets.only(bottom: 20, top: 20), + child: Icon( + Icons.settings, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ); + } + + Widget buildIcons() { + return Padding( + padding: const EdgeInsets.only(bottom: 20, top: 20), + child: Wrap( + spacing: 5, + children: [ + FeedbackWidget( + onPressed: () => _launchURL("http://blog.toly1994.com"), + child: Icon( + TolyIcon.icon_item, + color: Colors.white, + ), + ), + FeedbackWidget( + onPressed: () => + _launchURL("https://github.com/toly1994328/FlutterUnit"), + child: Icon( + TolyIcon.icon_github, + color: Colors.white, + ), + ), + FeedbackWidget( + onPressed: () => + _launchURL("https://juejin.im/user/5b42c0656fb9a04fe727eb37"), + child: Icon( + TolyIcon.icon_juejin, + color: Colors.white, + ), + ), + ], + ), + ); + } + + _launchURL(String url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + debugPrint('Could not launch $url'); + } + } +} + +class RightNavBar extends StatefulWidget { + final Color color; + final Map itemData; + final Function(int) onItemClick; + final Size itemSize; + + RightNavBar( + {this.color = Colors.blue, + this.itemData, + this.onItemClick, + this.itemSize = const Size(120, 35)}); + + @override + _RightNavBarState createState() => _RightNavBarState(); +} + +class _RightNavBarState extends State { + int _position = 0; + + List get info => widget.itemData.keys.toList(); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: info + .map((e) => _buildChild(context, info.indexOf(e), widget.color)) + .toList(), + ); + } + + Widget _buildChild(BuildContext context, int i, Color color) { + var active = i == _position; + + return GestureDetector( + onTap: () => _tapTab(i), + child: Container( + alignment: Alignment.topLeft, + margin: EdgeInsets.only(top: 10), + width: widget.itemSize.width, + child: UnconstrainedBox( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: active ? widget.color : Colors.white.withAlpha(33), + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + bottomRight: Radius.circular(20))), + width: + active ? widget.itemSize.width*0.95 : widget.itemSize.width * 0.85, + height: widget.itemSize.height, + child: Wrap( + spacing: 10, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Icon( + widget.itemData[info[i]], + size: active ? 24 : 20, + color: active ? Colors.white:Colors.white70, + ), + Text( + info[i], + style: TextStyle( + color: active ? Colors.white:Colors.white70, + ), + ), + ], + ), + ), + ), + )); + } + + _tapTab(int i) { + setState(() { + _position = i; + if (widget.onItemClick != null) { + widget.onItemClick(_position); + } + }); + } +} diff --git a/lib/views/pages/app/splash/unit_paint.dart b/lib/views/pages/app/splash/unit_paint.dart new file mode 100644 index 0000000..121c40d --- /dev/null +++ b/lib/views/pages/app/splash/unit_paint.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +class UnitPainter extends CustomPainter { + Paint _paint; + double width; + double factor; + Color color; + Path _path1 = Path(); + Path _path2 = Path(); + Path _path3 = Path(); + Path _path4 = Path(); + + UnitPainter({this.width = 200.0, this.factor,this.color=Colors.blue}) { + _paint = Paint(); + } + + @override + void paint(Canvas canvas, Size size) { + + + canvas.translate( + size.width / 2 - width * 0.5, size.height / 2 - width * 0.5); + + canvas.save(); + canvas.translate( + -size.width / 2 * (1 - factor), -size.width / 2 * (1 - factor)); + drawColor1(canvas); + canvas.restore(); + + canvas.save(); + + canvas.translate( + size.width / 2 * (1 - factor), -size.width / 2 * (1 - factor)); + drawColor2(canvas); + canvas.restore(); + + canvas.save(); + canvas.translate( + size.width / 2 * (1 - factor), size.width / 2 * (1 - factor)); + drawColor3(canvas); + canvas.restore(); + + canvas.save(); + canvas.translate( + -size.width / 2 * (1 - factor), size.width / 2 * (1 - factor)); + drawColor4(canvas); + canvas.restore(); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } + + void drawColor1(Canvas canvas) { + _path1.moveTo(0, 0); + _path1.lineTo(width * 0.618 * factor - 1, 0); + _path1.lineTo(width * 0.5 - 1, width * 0.5 - 1); + _path1.lineTo(0, width * (1 - 0.618) * factor - 1); + + canvas.drawPath(_clipAngle(_path1), _paint..color = Colors.red); + } + + void drawColor2(Canvas canvas) { + _path2.moveTo(width * 0.618 * factor, 0); + _path2.lineTo(width, 0); + _path2.lineTo(width, width * 0.618 * factor); + _path2.lineTo(width * 0.5, width * 0.5); + + canvas.drawPath(_clipAngle(_path2), _paint..color = Colors.blue); + } + + void drawColor3(Canvas canvas) { + _path3.moveTo(width * 0.5 + 1, width * 0.5 + 1); + _path3.lineTo(width, width * 0.618 * factor + 1); + _path3.lineTo(width, width); + _path3.lineTo(width * (1 - 0.618) * factor + 1, width); + canvas.drawPath(_clipAngle(_path3), _paint..color = Colors.green); + } + + void drawColor4(Canvas canvas) { + _path4.moveTo(0, width * (1 - 0.618) * factor); + _path4.lineTo(width * 0.5, width * 0.5); + _path4.lineTo(width * (1 - 0.618) * factor, width); + _path4.lineTo(0, width); + canvas.drawPath(_clipAngle(_path4), _paint..color = Colors.yellow); + } + + Path _clipAngle(Path path) { + return Path.combine( + PathOperation.difference, + path, + Path() + ..addOval(Rect.fromCircle( + center: Offset(width * 0.5, width * 0.5), radius: 25.0))); + } +} \ No newline at end of file diff --git a/lib/views/pages/app/splash/unit_splash.dart b/lib/views/pages/app/splash/unit_splash.dart new file mode 100644 index 0000000..3fec4f1 --- /dev/null +++ b/lib/views/pages/app/splash/unit_splash.dart @@ -0,0 +1,164 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'unit_paint.dart'; +import 'package:flutter_unit/app/router.dart'; + +/// create by 张风捷特烈 on 2020-03-07 +/// contact me by email 1981462002@qq.com +/// 说明: app 闪屏页 + +class UnitSplash extends StatefulWidget { + final double size; + + UnitSplash({this.size = 200}); + + @override + _UnitSplashState createState() => _UnitSplashState(); +} + +class _UnitSplashState extends State with TickerProviderStateMixin { + AnimationController _controller; + AnimationController _secondController; + double _factor=0.0; + Animation _curveAnim; + + bool _animEnd = false; + + @override + void initState() { + SystemUiOverlayStyle systemUiOverlayStyle = + SystemUiOverlayStyle(statusBarColor: Colors.transparent); + SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); + + _controller = + AnimationController(duration: Duration(milliseconds: 1000), vsync: this) + ..addListener(() => setState(() { + return _factor = _curveAnim.value; + })) + ..addStatusListener((s) { + if (s == AnimationStatus.completed) { + setState(() { + _animEnd = true; + Future.delayed(Duration(milliseconds: 600)).then((e){ + Navigator.of(context).pushReplacementNamed(UnitRouter.nav); + }); + }); + } + }); + + _curveAnim = + CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn); + _controller.forward(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + var winH = MediaQuery.of(context).size.height; + var winW = MediaQuery.of(context).size.width; + + return Scaffold( + body: Stack( + alignment: Alignment.center, + children: [ + buildLogo(Colors.blue), + Container( + width: winW, + height: winH, + child: Container() +// CustomPaint( +// painter: UnitPainter(factor: _factor), +// ), + ), + buildText(winH, winW), + buildHead(), + buildPower(), + ], + ), + ); + } + + Positioned buildText(double winH, double winW) { + final shadowStyle = TextStyle( + fontSize: 45, + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + shadows: [ + const Shadow( + color: Colors.grey, + offset: Offset(1.0, 1.0), + blurRadius: 1.0, + ) + ], + ); + + return Positioned( + top: winH / 1.4, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 600), + opacity: _animEnd ? 1.0 : 0.0, + child: Text( + 'Flutter Unit', + style: shadowStyle, + )), + ); + } + + final colors = [Colors.red, Colors.yellow, Colors.blue]; + + Widget buildLogo(Color primaryColor) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 0), + end: const Offset(0, -1.5), + ).animate(_controller), + child: RotationTransition( + turns: _controller, + child: ScaleTransition( + scale: Tween(begin: 2.0, end: 1.0).animate(_controller), + child: FadeTransition( + opacity: _controller, + child: Container( + height: 120, + child: FlutterLogo( + // colors: primaryColor, + size: 60, + ), + )), + )), + ); + } + + Widget buildHead() => SlideTransition( + position: Tween( + end: const Offset(0, 0), + begin: const Offset(0, -5), + ).animate(_controller), + child: Container( + height: 150*_factor, + width: 150*_factor, + child: Image.asset('assets/images/icon_head.png'), + )); + + Widget buildPower() => Positioned( + bottom: 30, + right: 30, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: _animEnd ? 1.0 : 0.0, + child: const Text("Power By 张风捷特烈", + style: TextStyle( + color: Colors.grey, + shadows: [ + Shadow( + color: Colors.black, + blurRadius: 1, + offset: Offset(0.3, 0.3)) + ], + fontSize: 16))), + ); +} diff --git a/lib/views/widgets/MultiChildRenderObjectWidget/NestedScrollViewViewport/node1_base.dart b/lib/views/widgets/MultiChildRenderObjectWidget/NestedScrollViewViewport/node1_base.dart new file mode 100644 index 0000000..c71b927 --- /dev/null +++ b/lib/views/widgets/MultiChildRenderObjectWidget/NestedScrollViewViewport/node1_base.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 344 NestedScrollViewViewport 嵌套滑动视口 +/// 在 NestedScrollView 中使用的视口,该视口持有 SliverOverlapAbsorberHandle,会在视口需要重新计算布局时通知它。例如,当滚动它时。 +/// +// { +// "widgetId": 344, +// "name": 'NestedScrollViewViewport 介绍', +// "priority": 1, +// "subtitle": +// "【offset】 : *偏移 【ViewportOffset】\n" +// "【handle】 : *处理器 【SliverOverlapAbsorberHandle】\n" +// "【axisDirection】 : 轴向 【AxisDirection】\n" +// "【crossAxisDirection】 : 交叉轴向 【AxisDirection】\n" +// "【slivers】 : 子组件 【List】\n" +// "【clipBehavior】 : 裁剪行为 【Clip】\n" +// "【anchor】 : 锚点 【double】", +// } + +class NestedScrollViewViewportDemo extends StatelessWidget { + final String info = + 'NestedScrollViewViewport 在源码中只有一处使用:' + '_NestedScrollViewCustomScrollView 继承自 CustomScrollView,复写了 buildViewport 方法,返回 NestedScrollViewViewport 。' + '而 NestedScrollView 构建时使用了 _NestedScrollViewCustomScrollView,也就是 NestedScrollView 的视口依赖于 NestedScrollViewViewport。' + 'NestedScrollViewViewport 的特点是持有 SliverOverlapAbsorberHandle 类对象 handle,源码中该 handle 在 NestedScrollViewState 中初始化。' + '可通过上下文获取,用于 SliverOverlapAbsorber/SliverOverlapInjector 组件,使用详见相关组件。'; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ); + } +} diff --git a/lib/views/widgets/MultiChildRenderObjectWidget/ShrinkWrappingViewport/node1_base.dart b/lib/views/widgets/MultiChildRenderObjectWidget/ShrinkWrappingViewport/node1_base.dart new file mode 100644 index 0000000..74cb023 --- /dev/null +++ b/lib/views/widgets/MultiChildRenderObjectWidget/ShrinkWrappingViewport/node1_base.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 343 ShrinkWrappingViewport 收缩包围视图 +/// 和 ScrollView 的 shrinkWrap 属性之间关联。ShrinkWrappingViewport 在主轴上调整自身的大小以适应它的子节点,在无边界约束的情况下使用。 +/// +// { +// "widgetId": 343, +// "name": 'NestedScrollViewViewport 介绍', +// "priority": 1, +// "subtitle": +// "【offset】 : *偏移 【ViewportOffset】\n" +// "【axisDirection】 : 轴向 【AxisDirection】\n" +// "【crossAxisDirection】 : 交叉轴向 【AxisDirection】\n" +// "【slivers】 : 子组件 【List】\n" +// "【clipBehavior】 : 裁剪行为 【Clip】", +// } + +class ShrinkWrappingViewportDemo extends StatelessWidget { + final String info = + 'ShrinkWrappingViewport 在源码中只有一处使用:' + '在 ScrollView 中如果 shrinkWrap 为 true,会使用 ShrinkWrappingViewport,该属性在其子类 ListView、GridView、CustomScrollView 中可指定。' + '如果 shrinkWrap 为 false,视口会使用 Viewport,此时,视图区域将会沿滑动方向尽可能延伸。在无边界约束的情况下,shrinkWrap 需要是 true。' + '另外 ShrinkWrappingViewport 使用比较昂贵,因为滑动时需要重新计算滑动视图的尺寸。'; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ); + } +} diff --git a/lib/views/widgets/Other/ListWheelViewport/node1_base.dart b/lib/views/widgets/Other/ListWheelViewport/node1_base.dart new file mode 100644 index 0000000..a5a7390 --- /dev/null +++ b/lib/views/widgets/Other/ListWheelViewport/node1_base.dart @@ -0,0 +1,58 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 291 ListWheelViewport 列表滚轮视口 一个将孩子列表显示在柱状滚轮上的视口,是 ListWheelScrollView、CupertinoPicker 的底层依赖。 +/// link 179,139,137,253 +/// +// { +// "widgetId": 291, +// "name": 'ListWheelViewport 简单使用', +// "priority": 1, +// "subtitle": +// "【itemExtent】 : 轴向item尺寸 【double】\n" +// "【offset】 : 视口偏移 【ViewportOffset】\n" +// "【childDelegate】 : 孩子代理构造器 【ListWheelChildDelegate】", +// } + +class ListWheelViewportDemo extends StatelessWidget { + final List data = [ + Colors.blue[50], Colors.blue[100], Colors.blue[200], + Colors.blue[300], Colors.blue[400], Colors.blue[500], + Colors.blue[600], Colors.blue[700], Colors.blue[800], + Colors.blue[900], Colors.blue[800], Colors.blue[700], + Colors.blue[600], Colors.blue[500], Colors.blue[400], + Colors.blue[300], Colors.blue[200], Colors.blue[100], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 250, + width: 320, + child: Scrollable( + axisDirection: AxisDirection.down, + physics: BouncingScrollPhysics(), + dragStartBehavior: DragStartBehavior.start, + viewportBuilder: (ctx, position) => ListWheelViewport( + itemExtent: 100, + offset: position, + childDelegate: ListWheelChildLoopingListDelegate( + children: data.map((e) => _buildItem(e)).toList()), + )), + ); + } + + Widget _buildItem(Color color) => Container( + alignment: Alignment.center, + color: color, + child: Text(colorString(color), + style: TextStyle(color: Colors.white, shadows: [ + Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) + ])), + ); + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/Other/ListWheelViewport/node2_perspective.dart b/lib/views/widgets/Other/ListWheelViewport/node2_perspective.dart new file mode 100644 index 0000000..fc9f260 --- /dev/null +++ b/lib/views/widgets/Other/ListWheelViewport/node2_perspective.dart @@ -0,0 +1,59 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: +// { +// "widgetId": 291, +// "name": 'ListWheelViewport 透视效果', +// "priority": 2, +// "subtitle": +// "【perspective】 : 透视参数 【double】\n" +// "【squeeze】 : 挤压值 【double】\n" +// "【diameterRatio】 : 直径分率 【double】", +// } + +class ListWheelViewportDemo2 extends StatelessWidget { + final List data = [ + Colors.blue[50], Colors.blue[100], Colors.blue[200], + Colors.blue[300], Colors.blue[400], Colors.blue[500], + Colors.blue[600], Colors.blue[700], Colors.blue[800], + Colors.blue[900], Colors.blue[800], Colors.blue[700], + Colors.blue[600], Colors.blue[500], Colors.blue[400], + Colors.blue[300], Colors.blue[200], Colors.blue[100], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 250, + width: 320, + child: Scrollable( + axisDirection: AxisDirection.down, + physics: BouncingScrollPhysics(), + dragStartBehavior: DragStartBehavior.start, + viewportBuilder: (ctx, position) => ListWheelViewport( + perspective: 0.008, + squeeze: 1, + diameterRatio: 2, + itemExtent: 50, + offset: position, + childDelegate: ListWheelChildLoopingListDelegate( + children: data.map((e) => _buildItem(e)).toList()), + )), + ); + } + + Widget _buildItem(Color color) => Container( + alignment: Alignment.center, + color: color, + child: Text(colorString(color), + style: TextStyle(color: Colors.white, shadows: [ + Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) + ])), + ); + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/Other/ListWheelViewport/node3_magnifier.dart b/lib/views/widgets/Other/ListWheelViewport/node3_magnifier.dart new file mode 100644 index 0000000..1248f0f --- /dev/null +++ b/lib/views/widgets/Other/ListWheelViewport/node3_magnifier.dart @@ -0,0 +1,66 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: +/// +// { +// "widgetId": 291, +// "name": 'ListWheelViewport 放大', +// "priority": 3, +// "subtitle": +// "【useMagnifier】 : 是否放大 【bool】\n" +// "【magnification】 : 放大比例 【double】\n" +// "【clipBehavior】 : 剪裁行为 【Clip】\n" +// "【renderChildrenOutsideViewport】 : 出视野是否渲染 【bool】", +// } + +class ListWheelViewportDemo3 extends StatelessWidget { + final List data = [ + Colors.blue[50], Colors.blue[100], Colors.blue[200], + Colors.blue[300], Colors.blue[400], Colors.blue[500], + Colors.blue[600], Colors.blue[700], Colors.blue[800], + Colors.blue[900], Colors.blue[800], Colors.blue[700], + Colors.blue[600], Colors.blue[500], Colors.blue[400], + Colors.blue[300], Colors.blue[200], Colors.blue[100], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 250, + width: 320, + // color: Colors.red, + child: Scrollable( + axisDirection: AxisDirection.down, + physics: BouncingScrollPhysics(), + dragStartBehavior: DragStartBehavior.start, + viewportBuilder: (ctx, position) => ListWheelViewport( + perspective: 0.008, + squeeze: 1, + diameterRatio: 2, + itemExtent: 50, + useMagnifier: true, + magnification: 2, + renderChildrenOutsideViewport: true, + clipBehavior: Clip.none, + offset: position, + childDelegate: ListWheelChildLoopingListDelegate( + children: data.map((e) => _buildItem(e)).toList()), + )), + ); + } + + Widget _buildItem(Color color) => Container( + alignment: Alignment.center, + color: color, + child: Text(colorString(color), + style: TextStyle(color: Colors.white, shadows: [ + Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) + ])), + ); + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/Other/ListWheelViewport/node4_opacity.dart b/lib/views/widgets/Other/ListWheelViewport/node4_opacity.dart new file mode 100644 index 0000000..a467b1d --- /dev/null +++ b/lib/views/widgets/Other/ListWheelViewport/node4_opacity.dart @@ -0,0 +1,62 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: +/// +// { +// "widgetId": 291, +// "name": '偏移和透明度', +// "priority": 4, +// "subtitle": +// "【offAxisFraction】 : 轴中心偏移比 【double】\n" +// "【overAndUnderCenterOpacity】 : 放大器之外的透明度 【double】", +// } + +class ListWheelViewportDemo4 extends StatelessWidget { + final List data = [ + Colors.blue[50], Colors.blue[100], Colors.blue[200], + Colors.blue[300], Colors.blue[400], Colors.blue[500], + Colors.blue[600], Colors.blue[700], Colors.blue[800], + Colors.blue[900], Colors.blue[800], Colors.blue[700], + Colors.blue[600], Colors.blue[500], Colors.blue[400], + Colors.blue[300], Colors.blue[200], Colors.blue[100], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 250, + width: 320, + // color: Colors.red, + child: Scrollable( + axisDirection: AxisDirection.down, + physics: BouncingScrollPhysics(), + dragStartBehavior: DragStartBehavior.start, + viewportBuilder: (ctx, position) => ListWheelViewport( + perspective: 0.008, + squeeze: 1, + diameterRatio: 2, + offAxisFraction: 0.2, + overAndUnderCenterOpacity: 0.4, + itemExtent: 50, + offset: position, + childDelegate: ListWheelChildLoopingListDelegate( + children: data.map((e) => _buildItem(e)).toList()), + )), + ); + } + + Widget _buildItem(Color color) => Container( + alignment: Alignment.center, + color: color, + child: Text(colorString(color), + style: TextStyle(color: Colors.white, shadows: [ + Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) + ])), + ); + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/Other/RenderObjectToWidgetAdapter/node1_base.dart b/lib/views/widgets/Other/RenderObjectToWidgetAdapter/node1_base.dart new file mode 100644 index 0000000..c11f144 --- /dev/null +++ b/lib/views/widgets/Other/RenderObjectToWidgetAdapter/node1_base.dart @@ -0,0 +1,33 @@ + +import 'package:flutter/material.dart'; + + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 289 RenderObjectToWidgetAdapter 根组件 RenderObject 和 Element 树的桥梁。 +/// +// { +// "widgetId": 289, +// "name": 'RenderObjectToWidgetAdapter 介绍', +// "priority": 1, +// "subtitle": +// "【container】 : 渲染对象 【RenderObjectWithChildMixin】\n" +// "【child】 : 子组件 【Widget】\n" +// "【debugShortDescription】 : 调试简介 【String】", +// } + +class RenderObjectToWidgetAdapterDemo extends StatelessWidget { + final String info = + '该组件并没有什么太大的使用价值,但却非常有纪念意义。它是 Flutter 框架中最顶层的 Widget,它的 child 是 runApp 传入的组件,在 attachRootWidget 方法中被实例化。' + '它持有根渲染对象 RenderView ,负责创建根元素 RenderObjectToWidgetElement,是一个无名英雄,一个深藏功与名的组件。'; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ); + } +} diff --git a/lib/views/widgets/ProxyWidget/CupertinoUserInterfaceLevel/node1_base.dart b/lib/views/widgets/ProxyWidget/CupertinoUserInterfaceLevel/node1_base.dart new file mode 100644 index 0000000..c34a93f --- /dev/null +++ b/lib/views/widgets/ProxyWidget/CupertinoUserInterfaceLevel/node1_base.dart @@ -0,0 +1,42 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-03-29 +/// contact me by email 1981462002@qq.com +/// 说明: 337 CupertinoUserInterfaceLevel 用户接口等级 +/// ios 中的概念,内容可视级别 UIUserInterfaceLevel ,分为 base 和 elevated。作为一个 InheritedWidget ,主要就是共享该数据。 + +// { +// "widgetId": 337, +// "name": 'CupertinoUserInterfaceLevel 介绍', +// "priority": 1, +// "subtitle": +// "CupertinoUserInterfaceLevel.of(context) 可以获取 CupertinoUserInterfaceLevelData 数据。也可以使用该组件设置该数据与子树共享。关于数据原图详见: https://developer.apple.com/documentation/uikit/uiuserinterfacelevel", +// } + +class CupertinoUserInterfaceLevelDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + + return CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: LevelShower() + ); + } + +} + +class LevelShower extends StatelessWidget { + @override + Widget build(BuildContext context) { + CupertinoUserInterfaceLevelData data = CupertinoUserInterfaceLevel.of(context); + return Container( + height: 150, + alignment: Alignment.center, + color: Theme.of(context).primaryColor.withOpacity(0.1), + child: Text(data.toString()), + ); + } +} + + diff --git a/lib/views/widgets/ProxyWidget/DefaultAssetBundle/node1_base.dart b/lib/views/widgets/ProxyWidget/DefaultAssetBundle/node1_base.dart new file mode 100644 index 0000000..942dcbc --- /dev/null +++ b/lib/views/widgets/ProxyWidget/DefaultAssetBundle/node1_base.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:ui' as ui; +/// create by 张风捷特烈 on 2020-04-01 +/// contact me by email 1981462002@qq.com +/// 说明: 320 DefaultAssetBundle 默认资源包 +/// 一个 InheritedWidget,设置 AssetBundle 对象后,该节点后的节点上下文可以通过 DefaultAssetBundle.of(context) 获取 AssetBundle 对象用于访问资源文件。 +// { +// "widgetId": 320, +// "name": 'DefaultAssetBundle 介绍', +// "priority": 1, +// "subtitle": +// "【bundle】 : *资源包 【AssetBundle】\n" +// "【child】 : *子组件 【Widget】\n" +// "我们可以定义自己的 DefaultAssetBundle 来供后续节点使用,也可以直接使用默认的。该案例演示通过框架提供的 DefaultAssetBundle 加载一张资源图片进行显示。", +// } +class DefaultAssetBundleDemo extends StatefulWidget { + @override + _DefaultAssetBundleDemoState createState() => _DefaultAssetBundleDemoState(); +} + +class _DefaultAssetBundleDemoState extends State { + ui.Image _image; + @override + void initState() { + super.initState(); + _load(); + } + + @override + Widget build(BuildContext context) { + + return Container( + width: 150, + height: 150, + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: _image==null?Container():RawImage(image: _image,fit: BoxFit.cover,), + ); + } + + void _load() async{ + AssetBundle info = DefaultAssetBundle.of(context); + ByteData data = await info.load('assets/images/sabar.webp'); + _image = await decodeImageFromList(data.buffer.asUint8List()); + setState(() { + + }); + } +} diff --git a/lib/views/widgets/ProxyWidget/Directionality/node1_base.dart b/lib/views/widgets/ProxyWidget/Directionality/node1_base.dart new file mode 100644 index 0000000..cca2a0c --- /dev/null +++ b/lib/views/widgets/ProxyWidget/Directionality/node1_base.dart @@ -0,0 +1,63 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-03-23 +/// contact me by email 1981462002@qq.com +/// 说明: 319 Directionality 定向性 为后代改变有textDirection属性的组件统一设置属性值,也可以通过Directionality.of(context)获取当前textDirection默认属性。 +// { +// "widgetId": 319, +// "name": "Directionality基本使用", +// "priority": 1, +// "subtitle": "【textDirection】 : 文字排列方向 【TextDirection】\n" +// "【child】 : 子组件 【Widget】", +// } +class DirectionalityDemo extends StatefulWidget { + @override + _DirectionalityDemoState createState() => _DirectionalityDemoState(); +} + +class _DirectionalityDemoState extends State { + TextDirection _textDirection = TextDirection.rtl; + + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: _textDirection, + child: Container( + padding: EdgeInsets.all(8), + width: 250, + color: Colors.grey.withAlpha(33), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'A widget that determines the ambient directionality of text and text direction sensitive render objects.'), + _buildSwitch(), + Text( + 'The text direction from the closest instance of this class that encloses the given context.'), + ], + ), + ), + ); + } + + Widget _buildSwitch() { + return Row( + children: [ + Switch( + value: _textDirection == TextDirection.rtl, + onChanged: (v) { + setState(() { + _textDirection = + v ? TextDirection.rtl : TextDirection.ltr; + }); + }, + ), + Text( + _textDirection.toString(), + style: TextStyle(color: Colors.blue, fontSize: 18), + ) + ], + ); + } +} diff --git a/lib/views/widgets/ProxyWidget/InheritedTheme/node1_base.dart b/lib/views/widgets/ProxyWidget/InheritedTheme/node1_base.dart new file mode 100644 index 0000000..da33c08 --- /dev/null +++ b/lib/views/widgets/ProxyWidget/InheritedTheme/node1_base.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 345 InheritedTheme 传承主题 +/// 它是抽象类,有非常多的 XXXTheme 相关子类,用于定义颜色、文字样式等属性,在子树中共享这些属性。 +/// link 324,326,328,329 +/// +// { +// "widgetId": 345, +// "name": 'InheritedTheme 介绍', +// "priority": 1, +// "subtitle": +// "InheritedTheme.capture 可以抓取上层主题,获取 CapturedThemes 对象,通过该对象 wrap 方法可以跨路由使用抓到的主题。", +// } + +class InheritedThemeDemo extends StatelessWidget { + + @override + Widget build(BuildContext context) { + return DefaultTextStyle( + style: TextStyle(fontSize: 24, color: Colors.blue), + child: TestBody(), + ); + } +} + +class TestBody extends StatelessWidget { + @override + Widget build(BuildContext context) { + + return GestureDetector( + onTap: () => _toNextPage(context), + child: Container( + height: 60, + margin: EdgeInsets.only(left: 40,right: 40), + alignment: Alignment.center, + color: Theme.of(context).primaryColor.withOpacity(0.1), + child: Text('InheritedTheme'))); + } + + void _toNextPage(BuildContext context) { + // final NavigatorState navigator = Navigator.of(context); + // final CapturedThemes themes = + // InheritedTheme.capture(from: context, to: navigator.context); + // + // Navigator.of(context).push( + // MaterialPageRoute( + // builder: (BuildContext _) { + // return themes.wrap(Container( + // alignment: Alignment.center, + // color: Colors.white, + // child: Text('Flutter Unit'), + // )); + // }, + // ), + // ); + } +} diff --git a/lib/views/widgets/ProxyWidget/InheritedWidget/node1_base.dart b/lib/views/widgets/ProxyWidget/InheritedWidget/node1_base.dart new file mode 100644 index 0000000..2845693 --- /dev/null +++ b/lib/views/widgets/ProxyWidget/InheritedWidget/node1_base.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 346 InheritedWidget 传承组件 +/// 该类是抽象类,作用是可以在本上下文存储数据,在其后续节点的上下文中共享该数据。有很多实现类,包括各种主题组件、MediaQuery等。 +/// link: 167,319,328,324,331 +/// +// { +// "widgetId": 346, +// "name": 'InheritedWidget 使用', +// "priority": 1, +// "subtitle": +// "【child】 : 子组件 【Widget】\n" +// "下面是一个简单的自定义 InheritedWidget,实现信息的子树共享。", +// } + +class InheritedWidgetDemo extends StatelessWidget { + final String info = + 'InheritedWidget 是一个抽象类,不可以直接使用。可以自定义对应共享数据的子类,如这里的通过 InfoInheritedWidget 实现:当前这段话可以在任意子树节点上下文获取。' + '一般都会定义一个 XXX.of(context) 的方法来获取数据,如 MediaQuery.of,Theme.of 等。'; + + @override + Widget build(BuildContext context) { + return InfoInheritedWidget( + info: info, + child: InfoWidget(), + ); + } +} + +class InfoWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + String info = InfoInheritedWidget.of(context).info; + + return Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ); + } +} + +class InfoInheritedWidget extends InheritedWidget { + final String info; + + InfoInheritedWidget({Key key, this.info, @required Widget child}) + : super(key: key, child: child); + + @override + bool updateShouldNotify(covariant InfoInheritedWidget oldWidget) => + info != oldWidget.info; + + static InfoInheritedWidget of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType(); +} diff --git a/lib/views/widgets/ProxyWidget/KeepAlive/node1_base.dart b/lib/views/widgets/ProxyWidget/KeepAlive/node1_base.dart new file mode 100644 index 0000000..135d896 --- /dev/null +++ b/lib/views/widgets/ProxyWidget/KeepAlive/node1_base.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 316 KeepAlive 保活 在懒加载的列表中,孩子的状态是否需要保活。是 AutomaticKeepAlive 的底层实现,一般不单独使用。 +/// link 239 +/// +// { +// "widgetId": 316, +// "name": 'KeepAlive 介绍', +// "priority": 1, +// "subtitle": +// "【child】 : *子组件 【Widget】\n" +// "【keepAlive】 : *是否保活 【bool】\n" +// "在 flutter 框架层中,只用于 AutomaticKeepAlive 中,源码中也说很少单独使用它。该示例展示出 ListView 条目的状态保活。", +// } + +class KeepAliveDemo extends StatelessWidget { + + final List data = [ + Colors.purple[50], + Colors.purple[100], + Colors.purple[200], + Colors.purple[300], + Colors.purple[400], + Colors.purple[500], + Colors.purple[600], + Colors.purple[700], + Colors.purple[800], + Colors.purple[900], + Colors.red[50], + Colors.red[100], + Colors.red[200], + Colors.red[300], + Colors.red[400], + Colors.red[500], + Colors.red[600], + Colors.red[700], + Colors.red[800], + Colors.red[900], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: ListView.builder( + itemCount: data.length, + itemBuilder: (_, index) => ColorBox( + color: data[index], + index: index, + ), + ), + ); + } +} + +class ColorBox extends StatefulWidget { + final Color color; + final int index; + + ColorBox({Key key, this.color, this.index}) : super(key: key); + + @override + _ColorBoxState createState() => _ColorBoxState(); +} + +class _ColorBoxState extends State with AutomaticKeepAliveClientMixin { + bool _checked = false; + + @override + void initState() { + super.initState(); + _checked = false; + print('-----_ColorBoxState#initState---${widget.index}-------'); + } + + @override + void dispose() { + print('-----_ColorBoxState#dispose---${widget.index}-------'); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + + return Container( + alignment: Alignment.center, + height: 50, + color: widget.color, + child: Row( + children: [ + SizedBox(width: 60,), + Checkbox( + value: _checked, + onChanged: (v) { + setState(() { + _checked = v; + }); + }, + ), + Text( + "index ${widget.index}: ${colorString(widget.color)}", + style: TextStyle(color: Colors.white, shadows: [ + Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) + ]), + ), + ], + ), + ); + } + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/views/widgets/ProxyWidget/ParentDataWidget/node1_base.dart b/lib/views/widgets/ProxyWidget/ParentDataWidget/node1_base.dart new file mode 100644 index 0000000..192323a --- /dev/null +++ b/lib/views/widgets/ProxyWidget/ParentDataWidget/node1_base.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 347 ParentDataWidget 父数据组件 +/// 抽象类,用于将 ParentData 信息挂钩到 RenderObjectWidget 子组件上。其子类有 Positioned、Flexible、Expanded等,这些组件只能用于特定的组件之下。 +/// +// { +// "widgetId": 347, +// "name": 'ParentDataWidget 介绍', +// "priority": 1, +// "subtitle": +// "【child】 : 子组件 【Widget】", +// } + +class ParentDataWidgetDemo extends StatelessWidget { + final String info = + 'ParentDataWidget 是一个抽象类,不能直接使用,它拥有 ParentData 子类型的泛型,该泛型会限定该组件的适应场景。' + '如 Positioned 组件继承自 ParentDataWidget,就说明 Positioned 的上层组件必须使用 Stack 族组件。' + '如 Flexible 组件继承自 ParentDataWidget,就说明 Flexible 的上层组件必须使用 Flex 族组件。'; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ); + } +} diff --git a/lib/views/widgets/ProxyWidget/TableCell/node1_base.dart b/lib/views/widgets/ProxyWidget/TableCell/node1_base.dart new file mode 100644 index 0000000..fb313d1 --- /dev/null +++ b/lib/views/widgets/ProxyWidget/TableCell/node1_base.dart @@ -0,0 +1,79 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 317 TableCell 表室 必须在 Table 组件的后代中使用,用于控制表孩子的竖直方向对齐方式,并没是什么太大的作用。 +// { +// "widgetId": 317, +// "name": 'TableCell基本使用', +// "priority": 1, +// "subtitle": +// "【child】 : 组件 【Widget】\n" +// "【verticalAlignment】 : 竖直对齐方式 【TableCellVerticalAlignment】", +// } + +class TableCellDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + var title = _ItemBean("单位称", "量纲", "单位", "单位名称", "单位符号"); + var m = _ItemBean("长度", "L", "1m", "米", "m"); + var kg = _ItemBean("质量", "M", "1Kg", "千克", "Kg"); + var s = _ItemBean("时间", "T", "1s", "秒", "s"); + var a = _ItemBean("安培", "Ι", "1A", "安培", "A"); + var k = _ItemBean("热力学温度", "θ", "1K", "开尔文", "K"); + var mol = _ItemBean("物质的量", "N", "1mol", "摩尔", "mol"); + var cd = _ItemBean("发光强度", "J", "1cd", "坎德拉", "cd"); + + var data = <_ItemBean>[title, m, kg, s, a, k, mol, cd]; + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Table( + columnWidths: const { + 0: FixedColumnWidth(80.0), + 1: FixedColumnWidth(80.0), + 2: FixedColumnWidth(80.0), + 3: FixedColumnWidth(80.0), + 4: FixedColumnWidth(80.0), + }, + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + border: TableBorder.all( + color: Colors.orangeAccent, width: 1.0, style: BorderStyle.solid), + children: data + .map((item) => TableRow(children: [ + TableCell( + verticalAlignment: TableCellVerticalAlignment.bottom, + child: Text( + item.name, + style: TextStyle(color: Colors.blue), + )), + TableCell( + verticalAlignment: TableCellVerticalAlignment.baseline, + child: Text(item.symbol)), + TableCell( + verticalAlignment: TableCellVerticalAlignment.top, + child: Text(item.unitSymbol)), + TableCell( + verticalAlignment: TableCellVerticalAlignment.fill, + child: Text(item.unitName)), + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Container(height: 30, child: Text(item.unit)), + ), + ])) + .toList(), + ), + ); + } +} + +class _ItemBean { + String name; + String symbol; + String unit; + String unitName; + String unitSymbol; + + _ItemBean(this.name, this.symbol, this.unit, this.unitName, this.unitSymbol); +} diff --git a/lib/views/widgets/SingleChildRenderObjectWidget/AnnotatedRegion/node1_base.dart b/lib/views/widgets/SingleChildRenderObjectWidget/AnnotatedRegion/node1_base.dart new file mode 100644 index 0000000..2220857 --- /dev/null +++ b/lib/views/widgets/SingleChildRenderObjectWidget/AnnotatedRegion/node1_base.dart @@ -0,0 +1,85 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 288 AnnotatedRegion 有一个泛型,源码中仅适用该组件改变状态量、导航栏样式,泛型通常为SystemUiOverlayStyle。 +// { +// "widgetId": 288, +// "name": 'AnnotatedRegion改变状态量样式', +// "priority": 1, +// "subtitle": +// "【value】 : 值 【T】\n" +// "【sized】 : 是否提供大小 【bool】\n" +// "【child】 : 子组件 【Widget】", +// } + + +class AnnotatedRegionDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(10), + child: ElevatedButton( + onPressed: (){ + Navigator.push(context, + MaterialPageRoute(builder: (context) => AnnotatedRegionTestPage()), + ); + }, + child: Text("进入 AnnotatedRegion 测试页"), + ), + ); + } +} + + +class AnnotatedRegionTestPage extends StatelessWidget{ + @override + Widget build(BuildContext context) { + final SystemUiOverlayStyle overlayStyle = SystemUiOverlayStyle( + systemNavigationBarColor: Colors.green, + // 导航栏颜色 + systemNavigationBarDividerColor: Colors.red, + statusBarColor: Colors.blue, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.light, + ); + + return AnnotatedRegion( + value: overlayStyle, + child: Scaffold( + body: Container( + child: Column( + children: [ + Container(height: 56+30.0,color: Colors.blue, + alignment: Alignment(0,0.55), + child: Row( + children: [ + BackButton(color: Colors.white,), + Text("AnnotatedRegion测试",style: TextStyle(color: Colors.white,fontSize: 18),) + ], + ), + ), + SizedBox(height: 30,), + Text( + "上面标题栏背景颜色为蓝色\n" + "上面标题栏图标为亮调", + + style: TextStyle(color: Colors.black,fontSize: 18),), + Spacer(), + Text( + "下面导航栏背景颜色为绿色\n" + "下面导航栏图标为亮调", + + style: TextStyle(color: Colors.black,fontSize: 18),), + SizedBox(height: 30,), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/SingleChildRenderObjectWidget/ColoredBox/node1_base.dart b/lib/views/widgets/SingleChildRenderObjectWidget/ColoredBox/node1_base.dart new file mode 100644 index 0000000..527af6f --- /dev/null +++ b/lib/views/widgets/SingleChildRenderObjectWidget/ColoredBox/node1_base.dart @@ -0,0 +1,39 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 267 ColoredBox 在子组件的布局区域上绘制颜色,然后子组件绘制在背景色上。 +// { +// "widgetId": 267, +// "name": 'ColoredBox基本使用', +// "priority": 1, +// "subtitle": +// "【color】 : 组件 【Color】\n" +// "【child】 : 组件 【Widget】", +// } + +class ColoredBoxDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ColoredBox( + color: Colors.red, + child: Container( + margin: EdgeInsets.all(20), + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: Colors.blue + ), + alignment: Alignment.center, + width: 250, + height: 100, + // color: Theme.of(context).primaryColor, + child: Text( + "蓝色是加了 margin 和圆角的 Container,外层包裹红色的 ColoredBox,注意作用范围。", + style: TextStyle(color: Colors.white), + ), + ), + ); + } +} diff --git a/lib/views/widgets/SingleChildRenderObjectWidget/CompositedTransformFollower/node1_base.dart b/lib/views/widgets/SingleChildRenderObjectWidget/CompositedTransformFollower/node1_base.dart new file mode 100644 index 0000000..d09327e --- /dev/null +++ b/lib/views/widgets/SingleChildRenderObjectWidget/CompositedTransformFollower/node1_base.dart @@ -0,0 +1,119 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/3/31 +/// contact me by email 1981462002@qq.com +/// +/// 说明: 265 CompositedTransformFollower 2 合成变换跟随者,一般与 CompositedTransformTarget 组件联合使用,可以使 Overlay 伴随目标变换。 +// { +// "widgetId": 265, +// "name": "基本使用", +// "name": "CompositedTransformFollower 使用", +// "priority": 1, +// "subtitle": +// "【child】 : 子组件 【Widget】\n" +// "【link】 : 链接 【LayerLink】\n" +// "【offset】 : 偏移 【Offset】\n" +// "【targetAnchor】 : 目标锚点 【Alignment】\n" +// "【followerAnchor】 : 伴随者锚点 【Alignment】\n" +// "【showWhenUnlinked】 : 为链接是否显示 【bool】", +// } + +class CompositedTransformFollowerDemo extends StatelessWidget { + + const CompositedTransformFollowerDemo(); + + static const List colors =[Colors.red,Colors.yellow,Colors.blue,Colors.green]; + + @override + Widget build(BuildContext context) { + return Container( + transform: Matrix4.rotationZ(-15/180*pi), + height: 250, + padding: const EdgeInsets.all(50.0), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [_LogoTips(), const Text('点击图标\n显隐弹框')], + ), + ...colors.map((color) => Container(width: 80, color: color)) + ], + ), + ); + } + + +} + +class _LogoTips extends StatefulWidget { + @override + _LogoTipsState createState() => _LogoTipsState(); +} + +class _LogoTipsState extends State<_LogoTips> { + OverlayEntry _overlayEntry; + + final LayerLink _layerLink = LayerLink(); + + bool show = false; + + OverlayEntry _createOverlayEntry() { + + return OverlayEntry( + builder: (context) => Positioned( + width: 150, + child: CompositedTransformFollower( + link: this._layerLink, + showWhenUnlinked: false, + offset: Offset(0,-10), + targetAnchor: Alignment.topRight, + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text('我是一个 Overlay,目标组件为图标,当它变换时,我会伴随变换。'), + ), + ), + ), + )); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _toggleOverlay, + child: CompositedTransformTarget( + link: this._layerLink, + child: + FlutterLogo( + size: 80, + ), + )); + } + + void _toggleOverlay() { + if (!show) { + _showOverlay(); + } else { + _hideOverlay(); + } + show = !show; + } + + void _showOverlay() { + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry); + } + + void _hideOverlay() { + _overlayEntry?.remove(); + } + + @override + void dispose() { + _hideOverlay(); + super.dispose(); + } +} diff --git a/lib/views/widgets/SingleChildRenderObjectWidget/CompositedTransformTarget/node1_base.dart b/lib/views/widgets/SingleChildRenderObjectWidget/CompositedTransformTarget/node1_base.dart new file mode 100644 index 0000000..f6e94e7 --- /dev/null +++ b/lib/views/widgets/SingleChildRenderObjectWidget/CompositedTransformTarget/node1_base.dart @@ -0,0 +1,112 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/3/31 +/// contact me by email 1981462002@qq.com +/// +/// 说明: 266 CompositedTransformTarget 2 合成变换目标,一般与 CompositedTransformFollower 组件联合使用,可以使 Overlay 伴随目标变换。 +// { +// "widgetId": 266, +// "name": "CompositedTransformTarget 使用", +// "priority": 1, +// "subtitle": +// "【child】 : 子组件 【Widget】\n" +// "【link】 : 链接 【LayerLink】", +// } + +class CompositedTransformTargetDemo extends StatelessWidget { + + + const CompositedTransformTargetDemo(); + + static const List colors =[Colors.red,Colors.yellow,Colors.blue,Colors.green]; + + @override + Widget build(BuildContext context) { + return Container( + transform: Matrix4.rotationZ(-15/180*pi), + height: 250, + padding: const EdgeInsets.all(50.0), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [_LogoTips(), const Text('点击图标\n显隐弹框')], + ), + ...colors.map((color) => Container(width: 80, color: color)) + ], + ), + ); + } + +} + +class _LogoTips extends StatefulWidget { + @override + _LogoTipsState createState() => _LogoTipsState(); +} + +class _LogoTipsState extends State<_LogoTips> { + OverlayEntry _overlayEntry; + + final LayerLink _layerLink = LayerLink(); + + bool show = false; + + OverlayEntry _createOverlayEntry() { + return OverlayEntry( + builder: (context) => Positioned( + width: 150, + child: CompositedTransformFollower( + link: this._layerLink, + showWhenUnlinked: false, + targetAnchor: Alignment.topRight, + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text('我是一个 Overlay,目标组件为图标,当它变换时,我会伴随变换。'), + ), + ), + ), + )); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _toggleOverlay, + child: CompositedTransformTarget( + link: this._layerLink, + child: + FlutterLogo( + size: 80, + ), + )); + } + + void _toggleOverlay() { + if (!show) { + _showOverlay(); + } else { + _hideOverlay(); + } + show = !show; + } + + void _showOverlay() { + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry); + } + + void _hideOverlay() { + _overlayEntry?.remove(); + } + + @override + void dispose() { + _hideOverlay(); + super.dispose(); + } +} diff --git a/lib/views/widgets/SingleChildRenderObjectWidget/CupertinoTextSelectionToolbar/node1_base.dart b/lib/views/widgets/SingleChildRenderObjectWidget/CupertinoTextSelectionToolbar/node1_base.dart new file mode 100644 index 0000000..4896231 --- /dev/null +++ b/lib/views/widgets/SingleChildRenderObjectWidget/CupertinoTextSelectionToolbar/node1_base.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 299 CupertinoTextSelectionToolbar 对文本选择做出响应的 ios 风格的工具栏。 +// { +// "widgetId": 299, +// "name": '该组件无法使用', +// "priority": 1, +// "subtitle": +// "【-】 : - 【-】", +// } + +class CupertinoTextSelectionToolbarDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.all(10), + width: 300, + // color: Theme.of(context).primaryColor, + child: Text( + "注:此组件私有构造器,外部无法使用,并没有使用价值。", + style: TextStyle(color: Colors.red, fontSize: 18), + ), + ); + } +} diff --git a/lib/views/widgets/SingleChildRenderObjectWidget/PhysicalModel/node1_base.dart b/lib/views/widgets/SingleChildRenderObjectWidget/PhysicalModel/node1_base.dart new file mode 100644 index 0000000..ff28e1e --- /dev/null +++ b/lib/views/widgets/SingleChildRenderObjectWidget/PhysicalModel/node1_base.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-03-23 +/// contact me by email 1981462002@qq.com +/// 说明: 296 PhysicalModel 物理模块 可以让子组件按照圆形、方行进行剪裁,并且可以指定背景色、圆角、影深、阴影颜色、剪切行为。 +// { +// "widgetId": 296, +// "name": "PhysicalModel基本使用", +// "priority": 1, +// "subtitle": "【clipBehavior】 : 裁剪行为 【Clip】\n" +// "【borderRadius】 : 圆角 【BorderRadius】\n" +// "【child】 : 子组件 【Widget】\n" +// "【elevation】 : 阴影深 【double】\n" +// "【shadowColor】 : 阴影颜色 【Color】\n" +// "【shape】 : 形状 【BoxShape】\n" +// "【color】: 颜色 【Color】", +// } +class PhysicalModelDemo extends StatelessWidget{ + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + width: 150, + height: 150, + child: PhysicalModel( + shadowColor: Colors.orange, + elevation: 3, + child: Image.asset( + 'assets/images/caver.webp', + fit: BoxFit.cover, + ), + clipBehavior: Clip.hardEdge, + shape: BoxShape.circle, + color: Colors.deepPurpleAccent), + ), + + Container( + width: 150, + height: 150, + child: PhysicalModel( + shadowColor: Colors.orange, + elevation: 3, + child: Image.asset( + 'assets/images/caver.webp', + fit: BoxFit.cover, + ), + borderRadius: BorderRadius.all(Radius.circular(20)), + clipBehavior: Clip.hardEdge, + shape: BoxShape.rectangle, + color: Colors.deepPurpleAccent), + ), + ], + ); + } + +} diff --git a/lib/views/widgets/SingleChildRenderObjectWidget/RepaintBoundary/main.dart b/lib/views/widgets/SingleChildRenderObjectWidget/RepaintBoundary/main.dart new file mode 100644 index 0000000..05ca9f7 --- /dev/null +++ b/lib/views/widgets/SingleChildRenderObjectWidget/RepaintBoundary/main.dart @@ -0,0 +1,24 @@ +/// create by 张风捷特烈 on 2020/7/22 +/// contact me by email 1981462002@qq.com +/// 说明: + +import 'package:flutter/material.dart'; +import 'node2_save.dart'; + + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: Scaffold( + appBar: AppBar(), + body: Center(child: RepaintBoundarySave()), + )); + } +} diff --git a/lib/views/widgets/SingleChildRenderObjectWidget/SizeChangedLayoutNotifier/node1_base.dart b/lib/views/widgets/SingleChildRenderObjectWidget/SizeChangedLayoutNotifier/node1_base.dart new file mode 100644 index 0000000..9e3b22f --- /dev/null +++ b/lib/views/widgets/SingleChildRenderObjectWidget/SizeChangedLayoutNotifier/node1_base.dart @@ -0,0 +1,71 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 294 SizeChangedLayoutNotifier 尺寸变化通告 使用 SizeChangedLayoutNotifier 可以在子组件布局区域发生变化后,发出通知。使用NotificationListener可以进行监听。 +// { +// "widgetId": 294, +// "name": '基本使用', +// "priority": 1, +// "subtitle": +// "【child】 : 组件 【Widget】", +// } + +class SizeChangedLayoutNotifierDemo extends StatefulWidget { + @override + _SizeChangedLayoutNotifierDemoState createState() => _SizeChangedLayoutNotifierDemoState(); +} + +class _SizeChangedLayoutNotifierDemoState extends State { + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: _onNotification, + child: ChangeableBox(), + ); + } + + bool _onNotification(SizeChangedLayoutNotification notification) { + print('---------SizeChangedLayoutNotification------'); + return false; + } +} + +class ChangeableBox extends StatefulWidget { + @override + _ChangeableBoxState createState() => _ChangeableBoxState(); +} + +class _ChangeableBoxState extends State { + double width = 40; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizeChangedLayoutNotifier( + child: Container( + width: width, + height: 100, + color: Colors.blue, + ), + ), + Slider( + max: 200, + min: 20, + divisions: 10, + value: width, + onChanged: _changeWidth, + ) + ], + ); + } + + void _changeWidth(double value) { + setState(() { + width = value; + }); + } +} diff --git a/lib/views/widgets/Sliver/CupertinoSliverNavigationBar/node1_base.dart b/lib/views/widgets/Sliver/CupertinoSliverNavigationBar/node1_base.dart new file mode 100644 index 0000000..6f78cef --- /dev/null +++ b/lib/views/widgets/Sliver/CupertinoSliverNavigationBar/node1_base.dart @@ -0,0 +1,99 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 302 CupertinoSliverNavigationBar Sliver导航条 iOS11中导航条效果,展开时largeTitle显示,列表上滑后不显示,如果middle为空,largeTitle会以小字号作为middle。 +// { +// "widgetId": 302, +// "name": '导航条基本使用', +// "priority": 1, +// "subtitle": +// "【leading】 : 左侧组件 【Widget】\n" +// "【middle】 : 中间组件 【Widget】\n" +// "【trailing】 : 尾部组件 【Widget】\n" +// "【largeTitle】 : 底部折展组件 【Widget】\n" +// "【border】 : 边线 【Border】\n" +// "【backgroundColor】 : 背景色 【Color】\n" +// "【padding】 : 内边距 【EdgeInsetsDirectional】", +// } +class CupertinoSliverNavigationBarDemo extends StatelessWidget { + final data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CustomScrollView( + slivers: [ + CupertinoSliverNavigationBar( + trailing: Icon( + CupertinoIcons.share, + size: 25, + ), + leading: _buildLeading(), + backgroundColor: Colors.white, + // middle: Text('张风捷特烈'), + largeTitle: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.ac_unit, + size: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text('张风捷特烈'), + ), + Icon(Icons.ac_unit, size: 20), + ], + ), + ), + _buildSliverList() + ], + ), + ); + } + + Widget _buildSliverList() => SliverPrototypeExtentList( + prototypeItem: Container( + height: 40, + ), + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); + + Widget _buildLeading() => Container( + margin: EdgeInsets.all(10), + child: Image.asset('assets/images/icon_head.webp')); + + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/Sliver/CupertinoSliverRefreshControl/node1_base.dart b/lib/views/widgets/Sliver/CupertinoSliverRefreshControl/node1_base.dart new file mode 100644 index 0000000..7e2e4dd --- /dev/null +++ b/lib/views/widgets/Sliver/CupertinoSliverRefreshControl/node1_base.dart @@ -0,0 +1,127 @@ +import 'dart:math'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 303 CupertinoSliverRefreshControl Sliver刷新控制器 iOS风格的下拉刷新控制器,可执行异步刷新方法、自定义控制器组件、指示器停留高度和触发加载的滑动高度。 +// { +// "widgetId": 303, +// "name": '刷新控制器基本使用', +// "priority": 1, +// "subtitle": +// "【refreshIndicatorExtent】 : 加载中指示器高度 【double】\n" +// "【refreshTriggerPullDistance】 : 触发加载的滑动高度 【double】\n" +// "【onRefresh】 : 下拉事件 【RefreshCallback】\n" +// "【builder】 : 指示器构造器 【RefreshControlIndicatorBuilder】", +// } +class CupertinoSliverRefreshControlDemo extends StatefulWidget { + @override + _CupertinoSliverRefreshControlDemoState createState() => + _CupertinoSliverRefreshControlDemoState(); +} + +class _CupertinoSliverRefreshControlDemoState + extends State { + final data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + ]; + + final r = Random(); + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CustomScrollView( + physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + _buildSliverAppBar(), + CupertinoSliverRefreshControl( + refreshIndicatorExtent: 60, + refreshTriggerPullDistance: 80, + onRefresh: _doRefresh, + ), + _buildSliverList() + ], + ), + ); + } + + Widget _buildSliverList() => SliverFixedExtentList( + itemExtent: 50, + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); + + Widget _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 120.0, + leading: Container( + margin: EdgeInsets.all(10), + child: Image.asset('assets/images/icon_head.webp')), + title: Text('张风捷特烈'), + actions: _buildActions(), + elevation: 5, + pinned: true, + backgroundColor: Colors.orange, + flexibleSpace: FlexibleSpaceBar( + //伸展处布局 + titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距 + collapseMode: CollapseMode.parallax, //视差效果 + background: Image.asset( + "assets/images/caver.webp", + fit: BoxFit.cover, + ), + ), + ); + } + + List _buildActions() => [ + IconButton( + onPressed: () {}, + icon: Icon( + Icons.star_border, + color: Colors.white, + ), + ) + ]; + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + + Color randomColor() => Color.fromARGB( + r.nextInt(255), r.nextInt(255), r.nextInt(255), r.nextInt(255)); + + Future _doRefresh() async { + await Future.delayed(Duration(seconds: 2)); + setState(() { + data.insertAll(0, [randomColor()]); + }); + } +} diff --git a/lib/views/widgets/Sliver/SliverAnimatedList/node1_base.dart b/lib/views/widgets/Sliver/SliverAnimatedList/node1_base.dart new file mode 100644 index 0000000..be69ccc --- /dev/null +++ b/lib/views/widgets/Sliver/SliverAnimatedList/node1_base.dart @@ -0,0 +1,203 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 301 SliverAnimatedList Sliver动画列表 在插入或删除项目时使其有动画效果的sliver组件。 +// { +// "widgetId": 301, +// "name": 'SliverAnimatedList基本使用', +// "priority": 1, +// "subtitle": +// "【itemBuilder】 : item构造器 【AnimatedListItemBuilder】\n" +// "【initialItemCount】 : 初始item个数 【int】", +// } +class SliverAnimatedListDemo extends StatefulWidget { + @override + _SliverAnimatedListDemoState createState() => + _SliverAnimatedListDemoState(); +} + +class _SliverAnimatedListDemoState extends State { + + final GlobalKey _listKey = GlobalKey(); + ListModel _list; + int _selectedItem; + int _nextItem; + + @override + void initState() { + super.initState(); + _list = ListModel( + listKey: _listKey, + initialItems: [0, 1, 2], + removedItemBuilder: _buildRemovedItem, + ); + _nextItem = 3; + } + + Widget _buildItem(BuildContext context, int index, Animation animation) { + return CardItem( + animation: animation, + item: _list[index], + selected: _selectedItem == _list[index], + onTap: () { + setState(() { + _selectedItem = _selectedItem == _list[index] ? null : _list[index]; + }); + }, + ); + } + + Widget _buildRemovedItem(int item, BuildContext context, Animation animation) { + return CardItem( + animation: animation, + item: item, + selected: false, + ); + } + + void _insert() { + final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem); + _list.insert(index, _nextItem++); + } + + void _remove() { + if (_selectedItem != null) { + _list.removeAt(_list.indexOf(_selectedItem)); + setState(() { + _selectedItem = null; + }); + } else { + if(_list.length>0){ + _list.removeAt(0); + setState(() { + _selectedItem = null; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CustomScrollView( + slivers: [ + SliverAppBar( + title: Text( + 'SliverAnimatedList', + style: TextStyle(fontSize: 20), + ), + expandedHeight: 60, + centerTitle: true, + leading: IconButton( + icon: const Icon(Icons.add_circle), + onPressed: _insert, + tooltip: '插入一个item', + iconSize: 32, + ), + actions: [ + IconButton( + icon: const Icon(Icons.remove_circle), + onPressed: _remove, + tooltip: '删除选中的item', + iconSize: 32, + ), + ], + ), + SliverAnimatedList( + key: _listKey, + initialItemCount: _list.length, + itemBuilder: _buildItem, + ), + ], + ), + ); + } + + +} + +class ListModel { + ListModel({ + @required this.listKey, + @required this.removedItemBuilder, + Iterable initialItems, + }) : assert(listKey != null), + assert(removedItemBuilder != null), + _items = List.from(initialItems ?? []); + final GlobalKey listKey; + final dynamic removedItemBuilder; + final List _items; + SliverAnimatedListState get _animatedList => listKey.currentState; + void insert(int index, E item) { + _items.insert(index, item); + _animatedList.insertItem(index); + } + E removeAt(int index) { + final E removedItem = _items.removeAt(index); + if (removedItem != null) { + _animatedList.removeItem( + index, + (BuildContext context, Animation animation) => removedItemBuilder(removedItem, context, animation), + ); + } + return removedItem; + } + int get length => _items.length; + E operator [](int index) => _items[index]; + int indexOf(E item) => _items.indexOf(item); +} + + +class CardItem extends StatelessWidget { + const CardItem({ + Key key, + @required this.animation, + @required this.item, + this.onTap, + this.selected = false, + }) : assert(animation != null), + assert(item != null && item >= 0), + assert(selected != null), + super(key: key); + final Animation animation; + final VoidCallback onTap; + final int item; + final bool selected; + @override + Widget build(BuildContext context) { + return Padding( + padding: + const EdgeInsets.only( + left: 2.0, + right: 2.0, + top: 2.0, + bottom: 0.0, + ), + child: SizeTransition( + axis: Axis.vertical, + sizeFactor: animation, + child: GestureDetector( + onTap: onTap, + child: SizedBox( + height: 60.0, + child: Card( + color: selected + ? Colors.black12 + : Colors.primaries[item % Colors.primaries.length], + child: Center( + child: Text( + 'Item $item', + style: TextStyle(color: Colors.white,fontSize: 16), + + ), + ), + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/views/widgets/Sliver/SliverFillRemaining/node1_base.dart b/lib/views/widgets/Sliver/SliverFillRemaining/node1_base.dart new file mode 100644 index 0000000..31d4357 --- /dev/null +++ b/lib/views/widgets/Sliver/SliverFillRemaining/node1_base.dart @@ -0,0 +1,156 @@ +import 'dart:math'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 306 SliverFillRemaining Sliver填补剩余 一个包含单个box子元素的sliver,它填充了视窗中的剩余空间。 +// { +// "widgetId": 306, +// "name": 'SliverFillRemaining基本使用', +// "priority": 1, +// "subtitle": +// "【hasScrollBody】 : 是否具有滚动主体 【bool】\n" +// "【fillOverscroll】 : 是否可填充滚动区域 【bool】\n" +// "【child】 : 子组件 【Widget】", +// } +class SliverFillRemainingDemo extends StatefulWidget { + @override + _SliverFillRemainingDemoState createState() => + _SliverFillRemainingDemoState(); +} + +class _SliverFillRemainingDemoState extends State { + final data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + ]; + + final r = Random(); + + bool hasScrollBody = false; + bool fillOverscroll = true; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CustomScrollView( + physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + _buildSliverAppBar(), + _buildSliverList(), + SliverFillRemaining( + hasScrollBody: hasScrollBody, + fillOverscroll: fillOverscroll, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + image: AssetImage("assets/images/sabar_bar.webp"))), + // // color: Colors.teal[100], + child: _buildBottomChild(), + ), + ), + ], + ), + ); + } + + Widget _buildBottomChild() => Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Wrap( + spacing: 10, + children: [ + RaisedButton( + color: Colors.blue, + onPressed: () { + setState(() { + hasScrollBody = !hasScrollBody; + }); + }, + child: Text('hasScrollBody:$hasScrollBody',style: TextStyle(color: Colors.white),), + ), + RaisedButton( + color: Colors.blue, + + onPressed: () { + setState(() { + fillOverscroll = !fillOverscroll; + }); + }, + child: Text('fillOverscroll:$fillOverscroll',style: TextStyle(color: Colors.white)), + ), + ], + ), + ), + ); + + Widget _buildSliverList() => SliverFixedExtentList( + itemExtent: 50, + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); + + Widget _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 120.0, + leading: Container( + margin: EdgeInsets.all(10), + child: Image.asset('assets/images/icon_head.webp')), + title: Text('张风捷特烈'), + actions: _buildActions(), + elevation: 5, + pinned: true, + backgroundColor: Colors.orange, + flexibleSpace: FlexibleSpaceBar( + titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距 + collapseMode: CollapseMode.parallax, //视差效果 + background: Image.asset( + "assets/images/caver.webp", + fit: BoxFit.cover, + ), + ), + ); + } + + List _buildActions() => [ + IconButton( + onPressed: () {}, + icon: Icon( + Icons.star_border, + color: Colors.white, + ), + ) + ]; + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + +} diff --git a/lib/views/widgets/Sliver/SliverIgnorePointer/node1_base.dart b/lib/views/widgets/Sliver/SliverIgnorePointer/node1_base.dart new file mode 100644 index 0000000..1046683 --- /dev/null +++ b/lib/views/widgets/Sliver/SliverIgnorePointer/node1_base.dart @@ -0,0 +1,158 @@ +import 'dart:math'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 305 SliverIgnorePointer Sliver忽略事件 可以包裹一个sliver组件,通过ignoring来控制该sliver组件是否可以响应事件。 +// { +// "widgetId": 305, +// "name": 'SliverIgnorePointer基本使用', +// "priority": 1, +// "subtitle": +// "【sliver】 : sliver组件 【Widget】\n" +// "【ignoring】 : 是否忽略事件 【bool】\n", +// } +class SliverIgnorePointerDemo extends StatefulWidget { + @override + _SliverIgnorePointerDemoState createState() => + _SliverIgnorePointerDemoState(); +} + +class _SliverIgnorePointerDemoState extends State { + final data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + ]; + + final r = Random(); + + bool hasScrollBody = false; + bool fillOverscroll = true; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CustomScrollView( + physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + _buildSliverAppBar(), + _buildSliverList(), + SliverIgnorePointer( + ignoring: true, + sliver: SliverFillRemaining( + hasScrollBody: hasScrollBody, + fillOverscroll: fillOverscroll, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + image: AssetImage("assets/images/sabar_bar.webp"))), + // // color: Colors.teal[100], + child: _buildBottomChild(), + ), + ), + ), + ], + ), + ); + } + + Widget _buildBottomChild() => Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Wrap( + spacing: 10, + children: [ + RaisedButton( + color: Colors.blue, + onPressed: () { + setState(() { + hasScrollBody = !hasScrollBody; + }); + }, + child: Text('hasScrollBody:$hasScrollBody',style: TextStyle(color: Colors.white),), + ), + RaisedButton( + color: Colors.blue, + + onPressed: () { + setState(() { + fillOverscroll = !fillOverscroll; + }); + }, + child: Text('fillOverscroll:$fillOverscroll',style: TextStyle(color: Colors.white)), + ), + ], + ), + ), + ); + + Widget _buildSliverList() => SliverFixedExtentList( + itemExtent: 50, + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); + + Widget _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 120.0, + leading: Container( + margin: EdgeInsets.all(10), + child: Image.asset('assets/images/icon_head.webp')), + title: Text('张风捷特烈'), + actions: _buildActions(), + elevation: 5, + pinned: true, + backgroundColor: Colors.orange, + flexibleSpace: FlexibleSpaceBar( + titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距 + collapseMode: CollapseMode.parallax, //视差效果 + background: Image.asset( + "assets/images/caver.webp", + fit: BoxFit.cover, + ), + ), + ); + } + + List _buildActions() => [ + IconButton( + onPressed: () {}, + icon: Icon( + Icons.star_border, + color: Colors.white, + ), + ) + ]; + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + +} diff --git a/lib/views/widgets/Sliver/SliverLayoutBuilder/node1_base.dart b/lib/views/widgets/Sliver/SliverLayoutBuilder/node1_base.dart new file mode 100644 index 0000000..274de28 --- /dev/null +++ b/lib/views/widgets/Sliver/SliverLayoutBuilder/node1_base.dart @@ -0,0 +1,123 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 304 SliverLayoutBuilder Sliver布局构造器 Sliver家族一员,在滑动过程中可以通过回调出的 SliverConstraints 对象进行子组件的构造。 +// { +// "widgetId": 304, +// "name": 'SliverLayoutBuilder基本使用', +// "priority": 1, +// "subtitle": +// "【builder】 : 组件构造器 【SliverLayoutWidgetBuilder】", +// } +class SliverLayoutBuilderDemo extends StatefulWidget { + @override + _SliverLayoutBuilderDemoState createState() => + _SliverLayoutBuilderDemoState(); +} + +class _SliverLayoutBuilderDemoState extends State { + final data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CustomScrollView( + physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + _buildSliverAppBar(), + SliverLayoutBuilder( + builder: _buildSliver, + ), + _buildSliverList(), + ], + ), + ); + } + + Widget _buildSliverList() => SliverFixedExtentList( + itemExtent: 50, + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); + + Widget _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 120.0, + leading: Container( + margin: EdgeInsets.all(10), + child: Image.asset('assets/images/icon_head.webp')), + title: Text('张风捷特烈'), + actions: _buildActions(), + elevation: 5, + pinned: true, + backgroundColor: Colors.orange, + flexibleSpace: FlexibleSpaceBar( + titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距 + collapseMode: CollapseMode.parallax, //视差效果 + background: Image.asset( + "assets/images/caver.webp", + fit: BoxFit.cover, + ), + ), + ); + } + + List _buildActions() => [ + IconButton( + onPressed: () {}, + icon: Icon( + Icons.star_border, + color: Colors.white, + ), + ) + ]; + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + + Widget _buildSliver(BuildContext context, SliverConstraints constraints) { + return SliverToBoxAdapter( + child: Container( + alignment: Alignment.center, + height: constraints.remainingPaintExtent / 3, + color: Colors.red, + child: Text( + "SliverLayoutBuilder", + style: TextStyle(color: Colors.white, fontSize: 20), + ), + ), + ); + } +} diff --git a/lib/views/widgets/Sliver/SliverPrototypeExtentList/SliverPrototypeExtentList.dart b/lib/views/widgets/Sliver/SliverPrototypeExtentList/SliverPrototypeExtentList.dart new file mode 100644 index 0000000..e68f845 --- /dev/null +++ b/lib/views/widgets/Sliver/SliverPrototypeExtentList/SliverPrototypeExtentList.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 314 SliverPrototypeExtentList Sliver原型延伸列表 prototypeItem属性是一个Widget,该Widget负责在主轴方向上约束item尺寸,但会不显示出来。delegate接受一个SliverChildDelegate完成item的创建。 +// { +// "widgetId": 314, +// "name": 'SliverPrototypeExtentList基本使用', +// "priority": 1, +// "subtitle": +// "【prototypeItem】 : 主轴方向尺寸组件 【Widget】\n" +// "【delegate】 : 孩子代理 【SliverChildDelegate】", +// } +class SliverPrototypeExtentListDemo extends StatefulWidget { + @override + _SliverPrototypeExtentListDemoState createState() => + _SliverPrototypeExtentListDemoState(); +} + +class _SliverPrototypeExtentListDemoState + extends State { + final data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CustomScrollView( + slivers: [_buildSliverAppBar(), _buildSliverList()], + ), + ); + } + + Widget _buildSliverList() => SliverPrototypeExtentList( + prototypeItem: Container( + height: 80, + ), + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); + + Widget _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 150.0, + leading: _buildLeading(), + title: Text('张风捷特烈'), + actions: _buildActions(), + elevation: 5, + pinned: true, + backgroundColor: Colors.orange, + flexibleSpace: FlexibleSpaceBar( + //伸展处布局 + titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距 + collapseMode: CollapseMode.parallax, //视差效果 + background: Image.asset( + "assets/images/caver.webp", + fit: BoxFit.cover, + ), + ), + ); + } + + Widget _buildLeading() => Container( + margin: EdgeInsets.all(10), + child: Image.asset('assets/images/icon_head.webp')); + + List _buildActions() => [ + IconButton( + onPressed: () {}, + icon: Icon( + Icons.star_border, + color: Colors.white, + ), + ) + ]; + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/Sliver/SliverPrototypeExtentList/node1_base.dart b/lib/views/widgets/Sliver/SliverPrototypeExtentList/node1_base.dart new file mode 100644 index 0000000..a8f3b12 --- /dev/null +++ b/lib/views/widgets/Sliver/SliverPrototypeExtentList/node1_base.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 314 SliverPrototypeExtentList Sliver原型延伸列表 其中prototypeItem属性是一个Widget,该Widget负责在主轴方向上约束item尺寸,但会不显示出来。delegate接受一个SliverChildDelegate完成item的创建。 +// { +// "widgetId": 314, +// "name": 'SliverPrototypeExtentList基本使用', +// "priority": 1, +// "subtitle": +// "【prototypeItem】 : 主轴方向尺寸组件 【Widget】\n" +// "【delegate】 : 孩子代理 【SliverChildDelegate】", +// } +class SliverPrototypeExtentListDemo extends StatefulWidget { + @override + _SliverPrototypeExtentListDemoState createState() => + _SliverPrototypeExtentListDemoState(); +} + +class _SliverPrototypeExtentListDemoState + extends State { + final data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CustomScrollView( + slivers: [_buildSliverAppBar(), _buildSliverList()], + ), + ); + } + + Widget _buildSliverList() => SliverPrototypeExtentList( + prototypeItem: Container( + height: 80, + ), + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); + + Widget _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 150.0, + leading: _buildLeading(), + title: Text('张风捷特烈'), + actions: _buildActions(), + elevation: 5, + pinned: true, + backgroundColor: Colors.orange, + flexibleSpace: FlexibleSpaceBar( + //伸展处布局 + titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距 + collapseMode: CollapseMode.parallax, //视差效果 + background: Image.asset( + "assets/images/caver.webp", + fit: BoxFit.cover, + ), + ), + ); + } + + Widget _buildLeading() => Container( + margin: EdgeInsets.all(10), + child: Image.asset('assets/images/icon_head.webp')); + + List _buildActions() => [ + IconButton( + onPressed: () {}, + icon: Icon( + Icons.star_border, + color: Colors.white, + ), + ) + ]; + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/Sliver/SliverWithKeepAliveWidget/node1_base.dart b/lib/views/widgets/Sliver/SliverWithKeepAliveWidget/node1_base.dart new file mode 100644 index 0000000..cd3d8a7 --- /dev/null +++ b/lib/views/widgets/Sliver/SliverWithKeepAliveWidget/node1_base.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 348 SliverWithKeepAliveWidget Sliver保活容器 +/// 它是抽象类,不能单独使用。只有其子类才可以容纳 KeepAlive 的孩子。 +/// link 316,239,188,185,314,186 +/// +// { +// "widgetId": 348, +// "name": 'SliverWithKeepAliveWidget 介绍', +// "priority": 1, +// "subtitle": +// "【key】 : 键 【Key】", +// } + + +class SliverWithKeepAliveWidgetDemo extends StatelessWidget { + final String info = + '只有 SliverWithKeepAliveWidget 之下才可以包含 KeepAlive 组件, 由于其为抽象类,不能直接使用。其子类 SliverMultiBoxAdaptorWidget 也说抽象类,' + '用于容纳多个孩子,帮助它的子类使用 SliverChildDelegate 构建懒加载 children。' + '最终实现类为 SliverGrid、SliverList、SliverPrototypeExtentList、SliverFixedExtentList,表示他们都可以支持 item 的状态保持。' + '除此之外还有 _SliverFillViewportRenderObjectWidget 的私有实现类,这是 PageView 的底层实现,这也是为什么 PageView 也支持保活的原因。'; + + @override + Widget build(BuildContext context) { + + return Container( + color: Theme.of(context).primaryColor.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ); + } +} \ No newline at end of file diff --git a/lib/views/widgets/StatefulWidget/AnimatedBuilder/node1_base.dart b/lib/views/widgets/StatefulWidget/AnimatedBuilder/node1_base.dart new file mode 100644 index 0000000..01ca1c7 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/AnimatedBuilder/node1_base.dart @@ -0,0 +1,68 @@ + +import 'package:flutter/material.dart'; + + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 228 AnimatedBuilder 动画构造器 +/// 通过 builder 使动画对应的节点变为局部更新,并且可避免子组件刷新,减少构建的时间,提高动画性能。 +/// +// { +// "widgetId": 228, +// "name": 'AnimatedBuilder 使用案例', +// "priority": 1, +// "subtitle": +// "【animation】 : *可监听对象 【Listenable】\n" +// "【builder】 : *组件构造器 【TransitionBuilder】\n" +// "【child】 : 子组件 【Widget】", +// } + +class AnimatedBuilderDemo extends StatefulWidget { + @override + _AnimatedBuilderDemoState createState() => _AnimatedBuilderDemoState(); +} + +class _AnimatedBuilderDemoState extends State + with SingleTickerProviderStateMixin { + AnimationController controller; + + @override + void initState() { + super.initState(); + controller = AnimationController( + vsync: this, + lowerBound: 0.3, + upperBound: 1.0, + duration: const Duration(milliseconds: 500)) + ..forward(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + controller.forward(from: 0); + }, + child: AnimatedBuilder( + animation: controller, + builder: (ctx, child) { + return Transform.scale( + scale: controller.value, + child: Opacity(opacity: controller.value, child: child), + ); + }, + child: buildChild()), + ); + } + + Widget buildChild() => Container( + height: 100, + width: 100, + decoration: BoxDecoration(color: Colors.orange, shape: BoxShape.circle), + alignment: Alignment.center, + child: Text( + 'Toly', + style: TextStyle(fontSize: 40, color: Colors.white), + ), + ); +} diff --git a/lib/views/widgets/StatefulWidget/AnimatedModalBarrier/node1_base.dart b/lib/views/widgets/StatefulWidget/AnimatedModalBarrier/node1_base.dart new file mode 100644 index 0000000..95ad73f --- /dev/null +++ b/lib/views/widgets/StatefulWidget/AnimatedModalBarrier/node1_base.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-04-01 +/// contact me by email 1981462002@qq.com +/// 说明: 227 AnimatedModalBarrier 动画屏障模 +/// 内部依赖 ModalBarrier 实现,功能一致,只不过该组件可以传入一个颜色动画,进行过渡展现。 +/// link: 212 +// { +// "widgetId": 227, +// "name": 'AnimatedModalBarrier 介绍', +// "priority": 1, +// "subtitle": +// "【dismissible】 : 点击是否返回 【bool】\n" +// "【color】 : 颜色 【Animation】", +// } +class AnimatedModalBarrierDemo extends StatefulWidget { + @override + _AnimatedModalBarrierDemoState createState() => _AnimatedModalBarrierDemoState(); +} + +class _AnimatedModalBarrierDemoState extends State + with SingleTickerProviderStateMixin { + AnimationController _controller; + Animation _color; + + @override + void initState() { + super.initState(); + _controller = + AnimationController(vsync: this, duration: Duration(seconds: 2))..forward(); + _color = ColorTween(begin: Colors.blue, end: Colors.purple) + .animate(_controller); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 200, + height: 100, + child: Stack(alignment: Alignment.center, children: [ + AnimatedModalBarrier( + dismissible: true, + color: _color, + ), + Text('点击背景返回',style: TextStyle(color: Colors.white),) + ]), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/AnimatedPhysicalModel/node1_base.dart b/lib/views/widgets/StatefulWidget/AnimatedPhysicalModel/node1_base.dart new file mode 100644 index 0000000..1c17adf --- /dev/null +++ b/lib/views/widgets/StatefulWidget/AnimatedPhysicalModel/node1_base.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-03-23 +/// contact me by email 1981462002@qq.com +/// 说明: 225 相关属性变化时具有动画效果的PhysicalModel组件,本质是PhysicalModel和动画结合的产物。可指定阴影、影深、圆角、动画时长、结束回调等属性。 +// { +// "widgetId": 225 , +// "name": 'AnimatedPhysicalModel基本使用', +// "priority": 1, +// "subtitle": +// "【color】 : 背景色 【Color】\n" +// "【duration】 : 动画时长 【Duration】\n" +// "【onEnd】 : 动画结束回调 【Function()】\n" +// "【curve】 : 动画曲线 【Duration】\n" +// "【shape】 : 形状 【BoxShape】\n" +// "【elevation】 : 影深 【double】\n" +// "【borderRadius】 : 圆角 【BorderRadius】\n" +// "【shadowColor】 : 阴影色 【Color】\n" +// "【child】 : 子组件 【Widget】", +// } +class AnimatedPhysicalModelDemo extends StatefulWidget { + @override + _AnimatedPhysicalModelDemoState createState() => + _AnimatedPhysicalModelDemoState(); +} + +class _AnimatedPhysicalModelDemoState extends State { + bool flag = false; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _buildSwitch(), + Container( + width: 150, + height: 150, + child: AnimatedPhysicalModel( + duration: Duration(seconds: 2), + curve: Curves.fastOutSlowIn, + shadowColor: flag?Colors.orange:Colors.purple, + elevation: flag?10:5, + child: Image.asset( + 'assets/images/caver.webp', + fit: BoxFit.cover, + ), + borderRadius: BorderRadius.all(Radius.circular(flag? 10:75)), + clipBehavior: Clip.hardEdge, + shape: BoxShape.rectangle, + color: Colors.deepPurpleAccent, + onEnd: () { + print('----onEnd---'); + }, + ), + ), + ], + ); + } + + Widget _buildSwitch() { + return Switch( + value: flag, + onChanged: (v) { + setState(() { + flag = v; + }); + }); + } +} \ No newline at end of file diff --git a/lib/views/widgets/StatefulWidget/AnimatedTheme/node1_base.dart b/lib/views/widgets/StatefulWidget/AnimatedTheme/node1_base.dart new file mode 100644 index 0000000..385a943 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/AnimatedTheme/node1_base.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-03-23 +/// contact me by email 1981462002@qq.com +/// 说明: 224 主题变化时具有动画效果的组件,本质是Theme组件和动画结合的产物。可指定ThemeData、动画时长、曲线、结束回调等。相当于增强版的Theme组件。 +// { +// "widgetId": 224 , +// "name": 'AnimatedTheme基本使用', +// "priority": 1, +// "subtitle": +// "【data】 : 主题数据 【ThemeData】\n" +// "【duration】 : 动画时长 【Duration】\n" +// "【onEnd】 : 动画结束回调 【Function()】\n" +// "【curve】 : 动画曲线 【Duration】\n" +// "【child】 : 子组件 【Widget】", +// } +class AnimatedThemeDemo extends StatefulWidget { + @override + _AnimatedThemeDemoState createState() => _AnimatedThemeDemoState(); +} + +class _AnimatedThemeDemoState extends State { + ThemeData startThem = ThemeData( + primaryColor: Colors.blue, + textTheme: TextTheme( + headline1: TextStyle( + color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold), + )); + + ThemeData endThem = ThemeData( + primaryColor: Colors.red, + textTheme: TextTheme( + headline1: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.normal))); + + ThemeData them; + + @override + void initState() { + super.initState(); + them = startThem; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _buildSwitch(), + AnimatedTheme( + data: them, + duration: Duration(seconds: 2), + curve: Curves.fastOutSlowIn, + onEnd: () { + print('----onEnd---'); + }, + child: ChildContent(), + ), + ], + ); + } + + Widget _buildSwitch() { + print(them == endThem); + return Switch( + value: them == endThem, + onChanged: (v) { + setState(() { + them = v ? endThem : startThem; + }); + }); + } +} + +class ChildContent extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + width: 250, + height: 60, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(5)), + color: Theme.of(context).primaryColor, + ), + padding: EdgeInsets.all(10), + child: Text( + 'Flutter Unit', + style: Theme.of(context).textTheme.headline1, + ), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/AutomaticKeepAlive/node1_base.dart b/lib/views/widgets/StatefulWidget/AutomaticKeepAlive/node1_base.dart new file mode 100644 index 0000000..fe187be --- /dev/null +++ b/lib/views/widgets/StatefulWidget/AutomaticKeepAlive/node1_base.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 239 AutomaticKeepAlive 自动保活 在懒加载的列表中,允许子树请求保持状态,单独使用无效果,需要配合 KeepAliveNotification 使用。 +/// link 59,162,163,165,185,188 +/// +// { +// "widgetId": 239, +// "name": 'AutomaticKeepAlive 介绍', +// "priority": 1, +// "subtitle": +// "【child】 : 子组件 【Widget】\n" +// "在 ListView、SliverList、GridView、SliverGrid、PageView、TabBarView 等列表、切页组件源码中都有使用到 AutomaticKeepAlive 组件。在保活某个 State 时,可以使用 AutomaticKeepAliveClientMixin 进行操作,它是对 KeepAliveNotification 使用的一个简易封装。该示例展示出 ListView 条目的状态保活。", +// } + +class AutomaticKeepAliveDemo extends StatelessWidget { + + final List data = [ + Colors.purple[50], + Colors.purple[100], + Colors.purple[200], + Colors.purple[300], + Colors.purple[400], + Colors.purple[500], + Colors.purple[600], + Colors.purple[700], + Colors.purple[800], + Colors.purple[900], + Colors.red[50], + Colors.red[100], + Colors.red[200], + Colors.red[300], + Colors.red[400], + Colors.red[500], + Colors.red[600], + Colors.red[700], + Colors.red[800], + Colors.red[900], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: ListView.builder( + itemCount: data.length, + itemBuilder: (_, index) => ColorBox( + color: data[index], + index: index, + ), + ), + ); + } +} + +class ColorBox extends StatefulWidget { + final Color color; + final int index; + + ColorBox({Key key, this.color, this.index}) : super(key: key); + + @override + _ColorBoxState createState() => _ColorBoxState(); +} + +class _ColorBoxState extends State with AutomaticKeepAliveClientMixin { + bool _checked = false; + + @override + void initState() { + super.initState(); + _checked = false; + print('-----_ColorBoxState#initState---${widget.index}-------'); + } + + @override + void dispose() { + print('-----_ColorBoxState#dispose---${widget.index}-------'); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + + return Container( + alignment: Alignment.center, + height: 50, + color: widget.color, + child: Row( + children: [ + SizedBox(width: 60,), + Checkbox( + value: _checked, + onChanged: (v) { + setState(() { + _checked = v; + }); + }, + ), + Text( + "index ${widget.index}: ${colorString(widget.color)}", + style: TextStyle(color: Colors.white, shadows: [ + Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) + ]), + ), + ], + ), + ); + } + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/views/widgets/StatefulWidget/CupertinoSlidingSegmentedControl/node1_base.dart b/lib/views/widgets/StatefulWidget/CupertinoSlidingSegmentedControl/node1_base.dart new file mode 100644 index 0000000..24a542b --- /dev/null +++ b/lib/views/widgets/StatefulWidget/CupertinoSlidingSegmentedControl/node1_base.dart @@ -0,0 +1,55 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/7/22 +/// contact me by email 1981462002@qq.com +/// 说明: 256 CupertinoSlidingSegmentedControl iOS滑动页签 iOS风格的滑动页签,支持点击、滑动切换。可指定页签颜色、背景色、边距等属性。 +// { +// "widgetId": 256, +// "name": 'iOS滑动页签基本使用', +// "priority": 1, +// "subtitle": +// "【children】 : 组件Map 【Map】\n" +// "【onValueChanged】 : 值改变回调 【ValueChanged】\n" +// "【groupValue】 : 选中值 【T】\n" +// "【thumbColor】 : 选中色 【Color】\n" +// "【backgroundColor】 : 背景色 【Color】\n" +// "【padding】 : 内边距 【EdgeInsetsGeometry】", +// } +class CupertinoSlidingSegmentedControlDemo extends StatefulWidget { + @override + _CupertinoSlidingSegmentedControlDemoState createState() => + _CupertinoSlidingSegmentedControlDemoState(); +} + +class _CupertinoSlidingSegmentedControlDemoState + extends State { + var _value = 1; + + @override + Widget build(BuildContext context) { + return Container( + child: CupertinoSlidingSegmentedControl( + groupValue: _value, + onValueChanged: _onValueChanged, + thumbColor: Colors.amberAccent, + backgroundColor: Colors.green.withAlpha(99), + padding: EdgeInsets.all(5), + children: { + 1: Padding( + padding: EdgeInsets.only(left: 20, right: 20), + child: Text("混沌战士"), + ), + 2: Text("青眼白龙"), + 3: Text("黑魔导"), + }, + ), + ); + } + + void _onValueChanged(int value) { + setState(() { + _value=value; + }); + } +} diff --git a/lib/views/widgets/StatefulWidget/CupertinoTabView/node1_base.dart b/lib/views/widgets/StatefulWidget/CupertinoTabView/node1_base.dart new file mode 100644 index 0000000..dce35fb --- /dev/null +++ b/lib/views/widgets/StatefulWidget/CupertinoTabView/node1_base.dart @@ -0,0 +1,107 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 229 CupertinoTabView Cupertino页面 CupertinoTabView 可以像 MaterialApp 一样维护一个路由栈。通过 routes 、onGenerateRoute 来构建路由,可以通过 navigatorObservers 监听路由。 +// { +// "widgetId": 229, +// "name": 'CupertinoTabView基本使用', +// "priority": 1, +// "subtitle": +// "【builder】 : 主页构造器 【WidgetBuilder】\n" +// "【navigatorObservers】 : 路由监听器 【List】\n" +// "【routes】 : 路由映射 【Map】\n" +// "【onGenerateRoute】 : 路由工厂 【RouteFactory】", +// } + +class CupertinoTabViewDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(10), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CupertinoTabViewPage()), + ); + }, + child: Text("进入 CupertinoTabView 测试页"), + ), + ); + } +} + + +class CupertinoTabViewPage extends StatelessWidget { + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: CupertinoTabView( + routes: { + '/': (context) => HomePage(), + '/test_detail': (context) => DetailPage(), + }, + ), + ); + } +} + +class DetailPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text('我是详情页'), + ), + child: Center( + child: Container( + width: 200, + height: 200, + color: Colors.blue, + ), + ), + ); + } +} + +class HomePage extends StatelessWidget { + + final String info = "CupertinoTabView 可以像 MaterialApp 一样维护一个路由栈。" + "通过 routes 、onGenerateRoute 来构建路由,可以通过 navigatorObservers 监听路由。" + "在这个路由栈中可以进行指定名称跳转,如下通过 /test_detail 跳到详情页。"; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text('我是主页'), + ), + child: Center(child: Column( + + children: [ + Spacer(), + Material(child: Padding( + padding: const EdgeInsets.only(left:18.0,right: 18,bottom: 20), + child: Text(info), + )), + CupertinoButton( + padding: EdgeInsets.only(left: 10,right: 10), + color: Colors.blue, + onPressed: () { + Navigator.pushNamed( + context, "/test_detail" + ); + }, + child: Text("进入详情页"), + ), + Spacer(), + ], + )), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/DefaultTabController/node1_base.dart b/lib/views/widgets/StatefulWidget/DefaultTabController/node1_base.dart new file mode 100644 index 0000000..34791a2 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/DefaultTabController/node1_base.dart @@ -0,0 +1,51 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 230 DefaultTabController 默认Tab控制器 在使用TabBar和TabBarView时,需要同一个控制器实现页签和页面的控制。DefaultTabController会在未指定控制器时提供默认控制器,简化使用。 +// { +// "widgetId": 230, +// "name": 'DefaultTabController基本使用', +// "priority": 1, +// "subtitle": +// "【length】 : 页签数量 【int】\n" +// "【initialIndex】 : 初始页签索引 【int】\n" +// "【child】 : 组件 【Widget】", +// } + +class DefaultTabControllerDemo extends StatelessWidget { + final List tabs = [ + Tab(text: '青眼白龙'), + Tab(text: '黑魔术师'), + Tab(text: '混沌战士'), + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: DefaultTabController( + length: tabs.length, + child: Scaffold( + appBar: AppBar( + title: Text("DefaultTabController"), + bottom: TabBar( + tabs: tabs, + ), + ), + body: TabBarView( + children: tabs.map((Tab tab) { + return Center( + child: Text( + tab.text, + style: const TextStyle(fontSize: 20), + ), + ); + }).toList(), + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/DraggableScrollableSheet/node1_base.dart b/lib/views/widgets/StatefulWidget/DraggableScrollableSheet/node1_base.dart new file mode 100644 index 0000000..9d0d290 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/DraggableScrollableSheet/node1_base.dart @@ -0,0 +1,100 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 252 DraggableScrollableSheet 拖滑页 可拖动和滑动的Sheet,可指定最大、最小、最初的分度现在滑动范围。构造器builder需要返回一个可滑动组件。 +// { +// "widgetId": 252, +// "name": 'DraggableScrollableSheet基本使用', +// "priority": 1, +// "subtitle": +// "【initialChildSize】 : 初始分度 【double】\n" +// "【minChildSize】 : 最小分度 【double】\n" +// "【maxChildSize】 : 最大分度 【double】\n" +// "【builder】 : 滑动组件构造器 【ScrollableWidgetBuilder】\n" +// "【expand】 : 是否延展 【bool】", +// } + +class DraggableScrollableSheetDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(10), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DraggableScrollableSheetPage()), + ); + }, + child: Text("进入 DraggableScrollableSheet 测试页"), + ), + ); + } +} + +class DraggableScrollableSheetPage extends StatelessWidget { + final data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + Colors.red[50], + Colors.red[100], + Colors.red[200], + Colors.red[300], + Colors.red[400], + Colors.red[500], + Colors.red[600], + Colors.red[700], + Colors.red[800], + Colors.red[900], + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("DraggableScrollableSheet"), + ), + body: SizedBox.expand( + child: DraggableScrollableSheet( + initialChildSize: 0.3, + minChildSize: 0.2, + maxChildSize: 0.5, + expand: true, + builder: (BuildContext context, ScrollController scrollController)=> + ListView.builder( + controller: scrollController, + itemCount: data.length, + itemBuilder: buildColorItem, + ), + )), + ); + } + + Widget buildColorItem(BuildContext context, int index) { + return Container( + alignment: Alignment.center, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) + ]), + ), + ); + } + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/StatefulWidget/DrawerController/node1_base.dart b/lib/views/widgets/StatefulWidget/DrawerController/node1_base.dart new file mode 100644 index 0000000..06653af --- /dev/null +++ b/lib/views/widgets/StatefulWidget/DrawerController/node1_base.dart @@ -0,0 +1,75 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 257 DrawerController 为 Drawer 组件提供交互行为,一般很少使用。在 Scaffold 组件源码中有使用场景。 +// { +// "widgetId": 257, +// "name": 'DrawerController基本使用', +// "priority": 1, +// "subtitle": +// "【drawerCallback】 : 事件回调 【DrawerCallback】\n" +// "【enableOpenDragGesture】 : 是否侧边滑开 【bool】\n" +// "【alignment】 : 对齐方式 【DrawerAlignment】\n" +// "【scrimColor】 : 背景颜色 【Color】\n" +// "【child】 : Drawer组件 【Widget】", +// } + +class DrawerControllerDemo extends StatefulWidget { + @override + _DrawerControllerDemoState createState() => _DrawerControllerDemoState(); +} + +class _DrawerControllerDemoState extends State { + final GlobalKey _drawerKey = + GlobalKey(); + + bool _open = false; + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + children: [ + ElevatedButton( + onPressed: toggleDrawer, + child: Text("显隐 Drawer"), + ), + Container( + height: 200, + child: DrawerController( + scrimColor: Colors.blue.withAlpha(88), + enableOpenDragGesture: true, + key: _drawerKey, + alignment: DrawerAlignment.start, + drawerCallback: (value) { + _open = value; + }, + child: Drawer( + child: Container( + alignment: Alignment.center, + color: Colors.red, + child: Text( + "I am Drawer!", + style: TextStyle(color: Colors.white, fontSize: 18), + ), + ), + ), + ), + ), + ], + ), + ); + } + + void toggleDrawer() { + if (_open) { + _drawerKey.currentState?.close(); + } else { + print('---open--$_open-------'); + _drawerKey.currentState?.open(); + } + } +} diff --git a/lib/views/widgets/StatefulWidget/DropdownButtonFormField/node1_base.dart b/lib/views/widgets/StatefulWidget/DropdownButtonFormField/node1_base.dart new file mode 100644 index 0000000..18b6495 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/DropdownButtonFormField/node1_base.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 223 DropdownButtonFormField 表单下拉框 +/// 底层依赖 DropdownButton 实现,所以基本属性类似。但拥有 FormField 的特性,可以回调 onSaved、validator 方法。 +/// link: 55,222 +/// +// { +// "widgetId": 223, +// "name": '表单下拉框简单使用', +// "priority": 1, +// "subtitle": +// "【items】 : 子组件列表 【List>】\n" +// "【validator】 : 表单验证回调 【FormFieldValidator】\n" +// "【onSaved】 : 表单保存回调 【FormFieldSetter】\n" +// "其他属性详见 DropdownButton,表单校验特性详见 FormField。", +// } + +class DropdownButtonFormFieldDemo extends StatefulWidget { + @override + _DropdownButtonFormFieldDemoState createState() => _DropdownButtonFormFieldDemoState(); +} + +class _DropdownButtonFormFieldDemoState extends State { + Color _color; + final _colors = [Colors.red, Colors.yellow, Colors.blue, Colors.green]; + final _info = ["红色", "黄色", "蓝色", "绿色"]; + + @override + Widget build(BuildContext context) { + return Wrap( + children: [ + Container( + margin: EdgeInsets.symmetric(horizontal: 20), + width: 50, + height: 50, + color: _color??_colors[0], + ), + + SizedBox( + width: 80, + child: DropdownButtonFormField( + value: _color, + elevation: 1, + hint: Text('选择颜色',style: TextStyle(fontSize: 12),), + icon: Icon( + Icons.expand_more, + size: 20, + color: _color, + ), + items: _buildItems(), + onChanged: (v) => setState(() => _color = v) + ), + ) + + ], + ); + } + + List> _buildItems() => _colors + .map((e) => DropdownMenuItem( + value: e, + child: Text( + _info[_colors.indexOf(e)], + style: TextStyle(color: e), + ))) + .toList(); +} \ No newline at end of file diff --git a/lib/views/widgets/StatefulWidget/ElevatedButton/node1_base.dart b/lib/views/widgets/StatefulWidget/ElevatedButton/node1_base.dart new file mode 100644 index 0000000..6a13290 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/ElevatedButton/node1_base.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 354 ElevatedButton Material风格的升起按钮,表现和RaisedButton类似。可通过样式更改边框、颜色、阴影等属性。 +// { +// "widgetId": 354, +// "name": 'ElevatedButton基本使用', +// "priority": 1, +// "subtitle": +// "【child】 : 是否具有滚动主体 【Widget】\n" +// "【onPressed】 : 点击事件 【VoidCallback】\n" +// "【onLongPress】 : 长按事件 【VoidCallback】", +// } + +class ElevatedButtonDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + height: 60, + child: Wrap( + spacing: 20, + children: [ + ElevatedButton( + child: Text('ElevatedButton'), + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + ElevatedButton( + child: Text('禁用按钮'), + onPressed: null, + onLongPress: null, + ), + ], + )); + } + + _onPressed() {} + + _onLongPress() {} +} diff --git a/lib/views/widgets/StatefulWidget/ElevatedButton/node2_style.dart b/lib/views/widgets/StatefulWidget/ElevatedButton/node2_style.dart new file mode 100644 index 0000000..d4d6100 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/ElevatedButton/node2_style.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: +// { +// "widgetId": 354, +// "name": 'ElevatedButton样式', +// "priority": 2, +// "subtitle": +// "【style】 : 按钮样式 【ButtonStyle】\n" +// "【focusNode】 : 焦点 【FocusNode】\n" +// "【clipBehavior】 : 裁剪行为 【Clip】\n" +// "【autofocus】 : 自动聚焦 【bool】", +// } + +class ElevatedButtonStyleDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + child: Wrap( + spacing: 10, + children: [ + ElevatedButton( + style: TextButton.styleFrom( + backgroundColor: Colors.orange, + primary: Colors.white, + elevation: 2, + shadowColor: Colors.orangeAccent), + child: Text('ElevatedButton样式'), + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + ElevatedButton( + style: TextButton.styleFrom( + backgroundColor: Colors.white, + primary: Colors.black, + side: BorderSide(color: Colors.blue,width: 1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)) + ), + // elevation: 2, + shadowColor: Colors.orangeAccent), + child: Text('ElevatedButton边线'), + autofocus: false, + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + ], + ), + ); + } + + _onPressed() {} + + _onLongPress() {} +} diff --git a/lib/views/widgets/StatefulWidget/FormField/node1_base.dart b/lib/views/widgets/StatefulWidget/FormField/node1_base.dart new file mode 100644 index 0000000..dd7fab8 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/FormField/node1_base.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-04-01 +/// contact me by email 1981462002@qq.com +/// 说明: 222 FormField 表单字段 +/// 一个表单字段,需要在 Form 组件中使用,内含泛型 T 的字段作为状态量,对根据字段的更新和验证会触发相应回调。 +/// link:198,199,223, +// { +// "widgetId": 222, +// "name": 'FormField 介绍', +// "priority": 1, +// "subtitle": +// "【builder】 : 内容构造器 【FormFieldBuilder】\n" +// "【initialValue】 : 初始值 【T】\n" +// "【validator】 : 验证函数 【FormFieldValidator 】\n" +// "【enabled】 : 是否有效 【bool】\n" +// "【onSaved】 : 表单save时回调 【FormFieldSetter】", +// } +class FormFieldDemo extends StatelessWidget { + final String info = + 'FormField 代表表单中的一个字段,对于字符串类型的字段,框架中封装了 TextFormField 以便使用;下拉选择的字段,用 DropdownButtonFormField。' + '目前框架中 FormField 的子类也只有这两个。既然是表单字段,必然是要和 Form 组件一起使用。通过对 Form 添加 GlobalKey ,来获取 FormState 对象。' + '当 FormState 调用 save 方法时,所有的 FormField 都会触发 onSave 方法,当 FormState 调用 validate 方法时,所有的 FormField 都会触发 validate 方法。'; + + @override + Widget build(BuildContext context) { + + + return Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/GlowingOverscrollIndicator/node1_base.dart b/lib/views/widgets/StatefulWidget/GlowingOverscrollIndicator/node1_base.dart new file mode 100644 index 0000000..5fab499 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/GlowingOverscrollIndicator/node1_base.dart @@ -0,0 +1,63 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 250 GlowingOverscrollIndicator 孩子为可滑动列表,当滑动到顶部和底部时的指示效果,可指定颜色,没什么太大卵用。是Android和fuchsia系统默认滑动效果。 +// { +// "widgetId": 250, +// "name": '基本使用', +// "priority": 1, +// "subtitle": +// "【showLeading】 : 头部是否生效 【bool】\n" +// "【showTrailing】 : 底部是否生效 【bool】\n" +// "【axisDirection】 : 轴向 【AxisDirection】\n" +// "【color】 : 颜色 【Color】\n" +// "【child】 : 子组件 【Widget】", +// } + +class GlowingOverscrollIndicatorDemo extends StatelessWidget { + final List data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + Colors.red[50], + Colors.red[100], + Colors.red[200], + Colors.red[300], + Colors.red[400], + Colors.red[500], + Colors.red[600], + Colors.red[700], + Colors.red[800], + Colors.red[900], + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: GlowingOverscrollIndicator( + color: Colors.purple, + // showLeading: false, + // showTrailing: false, + axisDirection: AxisDirection.down, + child: ListView.builder( + itemBuilder: (_, index) => Container( + margin: EdgeInsets.all(10), + height: 60, + color: data[index], + ), + itemCount: data.length, + ), + ), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/MergeableMaterial/node1_base.dart b/lib/views/widgets/StatefulWidget/MergeableMaterial/node1_base.dart new file mode 100644 index 0000000..c4b9843 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/MergeableMaterial/node1_base.dart @@ -0,0 +1,81 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 261 MergeableMaterial 可合并材料 用于展示 MergeableMaterialItem 的列表,包括 MaterialSlice(主体) 和 MaterialGap(分隔) 。 +// { +// "widgetId": 261, +// "name": 'MergeableMaterial基本使用', +// "priority": 1, +// "subtitle": +// "【elevation】 : 影深 【double】\n" +// "【hasDividers】 : 是否有分隔线 【bool】\n" +// "【dividerColor】 : 分隔线颜色 【Color】\n" +// "【mainAxis】 : 轴向 【Axis】\n" +// "【children】 : 子组件集 【List】", +// } + +class MergeableMaterialDemo extends StatefulWidget { + @override + _MergeableMaterialDemoState createState() => _MergeableMaterialDemoState(); +} + +class _MergeableMaterialDemoState extends State { + List items=[]; + + @override + void initState() { + super.initState(); + _init(20); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: SingleChildScrollView( + child: MergeableMaterial( + elevation: 1, + hasDividers: true, + dividerColor: Colors.red, + children: items, + ), + ), + ); + } + + final List data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900] + ]; + + void _init(int count) { + for (int i = 0; i < count; i++) { + items.add(MaterialSlice( + key: ValueKey(i), + child: Container( + alignment: Alignment.center, + height: 60, + color: data[i % data.length], + child: Text(colorString(data[i % data.length])), + ))); + if(i!=count-1){ + items.add(MaterialGap( + key: ValueKey(i), + size: 5)); + } + } + } + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/StatefulWidget/OutlinedButton/node1_base.dart b/lib/views/widgets/StatefulWidget/OutlinedButton/node1_base.dart new file mode 100644 index 0000000..27498b8 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/OutlinedButton/node1_base.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 355 OutlinedButton Material风格的边线按钮,表现和OutlineButton类似。可通过样式更改边框、颜色、阴影等属性。 +// { +// "widgetId": 355, +// "name": 'OutlinedButton基本使用', +// "priority": 1, +// "subtitle": +// "【child】 : 是否具有滚动主体 【Widget】\n" +// "【onPressed】 : 点击事件 【VoidCallback】\n" +// "【onLongPress】 : 长按事件 【VoidCallback】", +// } + +class OutlinedButtonDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + height: 60, + child: Wrap( + spacing: 20, + children: [ + OutlinedButton( + child: Text('OutlinedButton'), + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + OutlinedButton( + child: Text('禁用按钮'), + onPressed: null, + onLongPress: null, + ), + ], + )); + } + + _onPressed() {} + + _onLongPress() {} +} diff --git a/lib/views/widgets/StatefulWidget/OutlinedButton/node2_style.dart b/lib/views/widgets/StatefulWidget/OutlinedButton/node2_style.dart new file mode 100644 index 0000000..48a97a9 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/OutlinedButton/node2_style.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: +// { +// "widgetId": 355, +// "name": 'OutlinedButton样式', +// "priority": 2, +// "subtitle": +// "【style】 : 按钮样式 【ButtonStyle】\n" +// "【focusNode】 : 焦点 【FocusNode】\n" +// "【clipBehavior】 : 裁剪行为 【Clip】\n" +// "【autofocus】 : 自动聚焦 【bool】", +// } + +class OutlinedButtonStyleDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + child: Wrap( + spacing: 10, + children: [ + OutlinedButton( + style: TextButton.styleFrom( + backgroundColor: Colors.orange, + primary: Colors.white, + elevation: 2, + shadowColor: Colors.orangeAccent), + child: Text('ElevatedButton样式'), + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + OutlinedButton( + style: TextButton.styleFrom( + backgroundColor: Colors.white, + primary: Colors.black, + side: BorderSide(color: Colors.blue,width: 1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)) + ), + // elevation: 2, + shadowColor: Colors.orangeAccent), + child: Text('ElevatedButton边线'), + autofocus: false, + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + ], + ), + ); + } + + _onPressed() {} + + _onLongPress() {} +} diff --git a/lib/views/widgets/StatefulWidget/PaginatedDataTable/node1_base.dart b/lib/views/widgets/StatefulWidget/PaginatedDataTable/node1_base.dart new file mode 100644 index 0000000..2d56de2 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/PaginatedDataTable/node1_base.dart @@ -0,0 +1,214 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-04-01 +/// contact me by email 1981462002@qq.com +/// 说明: 235 PaginatedDataTable 可分页表格 +/// 一个功能丰富的可分页表格组件,可指定分页数、排列、页码前后切换。 +/// link: 110,102 +// { +// "widgetId": 235 , +// "name": 'PaginatedDataTable 使用', +// "priority": 1, +// "subtitle": +// "【header】 : 表名 【Widget】\n" +// "【rowsPerPage】 : 每页记录数 【int】\n" +// "【actions】 : 操作组件 【List】\n" +// "【columns】 : 数据列 【List】\n" +// "【sortColumnIndex】 : 排序列索引 【int】\n" +// "【sortAscending】 : 是否升序 【bool】\n" +// "【onSelectAll】 : 全选回调 【ValueSetter】\n" +// "【onRowsPerPageChanged】 : 分页改变监听 【ValueChanged】\n" +// "【availableRowsPerPage】 : 可用分页列表 【List】\n" +// "【source】 : 数据源 【DataTableSource】", +// } +class PaginatedDataTableDemo extends StatefulWidget { + @override + State createState() => _PaginatedDataTableDemoState(); +} + +class _PaginatedDataTableDemoState extends State { + int _rowsPerPage = 5; + + int _sortColumnIndex; + bool _sortAscending = true; + + final DessertDataSource _dessertsDataSource = DessertDataSource(); + + void sort( + Comparable getField(HeroInfo d), int columnIndex, bool ascending) { + _dessertsDataSource.sort(getField, ascending); + setState(() { + _sortColumnIndex = columnIndex; + _sortAscending = ascending; + }); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + width: 350, + child: SingleChildScrollView( + child: PaginatedDataTable( + actions: [ + IconButton(icon: Icon(Icons.add), onPressed: null), + ], + header: const Text( + '《旷古奇书》-角色预设', + style: TextStyle(color: Colors.blue), + ), + rowsPerPage: _rowsPerPage, + availableRowsPerPage: [5, 8, 10, 15], + onRowsPerPageChanged: (int value) { + setState(() { + _rowsPerPage = value; + }); + }, + sortColumnIndex: _sortColumnIndex, + sortAscending: _sortAscending, + onSelectAll: _dessertsDataSource._selectAll, + columns: [ + DataColumn( + label: const Text('角色名称'), + onSort: (int columnIndex, bool ascending) => sort( + (HeroInfo d) => d.name, columnIndex, ascending)), + DataColumn( + label: const Text('主场卷部'), + tooltip: '人物主要出场的作品.', + numeric: true, + onSort: (int columnIndex, bool ascending) => sort( + (HeroInfo d) => d.calories, columnIndex, ascending)), + DataColumn( + label: const Text('种族'), + numeric: true, + onSort: (int columnIndex, bool ascending) => sort( + (HeroInfo d) => d.fat, columnIndex, ascending)), + DataColumn( + label: const Text('性别'), + numeric: true, + onSort: (int columnIndex, bool ascending) => sort( + (HeroInfo d) => d.carbs, columnIndex, ascending)), + ], + source: _dessertsDataSource), + )); + } +} + +class HeroInfo { + HeroInfo(this.name, this.calories, this.fat, this.carbs); + + final String name; + final String calories; + final String fat; + final String carbs; + bool selected = false; +} + +class DessertDataSource extends DataTableSource { + final List _desserts = [ + HeroInfo('捷特', '《幻将录》', "人族", "男"), + HeroInfo('龙少', '《幻将录》', "人族", "男"), + HeroInfo('巫缨', '《幻将录》', "人族", "女"), + HeroInfo('林兮', '《幻将录》', "人族", "男"), + HeroInfo('九方玄玉', '《风神传》', "神族", "男"), + HeroInfo('七日洪荒', '《风神传》', "魔族", "男"), + HeroInfo('林昔瑶', '《封妖志》', "鬼族", "女"), + HeroInfo('林兮鬼帝', '《封妖志》', "鬼族", "男"), + HeroInfo('艾隆', '《封妖志》', "鬼族", "男"), + HeroInfo('语熙华', '《风神传》', "道族", "男"), + HeroInfo('雪玉宛如', '《幻将录》', "人族", "女"), + HeroInfo('破千', '《幻将录》', "人族", "男"), + HeroInfo('浪封', '《幻将录》', "人族", "男"), + HeroInfo('虎翼穷奇', '《封妖志》', "妖族", "男"), + HeroInfo('凯', '《幻将录》', "人族", "男"), + HeroInfo('荆棘', '《幻将录》', "人族", "女"), + HeroInfo('龙右', '《幻将录》', "人族", "男"), + HeroInfo('梦千', '《幻将录》', "人族", "男"), + HeroInfo('梦小梦', '《幻将录》', "人族", "女"), + HeroInfo('梦瞳', '《幻将录》', "人族", "男"), + HeroInfo('十戈', '《幻将录》', "人族", "男"), + HeroInfo('计画天', '《幻将录》', "人族", "女"), + HeroInfo('士方', '《幻将录》', "人族", "男"), + HeroInfo('巫妻孋', '《幻将录》', "人族", "女"), + HeroInfo('木时黎', '《永恒传说》', "人族", "男"), + HeroInfo('木艾奇', '《永恒传说》', "人族", "男"), + HeroInfo('张风', '《永恒传说》', "人族", "男"), + HeroInfo('薛剑儿', '《永恒传说》', "人族", "男"), + HeroInfo('李月', '《永恒传说》', "人族", "女"), + HeroInfo('刘雪', '《永恒传说》', "人族", "女"), + HeroInfo('葛心', '《永恒传说》', "人族", "女"), + HeroInfo('步映容', '《幻将录》', "人族", "女"), + HeroInfo('莫慈良', '《幻将录》', "人族", "男"), + HeroInfo('莫向阳', '《幻将录》', "人族", "男"), + HeroInfo('莫子薇', '《永恒传说》', "人族", "女"), + HeroInfo('藏凯阳', '《永恒传说》', "人族", "男"), + HeroInfo('奇雨歆', '《永恒传说》', "人族", "女"), + HeroInfo('林天蕊', '《永恒传说》', "人族", "女"), + HeroInfo('吴灏然', '《永恒传说》', "人族", "男"), + HeroInfo('何解连', '《永恒传说》', "人族", "男"), + HeroInfo('步络尘', '《幻将录》', "人族", "男"), + HeroInfo('拓雷', '《幻将录》', "人族", "男"), + HeroInfo('炽阳骑', '《幻将录》', "人族", "男"), + HeroInfo('正构', '《幻将录》', "人族", "男"), + HeroInfo('烈', '《幻将录》', "人族", "男"), + HeroInfo('梦华君', '《幻将录》', "人族", "男"), + HeroInfo('初星', '《幻将录》', "人族", "男"), + HeroInfo('梦飞烟', '《幻将录》', "人族", "男"), + HeroInfo('武落英', '《幻将录》', "人族", "女"), + HeroInfo('古千缘', '《幻将录》', "人族", "男"), + ]; + + void sort(Comparable getField(HeroInfo d), bool ascending) { + _desserts.sort((HeroInfo a, HeroInfo b) { + if (!ascending) { + final HeroInfo c = a; + a = b; + b = c; + } + final Comparable aValue = getField(a); + final Comparable bValue = getField(b); + return Comparable.compare(aValue, bValue); + }); + notifyListeners(); + } + + int _selectedCount = 0; + + @override + DataRow getRow(int index) { + if (index >= _desserts.length) return null; + final HeroInfo dessert = _desserts[index]; + return DataRow.byIndex( + index: index, + selected: dessert.selected, + onSelectChanged: (bool value) { + if (dessert.selected != value) { + _selectedCount += value ? 1 : -1; + assert(_selectedCount >= 0); + dessert.selected = value; + notifyListeners(); + } + }, + cells: [ + DataCell(Center(child: Text('${dessert.name}'))), + DataCell(Center(child: Text('${dessert.calories}'))), + DataCell(Center(child: Text('${dessert.fat}'))), + DataCell(Center(child: Text('${dessert.carbs}'))), + ]); + } + + @override + bool get isRowCountApproximate => false; + + @override + int get rowCount => _desserts.length; + + @override + int get selectedRowCount => _selectedCount; + + void _selectAll(bool checked) { + for (HeroInfo dessert in _desserts) dessert.selected = checked; + _selectedCount = checked ? _desserts.length : 0; + notifyListeners(); + } +} diff --git a/lib/views/widgets/StatefulWidget/RawGestureDetector/node1_base.dart b/lib/views/widgets/StatefulWidget/RawGestureDetector/node1_base.dart new file mode 100644 index 0000000..f493088 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/RawGestureDetector/node1_base.dart @@ -0,0 +1,68 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 248 RawGestureDetector 可以用来检测给定手势工厂描述的手势,在开发自己的手势识别器时非常有用。对于常见的手势,使用 GestureRecognizer。 +// { +// "widgetId": 248, +// "name": 'RawGestureDetector基本使用', +// "priority": 1, +// "subtitle": +// "【behavior】 : 侦测行为 【HitTestBehavior】\n" +// "【gestures】 : 手势映射 【Map】\n" +// "【child】 : 子组件 【Widget】", +// } + +class RawGestureDetectorDemo extends StatefulWidget { + @override + _RawGestureDetectorDemoState createState() => _RawGestureDetectorDemoState(); +} + +class _RawGestureDetectorDemoState extends State { + String _last = ""; + + @override + Widget build(BuildContext context) { + return RawGestureDetector( + gestures: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + init, + ), + }, + child: Container( + width: 300.0, + height: 100.0, + alignment: Alignment.center, + color: Colors.yellow, + child: Text(_last)), + ); + } + + void init(TapGestureRecognizer instance) { + instance..onTapDown = (TapDownDetails details) { + setState(() { + _last = 'down'; + }); + } + ..onTapUp = (TapUpDetails details) { + setState(() { + _last = 'up'; + }); + } + ..onTap = () { + setState(() { + _last = 'tap'; + }); + } + ..onTapCancel = () { + setState(() { + _last = 'cancel'; + }); + } + ; + } +} diff --git a/lib/views/widgets/StatefulWidget/RawKeyboardListener/node1_base.dart b/lib/views/widgets/StatefulWidget/RawKeyboardListener/node1_base.dart new file mode 100644 index 0000000..5dc1495 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/RawKeyboardListener/node1_base.dart @@ -0,0 +1,66 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 254 RawKeyboardListener 可以用来检测键盘按键和松键的事件,目前只能检测到物理键盘,可在桌面端使用。 +// { +// "widgetId": 254, +// "name": 'RawGestureDetector基本使用', +// "priority": 1, +// "subtitle": +// "【onKey】 : 键盘事件 【ValueChanged】\n" +// "【focusNode】 : 焦点 【FocusNode】\n" +// "【autofocus】 : 是否自动聚焦 【bool】\n" +// "【child】 : 子组件 【Widget】", +// } + +class RawKeyboardListenerDemo extends StatefulWidget { + @override + _RawKeyboardListenerDemoState createState() => _RawKeyboardListenerDemoState(); +} + +class _RawKeyboardListenerDemoState extends State { + String _info = ""; + + final FocusNode node = FocusNode(); + + @override + Widget build(BuildContext context) { + return RawKeyboardListener( + focusNode: node, + onKey: _onKey, + + child: Container( + width: 300, + child: Row( + children: [ + Expanded( + child: TextField( + decoration: InputDecoration( + border: OutlineInputBorder() + ), + ), + ), + SizedBox(width: 20,), + Text('$_info') + ], + ), + ), + ); + } + + void _onKey(RawKeyEvent value) { + print(value); + if(value is RawKeyDownEvent){ + _info = "按下: ${value.logicalKey.debugName}\nid: 0x${value.logicalKey.keyId.toRadixString(16).padLeft(9,"0")}"; + } + if(value is RawKeyUpEvent){ + _info = "抬起: ${value.logicalKey.debugName}\nid: 0x${value.logicalKey.keyId.toRadixString(16).padLeft(9,"0")}"; + } + setState(() { + + }); + } +} diff --git a/lib/views/widgets/StatefulWidget/StatefulBuilder/node1_base.dart b/lib/views/widgets/StatefulWidget/StatefulBuilder/node1_base.dart new file mode 100644 index 0000000..9725373 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/StatefulBuilder/node1_base.dart @@ -0,0 +1,33 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 242 StatefulBuilder 需要传入 builder 属性进行构造组件,在 builder 中可以使用 StateSetter 改变构造子组件的状态,即可以不用创建类而实现一个局部刷新的组件。 +// { +// "widgetId": 242, +// "name": 'StatefulBuilder基本使用', +// "priority": 1, +// "subtitle": +// "【builder】 : 组件构造器 【StatefulWidgetBuilder】", +// } + +class StatefulBuilderDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + int count = 0; + + return Container( + child: StatefulBuilder( + builder: (ctx, setState) => ElevatedButton( + child: Text("当前数字: $count"), + onPressed: () { + setState(() { + count++; + }); + }, + ), + ), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/StatusTransitionWidget/node1_base.dart b/lib/views/widgets/StatefulWidget/StatusTransitionWidget/node1_base.dart new file mode 100644 index 0000000..abfbd8d --- /dev/null +++ b/lib/views/widgets/StatefulWidget/StatusTransitionWidget/node1_base.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 233 StatusTransitionWidget 状态转变组件 +/// 抽象类,可以根据提供的动画器状态变化触发刷新。在 Flutter 框架层没有实现的子类,也没有使用的场景,感觉用处不是很大。 +/// +// { +// "widgetId": 233, +// "name": 'StatusTransitionWidget 介绍', +// "priority": 1, +// "subtitle": +// "【animation】 : 子组件 【Animation】\n" +// "这里自定义 ColorStatusTransitionWidget 进行使用,在动画器的状态改变时构建不同的颜色。", +// } + + +class StatusTransitionWidgetDemo extends StatefulWidget { + @override + _StatusTransitionWidgetDemoState createState() => _StatusTransitionWidgetDemoState(); +} + +class _StatusTransitionWidgetDemoState extends State + with SingleTickerProviderStateMixin { + AnimationController _ctrl; + + @override + void initState() { + super.initState(); + _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 1))..forward(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: (){ + _ctrl.forward(from: 0); + }, + child: ColorStatusTransitionWidget( + animation: _ctrl, + ), + ); + } +} + +class ColorStatusTransitionWidget extends StatusTransitionWidget { + final Animation animation; + + ColorStatusTransitionWidget({Key key, this.animation}) + : super(key: key, animation: animation); + + @override + Widget build(BuildContext context) { + Color color = Colors.blue; + switch (animation.status) { + case AnimationStatus.dismissed: + color = Colors.black; + break; + case AnimationStatus.forward: + color = Colors.blue; + break; + case AnimationStatus.reverse: + color = Colors.red; + break; + case AnimationStatus.completed: + color = Colors.green; + break; + } + + return Container( + alignment: Alignment.center, + width: 80, + height: 80, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + child: Text('${animation.status}'.split('.')[1],style: TextStyle(color: Colors.white),), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/TextButton/node1_base.dart b/lib/views/widgets/StatefulWidget/TextButton/node1_base.dart new file mode 100644 index 0000000..0a07887 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/TextButton/node1_base.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 353 TextButton Material风格的文字按钮,默认只有文字,点击时有水波纹。可通过样式更改边框、颜色、阴影等属性。 +// { +// "widgetId": 353, +// "name": 'TextButton基本使用', +// "priority": 1, +// "subtitle": +// "【child】 : 是否具有滚动主体 【Widget】\n" +// "【onPressed】 : 点击事件 【VoidCallback】\n" +// "【onLongPress】 : 长按事件 【VoidCallback】", +// } + +class TextButtonDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + height: 60, + child: Wrap( + spacing: 20, + children: [ + TextButton( + child: Text('TextButton 文字'), + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + TextButton( + child: Text('TextButton 禁用'), + onPressed: null, + onLongPress: null, + ), + ], + )); + } + + _onPressed() {} + + _onLongPress() {} +} diff --git a/lib/views/widgets/StatefulWidget/TextButton/node2_style.dart b/lib/views/widgets/StatefulWidget/TextButton/node2_style.dart new file mode 100644 index 0000000..8d3fe3f --- /dev/null +++ b/lib/views/widgets/StatefulWidget/TextButton/node2_style.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: +// { +// "widgetId": 353, +// "name": 'TextButton样式', +// "priority": 2, +// "subtitle": +// "【style】 : 按钮样式 【ButtonStyle】\n" +// "【focusNode】 : 焦点 【FocusNode】\n" +// "【clipBehavior】 : 裁剪行为 【Clip】\n" +// "【autofocus】 : 自动聚焦 【bool】", +// } + +class TextButtonStyleDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + child: Wrap( + spacing: 10, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.blue, + padding: EdgeInsets.symmetric(horizontal: 8), + primary: Colors.white, + elevation: 2, + shadowColor: Colors.orangeAccent), + child: Text('TextButton 样式'), + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.white, + primary: Colors.black, + side: BorderSide(color: Colors.blue,width: 1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)) + ), + // elevation: 2, + shadowColor: Colors.orangeAccent), + child: Text('TextButton 边线'), + autofocus: false, + onPressed: _onPressed, + onLongPress: _onLongPress, + ), + ], + ), + ); + } + + _onPressed() {} + + _onLongPress() {} +} diff --git a/lib/views/widgets/StatefulWidget/TweenAnimationBuilder/node1_base.dart b/lib/views/widgets/StatefulWidget/TweenAnimationBuilder/node1_base.dart new file mode 100644 index 0000000..38f8c8b --- /dev/null +++ b/lib/views/widgets/StatefulWidget/TweenAnimationBuilder/node1_base.dart @@ -0,0 +1,61 @@ + +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 226 TweenAnimationBuilder 渐变动画构造器 +/// 通过渐变器 Tween 对相关属性进行渐变动画,通过 builder 进行局部构建,减少刷新范围。不需要自定义动画器,可指定动画时长、曲线、结束回调。 +/// +// { +// "widgetId": 226, +// "name": 'TweenAnimationBuilder 使用案例', +// "priority": 1, +// "subtitle": +// "【tween】 : *渐变器 【Tween】\n" +// "【duration】 : *时长 【Duration】\n" +// "【builder】 : *构造器 【ValueWidgetBuilder】\n" +// "【curve】 : 动画曲线 【Curve】\n" +// "【onEnd】 : 结束回调 【VoidCallback】\n" +// "【child】 : 子组件 【Widget】", +// } + +class TweenAnimationBuilderDemo extends StatefulWidget { + @override + _TweenAnimationBuilderDemoState createState() => + _TweenAnimationBuilderDemoState(); +} + +class _TweenAnimationBuilderDemoState extends State { + Color _value = Colors.red; + + @override + Widget build(BuildContext context) { + return TweenAnimationBuilder( + tween: ColorTween(begin: Colors.blue, end: _value), + duration: Duration(milliseconds: 800), + builder: (BuildContext context, Color color, Widget child) { + return GestureDetector( + onTap: () { + setState(() { + _value = _value == Colors.red ? Colors.blue : Colors.red; + }); + }, + child: Container( + width: 40, + height: 40, + + child: child, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(5) + ), + ), + ); + }, + child: Icon( + Icons.android_outlined, + color: Colors.white, + ), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/UniqueWidget/node1_base.dart b/lib/views/widgets/StatefulWidget/UniqueWidget/node1_base.dart new file mode 100644 index 0000000..118ba45 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/UniqueWidget/node1_base.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 243 UniqueWidget 唯一组件 +/// 抽象类,必须提供一个 GlobalKey 进行身份标识,该类型组件只会 inflated 一个实例,同一时刻也只会有一个状态,可以通过 currentState 属性获取状态。 +/// +// { +// "widgetId": 243, +// "name": 'UniqueWidget 介绍', +// "priority": 1, +// "subtitle": +// "【child】 : 子组件 【Widget】", +// } + +class UniqueWidgetDemo extends StatelessWidget { + final String info = + '该类是抽象类,在 Flutter 框架层没有实现类,也没有其他源码使用到它,说明它基本上没啥用。' + '本质上它也非常简单,就是为组件添加一个 GlobalKey,在 Element#inflateWidget 时,会校验组件是否有 GlobalKey ,' + '如果有,则根据 key 找到之前的对应的 Element,就不会触发 Widget#createElement。为了方便获取 State,该类暴露 currentState 属性。' + '你瞄一下源码,就能看到这个组件是多么简单,简单到可以自己完成,以至于没什么大用。'; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/WidgetInspector/node1_base.dart b/lib/views/widgets/StatefulWidget/WidgetInspector/node1_base.dart new file mode 100644 index 0000000..100a495 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/WidgetInspector/node1_base.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/8/16 +/// contact me by email 1981462002@qq.com +/// 说明: 234 WidgetInspector 该组件可以让你很方便地查看子组件层级结构,是Flutter Inspector插件的功能之一。 +// { +// "widgetId": 234, +// "name": "WidgetInspector基本使用", +// "priority": 1, +// "subtitle": "【child】 : 子组件 【Widget】\n" +// "【selectButtonBuilder】: *选择按钮构造器 【InspectorSelectButtonBuilder】", +// } +class WidgetInspectorDemo extends StatelessWidget { + + @override + Widget build(BuildContext context) { + return Container( + height: 200, + child: WidgetInspector( + child: HomePage(), + selectButtonBuilder: _selectButtonBuilder, + ), + ); + } + + Widget _selectButtonBuilder(BuildContext context, onPressed) { + onPressed(); + return Container(); + } +} + +class HomePage extends StatefulWidget { + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + var _count = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + alignment: Alignment(0, 0.7), + child: Text( + '你点击了$_count次', + style: TextStyle(fontSize: 18, color: Colors.blue), + ), + ), + floatingActionButton: FloatingActionButton( + child: Icon(Icons.add), + onPressed: () { + setState(() { + _count++; + }); + }, + ), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/WidgetsApp/node1_base.dart b/lib/views/widgets/StatefulWidget/WidgetsApp/node1_base.dart new file mode 100644 index 0000000..3dee921 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/WidgetsApp/node1_base.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/8/16 +/// contact me by email 1981462002@qq.com +/// 说明: 236 WidgetsApp 集合一个应用程序需要的部件,如路由、语言、一些调试开关等。也是实现MaterialApp和CupertinoApp的核心组件。 +// { +// "widgetId": 236, +// "name": "WidgetsApp基本使用", +// "priority": 1, +// "subtitle": "【pageRouteBuilder】 : *路由构造器 【PageRouteFactory】\n" +// "【color】: *颜色 【Color】\n" +// "【debugShowWidgetInspector】: 是否显示z组件查看器 【bool】\n" +// "其他属性基本上同MaterialApp,详见之。", +// } +class WidgetsAppDemo extends StatefulWidget { + @override + _WidgetsAppDemoState createState() => _WidgetsAppDemoState(); +} + +class _WidgetsAppDemoState extends State { + var _debugShowCheckedModeBanner = false; + var _debugShowWidgetInspector = false; + var _showPerformanceOverlay = false; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildSwitchers(), + Container( + height: 250, + child: WidgetsApp( + color: Colors.blue, + debugShowCheckedModeBanner: _debugShowCheckedModeBanner, + showPerformanceOverlay: _showPerformanceOverlay, + debugShowWidgetInspector: _debugShowWidgetInspector, + pageRouteBuilder: + (RouteSettings settings, WidgetBuilder builder) { + return MaterialPageRoute(settings: settings, builder: builder); + }, + home: HomePage(), + ), + ), + ], + ); + } + + Widget _buildSwitchers() { + return DefaultTextStyle( + style: TextStyle(color: Colors.blue), + child: Wrap( + spacing: 10, + children: [ + Column( + children: [ + Switch( + value: _showPerformanceOverlay, + onChanged: (v) { + setState(() { + _showPerformanceOverlay = v; + }); + }, + ), + Text('性能浮层') + ], + ), + Column( + children: [ + Switch( + value: _debugShowCheckedModeBanner, + onChanged: (v) { + setState(() { + _debugShowCheckedModeBanner = v; + }); + }, + ), + Text('开启角标') + ], + ), + Column( + children: [ + Switch( + value: _debugShowWidgetInspector, + onChanged: (v) { + setState(() { + _debugShowWidgetInspector = v; + }); + }, + ), + Text('检查器') + ], + ) + ], + ), + ); + } +} + +class HomePage extends StatefulWidget { + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + var _count = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + alignment: Alignment(0, 0.7), + child: Text( + '你点击了$_count次', + style: TextStyle(fontSize: 18, color: Colors.blue), + ), + ), + floatingActionButton: FloatingActionButton( + child: Icon(Icons.add), + onPressed: () { + setState(() { + _count++; + }); + }, + ), + ); + } +} diff --git a/lib/views/widgets/StatelessWidget/BoxScrollView/node1_base.dart b/lib/views/widgets/StatelessWidget/BoxScrollView/node1_base.dart new file mode 100644 index 0000000..1170546 --- /dev/null +++ b/lib/views/widgets/StatelessWidget/BoxScrollView/node1_base.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 350 BoxScrollView 盒滑动视图 +/// BoxScrollView 类是一个继承自 ScrollView 的抽象类,所以无法直接使用,它的子类有 ListView、GridView。一般不自己实现子类使用它。 +/// link 183,162,163 +/// +// { +// "widgetId": 350, +// "name": 'BoxScrollView 介绍', +// "priority": 1, +// "subtitle": +// "【reverse】 : 是否反向 【bool】\n" +// "【scrollDirection】 : 滑动方向 【Axis】\n" +// "【cacheExtent】 : 缓存长 【double】\n" +// "【dragStartBehavior】 : 拖动行为 【DragStartBehavior】\n" +// "【clipBehavior】 : 裁剪行为 【ClipBehavior】\n" +// "【controller】 : 控制器 【ScrollController】", +// } + +class BoxScrollViewDemo extends StatelessWidget { + + final String info = + 'BoxScrollView 是 ScrollView 的子类,实现了它的抽象方法,且暴露出另一个抽象方法 buildChildLayout,返回 Sliver 家族 Widget,' + '其子类有 ListView 和 GridView,分别使用 Sliver 家族相关List、Gird列表组件实现的。'; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: Column( + children: [ + Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ), + Expanded(child: MyBoxScrollView()), + ], + ), + ); + } +} + +class MyBoxScrollView extends BoxScrollView { + + final List data = [ + Colors.purple[50], + Colors.purple[100], + Colors.purple[200], + Colors.purple[300], + Colors.purple[400], + Colors.purple[500], + Colors.purple[600], + Colors.purple[700], + Colors.purple[800], + Colors.purple[900], + ]; + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + + @override + Widget buildChildLayout(BuildContext context)=> SliverFixedExtentList( + itemExtent: 60, + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 50, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); +} diff --git a/lib/views/widgets/StatelessWidget/CheckedModeBanner/node1_base.dart b/lib/views/widgets/StatelessWidget/CheckedModeBanner/node1_base.dart new file mode 100644 index 0000000..c42a5c8 --- /dev/null +++ b/lib/views/widgets/StatelessWidget/CheckedModeBanner/node1_base.dart @@ -0,0 +1,31 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 215 CheckedModeBanner 仅在debug运行模式中显示右上角角标,没什么太大卵用。在 MaterialApp 组件源码中有使用场景。 +// { +// "widgetId": 215, +// "name": 'CheckedModeBanner基本使用', +// "priority": 1, +// "subtitle": +// "【child】 : 组件 【Widget】", +// } + +class CheckedModeBannerDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return CheckedModeBanner( + child: Container( + alignment: Alignment.center, + width: 250, + height: 150, + color: Theme.of(context).primaryColor, + child: Text( + "CheckedModeBanner", + style: TextStyle(color: Colors.white, fontSize: 20), + ), + ), + ); + } +} diff --git a/lib/views/widgets/StatelessWidget/CupertinoPopupSurface/node1_base.dart b/lib/views/widgets/StatelessWidget/CupertinoPopupSurface/node1_base.dart new file mode 100644 index 0000000..0806d45 --- /dev/null +++ b/lib/views/widgets/StatelessWidget/CupertinoPopupSurface/node1_base.dart @@ -0,0 +1,62 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 217 CupertinoPopupSurface 模糊弹出层 +/// ios 弹出框的圆角矩形模糊背景,源码中应用于 Cupertino 风格的对话框中。 +/// +// { +// "widgetId": 217, +// "name": 'CupertinoPopupSurface 使用', +// "priority": 1, +// "subtitle": +// "【isSurfacePainted】 : 是否绘白 【bool】\n" +// "【child】 : 子组件 【Widget】\n" +// "测试效果左侧 isSurfacePainted = false,右侧 isSurfacePainted = true", +// } + +class CupertinoPopupSurfaceDemo extends StatelessWidget { + final List rainbow = [ + 0xffff0000, + 0xffFF7F00, + 0xffFFFF00, + 0xff00FF00, + 0xff00FFFF, + 0xff0000FF, + 0xff8B00FF + ]; + + final List stops = [0.0, 1 / 6, 2 / 6, 3 / 6, 4 / 6, 5 / 6, 1.0]; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + gradient: RadialGradient( + radius: 1.8, + stops: stops, + colors: rainbow.map((e) => Color(e)).toList())), + padding: EdgeInsets.all(10), + child: Wrap( + spacing: 10, + children: [ + buildCupertinoPopupSurface(false), + buildCupertinoPopupSurface(true), + ], + ), + ); + } + + Widget buildCupertinoPopupSurface(bool isSurfacePainted) { + return CupertinoPopupSurface( + isSurfacePainted: isSurfacePainted, + child: Container( + width: 150, + height: 100, + color: Colors.white.withOpacity(0.3), + alignment: Alignment.center, + ), + ); + } +} diff --git a/lib/views/widgets/StatelessWidget/DraggableScrollableActuator/node1_base.dart b/lib/views/widgets/StatelessWidget/DraggableScrollableActuator/node1_base.dart new file mode 100644 index 0000000..710ea96 --- /dev/null +++ b/lib/views/widgets/StatelessWidget/DraggableScrollableActuator/node1_base.dart @@ -0,0 +1,117 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 221 DraggableScrollableActuator 拖滑重置器 它可以通知后代的 DraggableScrollableSheet,将其位置重置为初始状态。 +// { +// "widgetId": 221, +// "name": '基本使用方法', +// "priority": 1, +// "subtitle": +// "【child】 : 子组件 【Widget】\n" +// "使用 DraggableScrollableActuator.reset(context) 重置后代 DraggableScrollableSheet 位初始位置。", +// } + +class DraggableScrollableActuatorDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(10), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DraggableScrollableActuatorPage()), + ); + }, + child: Text("进入 DraggableScrollableActuator 测试页"), + ), + ); + } +} + +class DraggableScrollableActuatorPage extends StatelessWidget { + final List data = [ + Colors.orange[50], + Colors.orange[100], + Colors.orange[200], + Colors.orange[300], + Colors.orange[400], + Colors.orange[500], + Colors.orange[600], + Colors.orange[700], + Colors.orange[800], + Colors.orange[900], + Colors.red[50], + Colors.red[100], + Colors.red[200], + Colors.red[300], + Colors.red[400], + Colors.red[500], + Colors.red[600], + Colors.red[700], + Colors.red[800], + Colors.red[900], + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("DraggableScrollableActuator"), + ), + body: Container( + // height: 400, + child: DraggableScrollableActuator( + child: Builder( + builder: (ctx) => Column( + children: [ + ElevatedButton( + onPressed: () { + DraggableScrollableActuator.reset(ctx); + }, + child: Text("重置位置"), + ), + Expanded( + child: buildSheet(), + ), + ], + ), + ), + ), + ), + ); + } + + Widget buildSheet() => DraggableScrollableSheet( + initialChildSize: 0.3, + minChildSize: 0.2, + maxChildSize: 1, + expand: true, + builder: (BuildContext context, ScrollController scrollController) => + ListView.builder( + controller: scrollController, + itemCount: data.length, + itemBuilder: buildColorItem, + ), + ); + + Widget buildColorItem(BuildContext context, int index) { + return Container( + alignment: Alignment.center, + height: 60, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) + ]), + ), + ); + } + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +} diff --git a/lib/views/widgets/StatelessWidget/ModalBarrier/node1_base.dart b/lib/views/widgets/StatelessWidget/ModalBarrier/node1_base.dart new file mode 100644 index 0000000..8f5f2cd --- /dev/null +++ b/lib/views/widgets/StatelessWidget/ModalBarrier/node1_base.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020-04-01 +/// contact me by email 1981462002@qq.com +/// 说明: 212 ModalBarrier 屏障模 +/// 相当于一块幕布,防止用户与其背后的 Widget 交互,可以通过 dismissible 决定点击时,是否触发返回栈。源码中用于 Dialog 相关组件。 +/// link: 227,126,127,128 +// { +// "widgetId": 212, +// "name": 'ModalBarrier 介绍', +// "priority": 1, +// "subtitle": +// "【dismissible】 : 点击是否返回 【bool】\n" +// "【color】 : 颜色 【Color】", +// } +class ModalBarrierDemo extends StatelessWidget { + + @override + Widget build(BuildContext context) { + return Container( + width: 200, + height: 100, + child: Stack(alignment: Alignment.center, children: [ + ModalBarrier( + dismissible: true, + color: Colors.grey.withOpacity(0.3), + ), + Text('点击背景返回') + ]), + ); + } +} diff --git a/lib/views/widgets/StatelessWidget/NotificationListener/node1_base.dart b/lib/views/widgets/StatelessWidget/NotificationListener/node1_base.dart new file mode 100644 index 0000000..87e932b --- /dev/null +++ b/lib/views/widgets/StatelessWidget/NotificationListener/node1_base.dart @@ -0,0 +1,65 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/8/14 +/// contact me by email 1981462002@qq.com +/// 说明: NotificationListener 220 0 通知监听器 可指定Notification子泛型T,监听该类型的变化。Flutter内置很多滑动的Notification,当然你也可以自定义Notification进行监听。 +// { +// "widgetId": 220, +// "name": "监听OverscrollIndicatorNotification", +// "priority": 1, +// "subtitle": "该通知之后在滑动到最顶和最底是回调,通过leading属性判断是顶部还是底部。另外通过notification#disallowGlow(),可以去除顶底滑动蓝色阴影", +// } + +class NotificationListenerDemo extends StatefulWidget { + @override + _NotificationListenerDemoState createState() => _NotificationListenerDemoState(); +} + +class _NotificationListenerDemoState extends State { + final data = List.generate(30, (i) => '第${i + 1}条'); + + @override + Widget build(BuildContext context) { + return Container( + height: 250, + child: NotificationListener( + onNotification: _onNotification, + child: CupertinoScrollbar( + child: ListView.separated( + itemBuilder: _buildItem, + itemCount: data.length, + separatorBuilder: (_,__)=>Divider(height: 5,), + ), + )), + ); + } + + bool _onNotification(OverscrollIndicatorNotification notification) { + if (notification.leading) { + notification.disallowGlow(); + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('已滑到顶部'), + backgroundColor: Colors.blue, + duration: Duration(milliseconds: 200), + )); + } else { + notification.disallowGlow(); + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('已滑到底部'), + duration: Duration(milliseconds: 200), + backgroundColor: Colors.blue, + )); + } + + return true; + } + + Widget _buildItem(BuildContext context, int index) { + return Container( + height: 50, + alignment: Alignment.center, + child: Text(data[index],style: TextStyle(color: Theme.of(context).primaryColor,fontSize: 18),), + ); + } +} diff --git a/lib/views/widgets/StatelessWidget/NotificationListener/node2_update.dart b/lib/views/widgets/StatelessWidget/NotificationListener/node2_update.dart new file mode 100644 index 0000000..40e0fa8 --- /dev/null +++ b/lib/views/widgets/StatelessWidget/NotificationListener/node2_update.dart @@ -0,0 +1,87 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/8/14 +/// contact me by email 1981462002@qq.com +/// 说明: NotificationListener 210 0 通知监听器 可指定Notification子泛型T,监听该类型的变化。Flutter内置很多滑动的Notification,当然你也可以自定义Notification进行监听。 +// { +// "widgetId": 220, +// "name": "监听ScrollUpdateNotification", +// "priority": 2, +// "subtitle": "在滑动过程中对滑动数据进行回调,你可以获取大量数据来进行操作。", +// } + +class NotificationListenerUpdate extends StatefulWidget { + @override + _NotificationListenerUpdateState createState() => + _NotificationListenerUpdateState(); +} + +class _NotificationListenerUpdateState + extends State { + final data = List.generate(30, (i) => '第${i + 1}条'); + + var _info = ''; + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: Stack( + children: [ + Positioned(child: Padding( + padding: const EdgeInsets.only(left:8.0), + child: Text(_info), + )), + NotificationListener( + onNotification: _onNotification, + child: CupertinoScrollbar( + child: ListView.separated( + itemBuilder: _buildItem, + itemCount: data.length, + separatorBuilder: (_, __) => Divider( + height: 5, + ), + ), + )), + + ], + ), + ); + } + + bool _onNotification(ScrollUpdateNotification notification) { + + setState(() { + _info = 'axis------【${notification.metrics.axis}】------\n' + 'pixels------【${notification.metrics.pixels}】------\n' + 'atEdge------【${notification.metrics.atEdge}】------\n' + 'axisDirection------【${notification.metrics.axisDirection}】------\n' + 'extentInside------【${notification.metrics.extentInside}】------\n' + 'outOfRange------【${notification.metrics.outOfRange}】------\n' + 'minScrollExtent------【${notification.metrics.minScrollExtent}】------\n' + 'maxScrollExtent------【${notification.metrics.maxScrollExtent}】------\n' + 'viewportDimension------【${notification.metrics.viewportDimension}】------\n' + 'delta------【${notification.dragDetails?.delta}】------\n' + 'globalPosition------【${notification.dragDetails?.globalPosition}】------\n' + 'localPosition------【${notification.dragDetails?.localPosition}】------\n' + 'scrollDelta------【${notification.scrollDelta}】------\n' + 'depth------【${notification.depth}】------'; + + }); + + return true; + } + + Widget _buildItem(BuildContext context, int index) { + return Container( + height: 50, + alignment: Alignment.centerRight, + padding: EdgeInsets.only(right: 8), + child: Text( + data[index], + style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 18), + ), + ); + } +} diff --git a/lib/views/widgets/StatelessWidget/PageStorage/node1_base.dart b/lib/views/widgets/StatelessWidget/PageStorage/node1_base.dart new file mode 100644 index 0000000..09e4931 --- /dev/null +++ b/lib/views/widgets/StatelessWidget/PageStorage/node1_base.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/8/14 +/// contact me by email 1981462002@qq.com +/// 说明: PageStorage 210 0 页面存储器 可以将页面状态进行存储,在切页时可以保持状态。源码中在ScrollView、PageView、ExpansionTile等皆有应用。 +// { +// "widgetId": 210, +// "name": "PageStorage基本使用", +// "priority": 1, +// "subtitle": "【bucket】 : 存储区 【PageStorageBucket】\n" +// "【child】: 子组件 【Widget】\n" +// "上面切换界面初始化组件时并不会将状态重置。如上CountWidget,子组件需要在初始化时从存储器中读取状态,在改变状态时将状态量写入存储器。另外,如果使用MaterialApp已经内置了PageStorage,不过你也可以创建PageStorage。", +// } + +class PageStorageDemo extends StatefulWidget { + @override + _PageStorageDemoState createState() => _PageStorageDemoState(); +} + +class _PageStorageDemoState extends State { + int _pageIndex = 0; + final PageStorageBucket _bucket = PageStorageBucket(); + + @override + Widget build(BuildContext context) { + return Container( + height: 200, + child: Scaffold( + body: PageStorage( + child: _buildContentByIndex(), + bucket: _bucket, + ), + bottomNavigationBar: BottomNavigationBar( + elevation: 0, + backgroundColor: Colors.blueAccent.withAlpha(55), + currentIndex: _pageIndex, + onTap: (int index) { + setState(() { + _pageIndex = index; + }); + }, + items: [ + BottomNavigationBarItem( + icon: Icon(Icons.home), + title: Text('Home'), + ), + BottomNavigationBarItem( + icon: Icon(Icons.settings), + title: Text('Setting'), + ), + ], + ), + ), + ); + } + + Widget _buildContentByIndex() { + if (_pageIndex == 0) { + return CountWidget(key: PageStorageKey('CountWidget1')); + } + + if (_pageIndex == 1) { + return CountWidget(key: PageStorageKey('CountWidget2')); + } + + return ListView(); + } +} + +class CountWidget extends StatefulWidget { + CountWidget({Key key}) : super(key: key); + + @override + _CountWidgetState createState() => _CountWidgetState(); +} + +class _CountWidgetState extends State { + int _count = 0; + + @override + void initState() { + super.initState(); + _count = PageStorage.of(context)?.readState(context) as int ?? 0; + } + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('点击了$_count次'), + MaterialButton( + onPressed: _addCount, + child: Icon( + Icons.add, + color: Colors.white, + ), + color: Colors.green, + shape: CircleBorder( + side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)), + )) + ], + ), + ); + } + + void _addCount() { + setState(() { + _count++; + PageStorage.of(context)?.writeState(context, _count); + }); + } +} diff --git a/lib/views/widgets/StatelessWidget/SafeArea/node1_base.dart b/lib/views/widgets/StatelessWidget/SafeArea/node1_base.dart new file mode 100644 index 0000000..3702067 --- /dev/null +++ b/lib/views/widgets/StatelessWidget/SafeArea/node1_base.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 207 SafeArea 安全区 通过添加内边距,来适配一些手机本身特殊性(圆角、刘海屏等)而所造成的布局问题。 +// { +// "widgetId": 207, +// "name": 'SafeArea 使用测试', +// "priority": 1, +// "subtitle": +// "【left】 : 左侧是否启用 【bool】\n" +// "【top】 : 上方是否启用 【bool】\n" +// "【bottom】 : 下方是否启用 【bool】\n" +// "【right】 : 右侧是否启用 【bool】\n" +// "【child】 : 子组件 【Widget】", +// } + +class SafeAreaDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(10), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => SafeAreaPage()), + ); + }, + child: Text("进入 SafeArea 测试页"), + ), + ); + } +} + +class SafeAreaPage extends StatefulWidget { + @override + _SafeAreaPageState createState() => _SafeAreaPageState(); +} + +class _SafeAreaPageState extends State { + bool _top = true; + bool _left = true; + bool _right = true; + bool _bottom = true; + + @override + Widget build(BuildContext context) { + return SafeArea( + top: _top, + left: _left, + right: _right, + bottom: _bottom, + child: Scaffold( + appBar: AppBar( + title: Text( + 'SafeArea 测试', + ), + ), + body: Column( + children: [ + ..._buildSlider(), + Expanded( + child: ListView.separated( + itemCount: 20, + separatorBuilder: (_, __) => Divider( + height: 1, + ), + itemBuilder: (_, index) => Container( + color: Colors.blue, + // padding: EdgeInsets.only(left: 20), + alignment: Alignment.center, + height: 50, + child: Text( + "第$index个", + style: TextStyle(fontSize: 24, color: Colors.white), + ), + ), + ), + ), + ], + ), + ), + ); + } + + List _buildSlider()=>[Row( + children: [ + Switch( + value: _top, + onChanged: (v) => setState(() => _top = v), + ), + Text("top: $_top") + ], + ), + Row( + children: [ + Switch( + value: _left, + onChanged: (v) => setState(() => _left = v), + ), + Text("left: $_left") + ], + ), + Row( + children: [ + Switch( + value: _right, + onChanged: (v) => setState(() => _right = v), + ), + Text("right: $_right") + ], + ), + Row( + children: [ + Switch( + value: _bottom, + onChanged: (v) => setState(() => _bottom = v), + ), + Text("bottom: $_bottom") + ], + ),]; +} diff --git a/lib/views/widgets/StatelessWidget/ScrollView/node1_base.dart b/lib/views/widgets/StatelessWidget/ScrollView/node1_base.dart new file mode 100644 index 0000000..09e8ed6 --- /dev/null +++ b/lib/views/widgets/StatelessWidget/ScrollView/node1_base.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/9/21 +/// contact me by email 1981462002@qq.com +/// 说明: 349 ScrollView 滑动视图 +/// 该组件用于滑动的支持,该类是一个抽象类,所以无法直接使用,它有很多实现类,如 CustomScrollView、BoxScrollView、ListView、GridView。 +/// link 183,162,163,253,340 +/// +// { +// "widgetId": 349, +// "name": 'ScrollView 介绍', +// "priority": 1, +// "subtitle": +// "【reverse】 : 是否反向 【bool】\n" +// "【scrollDirection】 : 滑动方向 【Axis】\n" +// "【cacheExtent】 : 缓存长 【double】\n" +// "【dragStartBehavior】 : 拖动行为 【DragStartBehavior】\n" +// "【clipBehavior】 : 裁剪行为 【ClipBehavior】\n" +// "【controller】 : 控制器 【ScrollController】", +// } + +class ScrollViewDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: MyScrollView(), + ); + } +} + +class MyScrollView extends ScrollView { + final String info = + 'ScrollView 其内部依靠 Viewport + Scrollable 实现滑动。它只有一个 buildSlivers 的抽象方法,返回 Sliver 家族 Widget 列表,' + '其子类最简单的是 CustomScrollView,将 slivers 交由用户传递,自身打个酱油。' + 'ListView 和 GridView 在底层源码中也是使用 Sliver 家族相关组件实现的。'; + + final List data = [ + Colors.purple[50], + Colors.purple[100], + Colors.purple[200], + Colors.purple[300], + Colors.purple[400], + Colors.purple[500], + Colors.purple[600], + Colors.purple[700], + Colors.purple[800], + Colors.purple[900], + ]; + + @override + List buildSlivers(BuildContext context) { + return [ + _buildSliverAppBar(), + SliverToBoxAdapter( + child: Container( + color: Colors.blue.withOpacity(0.1), + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + child: Text(info), + ), + ), + _buildSliverFixedExtentList() + ]; + } + + _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 190.0, + leading: Container( + margin: EdgeInsets.all(10), + child: Image.asset('assets/images/icon_head.webp')), + flexibleSpace: FlexibleSpaceBar( + //伸展处布局 + titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距 + collapseMode: CollapseMode.parallax, //视差效果 + title: Text( + '张风捷特烈', + style: TextStyle(color: Colors.black, //标题 + shadows: [ + Shadow(color: Colors.blue, offset: Offset(1, 1), blurRadius: 2) + ]), + ), + background: Image.asset( + "assets/images/caver.webp", + fit: BoxFit.cover, + ), + ), + ); + } + + Widget _buildSliverFixedExtentList() => SliverFixedExtentList( + itemExtent: 60, + delegate: SliverChildBuilderDelegate( + (_, int index) => Container( + alignment: Alignment.center, + width: 100, + height: 50, + color: data[index], + child: Text( + colorString(data[index]), + style: TextStyle(color: Colors.white, shadows: [ + Shadow( + color: Colors.black, + offset: Offset(.5, .5), + blurRadius: 2) + ]), + ), + ), + childCount: data.length), + ); + + String colorString(Color color) => + "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; +}