Commit 99850947 authored by 汪林玲's avatar 汪林玲

Initial commit

parents
.DS_Store
.dart_tool/
.packages
.pub/
build/
ios/.generated/
ios/Flutter/Generated.xcconfig
ios/Runner/GeneratedPluginRegistrant.*
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 16f7d4016ef4f2989fc15444f1635035d121af94
channel: master
project_type: package
## 2.0.1
* Fix issue that list sync issue is not fixed #40
## 2.0.0
* Breaking change: add clipBehavior and restorationId
* Merge flutter/issues/29264(SliverAppBar's flexibleSpace glitch on iOS when NestedScrollView's body is a ScrollView)
## 1.0.1
* Merge flutter/flutter#59187(Support floating the header slivers of a NestedScrollView)
## 1.0.0
* Merge code from 1.17.0
* Fix analysis_options
## 0.4.1
* add demo to show how to change pinned header height dynamically.
## 0.4.0
* web support
## 0.3.8
* add NestedScrollViewState key to get currentInnerPosition/innerScrollPositions instead of ScrollController
* due to we can't set ScrollController for list in NestedScrollView's body(it will breaking behaviours of InnerScrollController in NestedScrollView), provide demos('PullToRefresh','LoadMore' and 'ScrollToTop') to show how to do it without ScrollController
## 0.3.6
* fix api error base on Flutter SDK v1.7.8+hotfix.2
## 0.3.5
* New ExtendedNestedScrollView still has some issues in special layout, make it as obsolete for now till find a better solution
## 0.3.3
* fix issue that Caught error: type 'Future<void>' is not a subtype of type 'Future<Null>'
for old extended_nested_scroll_view
## 0.2.9
* fix issue 0.2.5 for old extended_nested_scroll_view
## 0.2.7
* fix issue for quick change page
* handle unavailable page change(no actived nested positions in it)
## 0.2.5
* fix issue that ut postion is not overscroll actually,it get minimal value
and will scroll inner positions
igore minimal value here(value like following data)
/// I/flutter (14963): 5.684341886080802e-14
/// I/flutter (14963): -5.684341886080802e-14
if (innerDelta != 0.0 && innerDelta.abs() > 0.0001) {
for (_NestedScrollPosition position in _activedInnerPositions) {
position.applyFullDragUpdate(innerDelta);
}
}
## 0.2.0
* update new extended_nested_scroll_view demo
## 0.1.9
* set keepOnlyOneInnerNestedScrollPositionActive default value: false
## 0.1.8
* update new ExtendedNestedScrollView readme
## 0.1.7
* add assert for keepOnlyOneInnerNestedScrollPositionActive
///when ExtendedNestedScrollView body has TabBarView/PageView and children have
///AutomaticKeepAliveClientMixin or PageStorageKey,
///_innerController.nestedPositions will have more one,
///when you scroll, it will scroll all of nestedPositions
///set keepOnlyOneInnerNestedScrollPositionActive true to avoid it.
///notice: only for Axis.horizontal TabBarView/PageView and
///scrollDirection must be Axis.vertical.
assert(!(widget.keepOnlyOneInnerNestedScrollPositionActive && widget.scrollDirection == Axis.horizontal));
## 0.1.6
* fix issue: Actived _NestedScrollPosition is not right for multiple
## 0.1.5
* add new ExtendedNestedScrollView to slove issue more smartly.
## 0.1.4
* Update readme.
## 0.1.3
* Update demo.
## 0.1.2
* Remove unused method.
## 0.1.1
* Update demo.
## 0.1.0
* Upgrade Some Commments.
## 0.0.1
* Initial Open Source release.
MIT License
Copyright (c) 2018 zmtzawqlp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
\ No newline at end of file
# extended_nested_scroll_view
[![pub package](https://img.shields.io/pub/v/extended_nested_scroll_view.svg)](https://pub.dartlang.org/packages/extended_nested_scroll_view) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_nested_scroll_view)](https://github.com/fluttercandies/extended_nested_scroll_view/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_nested_scroll_view)](https://github.com/fluttercandies/extended_nested_scroll_view/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_nested_scroll_view)](https://github.com/fluttercandies/extended_nested_scroll_view/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_nested_scroll_view)](https://github.com/fluttercandies/extended_nested_scroll_view/issues) <a target="_blank" href="https://jq.qq.com/?_wv=1027&k=5bcc0gy"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="flutter-candies" title="flutter-candies"></a>
文档语言: [English](README.md) | [中文简体](README-ZH.md)
扩展NestedScrollView来修复了下面的问题
1.[pinned的Header的问题](https://github.com/flutter/flutter/issues/22393)
2.[body里面TabView列表滚动同步,互相影响的问题](https://github.com/flutter/flutter/issues/21868)
3.下拉刷新不能工作
4.在NestedScrollView的body中不通过设置ScrollController(设置了会跟内部Controller冲突)来完成下拉刷新,增量加载,滚动到顶部
[掘金](https://juejin.im/post/5bea43ade51d45544844010a)
[Web demo for ExtendedNestedScrollView](https://fluttercandies.github.io/extended_nested_scroll_view/)
- [extended_nested_scroll_view](#extendednestedscrollview)
- [Example for issue 1](#example-for-issue-1)
- [Example for issue 2](#example-for-issue-2)
- [步骤1](#%e6%ad%a5%e9%aa%a41)
- [步骤2](#%e6%ad%a5%e9%aa%a42)
- [Example for NestedScrollView pull to refresh](#example-for-nestedscrollview-pull-to-refresh)
- [Do without ScrollController in NestedScrollView's body](#do-without-scrollcontroller-in-nestedscrollviews-body)
# Example for issue 1
在pinnedHeaderSliverHeightBuilder回调中设置全部pinned的header的高度,
demo里面高度为 状态栏高度+SliverAppbar的高度
``` dart
var tabBarHeight = primaryTabBar.preferredSize.height;
var pinnedHeaderHeight =
//statusBar height
statusBarHeight +
//pinned SliverAppBar height in header
kToolbarHeight;
return NestedScrollView(
pinnedHeaderSliverHeightBuilder: () {
return pinnedHeaderHeight;
},
```
# Example for issue 2
## 步骤1
TabbarView里面的列表,使用NestedScrollViewInnerScrollPositionKeyWidget包住,并且设置唯一key,
这个key跟列表是第几个tab有关系。
``` dart
return extended.NestedScrollViewInnerScrollPositionKeyWidget(
widget.tabKey,
ListView.builder(
itemBuilder: (c, i) {
return Container(
alignment: Alignment.center,
height: 60.0,
child: Text(widget.tabKey.toString() + ": List$i"),
);
},
itemCount: 100)
);
```
## 步骤2
innerScrollPositionKeyBuilder回调中给出当前tab的key. 这个key应该跟第一步中相同
``` dart
extended.NestedScrollView(
innerScrollPositionKeyBuilder: () {
var index = "Tab";
if (primaryTC.index == 0) {
index +=
(primaryTC.index.toString() + secondaryTC.index.toString());
} else {
index += primaryTC.index.toString();
}
return Key(index);
},
```
# Example for NestedScrollView pull to refresh
NestedScrollViewRefreshIndicator is as the same as Flutter RefreshIndicator.
``` dart
NestedScrollViewRefreshIndicator(
onRefresh: onRefresh,
child: extended.NestedScrollView(
headerSliverBuilder: (c, f) {
return _buildSliverHeader(primaryTabBar);
},
```
[建议使用这个来做NestedScrollView的整体下拉刷新](https://github.com/fluttercandies/loading_more_list/blob/master/example/lib/demo/nested_scroll_view_demo.dart)
Please see the example app of this for a full example.
# Do without ScrollController in NestedScrollView's body
因为无法给NestedScrollView的body中的列表设置ScrollController(这样会破坏NestedScrollView内部的InnerScrollController的行为),所以我这里给大家提供了Demos来展示怎么不通过ScrollController来完成
* [下拉刷新](https://github.com/fluttercandies/extended_nested_scroll_view/tree/master/example/lib/pages/pull_to_refresh.dart),
* [增量加载](https://github.com/fluttercandies/extended_nested_scroll_view/tree/master/example/lib/pages/load_more.dart)
* [滚动到顶部](https://github.com/fluttercandies/extended_nested_scroll_view/tree/master/example/lib/pages/scroll_to_top.dart)
* [动态改变PinnedHeaderHeight](https://github.com/fluttercandies/extended_nested_scroll_view/tree/master/example/lib/pages/dynamic_pinned_header_height.dart)
# ☕️Buy me a coffee
![img](http://zmtzawqlp.gitee.io/my_images/images/qrcode.png)
# extended_nested_scroll_view
[![pub package](https://img.shields.io/pub/v/extended_nested_scroll_view.svg)](https://pub.dartlang.org/packages/extended_nested_scroll_view) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_nested_scroll_view)](https://github.com/fluttercandies/extended_nested_scroll_view/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_nested_scroll_view)](https://github.com/fluttercandies/extended_nested_scroll_view/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_nested_scroll_view)](https://github.com/fluttercandies/extended_nested_scroll_view/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_nested_scroll_view)](https://github.com/fluttercandies/extended_nested_scroll_view/issues) <a target="_blank" href="https://jq.qq.com/?_wv=1027&k=5bcc0gy"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="flutter-candies" title="flutter-candies"></a>
Language: [English](README.md) | [中文简体](README-ZH.md)
NestedScrollView: extended nested scroll view to fix following issues.
1.[pinned sliver header issue](https://github.com/flutter/flutter/issues/22393)
2.[inner scrollables in tabview sync issue](https://github.com/flutter/flutter/issues/21868)
3.pull to refresh is not work.
4.do without ScrollController in NestedScrollView's body
[Web demo for ExtendedNestedScrollView](https://fluttercandies.github.io/extended_nested_scroll_view/)
- [extended_nested_scroll_view](#extended_nested_scroll_view)
- [Example for issue 1](#example-for-issue-1)
- [Example for issue 2](#example-for-issue-2)
- [Step1](#step1)
- [Step2](#step2)
- [Example for NestedScrollView pull to refresh](#example-for-nestedscrollview-pull-to-refresh)
- [Do without ScrollController in NestedScrollView's body](#do-without-scrollcontroller-in-nestedscrollviews-body)
# Example for issue 1
give total height of pinned sliver headers in pinnedHeaderSliverHeightBuilder callback
``` dart
var tabBarHeight = primaryTabBar.preferredSize.height;
var pinnedHeaderHeight =
//statusBar height
statusBarHeight +
//pinned SliverAppBar height in header
kToolbarHeight;
return NestedScrollView(
pinnedHeaderSliverHeightBuilder: () {
return pinnedHeaderHeight;
},
```
# Example for issue 2
## Step1
Put your list which in tabview into NestedScrollViewInnerScrollPositionKeyWidget,and get unique a key
``` dart
return extended.NestedScrollViewInnerScrollPositionKeyWidget(
widget.tabKey,
ListView.builder(
itemBuilder: (c, i) {
return Container(
alignment: Alignment.center,
height: 60.0,
child: Text(widget.tabKey.toString() + ": List$i"),
);
},
itemCount: 100)
);
```
## Step2
get current tab key in innerScrollPositionKeyBuilder callback. this key should as same as in step 1 given.
``` dart
extended.NestedScrollView(
innerScrollPositionKeyBuilder: () {
var index = "Tab";
if (primaryTC.index == 0) {
index +=
(primaryTC.index.toString() + secondaryTC.index.toString());
} else {
index += primaryTC.index.toString();
}
return Key(index);
},
```
# Example for NestedScrollView pull to refresh
NestedScrollViewRefreshIndicator is as the same as Flutter RefreshIndicator.
``` dart
NestedScrollViewRefreshIndicator(
onRefresh: onRefresh,
child: extended.NestedScrollView(
headerSliverBuilder: (c, f) {
return _buildSliverHeader(primaryTabBar);
},
```
[Better one to pull to refresh](https://github.com/fluttercandies/loading_more_list/blob/master/example/lib/demo/nested_scroll_view_demo.dart)
Please see the example app of this for a full example.
# Do without ScrollController in NestedScrollView's body
* due to we can't set ScrollController for list in NestedScrollView's body(it will breaking behaviours of InnerScrollController in NestedScrollView),provide Demos
* [pull to refresh](https://github.com/fluttercandies/extended_nested_scroll_view/tree/master/example/lib/pages/pull_to_refresh.dart),
* [load more](https://github.com/fluttercandies/extended_nested_scroll_view/tree/master/example/lib/pages/load_more.dart)
* [scroll to top](https://github.com/fluttercandies/extended_nested_scroll_view/tree/master/example/lib/pages/scroll_to_top.dart)
to show how to do it without ScrollController
* [pinned header height](https://github.com/fluttercandies/extended_nested_scroll_view/tree/master/example/lib/pages/dynamic_pinned_header_height.dart)
to show how to change pinned header height dynamically.
# Specify analysis options.
#
# Until there are meta linter rules, each desired lint must be explicitly enabled.
# See: https://github.com/dart-lang/linter/issues/288
#
# For a list of lints, see: http://dart-lang.github.io/linter/lints/
# See the configuration guide for more
# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer
#
# There are other similar analysis options files in the flutter repos,
# which should be kept in sync with this file:
#
# - analysis_options.yaml (this file)
# - packages/flutter/lib/analysis_options_user.yaml
# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml
# - https://github.com/flutter/engine/blob/master/analysis_options.yaml
#
# This file contains the analysis options used by Flutter tools, such as IntelliJ,
# Android Studio, and the `flutter analyze` command.
analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
errors:
# treat missing required parameters as a warning (not a hint)
missing_required_param: warning
# treat missing returns as a warning (not a hint)
missing_return: warning
# allow having TODOs in the code
todo: ignore
# Ignore analyzer hints for updating pubspecs when using Future or
# Stream and not importing dart:async
# Please see https://github.com/flutter/flutter/pull/24528 for details.
sdk_version_async_exported_from_core: ignore
# exclude:
# - "bin/cache/**"
# # the following two are relative to the stocks example and the flutter package respectively
# # see https://github.com/dart-lang/sdk/issues/28463
# - "lib/i18n/messages_*.dart"
# - "lib/src/http/**"
linter:
rules:
# these rules are documented on and in the same order as
# the Dart Lint rules page to make maintenance easier
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
- always_declare_return_types
- always_put_control_body_on_new_line
# - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
- always_require_non_null_named_parameters
- always_specify_types
- annotate_overrides
# - avoid_annotating_with_dynamic # conflicts with always_specify_types
# - avoid_as # required for implicit-casts: true
- avoid_bool_literals_in_conditional_expressions
# - avoid_catches_without_on_clauses # we do this commonly
# - avoid_catching_errors # we do this commonly
- avoid_classes_with_only_static_members
# - avoid_double_and_int_checks # only useful when targeting JS runtime
- avoid_empty_else
# - avoid_equals_and_hash_code_on_mutable_classes # not yet tested
- avoid_field_initializers_in_const_classes
- avoid_function_literals_in_foreach_calls
# - avoid_implementing_value_types # not yet tested
- avoid_init_to_null
# - avoid_js_rounded_ints # only useful when targeting JS runtime
- avoid_null_checks_in_equality_operators
# - avoid_positional_boolean_parameters # not yet tested
# - avoid_print # not yet tested
# - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
# - avoid_redundant_argument_values # not yet tested
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
# - avoid_returning_null # there are plenty of valid reasons to return null
# - avoid_returning_null_for_future # not yet tested
- avoid_returning_null_for_void
# - avoid_returning_this # there are plenty of valid reasons to return this
# - avoid_setters_without_getters # not yet tested
# - avoid_shadowing_type_parameters # not yet tested
- avoid_single_cascade_in_expression_statements
- avoid_slow_async_io
- avoid_types_as_parameter_names
# - avoid_types_on_closure_parameters # conflicts with always_specify_types
# - avoid_unnecessary_containers # not yet tested
- avoid_unused_constructor_parameters
- avoid_void_async
# - avoid_web_libraries_in_flutter # not yet tested
- await_only_futures
- camel_case_extensions
- camel_case_types
- cancel_subscriptions
# - cascade_invocations # not yet tested
# - close_sinks # not reliable enough
# - comment_references # blocked on https://github.com/flutter/flutter/issues/20765
# - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204
- control_flow_in_finally
# - curly_braces_in_flow_control_structures # not yet tested
# - diagnostic_describe_all_properties # not yet tested
- directives_ordering
- empty_catches
- empty_constructor_bodies
- empty_statements
# - file_names # not yet tested
- flutter_style_todos
- hash_and_equals
- implementation_imports
# - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811
- iterable_contains_unrelated_type
# - join_return_with_assignment # not yet tested
- library_names
- library_prefixes
# - lines_longer_than_80_chars # not yet tested
- list_remove_unrelated_type
# - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181
# - missing_whitespace_between_adjacent_strings # not yet tested
- no_adjacent_strings_in_list
- no_duplicate_case_values
# - no_logic_in_create_state # not yet tested
# - no_runtimeType_toString # not yet tested
- non_constant_identifier_names
# - null_closures # not yet tested
# - omit_local_variable_types # opposite of always_specify_types
# - one_member_abstracts # too many false positives
# - only_throw_errors # https://github.com/flutter/flutter/issues/5792
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
# - parameter_assignments # we do this commonly
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
# - prefer_asserts_with_message # not yet tested
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
# - prefer_constructors_over_static_methods # not yet tested
- prefer_contains
# - prefer_double_quotes # opposite of prefer_single_quotes
- prefer_equal_for_default_values
# - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_for_elements_to_map_fromIterable
- prefer_foreach
# - prefer_function_declarations_over_variables # not yet tested
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
# - prefer_int_literals # not yet tested
# - prefer_interpolation_to_compose_strings # not yet tested
- prefer_is_empty
- prefer_is_not_empty
- prefer_is_not_operator
- prefer_iterable_whereType
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
# - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932
# - prefer_relative_imports # not yet tested
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- prefer_void_to_null
# - provide_deprecation_message # not yet tested
# - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml
- recursive_getters
- slash_for_doc_comments
# - sort_child_properties_last # not yet tested
- sort_constructors_first
- sort_pub_dependencies
- sort_unnamed_constructors_first
- test_types_in_equals
- throw_in_finally
# - type_annotate_public_apis # subset of always_specify_types
- type_init_formals
# - unawaited_futures # too many false positives
# - unnecessary_await_in_return # not yet tested
- unnecessary_brace_in_string_interps
- unnecessary_const
# - unnecessary_final # conflicts with prefer_final_locals
- unnecessary_getters_setters
# - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_in_if_null_operators
- unnecessary_overrides
- unnecessary_parenthesis
- unnecessary_statements
- unnecessary_string_interpolations
- unnecessary_this
- unrelated_type_equality_checks
# - unsafe_html # not yet tested
- use_full_hex_values_for_flutter_colors
# - use_function_type_syntax_for_parameters # not yet tested
# - use_key_in_widget_constructors # not yet tested
- use_rethrow_when_possible
# - use_setters_to_change_properties # not yet tested
# - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182
# - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review
- valid_regexps
- void_checks
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/build" />
<excludeFolder url="file://$MODULE_DIR$/example/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/example/build" />
</content>
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart Packages" level="project" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
</component>
</module>
\ No newline at end of file
library extended_nested_scroll_view;
//export 'src/extended_nested_scroll_view.dart';
export 'src/nested_scroll_view_inner_scroll_position_key_widget.dart';
export 'src/nested_scroll_view_refresh_indicator.dart';
export 'src/old_extended_nested_scroll_view.dart';
export 'src/util.dart';
// // 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 'dart:async';
// import 'dart:math' as math;
// //import 'package:extended_nested_scroll_view/src/nested_scroll_view_inner_scroll_position_key_widget.dart';
// //import 'package:extended_nested_scroll_view/src/util.dart';
// //import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'
// // as extend;
// import 'package:flutter/gestures.dart';
// import 'package:flutter/painting.dart';
// import 'package:flutter/physics.dart';
// import 'package:flutter/rendering.dart';
// import 'package:flutter/scheduler.dart';
// import 'package:flutter/widgets.dart';
// // Examples can assume:
// // List<String> _tabs;
// /// Signature used by [ExtendedNestedScrollView] for building its header.
// ///
// /// The `innerBoxIsScrolled` argument is typically used to control the
// /// [SliverAppBar.forceElevated] property to ensure that the app bar shows a
// /// shadow, since it would otherwise not necessarily be aware that it had
// /// content ostensibly below it.
// //it include statusBarHeight ,pinned appbar height ,pinned SliverPersistentHeader height
// //which are in NestedScrollViewHeaderSlivers
// typedef ExtendedNestedScrollViewPinnedHeaderSliverHeightBuilder = double
// Function();
// /// A scrolling view inside of which can be nested other scrolling views, with
// /// their scroll positions being intrinsically linked.
// ///
// /// The most common use case for this widget is a scrollable view with a
// /// flexible [SliverAppBar] containing a [TabBar] in the header (build by
// /// [headerSliverBuilder], and with a [TabBarView] in the [body], such that the
// /// scrollable view's contents vary based on which tab is visible.
// ///
// /// ## Motivation
// ///
// /// In a normal [ScrollView], there is one set of slivers (the components of the
// /// scrolling view). If one of those slivers hosted a [TabBarView] which scrolls
// /// in the opposite direction (e.g. allowing the user to swipe horizontally
// /// between the pages represented by the tabs, while the list scrolls
// /// vertically), then any list inside that [TabBarView] would not interact with
// /// the outer [ScrollView]. For example, flinging the inner list to scroll to
// /// the top would not cause a collapsed [SliverAppBar] in the outer [ScrollView]
// /// to expand.
// ///
// /// [ExtendedNestedScrollView] solves this problem by providing custom
// /// [ScrollController]s for the outer [ScrollView] and the inner [ScrollView]s
// /// (those inside the [TabBarView], hooking them together so that they appear,
// /// to the user, as one coherent scroll view.
// ///
// /// {@tool sample}
// ///
// /// This example shows a [ExtendedNestedScrollView] whose header is the combination of a
// /// [TabBar] in a [SliverAppBar] and whose body is a [TabBarView]. It uses a
// /// [SliverOverlapAbsorber]/[SliverOverlapInjector] pair to make the inner lists
// /// align correctly, and it uses [SafeArea] to avoid any horizontal disturbances
// /// (e.g. the "notch" on iOS when the phone is horizontal). In addition,
// /// [PageStorageKey]s are used to remember the scroll position of each tab's
// /// list.
// ///
// /// In the example below, `_tabs` is a list of strings, one for each tab, giving
// /// the tab labels. In a real application, it would be replaced by the actual
// /// data model being represented.
// ///
// /// ```dart
// /// DefaultTabController(
// /// length: _tabs.length, // This is the number of tabs.
// /// child: NestedScrollView(
// /// headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// /// // These are the slivers that show up in the "outer" scroll view.
// /// return <Widget>[
// /// SliverOverlapAbsorber(
// /// // This widget takes the overlapping behavior of the SliverAppBar,
// /// // and redirects it to the SliverOverlapInjector below. If it is
// /// // missing, then it is possible for the nested "inner" scroll view
// /// // below to end up under the SliverAppBar even when the inner
// /// // scroll view thinks it has not been scrolled.
// /// // This is not necessary if the "headerSliverBuilder" only builds
// /// // widgets that do not overlap the next sliver.
// /// handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
// /// child: SliverAppBar(
// /// title: const Text('Books'), // This is the title in the app bar.
// /// pinned: true,
// /// expandedHeight: 150.0,
// /// // The "forceElevated" property causes the SliverAppBar to show
// /// // a shadow. The "innerBoxIsScrolled" parameter is true when the
// /// // inner scroll view is scrolled beyond its "zero" point, i.e.
// /// // when it appears to be scrolled below the SliverAppBar.
// /// // Without this, there are cases where the shadow would appear
// /// // or not appear inappropriately, because the SliverAppBar is
// /// // not actually aware of the precise position of the inner
// /// // scroll views.
// /// forceElevated: innerBoxIsScrolled,
// /// bottom: TabBar(
// /// // These are the widgets to put in each tab in the tab bar.
// /// tabs: _tabs.map((String name) => Tab(text: name)).toList(),
// /// ),
// /// ),
// /// ),
// /// ];
// /// },
// /// body: TabBarView(
// /// // These are the contents of the tab views, below the tabs.
// /// children: _tabs.map((String name) {
// /// return SafeArea(
// /// top: false,
// /// bottom: false,
// /// child: Builder(
// /// // This Builder is needed to provide a BuildContext that is "inside"
// /// // the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
// /// // find the NestedScrollView.
// /// builder: (BuildContext context) {
// /// return CustomScrollView(
// /// // The "controller" and "primary" members should be left
// /// // unset, so that the NestedScrollView can control this
// /// // inner scroll view.
// /// // If the "controller" property is set, then this scroll
// /// // view will not be associated with the NestedScrollView.
// /// // The PageStorageKey should be unique to this ScrollView;
// /// // it allows the list to remember its scroll position when
// /// // the tab view is not on the screen.
// /// key: PageStorageKey<String>(name),
// /// slivers: <Widget>[
// /// SliverOverlapInjector(
// /// // This is the flip side of the SliverOverlapAbsorber above.
// /// handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
// /// ),
// /// SliverPadding(
// /// padding: const EdgeInsets.all(8.0),
// /// // In this example, the inner scroll view has
// /// // fixed-height list items, hence the use of
// /// // SliverFixedExtentList. However, one could use any
// /// // sliver widget here, e.g. SliverList or SliverGrid.
// /// sliver: SliverFixedExtentList(
// /// // The items in this example are fixed to 48 pixels
// /// // high. This matches the Material Design spec for
// /// // ListTile widgets.
// /// itemExtent: 48.0,
// /// delegate: SliverChildBuilderDelegate(
// /// (BuildContext context, int index) {
// /// // This builder is called for each child.
// /// // In this example, we just number each list item.
// /// return ListTile(
// /// title: Text('Item $index'),
// /// );
// /// },
// /// // The childCount of the SliverChildBuilderDelegate
// /// // specifies how many children this inner list
// /// // has. In this example, each tab has a list of
// /// // exactly 30 items, but this is arbitrary.
// /// childCount: 30,
// /// ),
// /// ),
// /// ),
// /// ],
// /// );
// /// },
// /// ),
// /// );
// /// }).toList(),
// /// ),
// /// ),
// /// )
// /// ```
// /// {@end-tool}
// class ExtendedNestedScrollView extends StatefulWidget {
// /// Creates a nested scroll view.
// ///
// /// The [reverse], [headerSliverBuilder], and [body] arguments must not be
// /// null.
// const ExtendedNestedScrollView({
// Key key,
// this.controller,
// this.scrollDirection = Axis.vertical,
// this.reverse = false,
// this.physics,
// this.pinnedHeaderSliverHeightBuilder,
// this.pinnedHeaderSliverHeight,
// this.keepOnlyOneInnerNestedScrollPositionActive: false,
// @required this.headerSliverBuilder,
// @required this.body,
// }) : assert(false,
// "new ExtendedNestedScrollView still has some issues in special layout, make it as obsolete for now till find a better solution"),
// assert(scrollDirection != null),
// assert(reverse != null),
// assert(headerSliverBuilder != null),
// assert(body != null),
// ///don't use them at the same time
// assert(!(pinnedHeaderSliverHeight != null &&
// pinnedHeaderSliverHeightBuilder != null)),
// super(key: key);
// ///get the pinned header in NestedScrollView header.
// ///if your pinned header will changed, use this instead of [pinnedHeaderSliverHeight]
// final ExtendedNestedScrollViewPinnedHeaderSliverHeightBuilder
// pinnedHeaderSliverHeightBuilder;
// ///if your pinned header will not changed, use this instead of [pinnedHeaderSliverHeightBuilder]
// final double pinnedHeaderSliverHeight;
// ///when ExtendedNestedScrollView body has [TabBarView]/[PageView] and children have
// ///AutomaticKeepAliveClientMixin or PageStorageKey,
// ///[_innerController.nestedPositions] will have more one,
// ///when you scroll, it will scroll all of nestedPositions
// ///set [keepOnlyOneInnerNestedScrollPositionActive] true to avoid it.
// ///notice: only for Axis.horizontal PageView/TabBarView and
// ///[scrollDirection] must be Axis.vertical.
// final bool keepOnlyOneInnerNestedScrollPositionActive;
// /// An object that can be used to control the position to which the outer
// /// scroll view is scrolled.
// final ScrollController controller;
// /// The axis along which the scroll view scrolls.
// ///
// /// Defaults to [Axis.vertical].
// final Axis scrollDirection;
// /// Whether the scroll view scrolls in the reading direction.
// ///
// /// For example, if the reading direction is left-to-right and
// /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
// /// left to right when [reverse] is false and from right to left when
// /// [reverse] is true.
// ///
// /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
// /// scrolls from top to bottom when [reverse] is false and from bottom to top
// /// when [reverse] is true.
// ///
// /// Defaults to false.
// final bool reverse;
// /// How the scroll view should respond to user input.
// ///
// /// For example, determines how the scroll view continues to animate after the
// /// user stops dragging the scroll view (providing a custom implementation of
// /// [ScrollPhysics.createBallisticSimulation] allows this particular aspect of
// /// the physics to be overridden).
// ///
// /// Defaults to matching platform conventions.
// ///
// /// The [ScrollPhysics.applyBoundaryConditions] implementation of the provided
// /// object should not allow scrolling outside the scroll extent range
// /// described by the [ScrollMetrics.minScrollExtent] and
// /// [ScrollMetrics.maxScrollExtent] properties passed to that method. If that
// /// invariant is not maintained, the nested scroll view may respond to user
// /// scrolling erratically.
// final ScrollPhysics physics;
// /// A builder for any widgets that are to precede the inner scroll views (as
// /// given by [body]).
// ///
// /// Typically this is used to create a [SliverAppBar] with a [TabBar].
// final NestedScrollViewHeaderSliversBuilder headerSliverBuilder;
// /// The widget to show inside the [ExtendedNestedScrollView].
// ///
// /// Typically this will be [TabBarView].
// ///
// /// The [body] is built in a context that provides a [PrimaryScrollController]
// /// that interacts with the [ExtendedNestedScrollView]'s scroll controller. Any
// /// [ListView] or other [Scrollable]-based widget inside the [body] that is
// /// intended to scroll with the [ExtendedNestedScrollView] should therefore not be
// /// given an explicit [ScrollController], instead allowing it to default to
// /// the [PrimaryScrollController] provided by the [ExtendedNestedScrollView].
// final Widget body;
// /// Returns the [SliverOverlapAbsorberHandle] of the nearest ancestor
// /// [ExtendedNestedScrollView].
// ///
// /// This is necessary to configure the [SliverOverlapAbsorber] and
// /// [SliverOverlapInjector] widgets.
// ///
// /// For sample code showing how to use this method, see the [ExtendedNestedScrollView]
// /// documentation.
// static SliverOverlapAbsorberHandle sliverOverlapAbsorberHandleFor(
// BuildContext context) {
// final _InheritedNestedScrollView target = context
// .dependOnInheritedWidgetOfExactType<_InheritedNestedScrollView>();
// assert(target != null,
// 'NestedScrollView.sliverOverlapAbsorberHandleFor must be called with a context that contains a NestedScrollView.');
// return target.state._absorberHandle;
// }
// // List<Widget> _buildSlivers(BuildContext context,
// // ScrollController innerController, bool bodyIsScrolled) {
// // final List<Widget> slivers = <Widget>[];
// // slivers.addAll(headerSliverBuilder(context, bodyIsScrolled));
// // slivers.add(
// // SliverFillRemaining(
// // child: PrimaryScrollController(
// // controller: innerController,
// // child: body,
// // ),
// // ));
// // return slivers;
// // }
// @override
// _ExtendedNestedScrollViewState createState() =>
// _ExtendedNestedScrollViewState();
// }
// class _ExtendedNestedScrollViewState extends State<ExtendedNestedScrollView> {
// final SliverOverlapAbsorberHandle _absorberHandle =
// SliverOverlapAbsorberHandle();
// _NestedScrollCoordinator _coordinator;
// @override
// void initState() {
// ///when ExtendedNestedScrollView body has [TabBarView]/[PageView] and children have
// ///AutomaticKeepAliveClientMixin or PageStorageKey,
// ///[_innerController.nestedPositions] will have more one,
// ///when you scroll, it will scroll all of nestedPositions
// ///set [keepOnlyOneInnerNestedScrollPositionActive] true to avoid it.
// ///notice: only for Axis.horizontal [TabBarView]/[PageView] and
// ///[scrollDirection] must be Axis.vertical.
// assert(!(widget.keepOnlyOneInnerNestedScrollPositionActive &&
// widget.scrollDirection == Axis.horizontal));
// super.initState();
// _coordinator = _NestedScrollCoordinator(
// this, widget.controller, _handleHasScrolledBodyChanged);
// }
// @override
// void didChangeDependencies() {
// super.didChangeDependencies();
// _coordinator.setParent(widget.controller);
// }
// @override
// void didUpdateWidget(ExtendedNestedScrollView oldWidget) {
// super.didUpdateWidget(oldWidget);
// if (oldWidget.controller != widget.controller)
// _coordinator.setParent(widget.controller);
// }
// @override
// void dispose() {
// _coordinator.dispose();
// _coordinator = null;
// super.dispose();
// }
// bool _lastHasScrolledBody;
// void _handleHasScrolledBodyChanged() {
// if (!mounted) return;
// final bool newHasScrolledBody = _coordinator.hasScrolledBody;
// if (_lastHasScrolledBody != newHasScrolledBody) {
// setState(() {
// // _coordinator.hasScrolledBody changed (we use it in the build method)
// // (We record _lastHasScrolledBody in the build() method, rather than in
// // this setState call, because the build() method may be called more
// // often than just from here, and we want to only call setState when the
// // new value is different than the last built value.)
// });
// }
// }
// @override
// Widget build(BuildContext context) {
// var child = _InheritedNestedScrollView(
// state: this,
// child: Builder(
// builder: (BuildContext context) {
// _lastHasScrolledBody = _coordinator.hasScrolledBody;
// return _NestedScrollViewCustomScrollView(
// scrollDirection: widget.scrollDirection,
// reverse: widget.reverse,
// physics: widget.physics != null
// ? widget.physics.applyTo(const ClampingScrollPhysics())
// : const ClampingScrollPhysics(),
// controller: _coordinator._outerController,
// slivers: _buildSlivers(
// context,
// _coordinator._innerController,
// _lastHasScrolledBody,
// ),
// handle: _absorberHandle,
// );
// },
// ),
// );
// return child;
// }
// ///zmt
// List<Widget> _buildSlivers(BuildContext context,
// ScrollController innerController, bool bodyIsScrolled) {
// final List<Widget> slivers = <Widget>[];
// slivers.addAll(widget.headerSliverBuilder(context, bodyIsScrolled));
// Widget body = widget.body;
// if (widget.keepOnlyOneInnerNestedScrollPositionActive) {
// ///get notifications and compute active one in _innerController.nestedPositions
// body = NotificationListener<ScrollNotification>(
// onNotification: (ScrollNotification notification) {
// if (((notification is ScrollEndNotification) ||
// (notification is UserScrollNotification &&
// notification.direction == ScrollDirection.idle)) &&
// notification.metrics is PageMetrics &&
// notification.metrics.axis == Axis.horizontal) {
// _coordinator._innerController
// ._computeActivatedNestedPosition(notification);
// }
// return false;
// },
// child: body);
// }
// slivers.add(SliverFillRemaining(
// child: PrimaryScrollController(
// controller: innerController,
// child: body,
// ),
// ));
// return slivers;
// }
// }
// class _NestedScrollViewCustomScrollView extends CustomScrollView {
// const _NestedScrollViewCustomScrollView({
// @required Axis scrollDirection,
// @required bool reverse,
// @required ScrollPhysics physics,
// @required ScrollController controller,
// @required List<Widget> slivers,
// @required this.handle,
// }) : super(
// scrollDirection: scrollDirection,
// reverse: reverse,
// physics: physics,
// controller: controller,
// slivers: slivers,
// );
// final SliverOverlapAbsorberHandle handle;
// @override
// Widget buildViewport(
// BuildContext context,
// ViewportOffset offset,
// AxisDirection axisDirection,
// List<Widget> slivers,
// ) {
// assert(!shrinkWrap);
// return NestedScrollViewViewport(
// axisDirection: axisDirection,
// offset: offset,
// slivers: slivers,
// handle: handle,
// );
// }
// }
// class _InheritedNestedScrollView extends InheritedWidget {
// const _InheritedNestedScrollView({
// Key key,
// @required this.state,
// @required Widget child,
// }) : assert(state != null),
// assert(child != null),
// super(key: key, child: child);
// final _ExtendedNestedScrollViewState state;
// @override
// bool updateShouldNotify(_InheritedNestedScrollView old) => state != old.state;
// }
// class _NestedScrollMetrics extends FixedScrollMetrics {
// _NestedScrollMetrics({
// @required double minScrollExtent,
// @required double maxScrollExtent,
// @required double pixels,
// @required double viewportDimension,
// @required AxisDirection axisDirection,
// @required this.minRange,
// @required this.maxRange,
// @required this.correctionOffset,
// }) : super(
// minScrollExtent: minScrollExtent,
// maxScrollExtent: maxScrollExtent,
// pixels: pixels,
// viewportDimension: viewportDimension,
// axisDirection: axisDirection,
// );
// @override
// _NestedScrollMetrics copyWith({
// double minScrollExtent,
// double maxScrollExtent,
// double pixels,
// double viewportDimension,
// AxisDirection axisDirection,
// double minRange,
// double maxRange,
// double correctionOffset,
// }) {
// return _NestedScrollMetrics(
// minScrollExtent: minScrollExtent ?? this.minScrollExtent,
// maxScrollExtent: maxScrollExtent ?? this.maxScrollExtent,
// pixels: pixels ?? this.pixels,
// viewportDimension: viewportDimension ?? this.viewportDimension,
// axisDirection: axisDirection ?? this.axisDirection,
// minRange: minRange ?? this.minRange,
// maxRange: maxRange ?? this.maxRange,
// correctionOffset: correctionOffset ?? this.correctionOffset,
// );
// }
// final double minRange;
// final double maxRange;
// final double correctionOffset;
// }
// typedef _NestedScrollActivityGetter = ScrollActivity Function(
// _NestedScrollPosition position);
// class _NestedScrollCoordinator
// implements ScrollActivityDelegate, ScrollHoldController {
// _NestedScrollCoordinator(
// this._state, this._parent, this._onHasScrolledBodyChanged) {
// final double initialScrollOffset = _parent?.initialScrollOffset ?? 0.0;
// _outerController = _NestedScrollController(this,
// initialScrollOffset: initialScrollOffset, debugLabel: 'outer');
// _innerController = _NestedScrollController(this,
// initialScrollOffset: 0.0, debugLabel: 'inner');
// }
// final _ExtendedNestedScrollViewState _state;
// ScrollController _parent;
// final VoidCallback _onHasScrolledBodyChanged;
// _NestedScrollController _outerController;
// _NestedScrollController _innerController;
// _NestedScrollPosition get _outerPosition {
// if (!_outerController.hasClients) return null;
// return _outerController.nestedPositions.single;
// }
// ///zmt
// ///scroll only for actived one
// Iterable<_NestedScrollPosition> get _activedInnerPositions {
// var list = _innerController.nestedPositions;
// if (_state.widget.keepOnlyOneInnerNestedScrollPositionActive &&
// list.length > 1) {
// var temp = list.where((item) {
// return item._isActived;
// });
// //
// // if (temp.length == 0 &&
// // _innerController.prePageChangedRenderBox != null) {
// // _innerController._computeActivatedNestedPosition(null);
// // temp = list.where((item) {
// // return item._isActived;
// // });
// // }
// if (temp.length != 1) {
// return list;
// }
// return temp;
// }
// return list;
// }
// Iterable<_NestedScrollPosition> get _innerPositions {
// return _innerController.nestedPositions;
// }
// bool get canScrollBody {
// final _NestedScrollPosition outer = _outerPosition;
// if (outer == null) return true;
// return outer.haveDimensions && outer.extentAfter == 0.0;
// }
// bool get hasScrolledBody {
// for (_NestedScrollPosition position in _activedInnerPositions) {
// if (position.pixels > position.minScrollExtent) return true;
// }
// return false;
// }
// void updateShadow() {
// if (_onHasScrolledBodyChanged != null) _onHasScrolledBodyChanged();
// }
// ScrollDirection get userScrollDirection => _userScrollDirection;
// ScrollDirection _userScrollDirection = ScrollDirection.idle;
// void updateUserScrollDirection(ScrollDirection value) {
// assert(value != null);
// if (userScrollDirection == value) return;
// _userScrollDirection = value;
// _outerPosition.didUpdateScrollDirection(value);
// for (_NestedScrollPosition position in _innerPositions)
// position.didUpdateScrollDirection(value);
// }
// ScrollDragController _currentDrag;
// void beginActivity(ScrollActivity newOuterActivity,
// _NestedScrollActivityGetter innerActivityGetter) {
// _outerPosition.beginActivity(newOuterActivity);
// bool scrolling = newOuterActivity.isScrolling;
// for (_NestedScrollPosition position in _activedInnerPositions) {
// final ScrollActivity newInnerActivity = innerActivityGetter(position);
// position.beginActivity(newInnerActivity);
// scrolling = scrolling && newInnerActivity.isScrolling;
// }
// _currentDrag?.dispose();
// _currentDrag = null;
// if (!scrolling) updateUserScrollDirection(ScrollDirection.idle);
// }
// @override
// AxisDirection get axisDirection => _outerPosition.axisDirection;
// static IdleScrollActivity _createIdleScrollActivity(
// _NestedScrollPosition position) {
// return IdleScrollActivity(position);
// }
// @override
// void goIdle() {
// beginActivity(
// _createIdleScrollActivity(_outerPosition), _createIdleScrollActivity);
// }
// @override
// void goBallistic(double velocity) {
// beginActivity(
// createOuterBallisticScrollActivity(velocity),
// (_NestedScrollPosition position) =>
// createInnerBallisticScrollActivity(position, velocity),
// );
// }
// ScrollActivity createOuterBallisticScrollActivity(double velocity) {
// // This function creates a ballistic scroll for the outer scrollable.
// //
// // It assumes that the outer scrollable can't be overscrolled, and sets up a
// // ballistic scroll over the combined space of the innerPositions and the
// // outerPosition.
// // First we must pick a representative inner position that we will care
// // about. This is somewhat arbitrary. Ideally we'd pick the one that is "in
// // the center" but there isn't currently a good way to do that so we
// // arbitrarily pick the one that is the furthest away from the infinity we
// // are heading towards.
// _NestedScrollPosition innerPosition;
// if (velocity != 0.0) {
// for (_NestedScrollPosition position in _activedInnerPositions) {
// if (innerPosition != null) {
// if (velocity > 0.0) {
// if (innerPosition.pixels < position.pixels) continue;
// } else {
// assert(velocity < 0.0);
// if (innerPosition.pixels > position.pixels) continue;
// }
// }
// innerPosition = position;
// }
// }
// if (innerPosition == null) {
// // It's either just us or a velocity=0 situation.
// return _outerPosition.createBallisticScrollActivity(
// _outerPosition.physics
// .createBallisticSimulation(_outerPosition, velocity),
// mode: _NestedBallisticScrollActivityMode.independent,
// );
// }
// final _NestedScrollMetrics metrics = _getMetrics(innerPosition, velocity);
// return _outerPosition.createBallisticScrollActivity(
// _outerPosition.physics.createBallisticSimulation(metrics, velocity),
// mode: _NestedBallisticScrollActivityMode.outer,
// metrics: metrics,
// );
// }
// @protected
// ScrollActivity createInnerBallisticScrollActivity(
// _NestedScrollPosition position, double velocity) {
// return position.createBallisticScrollActivity(
// position.physics.createBallisticSimulation(
// velocity == 0
// ? position as ScrollMetrics
// : _getMetrics(position, velocity),
// velocity,
// ),
// mode: _NestedBallisticScrollActivityMode.inner,
// );
// }
// _NestedScrollMetrics _getMetrics(
// _NestedScrollPosition innerPosition, double velocity) {
// assert(innerPosition != null);
// double pixels, minRange, maxRange, correctionOffset, extra;
// if (innerPosition.pixels == innerPosition.minScrollExtent) {
// pixels = _outerPosition.pixels.clamp(
// _outerPosition.minScrollExtent,
// _outerPosition
// .maxScrollExtent);
// TODO(ianh): gracefully handle out-of-range outer positions
// minRange = _outerPosition.minScrollExtent;
// maxRange = _outerPosition.maxScrollExtent;
// assert(minRange <= maxRange);
// correctionOffset = 0.0;
// extra = 0.0;
// } else {
// assert(innerPosition.pixels != innerPosition.minScrollExtent);
// if (innerPosition.pixels < innerPosition.minScrollExtent) {
// pixels = innerPosition.pixels -
// innerPosition.minScrollExtent +
// _outerPosition.minScrollExtent;
// } else {
// assert(innerPosition.pixels > innerPosition.minScrollExtent);
// pixels = innerPosition.pixels -
// innerPosition.minScrollExtent +
// _outerPosition.maxScrollExtent;
// }
// if ((velocity > 0.0) &&
// (innerPosition.pixels > innerPosition.minScrollExtent)) {
// // This handles going forward (fling up) and inner list is scrolled past
// // zero. We want to grab the extra pixels immediately to shrink.
// extra = _outerPosition.maxScrollExtent - _outerPosition.pixels;
// assert(extra >= 0.0);
// minRange = pixels;
// maxRange = pixels + extra;
// assert(minRange <= maxRange);
// correctionOffset = _outerPosition.pixels - pixels;
// } else if ((velocity < 0.0) &&
// (innerPosition.pixels < innerPosition.minScrollExtent)) {
// // This handles going backward (fling down) and inner list is
// // underscrolled. We want to grab the extra pixels immediately to grow.
// extra = _outerPosition.pixels - _outerPosition.minScrollExtent;
// assert(extra >= 0.0);
// minRange = pixels - extra;
// maxRange = pixels;
// assert(minRange <= maxRange);
// correctionOffset = _outerPosition.pixels - pixels;
// } else {
// // This handles going forward (fling up) and inner list is
// // underscrolled, OR, going backward (fling down) and inner list is
// // scrolled past zero. We want to skip the pixels we don't need to grow
// // or shrink over.
// if (velocity > 0.0) {
// // shrinking
// extra = _outerPosition.minScrollExtent - _outerPosition.pixels;
// } else {
// assert(velocity < 0.0);
// // growing
// extra = _outerPosition.pixels -
// (_outerPosition.maxScrollExtent - _outerPosition.minScrollExtent);
// }
// assert(extra <= 0.0);
// minRange = _outerPosition.minScrollExtent;
// maxRange = _outerPosition.maxScrollExtent + extra;
// assert(minRange <= maxRange);
// correctionOffset = 0.0;
// }
// }
// return _NestedScrollMetrics(
// minScrollExtent: _outerPosition.minScrollExtent,
// maxScrollExtent: _outerPosition.maxScrollExtent +
// innerPosition.maxScrollExtent -
// innerPosition.minScrollExtent +
// extra,
// pixels: pixels,
// viewportDimension: _outerPosition.viewportDimension,
// axisDirection: _outerPosition.axisDirection,
// minRange: minRange,
// maxRange: maxRange,
// correctionOffset: correctionOffset,
// );
// }
// double unnestOffset(double value, _NestedScrollPosition source) {
// if (source == _outerPosition)
// return value.clamp(
// _outerPosition.minScrollExtent, _outerPosition.maxScrollExtent);
// if (value < source.minScrollExtent)
// return value - source.minScrollExtent + _outerPosition.minScrollExtent;
// return value - source.minScrollExtent + _outerPosition.maxScrollExtent;
// }
// double nestOffset(double value, _NestedScrollPosition target) {
// if (target == _outerPosition)
// return value.clamp(
// _outerPosition.minScrollExtent, _outerPosition.maxScrollExtent);
// if (value < _outerPosition.minScrollExtent)
// return value - _outerPosition.minScrollExtent + target.minScrollExtent;
// if (value > _outerPosition.maxScrollExtent)
// return value - _outerPosition.maxScrollExtent + target.minScrollExtent;
// return target.minScrollExtent;
// }
// void updateCanDrag() {
// if (!_outerPosition.haveDimensions) return;
// double maxInnerExtent = 0.0;
// for (_NestedScrollPosition position in _activedInnerPositions) {
// if (!position.haveDimensions) return;
// maxInnerExtent = math.max(
// maxInnerExtent, position.maxScrollExtent - position.minScrollExtent);
// }
// _outerPosition.updateCanDrag(maxInnerExtent);
// }
// Future<void> animateTo(
// double to, {
// @required Duration duration,
// @required Curve curve,
// }) async {
// final DrivenScrollActivity outerActivity =
// _outerPosition.createDrivenScrollActivity(
// nestOffset(to, _outerPosition),
// duration,
// curve,
// );
// final List<Future<void>> resultFutures = <Future<void>>[outerActivity.done];
// beginActivity(
// outerActivity,
// (_NestedScrollPosition position) {
// final DrivenScrollActivity innerActivity =
// position.createDrivenScrollActivity(
// nestOffset(to, position),
// duration,
// curve,
// );
// resultFutures.add(innerActivity.done);
// return innerActivity;
// },
// );
// await Future.wait<void>(resultFutures);
// }
// void jumpTo(double to) {
// goIdle();
// _outerPosition.localJumpTo(nestOffset(to, _outerPosition));
// for (_NestedScrollPosition position in _activedInnerPositions)
// position.localJumpTo(nestOffset(to, position));
// goBallistic(0.0);
// }
// @override
// double setPixels(double newPixels) {
// assert(false);
// return 0.0;
// }
// ScrollHoldController hold(VoidCallback holdCancelCallback) {
// beginActivity(
// HoldScrollActivity(
// delegate: _outerPosition, onHoldCanceled: holdCancelCallback),
// (_NestedScrollPosition position) =>
// HoldScrollActivity(delegate: position),
// );
// return this;
// }
// @override
// void cancel() {
// goBallistic(0.0);
// }
// Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
// final ScrollDragController drag = ScrollDragController(
// delegate: this,
// details: details,
// onDragCanceled: dragCancelCallback,
// );
// beginActivity(
// DragScrollActivity(_outerPosition, drag),
// (_NestedScrollPosition position) => DragScrollActivity(position, drag),
// );
// assert(_currentDrag == null);
// _currentDrag = drag;
// return drag;
// }
// @override
// ///zmt
// void applyUserOffset(double delta) {
// updateUserScrollDirection(
// delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
// assert(delta != 0.0);
// if (_innerPositions.isEmpty) {
// _outerPosition.applyFullDragUpdate(delta);
// } else if (delta < 0.0) {
// // dragging "up"
//
// TODO(ianh): prioritize first getting rid of overscroll, and then the
// // outer view, so that the app bar will scroll out of the way asap.
// // Right now we ignore overscroll. This works fine on Android but looks
// // weird on iOS if you fling down then up. The problem is it's not at all
// // clear what this should do when you have multiple inner positions at
// // different levels of overscroll.
// final double innerDelta = _outerPosition.applyClampedDragUpdate(delta);
// ///this is a bug that the out postion is not overscroll actually and it get minimal value
// ///do under code will scroll inner positions
// ///so i igore minimal value here(value like following data)
// /// I/flutter (14963): 5.684341886080802e-14
// /// I/flutter (14963): -5.684341886080802e-14
// /// I/flutter (14963): -5.684341886080802e-14
// /// I/flutter (14963): 5.684341886080802e-14
// /// I/flutter (14963): -5.684341886080802e-14
// /// I/flutter (14963): -5.684341886080802e-14
// /// I/flutter (14963): -5.684341886080802e-14
// if (innerDelta != 0.0 && innerDelta.abs() > 0.0001) {
// for (_NestedScrollPosition position in _activedInnerPositions) {
// position.applyFullDragUpdate(innerDelta);
// }
// }
// } else {
// // dragging "down" - delta is positive
// // prioritize the inner views, so that the inner content will move before the app bar grows
// double outerDelta = 0.0; // it will go positive if it changes
// final List<double> overscrolls = <double>[];
// final List<_NestedScrollPosition> innerPositions =
// _activedInnerPositions.toList();
// for (_NestedScrollPosition position in innerPositions) {
// final double overscroll = position.applyClampedDragUpdate(delta);
// outerDelta = math.max(outerDelta, overscroll);
// overscrolls.add(overscroll);
// }
// if (outerDelta != 0.0)
// outerDelta -= _outerPosition.applyClampedDragUpdate(outerDelta);
// // now deal with any overscroll
// for (int i = 0; i < innerPositions.length; ++i) {
// final double remainingDelta = overscrolls[i] - outerDelta;
// if (remainingDelta > 0.0)
// innerPositions[i].applyFullDragUpdate(remainingDelta);
// }
// }
// }
// void setParent(ScrollController value) {
// _parent = value;
// updateParent();
// }
// void updateParent() {
// _outerPosition
// ?.setParent(_parent ?? PrimaryScrollController.of(_state.context));
// }
// @mustCallSuper
// void dispose() {
// _currentDrag?.dispose();
// _currentDrag = null;
// _outerController.dispose();
// _innerController.dispose();
// }
// @override
// String toString() =>
// '$runtimeType(outer=$_outerController; inner=$_innerController)';
// }
// class _NestedScrollController extends ScrollController {
// _NestedScrollController(
// this.coordinator, {
// double initialScrollOffset = 0.0,
// String debugLabel,
// }) : super(initialScrollOffset: initialScrollOffset, debugLabel: debugLabel);
// final _NestedScrollCoordinator coordinator;
// @override
// ScrollPosition createScrollPosition(
// ScrollPhysics physics,
// ScrollContext context,
// ScrollPosition oldPosition,
// ) {
// return _NestedScrollPosition(
// coordinator: coordinator,
// physics: physics,
// context: context,
// initialPixels: initialScrollOffset,
// oldPosition: oldPosition,
// debugLabel: debugLabel,
// );
// }
// @override
// void attach(ScrollPosition position) {
// assert(position is _NestedScrollPosition);
// super.attach(position);
// coordinator.updateParent();
// coordinator.updateCanDrag();
// position.addListener(_scheduleUpdateShadow);
// _scheduleUpdateShadow();
// }
// @override
// void detach(ScrollPosition position) {
// assert(position is _NestedScrollPosition);
// position.removeListener(_scheduleUpdateShadow);
// if (position is _NestedScrollPosition) {
// position._isActived = false;
// }
// super.detach(position);
// _scheduleUpdateShadow();
// }
// void _scheduleUpdateShadow() {
// // We do this asynchronously for attach() so that the new position has had
// // time to be initialized, and we do it asynchronously for detach() and from
// // the position change notifications because those happen synchronously
// // during a frame, at a time where it's too late to call setState. Since the
// // result is usually animated, the lag incurred is no big deal.
// SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
// coordinator.updateShadow();
// });
// }
// ///store page index
// Map<Key, int> _pageMetricsList = Map<Key, int>();
// ///zmt
// ///compute activated one when page changed
// void _computeActivatedNestedPosition(ScrollNotification notification) {
// final key = notification.context.widget.key;
// var page = _pageMetricsList[key];
// ///it's not an available
// if (page == -1) {
// // print(
// // "${this.runtimeType}: it's not available pageMetrics(no actived nested positions in it)");
// return;
// }
// final PageMetrics metrics = notification.metrics;
// final int currentPage = metrics.page.round();
// _pageMetricsList[key] = currentPage;
// //ComputeActivatedNestedPosition only when page changed
// if (page != currentPage) {
// ///if layout is not completed, the data will has some gap.
// ///need more accurate time to compute
// ///delay it in case.
// ///to do
// Future.delayed(const Duration(milliseconds: 150), () {
// var list = nestedPositions.toList();
// if (list.length > 1) {
// int activeCount = 0;
// int exceptionCount = 0;
// /// this is the page changed of PageView's renderBox,
// /// it maybe not the renderBox of [nestedPositions]
// /// because it maybe has more one tabbarview or pageview in NestedScrollView body
// final RenderBox pageChangedRenderBox =
// notification.context.findRenderObject();
// var activedItem = list.firstWhere((x) {
// return x._isActived;
// }, orElse: () => null);
// list.forEach((item) {
// if (item._computeActived(pageChangedRenderBox)) {
// exceptionCount++;
// }
// if (item._isActived) {
// activeCount++;
// }
// });
// if (activeCount != 1) {
// //use prePageChangedRenderBox try one more time.
// //no actived nested positions in it, it will throw expection for all of nested positions
// if (activeCount == 0 && exceptionCount == list.length) {
// ///it's not available pageMetrics(no actived nested positions in it)
// _pageMetricsList[key] = -1;
// ///reset actived
// if (activedItem != null) {
// activedItem._isActived = true;
// }
// } else {
// print(
// "${this.runtimeType}: activeCount is $activeCount, please report to zmtzawqlp@live.com and show your case.");
// }
// } else {
// coordinator.updateCanDrag();
// }
// }
// });
// }
// }
// Iterable<_NestedScrollPosition> get nestedPositions sync* {
//
// TODO(vegorov): use instance method version of castFrom when it is available.
// yield* Iterable.castFrom<ScrollPosition, _NestedScrollPosition>(positions);
// }
// }
// // The _NestedScrollPosition is used by both the inner and outer viewports of a
// // NestedScrollView. It tracks the offset to use for those viewports, and knows
// // about the _NestedScrollCoordinator, so that when activities are triggered on
// // this class, they can defer, or be influenced by, the coordinator.
// class _NestedScrollPosition extends ScrollPosition
// implements ScrollActivityDelegate {
// _NestedScrollPosition({
// @required ScrollPhysics physics,
// @required ScrollContext context,
// double initialPixels = 0.0,
// ScrollPosition oldPosition,
// String debugLabel,
// @required this.coordinator,
// }) : super(
// physics: physics,
// context: context,
// oldPosition: oldPosition,
// debugLabel: debugLabel,
// ) {
// if (pixels == null && initialPixels != null) correctPixels(initialPixels);
// if (activity == null) goIdle();
// assert(activity != null);
// saveScrollOffset(); // in case we didn't restore but could, so that we don't restore it later
// }
// final _NestedScrollCoordinator coordinator;
// TickerProvider get vsync => context.vsync;
// ScrollController _parent;
// void setParent(ScrollController value) {
// _parent?.detach(this);
// _parent = value;
// _parent?.attach(this);
// }
// ///whether it is actived
// bool _isActived = false;
// //RenderBox _renderBox;
// ///zmt
// ///whether it's actived in its' owner viewport
// bool _computeActived(RenderBox pageChangedRenderBox) {
// var context = (this.context as ScrollableState)?.context;
// try {
// if (context == null) {
// _isActived = false;
// //print("$scrollPositionKey $_isActived");
// return false;
// }
// final RenderBox renderBox = context.findRenderObject();
// if (renderBox == null) {
// _isActived = false;
// //print("$scrollPositionKey $_isActived");
// return false;
// }
// ///the nearest pageview/tabview
// final RenderBox parentRenderBox = _getParentPageViewRenderBox(context);
// // RenderAbstractViewport viewport = RenderAbstractViewport.of(renderBox);
// // RenderAbstractViewport viewport1 =
// // RenderAbstractViewport.of(pageChangedRenderBox);
// // RenderAbstractViewport viewport2 =
// // RenderAbstractViewport.of(parentRenderBox);
// // var test = viewport.getOffsetToReveal(renderBox, 0.0);
// // var test1 = viewport1.getOffsetToReveal(pageChangedRenderBox, 0.0);
// // var test2 = viewport2.getOffsetToReveal(parentRenderBox, 0.0);
// // print("$test $test1 $test2");
// //
// // var test = viewport.getOffsetToReveal(renderBox, 0.0,
// // rect: viewport2.semanticBounds);
// _isActived = _childIsActivedInViewport(renderBox, pageChangedRenderBox) &&
// _childIsActivedInViewport(renderBox, parentRenderBox);
// // // just for test
// // var key = context.ancestorWidgetOfExactType(extend
// // .typeOf<extend.NestedScrollViewInnerScrollPositionKeyWidget>())
// // as extend.NestedScrollViewInnerScrollPositionKeyWidget;
// // scrollPositionKey = key?.scrollPositionKey;
// //
// // print("$scrollPositionKey $_isActived");
// return false;
// } catch (e) {
// //print("${this.runtimeType}: $e");
// _isActived = false;
// //print("$scrollPositionKey $_isActived");
// return true;
// }
// }
// //Key scrollPositionKey;
// ///whether child is zero to parent
// bool _childIsActivedInViewport(RenderBox child, RenderBox parent) {
// Size parentSize = parent?.size ?? Size(0.0, 0.0);
// final Offset position = child.localToGlobal(Offset.zero, ancestor: parent);
// ///remove the margin/padding
// final Offset size = Offset(parentSize.width - child.size.width,
// parentSize.height - child.size.height);
// ///if layout is not completed, the data will has some gap.
// ///need more accurate time to compute
// ///to do
// bool childIsActivedInViewport = ((position.dx - size.dx).abs() < 1 &&
// (position.dy - size.dy).abs() < 1);
// return childIsActivedInViewport;
// }
// ///the nearest pageview/tabbarview
// RenderBox _getParentPageViewRenderBox(BuildContext context) {
// ScrollableState parent = context.findAncestorStateOfType<ScrollableState>();
// if (parent == null) {
// return null;
// }
// ///find horizontal pageview/tabbarview
// if (parent.widget.controller is! PageController ||
// parent.widget.axis != Axis.horizontal) {
// return _getParentPageViewRenderBox(parent.context);
// }
// return parent.context.findRenderObject();
// }
// @override
// bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
// if (debugLabel == 'outer') {
// if (coordinator._state.widget.pinnedHeaderSliverHeight != null) {
// maxScrollExtent = maxScrollExtent -
// coordinator._state.widget.pinnedHeaderSliverHeight;
// maxScrollExtent = math.max(0.0, maxScrollExtent);
// } else if (coordinator._state.widget.pinnedHeaderSliverHeightBuilder !=
// null) {
// maxScrollExtent = maxScrollExtent -
// coordinator._state.widget.pinnedHeaderSliverHeightBuilder();
// maxScrollExtent = math.max(0.0, maxScrollExtent);
// }
// }
// return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
// }
// @override
// AxisDirection get axisDirection => context.axisDirection;
// @override
// void absorb(ScrollPosition other) {
// super.absorb(other);
// activity.updateDelegate(this);
// }
// @override
// void restoreScrollOffset() {
// if (coordinator.canScrollBody) super.restoreScrollOffset();
// }
// // Returns the amount of delta that was not used.
// //
// // Positive delta means going down (exposing stuff above), negative delta
// // going up (exposing stuff below).
// double applyClampedDragUpdate(double delta) {
// assert(delta != 0.0);
// // If we are going towards the maxScrollExtent (negative scroll offset),
// // then the furthest we can be in the minScrollExtent direction is negative
// // infinity. For example, if we are already overscrolled, then scrolling to
// // reduce the overscroll should not disallow the overscroll.
// //
// // If we are going towards the minScrollExtent (positive scroll offset),
// // then the furthest we can be in the minScrollExtent direction is wherever
// // we are now, if we are already overscrolled (in which case pixels is less
// // than the minScrollExtent), or the minScrollExtent if we are not.
// //
// // In other words, we cannot, via applyClampedDragUpdate, _enter_ an
// // overscroll situation.
// //
// // An overscroll situation might be nonetheless entered via several means.
// // One is if the physics allow it, via applyFullDragUpdate (see below). An
// // overscroll situation can also be forced, e.g. if the scroll position is
// // artificially set using the scroll controller.
// final double min =
// delta < 0.0 ? -double.infinity : math.min(minScrollExtent, pixels);
// // The logic for max is equivalent but on the other side.
// final double max =
// delta > 0.0 ? double.infinity : math.max(maxScrollExtent, pixels);
// final double oldPixels = pixels;
// final double newPixels = (pixels - delta).clamp(min, max);
// final double clampedDelta = newPixels - pixels;
// if (clampedDelta == 0.0) return delta;
// final double overscroll = physics.applyBoundaryConditions(this, newPixels);
// final double actualNewPixels = newPixels - overscroll;
// final double offset = actualNewPixels - oldPixels;
// if (offset != 0.0) {
// forcePixels(actualNewPixels);
// didUpdateScrollPositionBy(offset);
// }
// return delta + offset;
// }
// // Returns the overscroll.
// double applyFullDragUpdate(double delta) {
// assert(delta != 0.0);
// final double oldPixels = pixels;
// // Apply friction:
// final double newPixels =
// pixels - physics.applyPhysicsToUserOffset(this, delta);
// if (oldPixels == newPixels)
// return 0.0; // delta must have been so small we dropped it during floating point addition
// // Check for overscroll:
// final double overscroll = physics.applyBoundaryConditions(this, newPixels);
// final double actualNewPixels = newPixels - overscroll;
// if (actualNewPixels != oldPixels) {
// forcePixels(actualNewPixels);
// didUpdateScrollPositionBy(actualNewPixels - oldPixels);
// }
// if (overscroll != 0.0) {
// didOverscrollBy(overscroll);
// return overscroll;
// }
// return 0.0;
// }
// @override
// ScrollDirection get userScrollDirection => coordinator.userScrollDirection;
// DrivenScrollActivity createDrivenScrollActivity(
// double to, Duration duration, Curve curve) {
// return DrivenScrollActivity(
// this,
// from: pixels,
// to: to,
// duration: duration,
// curve: curve,
// vsync: vsync,
// );
// }
// @override
// double applyUserOffset(double delta) {
// assert(false);
// return 0.0;
// }
// // This is called by activities when they finish their work.
// @override
// void goIdle() {
// beginActivity(IdleScrollActivity(this));
// }
// // This is called by activities when they finish their work and want to go ballistic.
// @override
// void goBallistic(double velocity) {
// Simulation simulation;
// if (velocity != 0.0 || outOfRange)
// simulation = physics.createBallisticSimulation(this, velocity);
// beginActivity(createBallisticScrollActivity(
// simulation,
// mode: _NestedBallisticScrollActivityMode.independent,
// ));
// }
// ScrollActivity createBallisticScrollActivity(
// Simulation simulation, {
// @required _NestedBallisticScrollActivityMode mode,
// _NestedScrollMetrics metrics,
// }) {
// if (simulation == null) return IdleScrollActivity(this);
// assert(mode != null);
// switch (mode) {
// case _NestedBallisticScrollActivityMode.outer:
// assert(metrics != null);
// if (metrics.minRange == metrics.maxRange)
// return IdleScrollActivity(this);
// return _NestedOuterBallisticScrollActivity(
// coordinator, this, metrics, simulation, context.vsync);
// case _NestedBallisticScrollActivityMode.inner:
// return _NestedInnerBallisticScrollActivity(
// coordinator, this, simulation, context.vsync);
// case _NestedBallisticScrollActivityMode.independent:
// return BallisticScrollActivity(this, simulation, context.vsync);
// }
// return null;
// }
// @override
// Future<void> animateTo(
// double to, {
// @required Duration duration,
// @required Curve curve,
// }) {
// return coordinator.animateTo(coordinator.unnestOffset(to, this),
// duration: duration, curve: curve);
// }
// @override
// void jumpTo(double value) {
// return coordinator.jumpTo(coordinator.unnestOffset(value, this));
// }
// @override
// void jumpToWithoutSettling(double value) {
// assert(false);
// }
// void localJumpTo(double value) {
// if (pixels != value) {
// final double oldPixels = pixels;
// forcePixels(value);
// didStartScroll();
// didUpdateScrollPositionBy(pixels - oldPixels);
// didEndScroll();
// }
// }
// @override
// void applyNewDimensions() {
// super.applyNewDimensions();
// coordinator.updateCanDrag();
// }
// void updateCanDrag(double totalExtent) {
// context.setCanDrag(totalExtent > (viewportDimension - maxScrollExtent) ||
// minScrollExtent != maxScrollExtent);
// }
// @override
// ScrollHoldController hold(VoidCallback holdCancelCallback) {
// return coordinator.hold(holdCancelCallback);
// }
// @override
// Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
// return coordinator.drag(details, dragCancelCallback);
// }
// @override
// void dispose() {
// _parent?.detach(this);
// super.dispose();
// }
// }
// enum _NestedBallisticScrollActivityMode { outer, inner, independent }
// class _NestedInnerBallisticScrollActivity extends BallisticScrollActivity {
// _NestedInnerBallisticScrollActivity(
// this.coordinator,
// _NestedScrollPosition position,
// Simulation simulation,
// TickerProvider vsync,
// ) : super(position, simulation, vsync);
// final _NestedScrollCoordinator coordinator;
// @override
// _NestedScrollPosition get delegate => super.delegate;
// @override
// void resetActivity() {
// delegate.beginActivity(
// coordinator.createInnerBallisticScrollActivity(delegate, velocity));
// }
// @override
// void applyNewDimensions() {
// delegate.beginActivity(
// coordinator.createInnerBallisticScrollActivity(delegate, velocity));
// }
// @override
// bool applyMoveTo(double value) {
// return super.applyMoveTo(coordinator.nestOffset(value, delegate));
// }
// }
// class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity {
// _NestedOuterBallisticScrollActivity(
// this.coordinator,
// _NestedScrollPosition position,
// this.metrics,
// Simulation simulation,
// TickerProvider vsync,
// ) : assert(metrics.minRange != metrics.maxRange),
// assert(metrics.maxRange > metrics.minRange),
// super(position, simulation, vsync);
// final _NestedScrollCoordinator coordinator;
// final _NestedScrollMetrics metrics;
// @override
// _NestedScrollPosition get delegate => super.delegate;
// @override
// void resetActivity() {
// delegate.beginActivity(
// coordinator.createOuterBallisticScrollActivity(velocity));
// }
// @override
// void applyNewDimensions() {
// delegate.beginActivity(
// coordinator.createOuterBallisticScrollActivity(velocity));
// }
// @override
// bool applyMoveTo(double value) {
// bool done = false;
// if (velocity > 0.0) {
// if (value < metrics.minRange) return true;
// if (value > metrics.maxRange) {
// value = metrics.maxRange;
// done = true;
// }
// } else if (velocity < 0.0) {
// if (value > metrics.maxRange) return true;
// if (value < metrics.minRange) {
// value = metrics.minRange;
// done = true;
// }
// } else {
// value = value.clamp(metrics.minRange, metrics.maxRange);
// done = true;
// }
// final bool result = super.applyMoveTo(value + metrics.correctionOffset);
// assert(
// result); // since we tried to pass an in-range value, it shouldn't ever overflow
// return !done;
// }
// @override
// String toString() {
// return '$runtimeType(${metrics.minRange} .. ${metrics.maxRange}; correcting by ${metrics.correctionOffset})';
// }
// }
//pack your inner scrollables which are in NestedScrollView body
//so that it can find the active scrollable
//compare with NestedScrollViewInnerScrollPositionKeyBuilder
import 'package:flutter/material.dart';
class NestedScrollViewInnerScrollPositionKeyWidget extends StatefulWidget {
const NestedScrollViewInnerScrollPositionKeyWidget(
this.scrollPositionKey, this.child);
final Key scrollPositionKey;
final Widget child;
static State of(BuildContext context) {
return context.findAncestorStateOfType<
_NestedScrollViewInnerScrollPositionKeyWidgetState>();
}
@override
_NestedScrollViewInnerScrollPositionKeyWidgetState createState() =>
_NestedScrollViewInnerScrollPositionKeyWidgetState();
}
class _NestedScrollViewInnerScrollPositionKeyWidgetState
extends State<NestedScrollViewInnerScrollPositionKeyWidget> {
@override
Widget build(BuildContext context) {
return widget.child;
}
}
// 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 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
// The over-scroll distance that moves the indicator to its maximum
// displacement, as a percentage of the scrollable's container extent.
const double _kDragContainerExtentPercentage = 0.25;
// How much the scroll's drag gesture can overshoot the RefreshIndicator's
// displacement; max displacement = _kDragSizeFactorLimit * displacement.
const double _kDragSizeFactorLimit = 1.5;
// When the scroll ends, the duration of the refresh indicator's animation
// to the RefreshIndicator's displacement.
const Duration _kIndicatorSnapDuration = Duration(milliseconds: 150);
// The duration of the ScaleTransition that starts when the refresh action
// has completed.
const Duration _kIndicatorScaleDuration = Duration(milliseconds: 200);
/// The signature for a function that's called when the user has dragged a
/// [NestedScrollViewRefreshIndicator] far enough to demonstrate that they want the app to
/// refresh. The returned [Future] must complete when the refresh operation is
/// finished.
///
/// Used by [NestedScrollViewRefreshIndicator.onRefresh].
typedef NestedScrollViewRefreshCallback = Future<void> Function();
// The state machine moves through these modes only when the scrollable
// identified by scrollableKey has been scrolled to its min or max limit.
enum _RefreshIndicatorMode {
drag, // Pointer is down.
armed, // Dragged far enough that an up event will run the onRefresh callback.
snap, // Animating to the indicator's final "displacement".
refresh, // Running the refresh callback.
done, // Animating the indicator's fade-out after refreshing.
canceled, // Animating the indicator's fade-out after not arming.
}
/// A widget that supports the Material "swipe to refresh" idiom.
///
/// When the child's [Scrollable] descendant overscrolls, an animated circular
/// progress indicator is faded into view. When the scroll ends, if the
/// indicator has been dragged far enough for it to become completely opaque,
/// the [onRefresh] callback is called. The callback is expected to update the
/// scrollable's contents and then complete the [Future] it returns. The refresh
/// indicator disappears after the callback's [Future] has completed.
///
/// If the [Scrollable] might not have enough content to overscroll, consider
/// settings its `physics` property to [AlwaysScrollableScrollPhysics]:
///
/// ```dart
/// ListView(
/// physics: const AlwaysScrollableScrollPhysics(),
/// children: ...
// )
/// ```
///
/// Using [AlwaysScrollableScrollPhysics] will ensure that the scroll view is
/// always scrollable and, therefore, can trigger the [NestedScrollViewRefreshIndicator].
///
/// A [NestedScrollViewRefreshIndicator] can only be used with a vertical scroll view.
///
/// See also:
///
/// * <https://material.google.com/patterns/swipe-to-refresh.html>
/// * [NestedScrollViewRefreshIndicatorState], can be used to programmatically show the refresh indicator.
/// * [RefreshProgressIndicator], widget used by [NestedScrollViewRefreshIndicator] to show
/// the inner circular progress spinner during refreshes.
/// * [CupertinoSliverRefreshControl], an iOS equivalent of the pull-to-refresh pattern.
/// Must be used as a sliver inside a [CustomScrollView] instead of wrapping
/// around a [ScrollView] because it's a part of the scrollable instead of
/// being overlaid on top of it.
class NestedScrollViewRefreshIndicator extends StatefulWidget {
/// Creates a refresh indicator.
///
/// The [onRefresh], [child], and [notificationPredicate] arguments must be
/// non-null. The default
/// [displacement] is 40.0 logical pixels.
const NestedScrollViewRefreshIndicator({
Key key,
@required this.child,
this.displacement = 40.0,
@required this.onRefresh,
this.color,
this.backgroundColor,
this.notificationPredicate = nestedScrollViewScrollNotificationPredicate,
this.semanticsLabel,
this.semanticsValue,
}) : assert(child != null),
assert(onRefresh != null),
assert(notificationPredicate != null),
super(key: key);
/// The widget below this widget in the tree.
///
/// The refresh indicator will be stacked on top of this child. The indicator
/// will appear when child's Scrollable descendant is over-scrolled.
///
/// Typically a [ListView] or [CustomScrollView].
final Widget child;
/// The distance from the child's top or bottom edge to where the refresh
/// indicator will settle. During the drag that exposes the refresh indicator,
/// its actual displacement may significantly exceed this value.
final double displacement;
/// A function that's called when the user has dragged the refresh indicator
/// far enough to demonstrate that they want the app to refresh. The returned
/// [Future] must complete when the refresh operation is finished.
final NestedScrollViewRefreshCallback onRefresh;
/// The progress indicator's foreground color. The current theme's
/// [ThemeData.colorScheme.secondary] by default.
final Color color;
/// The progress indicator's background color. The current theme's
/// [ThemeData.canvasColor] by default.
final Color backgroundColor;
/// A check that specifies whether a [ScrollNotification] should be
/// handled by this widget.
///
/// By default, checks whether `notification.depth == 0`. Set it to something
/// else for more complicated layouts.
final ScrollNotificationPredicate notificationPredicate;
/// {@macro flutter.material.progressIndicator.semanticsLabel}
///
/// This will be defaulted to [MaterialLocalizations.refreshIndicatorSemanticLabel]
/// if it is null.
final String semanticsLabel;
/// {@macro flutter.material.progressIndicator.semanticsValue}
final String semanticsValue;
@override
NestedScrollViewRefreshIndicatorState createState() =>
NestedScrollViewRefreshIndicatorState();
}
/// Contains the state for a [NestedScrollViewRefreshIndicator]. This class can be used to
/// programmatically show the refresh indicator, see the [show] method.
class NestedScrollViewRefreshIndicatorState
extends State<NestedScrollViewRefreshIndicator>
with TickerProviderStateMixin<NestedScrollViewRefreshIndicator> {
AnimationController _positionController;
AnimationController _scaleController;
Animation<double> _positionFactor;
Animation<double> _scaleFactor;
Animation<double> _value;
Animation<Color> _valueColor;
_RefreshIndicatorMode _mode;
Future<void> _pendingRefreshFuture;
bool _isIndicatorAtTop;
double _dragOffset;
static final Animatable<double> _threeQuarterTween =
Tween<double>(begin: 0.0, end: 0.75);
static final Animatable<double> _kDragSizeFactorLimitTween =
Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
static final Animatable<double> _oneToZeroTween =
Tween<double>(begin: 1.0, end: 0.0);
@override
void initState() {
super.initState();
_positionController = AnimationController(vsync: this);
_positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
_value = _positionController.drive(
_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
_scaleController = AnimationController(vsync: this);
_scaleFactor = _scaleController.drive(_oneToZeroTween);
}
@override
void didChangeDependencies() {
final ThemeData theme = Theme.of(context);
_valueColor = _positionController.drive(
ColorTween(
begin: (widget.color ?? theme.colorScheme.secondary).withOpacity(0.0),
end: (widget.color ?? theme.colorScheme.secondary).withOpacity(1.0))
.chain(CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit))),
);
super.didChangeDependencies();
}
@override
void dispose() {
_positionController.dispose();
_scaleController.dispose();
super.dispose();
}
double maxContainerExtent = 0.0;
bool _handleScrollNotification(ScrollNotification notification) {
if (!widget.notificationPredicate(notification)) {
return false;
}
maxContainerExtent =
math.max(notification.metrics.viewportDimension, maxContainerExtent);
if (notification is ScrollStartNotification &&
notification.metrics.extentBefore == 0.0 &&
_mode == null &&
_start(notification.metrics.axisDirection)) {
setState(() {
_mode = _RefreshIndicatorMode.drag;
});
return false;
}
bool indicatorAtTopNow;
switch (notification.metrics.axisDirection) {
case AxisDirection.down:
indicatorAtTopNow = true;
break;
case AxisDirection.up:
indicatorAtTopNow = false;
break;
case AxisDirection.left:
case AxisDirection.right:
indicatorAtTopNow = null;
break;
}
if (indicatorAtTopNow != _isIndicatorAtTop) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed)
_dismiss(_RefreshIndicatorMode.canceled);
} else if (notification is ScrollUpdateNotification) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
if (notification.metrics.extentBefore > 0.0) {
_dismiss(_RefreshIndicatorMode.canceled);
} else {
_dragOffset -= notification.scrollDelta;
_checkDragOffset(maxContainerExtent);
}
}
if (_mode == _RefreshIndicatorMode.armed &&
notification.dragDetails == null) {
// On iOS start the refresh when the Scrollable bounces back from the
// overscroll (ScrollNotification indicating this don't have dragDetails
// because the scroll activity is not directly triggered by a drag).
_show();
}
} else if (notification is OverscrollNotification) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
_dragOffset -= notification.overscroll / 2.0;
_checkDragOffset(maxContainerExtent);
}
} else if (notification is ScrollEndNotification) {
switch (_mode) {
case _RefreshIndicatorMode.armed:
_show();
break;
case _RefreshIndicatorMode.drag:
_dismiss(_RefreshIndicatorMode.canceled);
break;
default:
// do nothing
break;
}
}
return false;
}
bool _handleGlowNotification(OverscrollIndicatorNotification notification) {
if (notification.depth != 0 || !notification.leading) {
return false;
}
if (_mode == _RefreshIndicatorMode.drag) {
notification.disallowGlow();
return true;
}
return false;
}
bool _start(AxisDirection direction) {
assert(_mode == null);
assert(_isIndicatorAtTop == null);
assert(_dragOffset == null);
switch (direction) {
case AxisDirection.down:
_isIndicatorAtTop = true;
break;
case AxisDirection.up:
_isIndicatorAtTop = false;
break;
case AxisDirection.left:
case AxisDirection.right:
_isIndicatorAtTop = null;
// we do not support horizontal scroll views.
return false;
}
_dragOffset = 0.0;
_scaleController.value = 0.0;
_positionController.value = 0.0;
return true;
}
void _checkDragOffset(double containerExtent) {
assert(_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed);
double newValue =
_dragOffset / (containerExtent * _kDragContainerExtentPercentage);
if (_mode == _RefreshIndicatorMode.armed)
newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit);
_positionController.value =
newValue.clamp(0.0, 1.0) as double; // this triggers various rebuilds
if (_mode == _RefreshIndicatorMode.drag && _valueColor.value.alpha == 0xFF)
_mode = _RefreshIndicatorMode.armed;
}
// Stop showing the refresh indicator.
Future<void> _dismiss(_RefreshIndicatorMode newMode) async {
await Future<void>.value();
// This can only be called from _show() when refreshing and
// _handleScrollNotification in response to a ScrollEndNotification or
// direction change.
assert(newMode == _RefreshIndicatorMode.canceled ||
newMode == _RefreshIndicatorMode.done);
setState(() {
_mode = newMode;
});
switch (_mode) {
case _RefreshIndicatorMode.done:
await _scaleController.animateTo(1.0,
duration: _kIndicatorScaleDuration);
break;
case _RefreshIndicatorMode.canceled:
await _positionController.animateTo(0.0,
duration: _kIndicatorScaleDuration);
break;
default:
assert(false);
}
if (mounted && _mode == newMode) {
_dragOffset = null;
_isIndicatorAtTop = null;
setState(() {
_mode = null;
});
}
}
void _show() {
assert(_mode != _RefreshIndicatorMode.refresh);
assert(_mode != _RefreshIndicatorMode.snap);
final Completer<void> completer = Completer<void>();
_pendingRefreshFuture = completer.future;
_mode = _RefreshIndicatorMode.snap;
_positionController
.animateTo(1.0 / _kDragSizeFactorLimit,
duration: _kIndicatorSnapDuration)
.then<void>((void value) {
if (mounted && _mode == _RefreshIndicatorMode.snap) {
assert(widget.onRefresh != null);
setState(() {
// Show the indeterminate progress indicator.
_mode = _RefreshIndicatorMode.refresh;
});
final Future<void> refreshResult = widget.onRefresh();
assert(() {
if (refreshResult == null)
FlutterError.reportError(FlutterErrorDetails(
exception: FlutterError('The onRefresh callback returned null.\n'
'The RefreshIndicator onRefresh callback must return a Future.'),
context: ErrorDescription('when calling onRefresh'),
library: 'material library',
));
return true;
}());
if (refreshResult == null) {
return;
}
refreshResult.whenComplete(() {
if (mounted && _mode == _RefreshIndicatorMode.refresh) {
completer.complete();
_dismiss(_RefreshIndicatorMode.done);
}
});
}
});
}
/// Show the refresh indicator and run the refresh callback as if it had
/// been started interactively. If this method is called while the refresh
/// callback is running, it quietly does nothing.
///
/// Creating the [NestedScrollViewRefreshIndicator] with a [GlobalKey<RefreshIndicatorState>]
/// makes it possible to refer to the [NestedScrollViewRefreshIndicatorState].
///
/// The future returned from this method completes when the
/// [NestedScrollViewRefreshIndicator.onRefresh] callback's future completes.
///
/// If you await the future returned by this function from a [State], you
/// should check that the state is still [mounted] before calling [setState].
///
/// When initiated in this manner, the refresh indicator is independent of any
/// actual scroll view. It defaults to showing the indicator at the top. To
/// show it at the bottom, set `atTop` to false.
Future<void> show({bool atTop = true}) {
if (_mode != _RefreshIndicatorMode.refresh &&
_mode != _RefreshIndicatorMode.snap) {
if (_mode == null) {
_start(atTop ? AxisDirection.down : AxisDirection.up);
}
_show();
}
return _pendingRefreshFuture;
}
final GlobalKey _key = GlobalKey();
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final Widget child = NotificationListener<ScrollNotification>(
key: _key,
onNotification: _handleScrollNotification,
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: _handleGlowNotification,
child: widget.child,
),
);
if (_mode == null) {
assert(_dragOffset == null);
assert(_isIndicatorAtTop == null);
return child;
}
assert(_dragOffset != null);
assert(_isIndicatorAtTop != null);
final bool showIndeterminateIndicator =
_mode == _RefreshIndicatorMode.refresh ||
_mode == _RefreshIndicatorMode.done;
return Stack(
children: <Widget>[
child,
Positioned(
top: _isIndicatorAtTop ? 0.0 : null,
bottom: !_isIndicatorAtTop ? 0.0 : null,
left: 0.0,
right: 0.0,
child: SizeTransition(
axisAlignment: _isIndicatorAtTop ? 1.0 : -1.0,
sizeFactor: _positionFactor, // this is what brings it down
child: Container(
padding: _isIndicatorAtTop
? EdgeInsets.only(top: widget.displacement)
: EdgeInsets.only(bottom: widget.displacement),
alignment: _isIndicatorAtTop
? Alignment.topCenter
: Alignment.bottomCenter,
child: ScaleTransition(
scale: _scaleFactor,
child: AnimatedBuilder(
animation: _positionController,
builder: (BuildContext context, Widget child) {
return RefreshProgressIndicator(
semanticsLabel: widget.semanticsLabel ??
MaterialLocalizations.of(context)
.refreshIndicatorSemanticLabel,
semanticsValue: widget.semanticsValue,
value: showIndeterminateIndicator ? null : _value.value,
valueColor: _valueColor,
backgroundColor: widget.backgroundColor,
);
},
),
),
),
),
),
],
);
}
}
//return true so that we can handle inner scroll notification
bool nestedScrollViewScrollNotificationPredicate(
ScrollNotification notification) {
return true;
}
import 'dart:async';
import 'dart:math' as math;
import 'package:extended_nested_scroll_view/src/nested_scroll_view_inner_scroll_position_key_widget.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
// Examples can assume:
// List<String> _tabs;
/// Signature used by [NestedScrollView] for building its header.
///
/// The `innerBoxIsScrolled` argument is typically used to control the
/// [SliverAppBar.forceElevated] property to ensure that the app bar shows a
/// shadow, since it would otherwise not necessarily be aware that it had
/// content ostensibly below it.
typedef NestedScrollViewHeaderSliversBuilder = List<Widget> Function(
BuildContext context, bool innerBoxIsScrolled);
//it include statusBarHeight ,pinned appbar height ,pinned SliverPersistentHeader height
//which are in NestedScrollViewHeaderSlivers
typedef NestedScrollViewPinnedHeaderSliverHeightBuilder = double Function();
//it used to get the inner scrollable widget key, to avoid sync the scroll state for
//Inner ScrollPositions (scrollables in NestedScrollView body)
//we only need to scroll the active scrollable(which is in current view)
//exmaple: body is tabview and child are used with AutomaticKeepAliveClientMixin or PageStorageKey
//T is the widget which the key store
//Key is current active scrollable
//exmaple: if tabbar index==0, then the active scrollable should be the first child
// if tabbar index ==1,hen the active scrollable should be the second child
typedef NestedScrollViewInnerScrollPositionKeyBuilder = Key Function();
/// A scrolling view inside of which can be nested other scrolling views, with
/// their scroll positions being intrinsically linked.
///
/// The most common use case for this widget is a scrollable view with a
/// flexible [SliverAppBar] containing a [TabBar] in the header (build by
/// [headerSliverBuilder], and with a [TabBarView] in the [body], such that the
/// scrollable view's contents vary based on which tab is visible.
///
/// ## Motivation
///
/// In a normal [ScrollView], there is one set of slivers (the components of the
/// scrolling view). If one of those slivers hosted a [TabBarView] which scrolls
/// in the opposite direction (e.g. allowing the user to swipe horizontally
/// between the pages represented by the tabs, while the list scrolls
/// vertically), then any list inside that [TabBarView] would not interact with
/// the outer [ScrollView]. For example, flinging the inner list to scroll to
/// the top would not cause a collapsed [SliverAppBar] in the outer [ScrollView]
/// to expand.
///
/// [NestedScrollView] solves this problem by providing custom
/// [ScrollController]s for the outer [ScrollView] and the inner [ScrollView]s
/// (those inside the [TabBarView], hooking them together so that they appear,
/// to the user, as one coherent scroll view.
///
/// ## Sample code
///
/// This example shows a [NestedScrollView] whose header is the combination of a
/// [TabBar] in a [SliverAppBar] and whose body is a [TabBarView]. It uses a
/// [SliverOverlapAbsorber]/[SliverOverlapInjector] pair to make the inner lists
/// align correctly, and it uses [SafeArea] to avoid any horizontal disturbances
/// (e.g. the "notch" on iOS when the phone is horizontal). In addition,
/// [PageStorageKey]s are used to remember the scroll position of each tab's
/// list.
///
/// In the example below, `_tabs` is a list of strings, one for each tab, giving
/// the tab labels. In a real application, it would be replaced by the actual
/// data model being represented.
///
/// ```dart
/// DefaultTabController(
/// length: _tabs.length, // This is the number of tabs.
/// child: NestedScrollView(
/// headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
/// // These are the slivers that show up in the "outer" scroll view.
/// return <Widget>[
/// SliverOverlapAbsorber(
/// // This widget takes the overlapping behavior of the SliverAppBar,
/// // and redirects it to the SliverOverlapInjector below. If it is
/// // missing, then it is possible for the nested "inner" scroll view
/// // below to end up under the SliverAppBar even when the inner
/// // scroll view thinks it has not been scrolled.
/// // This is not necessary if the "headerSliverBuilder" only builds
/// // widgets that do not overlap the next sliver.
/// handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
/// sliver: SliverAppBar(
/// title: const Text('Books'), // This is the title in the app bar.
/// pinned: true,
/// expandedHeight: 150.0,
/// // The "forceElevated" property causes the SliverAppBar to show
/// // a shadow. The "innerBoxIsScrolled" parameter is true when the
/// // inner scroll view is scrolled beyond its "zero" point, i.e.
/// // when it appears to be scrolled below the SliverAppBar.
/// // Without this, there are cases where the shadow would appear
/// // or not appear inappropriately, because the SliverAppBar is
/// // not actually aware of the precise position of the inner
/// // scroll views.
/// forceElevated: innerBoxIsScrolled,
/// bottom: TabBar(
/// // These are the widgets to put in each tab in the tab bar.
/// tabs: _tabs.map((String name) => Tab(text: name)).toList(),
/// ),
/// ),
/// ),
/// ];
/// },
/// body: TabBarView(
/// // These are the contents of the tab views, below the tabs.
/// children: _tabs.map((String name) {
/// return SafeArea(
/// top: false,
/// bottom: false,
/// child: Builder(
/// // This Builder is needed to provide a BuildContext that is
/// // "inside" the NestedScrollView, so that
/// // sliverOverlapAbsorberHandleFor() can find the
/// // NestedScrollView.
/// builder: (BuildContext context) {
/// return CustomScrollView(
/// // The "controller" and "primary" members should be left
/// // unset, so that the NestedScrollView can control this
/// // inner scroll view.
/// // If the "controller" property is set, then this scroll
/// // view will not be associated with the NestedScrollView.
/// // The PageStorageKey should be unique to this ScrollView;
/// // it allows the list to remember its scroll position when
/// // the tab view is not on the screen.
/// key: PageStorageKey<String>(name),
/// slivers: <Widget>[
/// SliverOverlapInjector(
/// // This is the flip side of the SliverOverlapAbsorber
/// // above.
/// handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
/// ),
/// SliverPadding(
/// padding: const EdgeInsets.all(8.0),
/// // In this example, the inner scroll view has
/// // fixed-height list items, hence the use of
/// // SliverFixedExtentList. However, one could use any
/// // sliver widget here, e.g. SliverList or SliverGrid.
/// sliver: SliverFixedExtentList(
/// // The items in this example are fixed to 48 pixels
/// // high. This matches the Material Design spec for
/// // ListTile widgets.
/// itemExtent: 48.0,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// // This builder is called for each child.
/// // In this example, we just number each list item.
/// return ListTile(
/// title: Text('Item $index'),
/// );
/// },
/// // The childCount of the SliverChildBuilderDelegate
/// // specifies how many children this inner list
/// // has. In this example, each tab has a list of
/// // exactly 30 items, but this is arbitrary.
/// childCount: 30,
/// ),
/// ),
/// ),
/// ],
/// );
/// },
/// ),
/// );
/// }).toList(),
/// ),
/// ),
/// )
/// ```
class NestedScrollView extends StatefulWidget {
/// Creates a nested scroll view.
///
/// The [reverse], [headerSliverBuilder], and [body] arguments must not be
/// null.
const NestedScrollView({
Key key,
this.controller,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.physics,
this.innerScrollPositionKeyBuilder,
this.pinnedHeaderSliverHeightBuilder,
@required this.headerSliverBuilder,
@required this.body,
this.dragStartBehavior = DragStartBehavior.start,
this.floatHeaderSlivers = false,
this.clipBehavior = Clip.hardEdge,
this.restorationId,
}) : assert(scrollDirection != null),
assert(reverse != null),
assert(headerSliverBuilder != null),
assert(body != null),
assert(floatHeaderSlivers != null),
assert(clipBehavior != null),
super(key: key);
/// An object that can be used to control the position to which the outer
/// scroll view is scrolled.
final ScrollController controller;
/// The axis along which the scroll view scrolls.
///
/// Defaults to [Axis.vertical].
final Axis scrollDirection;
/// Whether the scroll view scrolls in the reading direction.
///
/// For example, if the reading direction is left-to-right and
/// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
/// left to right when [reverse] is false and from right to left when
/// [reverse] is true.
///
/// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
/// scrolls from top to bottom when [reverse] is false and from bottom to top
/// when [reverse] is true.
///
/// Defaults to false.
final bool reverse;
/// How the scroll view should respond to user input.
///
/// For example, determines how the scroll view continues to animate after the
/// user stops dragging the scroll view (providing a custom implementation of
/// [ScrollPhysics.createBallisticSimulation] allows this particular aspect of
/// the physics to be overridden).
///
/// Defaults to matching platform conventions.
///
/// The [ScrollPhysics.applyBoundaryConditions] implementation of the provided
/// object should not allow scrolling outside the scroll extent range
/// described by the [ScrollMetrics.minScrollExtent] and
/// [ScrollMetrics.maxScrollExtent] properties passed to that method. If that
/// invariant is not maintained, the nested scroll view may respond to user
/// scrolling erratically.
final ScrollPhysics physics;
/// A builder for any widgets that are to precede the inner scroll views (as
/// given by [body]).
///
/// Typically this is used to create a [SliverAppBar] with a [TabBar].
final NestedScrollViewHeaderSliversBuilder headerSliverBuilder;
//get the pinned header in NestedScrollView header.
final NestedScrollViewPinnedHeaderSliverHeightBuilder
pinnedHeaderSliverHeightBuilder;
//get the current active key.
final NestedScrollViewInnerScrollPositionKeyBuilder
innerScrollPositionKeyBuilder;
/// The widget to show inside the [NestedScrollView].
///
/// Typically this will be [TabBarView].
///
/// The [body] is built in a context that provides a [PrimaryScrollController]
/// that interacts with the [NestedScrollView]'s scroll controller. Any
/// [ListView] or other [Scrollable]-based widget inside the [body] that is
/// intended to scroll with the [NestedScrollView] should therefore not be
/// given an explicit [ScrollController], instead allowing it to default to
/// the [PrimaryScrollController] provided by the [NestedScrollView].
final Widget body;
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
/// Whether or not the [NestedScrollView]'s coordinator should prioritize the
/// outer scrollable over the inner when scrolling back.
///
/// This is useful for an outer scrollable containing a [SliverAppBar] that
/// is expected to float. This cannot be null.
final bool floatHeaderSlivers;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
/// {@macro flutter.widgets.scrollable.restorationId}
final String restorationId;
/// Returns the [SliverOverlapAbsorberHandle] of the nearest ancestor
/// [NestedScrollView].
///
/// This is necessary to configure the [SliverOverlapAbsorber] and
/// [SliverOverlapInjector] widgets.
///
/// For sample code showing how to use this method, see the [NestedScrollView]
/// documentation.
static SliverOverlapAbsorberHandle sliverOverlapAbsorberHandleFor(
BuildContext context) {
final _InheritedNestedScrollView target = context
.dependOnInheritedWidgetOfExactType<_InheritedNestedScrollView>();
assert(target != null,
'NestedScrollView.sliverOverlapAbsorberHandleFor must be called with a context that contains a NestedScrollView.');
return target.state._absorberHandle;
}
List<Widget> _buildSlivers(BuildContext context,
ScrollController innerController, bool bodyIsScrolled) {
return <Widget>[
...headerSliverBuilder(context, bodyIsScrolled),
SliverFillRemaining(
child: PrimaryScrollController(
controller: innerController,
child: body,
),
),
];
}
@override
NestedScrollViewState createState() => NestedScrollViewState();
}
class NestedScrollViewState extends State<NestedScrollView> {
final SliverOverlapAbsorberHandle _absorberHandle =
SliverOverlapAbsorberHandle();
/// The [ScrollController] provided to the [ScrollView] in
/// [NestedScrollView.body].
///
/// Manipulating the [ScrollPosition] of this controller pushes the outer
/// header sliver(s) up and out of view. The position of the [outerController]
/// will be set to [ScrollPosition.maxScrollExtent], unless you use
/// [ScrollPosition.setPixels].
///
/// See also:
///
/// * [outerController], which exposes the [ScrollController] used by the
/// the sliver(s) contained in [NestedScrollView.headerSliverBuilder].
ScrollController get innerController => _coordinator._innerController;
/// The [ScrollController] provided to the [ScrollView] in
/// [NestedScrollView.headerSliverBuilder].
///
/// This is equivalent to [NestedScrollView.controller], if provided.
///
/// Manipulating the [ScrollPosition] of this controller pushes the inner body
/// sliver(s) down. The position of the [innerController] will be set to
/// [ScrollPosition.minScrollExtent], unless you use
/// [ScrollPosition.setPixels]. Visually, the inner body will be scrolled to
/// its beginning.
///
/// See also:
///
/// * [innerController], which exposes the [ScrollController] used by the
/// [ScrollView] contained in [NestedScrollView.body].
ScrollController get outerController => _coordinator._outerController;
_NestedScrollCoordinator _coordinator;
Iterable<_NestedScrollPosition> get innerScrollPositions =>
_coordinator.innerScrollPositions.cast<_NestedScrollPosition>();
_NestedScrollPosition get currentInnerPosition =>
_coordinator.currentInnerPositions.first;
@override
void initState() {
super.initState();
_coordinator = _NestedScrollCoordinator(
this,
widget.controller,
_handleHasScrolledBodyChanged,
widget.pinnedHeaderSliverHeightBuilder,
widget.innerScrollPositionKeyBuilder,
widget.floatHeaderSlivers,
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_coordinator.setParent(widget.controller);
}
@override
void didUpdateWidget(NestedScrollView oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller)
_coordinator.setParent(widget.controller);
}
@override
void dispose() {
_coordinator.dispose();
_coordinator = null;
super.dispose();
}
bool _lastHasScrolledBody;
void _handleHasScrolledBodyChanged() {
if (!mounted) {
return;
}
final bool newHasScrolledBody = _coordinator.hasScrolledBody;
if (_lastHasScrolledBody != newHasScrolledBody) {
setState(() {
// _coordinator.hasScrolledBody changed (we use it in the build method)
// (We record _lastHasScrolledBody in the build() method, rather than in
// this setState call, because the build() method may be called more
// often than just from here, and we want to only call setState when the
// new value is different than the last built value.)
});
}
}
@override
Widget build(BuildContext context) {
return _InheritedNestedScrollView(
state: this,
child: Builder(
builder: (BuildContext context) {
_lastHasScrolledBody = _coordinator.hasScrolledBody;
return _NestedScrollViewCustomScrollView(
dragStartBehavior: widget.dragStartBehavior,
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
physics: widget.physics != null
? widget.physics.applyTo(const ClampingScrollPhysics())
: const ClampingScrollPhysics(),
controller: _coordinator._outerController,
slivers: widget._buildSlivers(
context,
_coordinator._innerController,
_lastHasScrolledBody,
),
handle: _absorberHandle,
clipBehavior: widget.clipBehavior,
restorationId: widget.restorationId,
);
},
),
);
}
}
class _NestedScrollViewCustomScrollView extends CustomScrollView {
const _NestedScrollViewCustomScrollView({
@required Axis scrollDirection,
@required bool reverse,
@required ScrollPhysics physics,
@required ScrollController controller,
@required List<Widget> slivers,
@required this.handle,
@required Clip clipBehavior,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
String restorationId,
}) : super(
scrollDirection: scrollDirection,
reverse: reverse,
physics: physics,
controller: controller,
slivers: slivers,
dragStartBehavior: dragStartBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
final SliverOverlapAbsorberHandle handle;
@override
Widget buildViewport(
BuildContext context,
ViewportOffset offset,
AxisDirection axisDirection,
List<Widget> slivers,
) {
assert(!shrinkWrap);
return NestedScrollViewViewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
handle: handle,
clipBehavior: clipBehavior,
);
}
}
class _InheritedNestedScrollView extends InheritedWidget {
const _InheritedNestedScrollView({
Key key,
@required this.state,
@required Widget child,
}) : assert(state != null),
assert(child != null),
super(key: key, child: child);
final NestedScrollViewState state;
@override
bool updateShouldNotify(_InheritedNestedScrollView old) => state != old.state;
}
class _NestedScrollMetrics extends FixedScrollMetrics {
_NestedScrollMetrics({
@required double minScrollExtent,
@required double maxScrollExtent,
@required double pixels,
@required double viewportDimension,
@required AxisDirection axisDirection,
@required this.minRange,
@required this.maxRange,
@required this.correctionOffset,
}) : super(
minScrollExtent: minScrollExtent,
maxScrollExtent: maxScrollExtent,
pixels: pixels,
viewportDimension: viewportDimension,
axisDirection: axisDirection,
);
@override
_NestedScrollMetrics copyWith({
double minScrollExtent,
double maxScrollExtent,
double pixels,
double viewportDimension,
AxisDirection axisDirection,
double minRange,
double maxRange,
double correctionOffset,
}) {
return _NestedScrollMetrics(
minScrollExtent: minScrollExtent ?? this.minScrollExtent,
maxScrollExtent: maxScrollExtent ?? this.maxScrollExtent,
pixels: pixels ?? this.pixels,
viewportDimension: viewportDimension ?? this.viewportDimension,
axisDirection: axisDirection ?? this.axisDirection,
minRange: minRange ?? this.minRange,
maxRange: maxRange ?? this.maxRange,
correctionOffset: correctionOffset ?? this.correctionOffset,
);
}
final double minRange;
final double maxRange;
final double correctionOffset;
}
typedef _NestedScrollActivityGetter = ScrollActivity Function(
_NestedScrollPosition position);
class _NestedScrollCoordinator
implements ScrollActivityDelegate, ScrollHoldController {
_NestedScrollCoordinator(
this._state,
this._parent,
this._onHasScrolledBodyChanged,
this.pinnedHeaderSliverHeightBuilder,
this.innerScrollPositionKeyBuilder,
this._floatHeaderSlivers,
) {
final double initialScrollOffset = _parent?.initialScrollOffset ?? 0.0;
_outerController = _NestedScrollController(this,
initialScrollOffset: initialScrollOffset, debugLabel: 'outer');
_innerController = _NestedScrollController(
this,
initialScrollOffset: 0.0,
debugLabel: 'inner',
);
// 解决_innerController滚动至最顶部时,其他tab的滚动view还停留在原有滚动位置问题
_outerController.addListener(() {
resetInnerOuterPosition();
});
_innerController.addListener(() {
resetInnerOuterPosition();
});
}
final bool _floatHeaderSlivers;
final NestedScrollViewState _state;
final NestedScrollViewPinnedHeaderSliverHeightBuilder
pinnedHeaderSliverHeightBuilder;
//get the current active key.
final NestedScrollViewInnerScrollPositionKeyBuilder
innerScrollPositionKeyBuilder;
ScrollController _parent;
final VoidCallback _onHasScrolledBodyChanged;
_NestedScrollController _outerController;
_NestedScrollController _innerController;
_NestedScrollPosition get _outerPosition {
if (!_outerController.hasClients) {
return null;
}
return _outerController.nestedPositions.single;
}
Iterable<ScrollPosition> get innerScrollPositions => _innerPositions;
Iterable<_NestedScrollPosition> get _innerPositions {
//return _currentPositions;
return _innerController.nestedPositions;
}
Iterable<_NestedScrollPosition> get currentInnerPositions =>
_currentInnerPositions;
Iterable<_NestedScrollPosition> get _currentInnerPositions {
return _innerController
.getCurrentNestedPositions(innerScrollPositionKeyBuilder);
}
bool get canScrollBody {
final _NestedScrollPosition outer = _outerPosition;
if (outer == null) {
return true;
}
return outer.haveDimensions && outer.extentAfter == 0.0;
}
bool get hasScrolledBody {
for (final _NestedScrollPosition position in _currentInnerPositions) {
assert(position.minScrollExtent != null && position.physics != null);
if (position.pixels > position.minScrollExtent) {
return true;
}
}
return false;
}
void updateShadow() {
if (_onHasScrolledBodyChanged != null) {
_onHasScrolledBodyChanged();
}
}
ScrollDirection get userScrollDirection => _userScrollDirection;
ScrollDirection _userScrollDirection = ScrollDirection.idle;
void updateUserScrollDirection(ScrollDirection value) {
assert(value != null);
if (userScrollDirection == value) {
return;
}
_userScrollDirection = value;
_outerPosition.didUpdateScrollDirection(value);
for (final _NestedScrollPosition position in _currentInnerPositions)
position.didUpdateScrollDirection(value);
}
ScrollDragController _currentDrag;
void beginActivity(ScrollActivity newOuterActivity,
_NestedScrollActivityGetter innerActivityGetter) {
_outerPosition.beginActivity(newOuterActivity);
bool scrolling = newOuterActivity.isScrolling;
for (final _NestedScrollPosition position in _currentInnerPositions) {
final ScrollActivity newInnerActivity = innerActivityGetter(position);
position.beginActivity(newInnerActivity);
scrolling = scrolling && newInnerActivity.isScrolling;
}
_currentDrag?.dispose();
_currentDrag = null;
if (!scrolling) {
updateUserScrollDirection(ScrollDirection.idle);
}
}
@override
AxisDirection get axisDirection => _outerPosition.axisDirection;
static IdleScrollActivity _createIdleScrollActivity(
_NestedScrollPosition position) {
return IdleScrollActivity(position);
}
@override
void goIdle() {
beginActivity(
_createIdleScrollActivity(_outerPosition), _createIdleScrollActivity);
}
@override
void goBallistic(double velocity) {
beginActivity(
createOuterBallisticScrollActivity(velocity),
(_NestedScrollPosition position) =>
createInnerBallisticScrollActivity(position, velocity),
);
}
ScrollActivity createOuterBallisticScrollActivity(double velocity) {
// This function creates a ballistic scroll for the outer scrollable.
//
// It assumes that the outer scrollable can't be overscrolled, and sets up a
// ballistic scroll over the combined space of the innerPositions and the
// outerPosition.
// First we must pick a representative inner position that we will care
// about. This is somewhat arbitrary. Ideally we'd pick the one that is "in
// the center" but there isn't currently a good way to do that so we
// arbitrarily pick the one that is the furthest away from the infinity we
// are heading towards.
_NestedScrollPosition innerPosition;
if (velocity != 0.0) {
for (final _NestedScrollPosition position in _currentInnerPositions) {
if (innerPosition != null) {
if (velocity > 0.0) {
if (innerPosition.pixels < position.pixels) {
continue;
}
} else {
assert(velocity < 0.0);
if (innerPosition.pixels > position.pixels) {
continue;
}
}
}
innerPosition = position;
}
}
if (innerPosition == null) {
// It's either just us or a velocity=0 situation.
return _outerPosition.createBallisticScrollActivity(
_outerPosition.physics
.createBallisticSimulation(_outerPosition, velocity),
mode: _NestedBallisticScrollActivityMode.independent,
);
}
final _NestedScrollMetrics metrics = _getMetrics(innerPosition, velocity);
return _outerPosition.createBallisticScrollActivity(
_outerPosition.physics.createBallisticSimulation(metrics, velocity),
mode: _NestedBallisticScrollActivityMode.outer,
metrics: metrics,
);
}
@protected
ScrollActivity createInnerBallisticScrollActivity(
_NestedScrollPosition position, double velocity) {
return position.createBallisticScrollActivity(
position.physics.createBallisticSimulation(
velocity == 0 ? position : _getMetrics(position, velocity),
velocity,
),
mode: _NestedBallisticScrollActivityMode.inner,
);
}
_NestedScrollMetrics _getMetrics(
_NestedScrollPosition innerPosition, double velocity) {
assert(innerPosition != null);
double pixels, minRange, maxRange, correctionOffset;
double extra = 0.0;
if (innerPosition.pixels == innerPosition.minScrollExtent) {
pixels = _outerPosition.pixels.clamp(
_outerPosition.minScrollExtent, _outerPosition.maxScrollExtent)
as double; // TODO(ianh): gracefully handle out-of-range outer positions
minRange = _outerPosition.minScrollExtent;
maxRange = _outerPosition.maxScrollExtent;
assert(minRange <= maxRange);
correctionOffset = 0.0;
} else {
assert(innerPosition.pixels != innerPosition.minScrollExtent);
if (innerPosition.pixels < innerPosition.minScrollExtent) {
pixels = innerPosition.pixels -
innerPosition.minScrollExtent +
_outerPosition.minScrollExtent;
} else {
assert(innerPosition.pixels > innerPosition.minScrollExtent);
pixels = innerPosition.pixels -
innerPosition.minScrollExtent +
_outerPosition.maxScrollExtent;
}
if ((velocity > 0.0) &&
(innerPosition.pixels > innerPosition.minScrollExtent)) {
// This handles going forward (fling up) and inner list is scrolled past
// zero. We want to grab the extra pixels immediately to shrink.
extra = _outerPosition.maxScrollExtent - _outerPosition.pixels;
assert(extra >= 0.0);
minRange = pixels;
maxRange = pixels + extra;
assert(minRange <= maxRange);
correctionOffset = _outerPosition.pixels - pixels;
} else if ((velocity < 0.0) &&
(innerPosition.pixels < innerPosition.minScrollExtent)) {
// This handles going backward (fling down) and inner list is
// underscrolled. We want to grab the extra pixels immediately to grow.
extra = _outerPosition.pixels - _outerPosition.minScrollExtent;
assert(extra >= 0.0);
minRange = pixels - extra;
maxRange = pixels;
assert(minRange <= maxRange);
correctionOffset = _outerPosition.pixels - pixels;
} else {
// This handles going forward (fling up) and inner list is
// underscrolled, OR, going backward (fling down) and inner list is
// scrolled past zero. We want to skip the pixels we don't need to grow
// or shrink over.
if (velocity > 0.0) {
// shrinking
extra = _outerPosition.minScrollExtent - _outerPosition.pixels;
} else {
assert(velocity < 0.0);
// growing
extra = _outerPosition.pixels -
(_outerPosition.maxScrollExtent - _outerPosition.minScrollExtent);
}
assert(extra <= 0.0);
minRange = _outerPosition.minScrollExtent;
maxRange = _outerPosition.maxScrollExtent + extra;
assert(minRange <= maxRange);
correctionOffset = 0.0;
}
}
return _NestedScrollMetrics(
minScrollExtent: _outerPosition.minScrollExtent,
maxScrollExtent: _outerPosition.maxScrollExtent +
innerPosition.maxScrollExtent -
innerPosition.minScrollExtent +
extra,
pixels: pixels,
viewportDimension: _outerPosition.viewportDimension,
axisDirection: _outerPosition.axisDirection,
minRange: minRange,
maxRange: maxRange,
correctionOffset: correctionOffset,
);
}
double unnestOffset(double value, _NestedScrollPosition source) {
if (source == _outerPosition)
return value.clamp(
_outerPosition.minScrollExtent, _outerPosition.maxScrollExtent)
as double;
if (value < source.minScrollExtent)
return value - source.minScrollExtent + _outerPosition.minScrollExtent;
return value - source.minScrollExtent + _outerPosition.maxScrollExtent;
}
double nestOffset(double value, _NestedScrollPosition target) {
if (target == _outerPosition)
return value.clamp(
_outerPosition.minScrollExtent, _outerPosition.maxScrollExtent)
as double;
if (value < _outerPosition.minScrollExtent)
return value - _outerPosition.minScrollExtent + target.minScrollExtent;
if (value > _outerPosition.maxScrollExtent)
return value - _outerPosition.maxScrollExtent + target.minScrollExtent;
return target.minScrollExtent;
}
void updateCanDrag() {
if (!_outerPosition.haveDimensions) {
return;
}
double maxInnerExtent = 0.0;
for (final _NestedScrollPosition position in _currentInnerPositions) {
if (!position.haveDimensions) {
return;
}
maxInnerExtent = math.max(
maxInnerExtent, position.maxScrollExtent - position.minScrollExtent);
}
_outerPosition.updateCanDrag(maxInnerExtent);
}
Future<void> animateTo(
double to, {
@required Duration duration,
@required Curve curve,
}) async {
final DrivenScrollActivity outerActivity =
_outerPosition.createDrivenScrollActivity(
nestOffset(to, _outerPosition),
duration,
curve,
);
final List<Future<void>> resultFutures = <Future<void>>[outerActivity.done];
beginActivity(
outerActivity,
(_NestedScrollPosition position) {
final DrivenScrollActivity innerActivity =
position.createDrivenScrollActivity(
nestOffset(to, position),
duration,
curve,
);
resultFutures.add(innerActivity.done);
return innerActivity;
},
);
await Future.wait<void>(resultFutures);
}
void jumpTo(double to) {
goIdle();
_outerPosition.localJumpTo(nestOffset(to, _outerPosition));
for (final _NestedScrollPosition position in _currentInnerPositions)
position.localJumpTo(nestOffset(to, position));
goBallistic(0.0);
}
void pointerScroll(double delta) {
assert(delta != 0.0);
goIdle();
updateUserScrollDirection(
delta < 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
if (_innerPositions.isEmpty) {
// Does not enter overscroll.
_outerPosition.applyClampedPointerSignalUpdate(delta);
} else if (delta > 0.0) {
// Dragging "up" - delta is positive
// Prioritize getting rid of any inner overscroll, and then the outer
// view, so that the app bar will scroll out of the way asap.
double outerDelta = delta;
for (final _NestedScrollPosition position in _currentInnerPositions) {
if (position.pixels < 0.0) {
// This inner position is in overscroll.
final double potentialOuterDelta =
position.applyClampedPointerSignalUpdate(delta);
// In case there are multiple positions in varying states of
// overscroll, the first to 'reach' the outer view above takes
// precedence.
outerDelta = math.max(outerDelta, potentialOuterDelta);
}
}
if (outerDelta != 0.0) {
final double innerDelta =
_outerPosition.applyClampedPointerSignalUpdate(outerDelta);
if (innerDelta != 0.0) {
for (final _NestedScrollPosition position in _currentInnerPositions)
position.applyClampedPointerSignalUpdate(innerDelta);
}
}
} else {
// Dragging "down" - delta is negative
double innerDelta = delta;
// Apply delta to the outer header first if it is configured to float.
if (_floatHeaderSlivers)
innerDelta = _outerPosition.applyClampedPointerSignalUpdate(delta);
if (innerDelta != 0.0) {
// Apply the innerDelta, if we have not floated in the outer scrollable,
// any leftover delta after this will be passed on to the outer
// scrollable by the outerDelta.
double outerDelta = 0.0; // it will go negative if it changes
for (final _NestedScrollPosition position in _currentInnerPositions) {
final double overscroll =
position.applyClampedPointerSignalUpdate(innerDelta);
outerDelta = math.min(outerDelta, overscroll);
}
if (outerDelta != 0.0)
_outerPosition.applyClampedPointerSignalUpdate(outerDelta);
}
}
goBallistic(0.0);
}
@override
double setPixels(double newPixels) {
assert(false);
return 0.0;
}
ScrollHoldController hold(VoidCallback holdCancelCallback) {
beginActivity(
HoldScrollActivity(
delegate: _outerPosition, onHoldCanceled: holdCancelCallback),
(_NestedScrollPosition position) =>
HoldScrollActivity(delegate: position),
);
return this;
}
@override
void cancel() {
goBallistic(0.0);
}
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
final ScrollDragController drag = ScrollDragController(
delegate: this,
details: details,
onDragCanceled: dragCancelCallback,
);
beginActivity(
DragScrollActivity(_outerPosition, drag),
(_NestedScrollPosition position) => DragScrollActivity(position, drag),
);
assert(_currentDrag == null);
_currentDrag = drag;
return drag;
}
@override
void applyUserOffset(double delta) {
updateUserScrollDirection(
delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
assert(delta != 0.0);
if (_innerPositions.isEmpty) {
_outerPosition.applyFullDragUpdate(delta);
} else if (delta < 0.0) {
// Dragging "up"
// Prioritize getting rid of any inner overscroll, and then the outer
// view, so that the app bar will scroll out of the way asap.
double outerDelta = delta;
for (final _NestedScrollPosition position in _currentInnerPositions) {
if (position.pixels < 0.0) {
// This inner position is in overscroll.
final double potentialOuterDelta =
position.applyClampedDragUpdate(delta);
// In case there are multiple positions in varying states of
// overscroll, the first to 'reach' the outer view above takes
// precedence.
outerDelta = math.max(outerDelta, potentialOuterDelta);
}
}
if (outerDelta != 0.0) {
final double innerDelta =
_outerPosition.applyClampedDragUpdate(outerDelta);
if (innerDelta != 0.0) {
for (final _NestedScrollPosition position in _currentInnerPositions)
position.applyFullDragUpdate(innerDelta);
}
}
} else {
// dragging "down" - delta is positive
double innerDelta = delta;
// Apply delta to the outer header first if it is configured to float.
if (_floatHeaderSlivers) {
innerDelta = _outerPosition.applyClampedDragUpdate(delta);
}
if (innerDelta != 0.0) {
// Apply the innerDelta, if we have not floated in the outer scrollable,
// any leftover delta after this will be passed on to the outer
// scrollable by the outerDelta.
// prioritize the inner views, so that the inner content will move before the app bar grows
double outerDelta = 0.0; // it will go positive if it changes
final List<double> overscrolls = <double>[];
final List<_NestedScrollPosition> innerPositions =
_currentInnerPositions.toList();
for (final _NestedScrollPosition position in innerPositions) {
final double overscroll = position.applyClampedDragUpdate(delta);
outerDelta = math.max(outerDelta, overscroll);
overscrolls.add(overscroll);
}
if (outerDelta != 0.0)
outerDelta -= _outerPosition.applyClampedDragUpdate(outerDelta);
// Now deal with any overscroll
// TODO(Piinks): Configure which scrollable receives overscroll to
// support stretching app bars. createOuterBallisticScrollActivity will
// need to be updated as it currently assumes the outer position will
// never overscroll, https://github.com/flutter/flutter/issues/54059
for (int i = 0; i < innerPositions.length; ++i) {
final double remainingDelta = overscrolls[i] - outerDelta;
if (remainingDelta > 0.0)
innerPositions[i].applyFullDragUpdate(remainingDelta);
}
}
}
}
void resetInnerOuterPosition() {
if (!_innerController.coordinator.hasScrolledBody) {
_innerController.scrollPositionKeyMap.forEach((Key key, _NestedScrollPosition position) {
if (position.pixels > 0 && innerScrollPositionKeyBuilder() != key) {
position.localJumpTo(nestOffset(_outerController.offset, position));
}
});
}
}
void setParent(ScrollController value) {
_parent = value;
updateParent();
}
void updateParent() {
_outerPosition
?.setParent(_parent ?? PrimaryScrollController.of(_state.context));
}
@mustCallSuper
void dispose() {
_currentDrag?.dispose();
_currentDrag = null;
_outerController.dispose();
_innerController.dispose();
}
@override
String toString() =>
'$runtimeType(outer=$_outerController; inner=$_innerController)';
}
class _NestedScrollController extends ScrollController {
_NestedScrollController(
this.coordinator, {
double initialScrollOffset = 0.0,
String debugLabel,
}) : super(initialScrollOffset: initialScrollOffset, debugLabel: debugLabel) {
if (debugLabel == 'inner') {
scrollPositionKeyMap = <Key, _NestedScrollPosition>{};
}
}
final _NestedScrollCoordinator coordinator;
Map<Key, _NestedScrollPosition> scrollPositionKeyMap;
@override
ScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
) {
return _NestedScrollPosition(
coordinator: coordinator,
physics: physics,
context: context,
initialPixels: initialScrollOffset,
oldPosition: oldPosition,
debugLabel: debugLabel,
);
}
@override
void attach(ScrollPosition position) {
assert(position is _NestedScrollPosition);
super.attach(position);
attachScrollPositionKey(position as _NestedScrollPosition);
coordinator.updateParent();
coordinator.updateCanDrag();
position.addListener(_scheduleUpdateShadow);
_scheduleUpdateShadow();
}
@override
void detach(ScrollPosition position) {
assert(position is _NestedScrollPosition);
position.removeListener(_scheduleUpdateShadow);
super.detach(position);
detachScrollPositionKey(position as _NestedScrollPosition);
_scheduleUpdateShadow();
}
void _scheduleUpdateShadow() {
// We do this asynchronously for attach() so that the new position has had
// time to be initialized, and we do it asynchronously for detach() and from
// the position change notifications because those happen synchronously
// during a frame, at a time where it's too late to call setState. Since the
// result is usually animated, the lag incurred is no big deal.
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
coordinator.updateShadow();
});
}
Iterable<_NestedScrollPosition> get nestedPositions sync* {
// TODO(vegorov): use instance method version of castFrom when it is available.
yield* Iterable.castFrom<ScrollPosition, _NestedScrollPosition>(positions);
}
Iterable<_NestedScrollPosition> getCurrentNestedPositions(
NestedScrollViewInnerScrollPositionKeyBuilder
innerScrollPositionKeyBuilder) {
if (innerScrollPositionKeyBuilder != null &&
scrollPositionKeyMap.length > 1) {
final Key key = innerScrollPositionKeyBuilder();
if (scrollPositionKeyMap.containsKey(key)) {
return <_NestedScrollPosition>[scrollPositionKeyMap[key]];
// return nestedPositions.where((p) {
// return p.key == key;
// });
} else {
return nestedPositions;
}
}
// if (innerScrollPositionKeyBuilder != null &&
// scrollPositionKeyMap != null &&
// nestedPositions.isNotEmpty &&
// nestedPositions.length > 1 &&
// scrollPositionKeyMap.length > 1) {
// var key = innerScrollPositionKeyBuilder();
// if (!scrollPositionKeyMap.containsKey(key) &&
// scrollPositionKeyMap.length != nestedPositions.length) {
// resetScrollPositionKey();
// }
// if (scrollPositionKeyMap.containsKey(key)) {
// return <_NestedScrollPosition>[scrollPositionKeyMap[key]];
// }
// }
return nestedPositions;
}
void resetScrollPositionKey() {
nestedPositions.forEach(attachScrollPositionKey);
}
void attachScrollPositionKey(_NestedScrollPosition position) {
if (position != null && scrollPositionKeyMap != null) {
final Key key = position.setScrollPositionKey();
if (key != null) {
if (!scrollPositionKeyMap.containsKey(key)) {
scrollPositionKeyMap[key] = position;
} else if (scrollPositionKeyMap[key] != position) {
//in demo ,when tab to tab03, the tab02'key will be tab00 at first
//then it become tab02.
//this is not a good solution
position.clearScrollPositionKey();
Future<void>.delayed(const Duration(milliseconds: 500), () {
attachScrollPositionKey(position);
});
}
}
}
}
void detachScrollPositionKey(_NestedScrollPosition position) {
if (position != null &&
scrollPositionKeyMap != null &&
position.key != null &&
scrollPositionKeyMap.containsKey(position.key)) {
scrollPositionKeyMap.remove(position.key);
position.clearScrollPositionKey();
}
}
}
// The _NestedScrollPosition is used by both the inner and outer viewports of a
// NestedScrollView. It tracks the offset to use for those viewports, and knows
// about the _NestedScrollCoordinator, so that when activities are triggered on
// this class, they can defer, or be influenced by, the coordinator.
class _NestedScrollPosition extends ScrollPosition
implements ScrollActivityDelegate {
_NestedScrollPosition({
@required ScrollPhysics physics,
@required ScrollContext context,
double initialPixels = 0.0,
ScrollPosition oldPosition,
String debugLabel,
@required this.coordinator,
}) : super(
physics: physics,
context: context,
oldPosition: oldPosition,
debugLabel: debugLabel,
) {
if (pixels == null && initialPixels != null) {
correctPixels(initialPixels);
}
if (activity == null) {
goIdle();
}
assert(activity != null);
saveScrollOffset(); // in case we didn't restore but could, so that we don't restore it later
}
final _NestedScrollCoordinator coordinator;
TickerProvider get vsync => context.vsync;
ScrollController _parent;
Key _key;
//scrollPostionkey
Key get key {
return _key;
}
void setParent(ScrollController value) {
_parent?.detach(this);
_parent = value;
_parent?.attach(this);
}
@override
AxisDirection get axisDirection => context.axisDirection;
@override
void absorb(ScrollPosition other) {
super.absorb(other);
activity.updateDelegate(this);
}
@override
void restoreScrollOffset() {
if (coordinator.canScrollBody) {
super.restoreScrollOffset();
}
}
@override
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
if (debugLabel == 'outer' &&
coordinator.pinnedHeaderSliverHeightBuilder != null) {
maxScrollExtent =
maxScrollExtent - coordinator.pinnedHeaderSliverHeightBuilder();
maxScrollExtent = math.max(0.0, maxScrollExtent);
}
return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
}
Key setScrollPositionKey() {
//if (haveDimensions) {
final NestedScrollViewInnerScrollPositionKeyWidget keyWidget =
(context as ScrollableState)?.context?.findAncestorWidgetOfExactType<
NestedScrollViewInnerScrollPositionKeyWidget>();
_key = keyWidget?.scrollPositionKey;
//
// var b= a.widget.viewportBuilder(a.context,this);
// (this.context as ScrollableState).widget.viewportBuilder()
//print(_key);
return _key;
}
void clearScrollPositionKey() {
_key = null;
}
// Returns the amount of delta that was not used.
//
// Positive delta means going down (exposing stuff above), negative delta
// going up (exposing stuff below).
double applyClampedDragUpdate(double delta) {
assert(delta != 0.0);
// If we are going towards the maxScrollExtent (negative scroll offset),
// then the furthest we can be in the minScrollExtent direction is negative
// infinity. For example, if we are already overscrolled, then scrolling to
// reduce the overscroll should not disallow the overscroll.
//
// If we are going towards the minScrollExtent (positive scroll offset),
// then the furthest we can be in the minScrollExtent direction is wherever
// we are now, if we are already overscrolled (in which case pixels is less
// than the minScrollExtent), or the minScrollExtent if we are not.
//
// In other words, we cannot, via applyClampedDragUpdate, _enter_ an
// overscroll situation.
//
// An overscroll situation might be nonetheless entered via several means.
// One is if the physics allow it, via applyFullDragUpdate (see below). An
// overscroll situation can also be forced, e.g. if the scroll position is
// artificially set using the scroll controller.
final double min =
delta < 0.0 ? -double.infinity : math.min(minScrollExtent, pixels);
// The logic for max is equivalent but on the other side.
final double max =
delta > 0.0 ? double.infinity : math.max(maxScrollExtent, pixels);
final double oldPixels = pixels;
final double newPixels = (pixels - delta).clamp(min, max) as double;
final double clampedDelta = newPixels - pixels;
if (clampedDelta == 0.0) {
return delta;
}
final double overscroll = physics.applyBoundaryConditions(this, newPixels);
final double actualNewPixels = newPixels - overscroll;
final double offset = actualNewPixels - oldPixels;
if (offset != 0.0) {
forcePixels(actualNewPixels);
didUpdateScrollPositionBy(offset);
}
// if (debugLabel == 'inner') {
// print("applyClampedDragUpdate$key");
// }
return delta + offset;
}
// Returns the overscroll.
double applyFullDragUpdate(double delta) {
assert(delta != 0.0);
final double oldPixels = pixels;
// Apply friction:
final double newPixels =
pixels - physics.applyPhysicsToUserOffset(this, delta);
if (oldPixels == newPixels)
return 0.0; // delta must have been so small we dropped it during floating point addition
// Check for overscroll:
final double overscroll = physics.applyBoundaryConditions(this, newPixels);
final double actualNewPixels = newPixels - overscroll;
if (actualNewPixels != oldPixels) {
forcePixels(actualNewPixels);
didUpdateScrollPositionBy(actualNewPixels - oldPixels);
}
if (overscroll != 0.0) {
didOverscrollBy(overscroll);
return overscroll;
}
// if (debugLabel == 'inner') {
// print("applyFullDragUpdate$key");
// }
return 0.0;
}
// Returns the amount of delta that was not used.
//
// Negative delta represents a forward ScrollDirection, while the positive
// would be a reverse ScrollDirection.
//
// The method doesn't take into account the effects of [ScrollPhysics].
double applyClampedPointerSignalUpdate(double delta) {
assert(delta != 0.0);
final double min =
delta > 0.0 ? -double.infinity : math.min(minScrollExtent, pixels);
// The logic for max is equivalent but on the other side.
final double max =
delta < 0.0 ? double.infinity : math.max(maxScrollExtent, pixels);
final double newPixels = (pixels + delta).clamp(min, max) as double;
final double clampedDelta = newPixels - pixels;
if (clampedDelta == 0.0) {
return delta;
}
forcePixels(newPixels);
didUpdateScrollPositionBy(clampedDelta);
return delta - clampedDelta;
}
@override
ScrollDirection get userScrollDirection => coordinator.userScrollDirection;
DrivenScrollActivity createDrivenScrollActivity(
double to, Duration duration, Curve curve) {
return DrivenScrollActivity(
this,
from: pixels,
to: to,
duration: duration,
curve: curve,
vsync: vsync,
);
}
@override
double applyUserOffset(double delta) {
assert(false);
return 0.0;
}
// This is called by activities when they finish their work.
@override
void goIdle() {
beginActivity(IdleScrollActivity(this));
}
// This is called by activities when they finish their work and want to go ballistic.
@override
void goBallistic(double velocity) {
Simulation simulation;
if (velocity != 0.0 || outOfRange)
simulation = physics.createBallisticSimulation(this, velocity);
beginActivity(createBallisticScrollActivity(
simulation,
mode: _NestedBallisticScrollActivityMode.independent,
));
}
ScrollActivity createBallisticScrollActivity(
Simulation simulation, {
@required _NestedBallisticScrollActivityMode mode,
_NestedScrollMetrics metrics,
}) {
if (simulation == null) {
return IdleScrollActivity(this);
}
assert(mode != null);
switch (mode) {
case _NestedBallisticScrollActivityMode.outer:
assert(metrics != null);
if (metrics.minRange == metrics.maxRange)
return IdleScrollActivity(this);
return _NestedOuterBallisticScrollActivity(
coordinator, this, metrics, simulation, context.vsync);
case _NestedBallisticScrollActivityMode.inner:
return _NestedInnerBallisticScrollActivity(
coordinator, this, simulation, context.vsync);
case _NestedBallisticScrollActivityMode.independent:
return BallisticScrollActivity(this, simulation, context.vsync);
}
return null;
}
@override
Future<void> animateTo(
double to, {
@required Duration duration,
@required Curve curve,
}) {
return coordinator.animateTo(coordinator.unnestOffset(to, this),
duration: duration, curve: curve);
}
@override
void jumpTo(double value) {
return coordinator.jumpTo(coordinator.unnestOffset(value, this));
}
// @override
// void pointerScroll(double delta) {
// return coordinator.pointerScroll(delta);
// }
@override
void jumpToWithoutSettling(double value) {
assert(false);
}
void localJumpTo(double value) {
if (pixels != value) {
final double oldPixels = pixels;
forcePixels(value);
didStartScroll();
didUpdateScrollPositionBy(pixels - oldPixels);
didEndScroll();
}
}
@override
void applyNewDimensions() {
super.applyNewDimensions();
coordinator.updateCanDrag();
}
void updateCanDrag(double totalExtent) {
context.setCanDrag(totalExtent > (viewportDimension - maxScrollExtent) ||
minScrollExtent != maxScrollExtent);
}
@override
ScrollHoldController hold(VoidCallback holdCancelCallback) {
return coordinator.hold(holdCancelCallback);
}
@override
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
return coordinator.drag(details, dragCancelCallback);
}
@override
void dispose() {
_parent?.detach(this);
super.dispose();
}
@override
void pointerScroll(double delta) {
}
}
enum _NestedBallisticScrollActivityMode { outer, inner, independent }
class _NestedInnerBallisticScrollActivity extends BallisticScrollActivity {
_NestedInnerBallisticScrollActivity(
this.coordinator,
_NestedScrollPosition position,
Simulation simulation,
TickerProvider vsync,
) : super(position, simulation, vsync);
final _NestedScrollCoordinator coordinator;
@override
_NestedScrollPosition get delegate => super.delegate as _NestedScrollPosition;
@override
void resetActivity() {
delegate.beginActivity(
coordinator.createInnerBallisticScrollActivity(delegate, velocity));
}
@override
void applyNewDimensions() {
delegate.beginActivity(
coordinator.createInnerBallisticScrollActivity(delegate, velocity));
}
@override
bool applyMoveTo(double value) {
return super.applyMoveTo(coordinator.nestOffset(value, delegate));
}
}
class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity {
_NestedOuterBallisticScrollActivity(
this.coordinator,
_NestedScrollPosition position,
this.metrics,
Simulation simulation,
TickerProvider vsync,
) : assert(metrics.minRange != metrics.maxRange),
assert(metrics.maxRange > metrics.minRange),
super(position, simulation, vsync);
final _NestedScrollCoordinator coordinator;
final _NestedScrollMetrics metrics;
@override
_NestedScrollPosition get delegate => super.delegate as _NestedScrollPosition;
@override
void resetActivity() {
delegate.beginActivity(
coordinator.createOuterBallisticScrollActivity(velocity));
}
@override
void applyNewDimensions() {
delegate.beginActivity(
coordinator.createOuterBallisticScrollActivity(velocity));
}
@override
bool applyMoveTo(double value) {
bool done = false;
if (velocity > 0.0) {
if (value < metrics.minRange) {
return true;
}
if (value > metrics.maxRange) {
value = metrics.maxRange;
done = true;
}
} else if (velocity < 0.0) {
if (value > metrics.maxRange) {
return true;
}
if (value < metrics.minRange) {
value = metrics.minRange;
done = true;
}
} else {
value = value.clamp(metrics.minRange, metrics.maxRange) as double;
done = true;
}
final bool result = super.applyMoveTo(value + metrics.correctionOffset);
//to to
assert(
result); // since we tried to pass an in-range value, it shouldn't ever overflow
return !done;
}
@override
String toString() {
return '$runtimeType(${metrics.minRange} .. ${metrics.maxRange}; correcting by ${metrics.correctionOffset})';
}
}
///get type from T
Type typeOf<T>() => T;
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.8.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.15.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.7.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.4.2"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=1.22.4"
name: extended_nested_scroll_view
description: extended nested scroll view to fix pinned header and inner scrollables sync issues.
version: 2.5.0
homepage: https://github.com/fluttercandies/extended_nested_scroll_view
environment:
sdk: ">=2.6.0 <3.0.0"
flutter: ">=1.22.4"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment