Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
9f176fc8
Commit
9f176fc8
authored
Sep 24, 2015
by
Adam Barth
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Port widgets that depend on scrolling to fn3
parent
042f49a7
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1449 additions
and
2 deletions
+1449
-2
date_picker.dart
packages/flutter/lib/src/fn3/date_picker.dart
+403
-0
dialog.dart
packages/flutter/lib/src/fn3/dialog.dart
+169
-0
drawer.dart
packages/flutter/lib/src/fn3/drawer.dart
+148
-0
navigator.dart
packages/flutter/lib/src/fn3/navigator.dart
+2
-2
snack_bar.dart
packages/flutter/lib/src/fn3/snack_bar.dart
+107
-0
tabs.dart
packages/flutter/lib/src/fn3/tabs.dart
+620
-0
No files found.
packages/flutter/lib/src/fn3/date_picker.dart
0 → 100644
View file @
9f176fc8
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'package:intl/date_symbols.dart'
;
import
'package:intl/intl.dart'
;
import
'package:sky/material.dart'
;
import
'package:sky/painting.dart'
;
import
'package:sky/services.dart'
;
import
'package:sky/src/fn3/basic.dart'
;
import
'package:sky/src/fn3/framework.dart'
;
import
'package:sky/src/fn3/gesture_detector.dart'
;
import
'package:sky/src/fn3/ink_well.dart'
;
import
'package:sky/src/fn3/scrollable.dart'
;
import
'package:sky/src/fn3/theme.dart'
;
typedef
void
DatePickerValueChanged
(
DateTime
dateTime
);
enum
DatePickerMode
{
day
,
year
}
typedef
void
DatePickerModeChanged
(
DatePickerMode
value
);
class
DatePicker
extends
StatefulComponent
{
DatePicker
({
this
.
selectedDate
,
this
.
onChanged
,
this
.
firstDate
,
this
.
lastDate
})
{
assert
(
selectedDate
!=
null
);
assert
(
firstDate
!=
null
);
assert
(
lastDate
!=
null
);
}
final
DateTime
selectedDate
;
final
DatePickerValueChanged
onChanged
;
final
DateTime
firstDate
;
final
DateTime
lastDate
;
DatePickerState
createState
()
=>
new
DatePickerState
(
this
);
}
class
DatePickerState
extends
ComponentState
<
DatePicker
>
{
DatePickerState
(
DatePicker
config
)
:
super
(
config
);
DatePickerMode
_mode
=
DatePickerMode
.
day
;
void
_handleModeChanged
(
DatePickerMode
mode
)
{
userFeedback
.
performHapticFeedback
(
HapticFeedbackType
.
VIRTUAL_KEY
);
setState
(()
{
_mode
=
mode
;
});
}
void
_handleYearChanged
(
DateTime
dateTime
)
{
userFeedback
.
performHapticFeedback
(
HapticFeedbackType
.
VIRTUAL_KEY
);
setState
(()
{
_mode
=
DatePickerMode
.
day
;
});
if
(
config
.
onChanged
!=
null
)
config
.
onChanged
(
dateTime
);
}
void
_handleDayChanged
(
DateTime
dateTime
)
{
userFeedback
.
performHapticFeedback
(
HapticFeedbackType
.
VIRTUAL_KEY
);
if
(
config
.
onChanged
!=
null
)
config
.
onChanged
(
dateTime
);
}
static
const
double
_calendarHeight
=
210.0
;
Widget
build
(
BuildContext
context
)
{
Widget
header
=
new
DatePickerHeader
(
selectedDate:
config
.
selectedDate
,
mode:
_mode
,
onModeChanged:
_handleModeChanged
);
Widget
picker
;
switch
(
_mode
)
{
case
DatePickerMode
.
day
:
picker
=
new
MonthPicker
(
selectedDate:
config
.
selectedDate
,
onChanged:
_handleDayChanged
,
firstDate:
config
.
firstDate
,
lastDate:
config
.
lastDate
,
itemExtent:
_calendarHeight
);
break
;
case
DatePickerMode
.
year
:
picker
=
new
YearPicker
(
selectedDate:
config
.
selectedDate
,
onChanged:
_handleYearChanged
,
firstDate:
config
.
firstDate
,
lastDate:
config
.
lastDate
);
break
;
}
return
new
Column
([
header
,
new
Container
(
height:
_calendarHeight
,
child:
picker
)
],
alignItems:
FlexAlignItems
.
stretch
);
}
}
// Shows the selected date in large font and toggles between year and day mode
class
DatePickerHeader
extends
StatelessComponent
{
DatePickerHeader
({
this
.
selectedDate
,
this
.
mode
,
this
.
onModeChanged
})
{
assert
(
selectedDate
!=
null
);
assert
(
mode
!=
null
);
}
DateTime
selectedDate
;
DatePickerMode
mode
;
DatePickerModeChanged
onModeChanged
;
void
_handleChangeMode
(
DatePickerMode
value
)
{
if
(
value
!=
mode
)
onModeChanged
(
value
);
}
Widget
build
(
BuildContext
context
)
{
ThemeData
theme
=
Theme
.
of
(
context
);
TextTheme
headerTheme
;
Color
dayColor
;
Color
yearColor
;
switch
(
theme
.
primaryColorBrightness
)
{
case
ThemeBrightness
.
light
:
headerTheme
=
Typography
.
black
;
dayColor
=
mode
==
DatePickerMode
.
day
?
Colors
.
black87
:
Colors
.
black54
;
yearColor
=
mode
==
DatePickerMode
.
year
?
Colors
.
black87
:
Colors
.
black54
;
break
;
case
ThemeBrightness
.
dark
:
headerTheme
=
Typography
.
white
;
dayColor
=
mode
==
DatePickerMode
.
day
?
Colors
.
white87
:
Colors
.
white54
;
yearColor
=
mode
==
DatePickerMode
.
year
?
Colors
.
white87
:
Colors
.
white54
;
break
;
}
TextStyle
dayStyle
=
headerTheme
.
display3
.
copyWith
(
color:
dayColor
,
height:
1.0
,
fontSize:
100.0
);
TextStyle
monthStyle
=
headerTheme
.
headline
.
copyWith
(
color:
dayColor
,
height:
1.0
);
TextStyle
yearStyle
=
headerTheme
.
headline
.
copyWith
(
color:
yearColor
,
height:
1.0
);
return
new
Container
(
padding:
new
EdgeDims
.
all
(
10.0
),
decoration:
new
BoxDecoration
(
backgroundColor:
theme
.
primaryColor
),
child:
new
Column
([
new
GestureDetector
(
onTap:
()
=>
_handleChangeMode
(
DatePickerMode
.
day
),
child:
new
Text
(
new
DateFormat
(
"MMM"
).
format
(
selectedDate
).
toUpperCase
(),
style:
monthStyle
)
),
new
GestureDetector
(
onTap:
()
=>
_handleChangeMode
(
DatePickerMode
.
day
),
child:
new
Text
(
new
DateFormat
(
"d"
).
format
(
selectedDate
),
style:
dayStyle
)
),
new
GestureDetector
(
onTap:
()
=>
_handleChangeMode
(
DatePickerMode
.
year
),
child:
new
Text
(
new
DateFormat
(
"yyyy"
).
format
(
selectedDate
),
style:
yearStyle
)
)
])
);
}
}
// Fixed height component shows a single month and allows choosing a day
class
DayPicker
extends
StatelessComponent
{
DayPicker
({
this
.
selectedDate
,
this
.
currentDate
,
this
.
onChanged
,
this
.
displayedMonth
})
{
assert
(
selectedDate
!=
null
);
assert
(
currentDate
!=
null
);
assert
(
onChanged
!=
null
);
assert
(
displayedMonth
!=
null
);
}
final
DateTime
selectedDate
;
final
DateTime
currentDate
;
final
DatePickerValueChanged
onChanged
;
final
DateTime
displayedMonth
;
Widget
build
(
BuildContext
context
)
{
ThemeData
theme
=
Theme
.
of
(
context
);
TextStyle
headerStyle
=
theme
.
text
.
caption
.
copyWith
(
fontWeight:
FontWeight
.
w700
);
TextStyle
monthStyle
=
headerStyle
.
copyWith
(
fontSize:
14.0
,
height:
24.0
/
14.0
);
TextStyle
dayStyle
=
headerStyle
.
copyWith
(
fontWeight:
FontWeight
.
w500
);
DateFormat
dateFormat
=
new
DateFormat
();
DateSymbols
symbols
=
dateFormat
.
dateSymbols
;
List
<
Text
>
headers
=
[];
for
(
String
weekDay
in
symbols
.
NARROWWEEKDAYS
)
{
headers
.
add
(
new
Text
(
weekDay
,
style:
headerStyle
));
}
List
<
Widget
>
rows
=
[
new
Text
(
new
DateFormat
(
"MMMM y"
).
format
(
displayedMonth
),
style:
monthStyle
),
new
Flex
(
headers
,
justifyContent:
FlexJustifyContent
.
spaceAround
)
];
int
year
=
displayedMonth
.
year
;
int
month
=
displayedMonth
.
month
;
// Dart's Date time constructor is very forgiving and will understand
// month 13 as January of the next year. :)
int
daysInMonth
=
new
DateTime
(
year
,
month
+
1
).
difference
(
new
DateTime
(
year
,
month
)).
inDays
;
int
firstDay
=
new
DateTime
(
year
,
month
).
day
;
int
weeksShown
=
6
;
List
<
int
>
days
=
[
DateTime
.
SUNDAY
,
DateTime
.
MONDAY
,
DateTime
.
TUESDAY
,
DateTime
.
WEDNESDAY
,
DateTime
.
THURSDAY
,
DateTime
.
FRIDAY
,
DateTime
.
SATURDAY
];
int
daySlots
=
weeksShown
*
days
.
length
;
List
<
Widget
>
labels
=
[];
for
(
int
i
=
0
;
i
<
daySlots
;
i
++)
{
// This assumes a start day of SUNDAY, but could be changed.
int
day
=
i
-
firstDay
+
1
;
Widget
item
;
if
(
day
<
1
||
day
>
daysInMonth
)
{
item
=
new
Text
(
""
);
}
else
{
// Put a light circle around the selected day
BoxDecoration
decoration
=
null
;
if
(
selectedDate
.
year
==
year
&&
selectedDate
.
month
==
month
&&
selectedDate
.
day
==
day
)
decoration
=
new
BoxDecoration
(
backgroundColor:
theme
.
primarySwatch
[
100
],
shape:
Shape
.
circle
);
// Use a different font color for the current day
TextStyle
itemStyle
=
dayStyle
;
if
(
currentDate
.
year
==
year
&&
currentDate
.
month
==
month
&&
currentDate
.
day
==
day
)
itemStyle
=
itemStyle
.
copyWith
(
color:
theme
.
primaryColor
);
item
=
new
GestureDetector
(
onTap:
()
{
DateTime
result
=
new
DateTime
(
year
,
month
,
day
);
onChanged
(
result
);
},
child:
new
Container
(
height:
30.0
,
decoration:
decoration
,
child:
new
Center
(
child:
new
Text
(
day
.
toString
(),
style:
itemStyle
)
)
)
);
}
labels
.
add
(
new
Flexible
(
child:
item
));
}
for
(
int
w
=
0
;
w
<
weeksShown
;
w
++)
{
int
startIndex
=
w
*
days
.
length
;
rows
.
add
(
new
Row
(
labels
.
sublist
(
startIndex
,
startIndex
+
days
.
length
)
));
}
return
new
Column
(
rows
);
}
}
// Scrollable list of DayPickers to allow choosing a month
class
MonthPicker
extends
ScrollableWidgetList
{
MonthPicker
({
this
.
selectedDate
,
this
.
onChanged
,
this
.
firstDate
,
this
.
lastDate
,
double
itemExtent
})
:
super
(
itemExtent:
itemExtent
)
{
assert
(
selectedDate
!=
null
);
assert
(
onChanged
!=
null
);
assert
(
lastDate
.
isAfter
(
firstDate
));
}
final
DateTime
selectedDate
;
final
DatePickerValueChanged
onChanged
;
final
DateTime
firstDate
;
final
DateTime
lastDate
;
MonthPickerState
createState
()
=>
new
MonthPickerState
(
this
);
}
class
MonthPickerState
extends
ScrollableWidgetListState
<
MonthPicker
>
{
MonthPickerState
(
MonthPicker
config
)
:
super
(
config
)
{
_updateCurrentDate
();
}
DateTime
_currentDate
;
Timer
_timer
;
void
_updateCurrentDate
()
{
_currentDate
=
new
DateTime
.
now
();
DateTime
tomorrow
=
new
DateTime
(
_currentDate
.
year
,
_currentDate
.
month
,
_currentDate
.
day
+
1
);
Duration
timeUntilTomorrow
=
tomorrow
.
difference
(
_currentDate
);
timeUntilTomorrow
+=
const
Duration
(
seconds:
1
);
// so we don't miss it by rounding
if
(
_timer
!=
null
)
_timer
.
cancel
();
_timer
=
new
Timer
(
timeUntilTomorrow
,
()
{
setState
(()
{
_updateCurrentDate
();
});
});
}
int
get
itemCount
=>
(
config
.
lastDate
.
year
-
config
.
firstDate
.
year
)
*
12
+
config
.
lastDate
.
month
-
config
.
firstDate
.
month
+
1
;
List
<
Widget
>
buildItems
(
BuildContext
context
,
int
start
,
int
count
)
{
List
<
Widget
>
result
=
new
List
<
Widget
>();
DateTime
startDate
=
new
DateTime
(
config
.
firstDate
.
year
+
start
~/
12
,
config
.
firstDate
.
month
+
start
%
12
);
for
(
int
i
=
0
;
i
<
count
;
++
i
)
{
DateTime
displayedMonth
=
new
DateTime
(
startDate
.
year
+
i
~/
12
,
startDate
.
month
+
i
%
12
);
Widget
item
=
new
Container
(
height:
config
.
itemExtent
,
key:
new
ObjectKey
(
displayedMonth
),
child:
new
DayPicker
(
selectedDate:
config
.
selectedDate
,
currentDate:
_currentDate
,
onChanged:
config
.
onChanged
,
displayedMonth:
displayedMonth
)
);
result
.
add
(
item
);
}
return
result
;
}
void
dispose
()
{
if
(
_timer
!=
null
)
_timer
.
cancel
();
}
}
// Scrollable list of years to allow picking a year
class
YearPicker
extends
ScrollableWidgetList
{
YearPicker
({
this
.
selectedDate
,
this
.
onChanged
,
this
.
firstDate
,
this
.
lastDate
})
:
super
(
itemExtent:
50.0
)
{
assert
(
selectedDate
!=
null
);
assert
(
onChanged
!=
null
);
assert
(
lastDate
.
isAfter
(
firstDate
));
}
final
DateTime
selectedDate
;
final
DatePickerValueChanged
onChanged
;
final
DateTime
firstDate
;
final
DateTime
lastDate
;
YearPickerState
createState
()
=>
new
YearPickerState
(
this
);
}
class
YearPickerState
extends
ScrollableWidgetListState
<
YearPicker
>
{
YearPickerState
(
YearPicker
config
)
:
super
(
config
);
int
get
itemCount
=>
config
.
lastDate
.
year
-
config
.
firstDate
.
year
+
1
;
List
<
Widget
>
buildItems
(
BuildContext
context
,
int
start
,
int
count
)
{
TextStyle
style
=
Theme
.
of
(
context
).
text
.
body1
.
copyWith
(
color:
Colors
.
black54
);
List
<
Widget
>
items
=
new
List
<
Widget
>();
for
(
int
i
=
start
;
i
<
start
+
count
;
i
++)
{
int
year
=
config
.
firstDate
.
year
+
i
;
String
label
=
year
.
toString
();
Widget
item
=
new
GestureDetector
(
key:
new
Key
(
label
),
onTap:
()
{
DateTime
result
=
new
DateTime
(
year
,
config
.
selectedDate
.
month
,
config
.
selectedDate
.
day
);
config
.
onChanged
(
result
);
},
child:
new
InkWell
(
child:
new
Container
(
height:
config
.
itemExtent
,
decoration:
year
==
config
.
selectedDate
.
year
?
new
BoxDecoration
(
backgroundColor:
Theme
.
of
(
context
).
primarySwatch
[
100
],
shape:
Shape
.
circle
)
:
null
,
child:
new
Center
(
child:
new
Text
(
label
,
style:
style
)
)
)
)
);
items
.
add
(
item
);
}
return
items
;
}
}
packages/flutter/lib/src/fn3/dialog.dart
0 → 100644
View file @
9f176fc8
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'package:sky/animation.dart'
;
import
'package:sky/material.dart'
;
import
'package:sky/src/fn3/basic.dart'
;
import
'package:sky/src/fn3/focus.dart'
;
import
'package:sky/src/fn3/framework.dart'
;
import
'package:sky/src/fn3/gesture_detector.dart'
;
import
'package:sky/src/fn3/material.dart'
;
import
'package:sky/src/fn3/navigator.dart'
;
import
'package:sky/src/fn3/scrollable.dart'
;
import
'package:sky/src/fn3/theme.dart'
;
import
'package:sky/src/fn3/transitions.dart'
;
typedef
Widget
DialogBuilder
(
Navigator
navigator
);
/// A material design dialog
///
/// <https://www.google.com/design/spec/components/dialogs.html>
class
Dialog
extends
StatelessComponent
{
Dialog
({
Key
key
,
this
.
title
,
this
.
titlePadding
,
this
.
content
,
this
.
contentPadding
,
this
.
actions
,
this
.
onDismiss
}):
super
(
key:
key
);
/// The (optional) title of the dialog is displayed in a large font at the top
/// of the dialog.
final
Widget
title
;
// Padding around the title; uses material design default if none is supplied
// If there is no title, no padding will be provided
final
EdgeDims
titlePadding
;
/// The (optional) content of the dialog is displayed in the center of the
/// dialog in a lighter font.
final
Widget
content
;
// Padding around the content; uses material design default if none is supplied
final
EdgeDims
contentPadding
;
/// The (optional) set of actions that are displayed at the bottom of the
/// dialog.
final
List
<
Widget
>
actions
;
/// An (optional) callback that is called when the dialog is dismissed.
final
Function
onDismiss
;
Color
_getColor
(
BuildContext
context
)
{
switch
(
Theme
.
of
(
context
).
brightness
)
{
case
ThemeBrightness
.
light
:
return
Colors
.
white
;
case
ThemeBrightness
.
dark
:
return
Colors
.
grey
[
800
];
}
}
Widget
build
(
BuildContext
context
)
{
List
<
Widget
>
dialogBody
=
new
List
<
Widget
>();
if
(
title
!=
null
)
{
EdgeDims
padding
=
titlePadding
;
if
(
padding
==
null
)
padding
=
new
EdgeDims
(
24.0
,
24.0
,
content
==
null
?
20.0
:
0.0
,
24.0
);
dialogBody
.
add
(
new
Padding
(
padding:
padding
,
child:
new
DefaultTextStyle
(
style:
Theme
.
of
(
context
).
text
.
title
,
child:
title
)
));
}
if
(
content
!=
null
)
{
EdgeDims
padding
=
contentPadding
;
if
(
padding
==
null
)
padding
=
const
EdgeDims
(
20.0
,
24.0
,
24.0
,
24.0
);
dialogBody
.
add
(
new
Padding
(
padding:
padding
,
child:
new
DefaultTextStyle
(
style:
Theme
.
of
(
context
).
text
.
subhead
,
child:
content
)
));
}
if
(
actions
!=
null
)
{
dialogBody
.
add
(
new
Container
(
child:
new
Row
(
actions
,
justifyContent:
FlexJustifyContent
.
end
)
));
}
return
new
Stack
([
new
GestureDetector
(
onTap:
onDismiss
,
child:
new
Container
(
decoration:
const
BoxDecoration
(
backgroundColor:
const
Color
(
0x7F000000
)
)
)
),
new
Center
(
child:
new
Container
(
margin:
new
EdgeDims
.
symmetric
(
horizontal:
40.0
,
vertical:
24.0
),
child:
new
ConstrainedBox
(
constraints:
new
BoxConstraints
(
minWidth:
280.0
),
child:
new
Material
(
level:
4
,
color:
_getColor
(
context
),
child:
new
IntrinsicWidth
(
child:
new
Block
(
dialogBody
)
)
)
)
)
)
]);
}
}
const
Duration
_kTransitionDuration
=
const
Duration
(
milliseconds:
150
);
class
DialogRoute
extends
RouteBase
{
DialogRoute
({
this
.
completer
,
this
.
builder
});
final
Completer
completer
;
final
RouteBuilder
builder
;
Duration
get
transitionDuration
=>
_kTransitionDuration
;
bool
get
isOpaque
=>
false
;
Widget
build
(
Key
key
,
NavigatorState
navigator
,
WatchableAnimationPerformance
performance
)
{
return
new
FadeTransition
(
performance:
performance
,
opacity:
new
AnimatedValue
<
double
>(
0.0
,
end:
1.0
,
curve:
easeOut
),
child:
builder
(
navigator
,
this
)
);
}
void
popState
([
dynamic
result
])
{
completer
.
complete
(
result
);
}
}
Future
showDialog
(
NavigatorState
navigator
,
DialogBuilder
builder
)
{
Completer
completer
=
new
Completer
();
navigator
.
push
(
new
DialogRoute
(
completer:
completer
,
builder:
(
navigator
,
route
)
{
return
new
Focus
(
key:
new
GlobalObjectKey
(
route
),
autofocus:
true
,
child:
builder
(
navigator
)
);
}
));
return
completer
.
future
;
}
packages/flutter/lib/src/fn3/drawer.dart
0 → 100644
View file @
9f176fc8
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'package:sky/animation.dart'
;
import
'package:sky/material.dart'
;
import
'package:sky/src/fn3/framework.dart'
;
import
'package:sky/src/fn3/basic.dart'
;
import
'package:sky/src/fn3/gesture_detector.dart'
;
import
'package:sky/src/fn3/navigator.dart'
;
import
'package:sky/src/fn3/scrollable.dart'
;
import
'package:sky/src/fn3/theme.dart'
;
import
'package:sky/src/fn3/transitions.dart'
;
// TODO(eseidel): Draw width should vary based on device size:
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav
// Mobile:
// Width = Screen width − 56 dp
// Maximum width: 320dp
// Maximum width applies only when using a left nav. When using a right nav,
// the panel can cover the full width of the screen.
// Desktop/Tablet:
// Maximum width for a left nav is 400dp.
// The right nav can vary depending on content.
const
double
_kWidth
=
304.0
;
const
double
_kMinFlingVelocity
=
365.0
;
const
double
_kFlingVelocityScale
=
1.0
/
300.0
;
const
Duration
_kBaseSettleDuration
=
const
Duration
(
milliseconds:
246
);
const
Duration
_kThemeChangeDuration
=
const
Duration
(
milliseconds:
200
);
const
Point
_kOpenPosition
=
Point
.
origin
;
const
Point
_kClosedPosition
=
const
Point
(-
_kWidth
,
0.0
);
typedef
void
DrawerDismissedCallback
(
);
class
Drawer
extends
StatefulComponent
{
Drawer
({
Key
key
,
this
.
children
,
this
.
showing
:
false
,
this
.
level
:
0
,
this
.
onDismissed
,
this
.
navigator
})
:
super
(
key:
key
);
final
List
<
Widget
>
children
;
final
bool
showing
;
final
int
level
;
final
DrawerDismissedCallback
onDismissed
;
final
NavigatorState
navigator
;
DrawerState
createState
()
=>
new
DrawerState
(
this
);
}
class
DrawerState
extends
ComponentState
<
Drawer
>
{
DrawerState
(
Drawer
config
)
:
super
(
config
)
{
_performance
=
new
AnimationPerformance
(
duration:
_kBaseSettleDuration
);
_performance
.
addStatusListener
((
AnimationStatus
status
)
{
if
(
status
==
AnimationStatus
.
dismissed
)
_handleDismissed
();
});
// Use a spring force for animating the drawer. We can't use curves for
// this because we need a linear curve in order to track the user's finger
// while dragging.
_performance
.
attachedForce
=
kDefaultSpringForce
;
if
(
config
.
navigator
!=
null
)
{
// TODO(ianh): This is crazy. We should convert drawer to use a pattern like openDialog().
// https://github.com/domokit/sky_engine/pull/1186
scheduleMicrotask
(()
{
config
.
navigator
.
pushState
(
this
,
(
_
)
=>
_performance
.
reverse
());
});
}
_performance
.
play
(
_direction
);
}
AnimationPerformance
_performance
;
Direction
get
_direction
=>
config
.
showing
?
Direction
.
forward
:
Direction
.
reverse
;
void
didUpdateConfig
(
Drawer
oldConfig
)
{
if
(
config
.
showing
!=
oldConfig
.
showing
)
_performance
.
play
(
_direction
);
}
Widget
build
(
BuildContext
context
)
{
var
mask
=
new
GestureDetector
(
child:
new
ColorTransition
(
performance:
_performance
.
view
,
color:
new
AnimatedColorValue
(
Colors
.
transparent
,
end:
const
Color
(
0x7F000000
)),
child:
new
Container
()
),
onTap:
()
{
_performance
.
reverse
();
}
);
Widget
content
=
new
SlideTransition
(
performance:
_performance
.
view
,
position:
new
AnimatedValue
<
Point
>(
_kClosedPosition
,
end:
_kOpenPosition
),
// TODO(abarth): Use AnimatedContainer
child:
new
Container
(
// behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
decoration:
new
BoxDecoration
(
backgroundColor:
Theme
.
of
(
context
).
canvasColor
,
boxShadow:
shadows
[
config
.
level
]),
width:
_kWidth
,
child:
new
Block
(
config
.
children
)
)
);
return
new
GestureDetector
(
onHorizontalDragStart:
_performance
.
stop
,
onHorizontalDragUpdate:
_handleDragUpdate
,
onHorizontalDragEnd:
_handleDragEnd
,
child:
new
Stack
([
mask
,
content
])
);
}
void
_handleDismissed
()
{
if
(
config
.
navigator
!=
null
&&
config
.
navigator
.
currentRoute
is
RouteState
&&
(
config
.
navigator
.
currentRoute
as
RouteState
).
owner
==
this
)
// TODO(ianh): remove cast once analyzer is cleverer
config
.
navigator
.
pop
();
if
(
config
.
onDismissed
!=
null
)
config
.
onDismissed
();
}
bool
get
_isMostlyClosed
=>
_performance
.
progress
<
0.5
;
void
_settle
()
{
_isMostlyClosed
?
_performance
.
reverse
()
:
_performance
.
play
();
}
void
_handleDragUpdate
(
double
delta
)
{
_performance
.
progress
+=
delta
/
_kWidth
;
}
void
_handleDragEnd
(
Offset
velocity
)
{
if
(
velocity
.
dx
.
abs
()
>=
_kMinFlingVelocity
)
{
_performance
.
fling
(
velocity:
velocity
.
dx
*
_kFlingVelocityScale
);
}
else
{
_settle
();
}
}
}
packages/flutter/lib/src/fn3/navigator.dart
View file @
9f176fc8
...
...
@@ -90,7 +90,7 @@ class RouteState extends RouteBase {
Function
callback
;
RouteBase
route
;
StatefulComponent
owner
;
ComponentState
owner
;
bool
get
isOpaque
=>
false
;
...
...
@@ -160,7 +160,7 @@ class NavigatorState extends ComponentState<Navigator> {
RouteBase
get
currentRoute
=>
config
.
history
.
currentRoute
;
void
pushState
(
StatefulComponent
owner
,
Function
callback
)
{
void
pushState
(
ComponentState
owner
,
Function
callback
)
{
RouteBase
route
=
new
RouteState
(
owner:
owner
,
callback:
callback
,
...
...
packages/flutter/lib/src/fn3/snack_bar.dart
0 → 100644
View file @
9f176fc8
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:sky/animation.dart'
;
import
'package:sky/painting.dart'
;
import
'package:sky/material.dart'
;
import
'package:sky/src/fn3/animated_component.dart'
;
import
'package:sky/src/fn3/basic.dart'
;
import
'package:sky/src/fn3/framework.dart'
;
import
'package:sky/src/fn3/gesture_detector.dart'
;
import
'package:sky/src/fn3/material.dart'
;
import
'package:sky/src/fn3/theme.dart'
;
import
'package:sky/src/fn3/transitions.dart'
;
typedef
void
SnackBarDismissedCallback
(
);
const
Duration
_kSlideInDuration
=
const
Duration
(
milliseconds:
200
);
// TODO(ianh): factor out some of the constants below
class
SnackBarAction
extends
StatelessComponent
{
SnackBarAction
({
Key
key
,
this
.
label
,
this
.
onPressed
})
:
super
(
key:
key
)
{
assert
(
label
!=
null
);
}
final
String
label
;
final
Function
onPressed
;
Widget
build
(
BuildContext
)
{
return
new
GestureDetector
(
onTap:
onPressed
,
child:
new
Container
(
margin:
const
EdgeDims
.
only
(
left:
24.0
),
padding:
const
EdgeDims
.
only
(
top:
14.0
,
bottom:
14.0
),
child:
new
Text
(
label
)
)
);
}
}
class
SnackBar
extends
AnimatedComponent
{
SnackBar
({
Key
key
,
this
.
transitionKey
,
this
.
content
,
this
.
actions
,
bool
showing
,
this
.
onDismissed
})
:
super
(
key:
key
,
direction:
showing
?
Direction
.
forward
:
Direction
.
reverse
,
duration:
_kSlideInDuration
)
{
assert
(
content
!=
null
);
}
final
Key
transitionKey
;
final
Widget
content
;
final
List
<
SnackBarAction
>
actions
;
final
SnackBarDismissedCallback
onDismissed
;
SnackBarState
createState
()
=>
new
SnackBarState
(
this
);
}
class
SnackBarState
extends
AnimatedComponentState
<
SnackBar
>
{
SnackBarState
(
SnackBar
config
)
:
super
(
config
);
void
handleDismissed
()
{
if
(
config
.
onDismissed
!=
null
)
config
.
onDismissed
();
}
Widget
build
(
BuildContext
context
)
{
List
<
Widget
>
children
=
[
new
Flexible
(
child:
new
Container
(
margin:
const
EdgeDims
.
symmetric
(
vertical:
14.0
),
child:
new
DefaultTextStyle
(
style:
Typography
.
white
.
subhead
,
child:
config
.
content
)
)
)
];
if
(
config
.
actions
!=
null
)
children
.
addAll
(
config
.
actions
);
return
new
SlideTransition
(
key:
config
.
transitionKey
,
performance:
performance
.
view
,
position:
new
AnimatedValue
<
Point
>(
Point
.
origin
,
end:
const
Point
(
0.0
,
-
52.0
),
curve:
easeIn
,
reverseCurve:
easeOut
),
child:
new
Material
(
level:
2
,
color:
const
Color
(
0xFF323232
),
type:
MaterialType
.
canvas
,
child:
new
Container
(
margin:
const
EdgeDims
.
symmetric
(
horizontal:
24.0
),
child:
new
DefaultTextStyle
(
style:
new
TextStyle
(
color:
Theme
.
of
(
context
).
accentColor
),
child:
new
Row
(
children
)
)
)
)
);
}
}
packages/flutter/lib/src/fn3/tabs.dart
0 → 100644
View file @
9f176fc8
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:math'
as
math
;
import
'dart:sky'
as
sky
;
import
'package:newton/newton.dart'
;
import
'package:sky/animation.dart'
;
import
'package:sky/painting.dart'
;
import
'package:sky/rendering.dart'
;
import
'package:sky/material.dart'
;
import
'package:sky/src/fn3/basic.dart'
;
import
'package:sky/src/fn3/framework.dart'
;
import
'package:sky/src/fn3/gesture_detector.dart'
;
import
'package:sky/src/fn3/icon.dart'
;
import
'package:sky/src/fn3/ink_well.dart'
;
import
'package:sky/src/fn3/scrollable.dart'
;
import
'package:sky/src/fn3/theme.dart'
;
import
'package:sky/src/fn3/transitions.dart'
;
typedef
void
SelectedIndexChanged
(
int
selectedIndex
);
typedef
void
LayoutChanged
(
Size
size
,
List
<
double
>
widths
);
// See https://www.google.com/design/spec/components/tabs.html#tabs-specs
const
double
_kTabHeight
=
46.0
;
const
double
_kTextAndIconTabHeight
=
72.0
;
const
double
_kTabIndicatorHeight
=
2.0
;
const
double
_kMinTabWidth
=
72.0
;
const
double
_kMaxTabWidth
=
264.0
;
const
EdgeDims
_kTabLabelPadding
=
const
EdgeDims
.
symmetric
(
horizontal:
12.0
);
const
int
_kTabIconSize
=
24
;
const
double
_kTabBarScrollDrag
=
0.025
;
const
Duration
_kTabBarScroll
=
const
Duration
(
milliseconds:
200
);
class
TabBarParentData
extends
BoxParentData
with
ContainerParentDataMixin
<
RenderBox
>
{
}
class
RenderTabBar
extends
RenderBox
with
ContainerRenderObjectMixin
<
RenderBox
,
TabBarParentData
>,
RenderBoxContainerDefaultsMixin
<
RenderBox
,
TabBarParentData
>
{
RenderTabBar
(
this
.
onLayoutChanged
);
int
_selectedIndex
;
int
get
selectedIndex
=>
_selectedIndex
;
void
set
selectedIndex
(
int
value
)
{
if
(
_selectedIndex
!=
value
)
{
_selectedIndex
=
value
;
markNeedsPaint
();
}
}
Color
_backgroundColor
;
Color
get
backgroundColor
=>
_backgroundColor
;
void
set
backgroundColor
(
Color
value
)
{
if
(
_backgroundColor
!=
value
)
{
_backgroundColor
=
value
;
markNeedsPaint
();
}
}
Color
_indicatorColor
;
Color
get
indicatorColor
=>
_indicatorColor
;
void
set
indicatorColor
(
Color
value
)
{
if
(
_indicatorColor
!=
value
)
{
_indicatorColor
=
value
;
markNeedsPaint
();
}
}
Rect
_indicatorRect
;
Rect
get
indicatorRect
=>
_indicatorRect
;
void
set
indicatorRect
(
Rect
value
)
{
if
(
_indicatorRect
!=
value
)
{
_indicatorRect
=
value
;
markNeedsPaint
();
}
}
bool
_textAndIcons
;
bool
get
textAndIcons
=>
_textAndIcons
;
void
set
textAndIcons
(
bool
value
)
{
if
(
_textAndIcons
!=
value
)
{
_textAndIcons
=
value
;
markNeedsLayout
();
}
}
bool
_isScrollable
;
bool
get
isScrollable
=>
_isScrollable
;
void
set
isScrollable
(
bool
value
)
{
if
(
_isScrollable
!=
value
)
{
_isScrollable
=
value
;
markNeedsLayout
();
}
}
void
setupParentData
(
RenderBox
child
)
{
if
(
child
.
parentData
is
!
TabBarParentData
)
child
.
parentData
=
new
TabBarParentData
();
}
double
getMinIntrinsicWidth
(
BoxConstraints
constraints
)
{
BoxConstraints
widthConstraints
=
new
BoxConstraints
(
maxWidth:
constraints
.
maxWidth
,
maxHeight:
constraints
.
maxHeight
);
double
maxWidth
=
0.0
;
RenderBox
child
=
firstChild
;
while
(
child
!=
null
)
{
maxWidth
=
math
.
max
(
maxWidth
,
child
.
getMinIntrinsicWidth
(
widthConstraints
));
assert
(
child
.
parentData
is
TabBarParentData
);
child
=
child
.
parentData
.
nextSibling
;
}
double
width
=
isScrollable
?
maxWidth
:
maxWidth
*
childCount
;
return
constraints
.
constrainWidth
(
width
);
}
double
getMaxIntrinsicWidth
(
BoxConstraints
constraints
)
{
BoxConstraints
widthConstraints
=
new
BoxConstraints
(
maxWidth:
constraints
.
maxWidth
,
maxHeight:
constraints
.
maxHeight
);
double
maxWidth
=
0.0
;
RenderBox
child
=
firstChild
;
while
(
child
!=
null
)
{
maxWidth
=
math
.
max
(
maxWidth
,
child
.
getMaxIntrinsicWidth
(
widthConstraints
));
assert
(
child
.
parentData
is
TabBarParentData
);
child
=
child
.
parentData
.
nextSibling
;
}
double
width
=
isScrollable
?
maxWidth
:
maxWidth
*
childCount
;
return
constraints
.
constrainWidth
(
width
);
}
double
get
_tabHeight
=>
textAndIcons
?
_kTextAndIconTabHeight
:
_kTabHeight
;
double
get
_tabBarHeight
=>
_tabHeight
+
_kTabIndicatorHeight
;
double
_getIntrinsicHeight
(
BoxConstraints
constraints
)
=>
constraints
.
constrainHeight
(
_tabBarHeight
);
double
getMinIntrinsicHeight
(
BoxConstraints
constraints
)
=>
_getIntrinsicHeight
(
constraints
);
double
getMaxIntrinsicHeight
(
BoxConstraints
constraints
)
=>
_getIntrinsicHeight
(
constraints
);
void
layoutFixedWidthTabs
()
{
double
tabWidth
=
size
.
width
/
childCount
;
BoxConstraints
tabConstraints
=
new
BoxConstraints
.
tightFor
(
width:
tabWidth
,
height:
_tabHeight
);
double
x
=
0.0
;
RenderBox
child
=
firstChild
;
while
(
child
!=
null
)
{
child
.
layout
(
tabConstraints
);
assert
(
child
.
parentData
is
TabBarParentData
);
child
.
parentData
.
position
=
new
Point
(
x
,
0.0
);
x
+=
tabWidth
;
child
=
child
.
parentData
.
nextSibling
;
}
}
double
layoutScrollableTabs
()
{
BoxConstraints
tabConstraints
=
new
BoxConstraints
(
minWidth:
_kMinTabWidth
,
maxWidth:
_kMaxTabWidth
,
minHeight:
_tabHeight
,
maxHeight:
_tabHeight
);
double
x
=
0.0
;
RenderBox
child
=
firstChild
;
while
(
child
!=
null
)
{
child
.
layout
(
tabConstraints
,
parentUsesSize:
true
);
assert
(
child
.
parentData
is
TabBarParentData
);
child
.
parentData
.
position
=
new
Point
(
x
,
0.0
);
x
+=
child
.
size
.
width
;
child
=
child
.
parentData
.
nextSibling
;
}
return
x
;
}
Size
layoutSize
;
List
<
double
>
layoutWidths
;
LayoutChanged
onLayoutChanged
;
void
reportLayoutChangedIfNeeded
()
{
assert
(
onLayoutChanged
!=
null
);
List
<
double
>
widths
=
new
List
<
double
>(
childCount
);
if
(!
isScrollable
&&
childCount
>
0
)
{
double
tabWidth
=
size
.
width
/
childCount
;
widths
.
fillRange
(
0
,
widths
.
length
,
tabWidth
);
}
else
if
(
isScrollable
)
{
RenderBox
child
=
firstChild
;
int
childIndex
=
0
;
while
(
child
!=
null
)
{
widths
[
childIndex
++]
=
child
.
size
.
width
;
child
=
child
.
parentData
.
nextSibling
;
}
assert
(
childIndex
==
widths
.
length
);
}
if
(
size
!=
layoutSize
||
widths
!=
layoutWidths
)
{
layoutSize
=
size
;
layoutWidths
=
widths
;
onLayoutChanged
(
layoutSize
,
layoutWidths
);
}
}
void
performLayout
()
{
assert
(
constraints
is
BoxConstraints
);
if
(
childCount
==
0
)
return
;
if
(
isScrollable
)
{
double
tabBarWidth
=
layoutScrollableTabs
();
size
=
constraints
.
constrain
(
new
Size
(
tabBarWidth
,
_tabBarHeight
));
}
else
{
size
=
constraints
.
constrain
(
new
Size
(
constraints
.
maxWidth
,
_tabBarHeight
));
layoutFixedWidthTabs
();
}
if
(
onLayoutChanged
!=
null
)
reportLayoutChangedIfNeeded
();
}
void
hitTestChildren
(
HitTestResult
result
,
{
Point
position
})
{
defaultHitTestChildren
(
result
,
position:
position
);
}
void
_paintIndicator
(
PaintingCanvas
canvas
,
RenderBox
selectedTab
,
Offset
offset
)
{
if
(
indicatorColor
==
null
)
return
;
if
(
indicatorRect
!=
null
)
{
canvas
.
drawRect
(
indicatorRect
.
shift
(
offset
),
new
Paint
()..
color
=
indicatorColor
);
return
;
}
var
size
=
new
Size
(
selectedTab
.
size
.
width
,
_kTabIndicatorHeight
);
var
point
=
new
Point
(
selectedTab
.
parentData
.
position
.
x
,
_tabBarHeight
-
_kTabIndicatorHeight
);
Rect
rect
=
(
point
+
offset
)
&
size
;
canvas
.
drawRect
(
rect
,
new
Paint
()..
color
=
indicatorColor
);
}
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
if
(
backgroundColor
!=
null
)
{
double
width
=
layoutWidths
!=
null
?
layoutWidths
.
reduce
((
sum
,
width
)
=>
sum
+
width
)
:
size
.
width
;
Rect
rect
=
offset
&
new
Size
(
width
,
size
.
height
);
context
.
canvas
.
drawRect
(
rect
,
new
Paint
()..
color
=
backgroundColor
);
}
int
index
=
0
;
RenderBox
child
=
firstChild
;
while
(
child
!=
null
)
{
assert
(
child
.
parentData
is
TabBarParentData
);
context
.
paintChild
(
child
,
child
.
parentData
.
position
+
offset
);
if
(
index
++
==
selectedIndex
)
_paintIndicator
(
context
.
canvas
,
child
,
offset
);
child
=
child
.
parentData
.
nextSibling
;
}
}
}
class
_TabBarWrapper
extends
MultiChildRenderObjectWidget
{
_TabBarWrapper
({
Key
key
,
List
<
Widget
>
children
,
this
.
selectedIndex
,
this
.
backgroundColor
,
this
.
indicatorColor
,
this
.
indicatorRect
,
this
.
textAndIcons
,
this
.
isScrollable
:
false
,
this
.
onLayoutChanged
})
:
super
(
key:
key
,
children:
children
);
final
int
selectedIndex
;
final
Color
backgroundColor
;
final
Color
indicatorColor
;
final
Rect
indicatorRect
;
final
bool
textAndIcons
;
final
bool
isScrollable
;
final
LayoutChanged
onLayoutChanged
;
RenderTabBar
createRenderObject
()
{
RenderTabBar
result
=
new
RenderTabBar
(
onLayoutChanged
);
updateRenderObject
(
result
,
null
);
return
result
;
}
void
updateRenderObject
(
RenderTabBar
renderObject
,
_TabBarWrapper
oldWidget
)
{
renderObject
.
selectedIndex
=
selectedIndex
;
renderObject
.
backgroundColor
=
backgroundColor
;
renderObject
.
indicatorColor
=
indicatorColor
;
renderObject
.
indicatorRect
=
indicatorRect
;
renderObject
.
textAndIcons
=
textAndIcons
;
renderObject
.
isScrollable
=
isScrollable
;
renderObject
.
onLayoutChanged
=
onLayoutChanged
;
}
}
class
TabLabel
{
const
TabLabel
({
this
.
text
,
this
.
icon
});
final
String
text
;
final
String
icon
;
}
class
Tab
extends
StatelessComponent
{
Tab
({
Key
key
,
this
.
label
,
this
.
color
,
this
.
selected
:
false
,
this
.
selectedColor
})
:
super
(
key:
key
)
{
assert
(
label
.
text
!=
null
||
label
.
icon
!=
null
);
}
final
TabLabel
label
;
final
Color
color
;
final
bool
selected
;
final
Color
selectedColor
;
Widget
_buildLabelText
()
{
assert
(
label
.
text
!=
null
);
TextStyle
style
=
new
TextStyle
(
color:
selected
?
selectedColor
:
color
);
return
new
Text
(
label
.
text
,
style:
style
);
}
Widget
_buildLabelIcon
()
{
assert
(
label
.
icon
!=
null
);
Color
iconColor
=
selected
?
selectedColor
:
color
;
sky
.
ColorFilter
filter
=
new
sky
.
ColorFilter
.
mode
(
iconColor
,
sky
.
TransferMode
.
srcATop
);
return
new
Icon
(
type:
label
.
icon
,
size:
_kTabIconSize
,
colorFilter:
filter
);
}
Widget
build
(
BuildContext
context
)
{
Widget
labelContent
;
if
(
label
.
icon
==
null
)
{
labelContent
=
_buildLabelText
();
}
else
if
(
label
.
text
==
null
)
{
labelContent
=
_buildLabelIcon
();
}
else
{
labelContent
=
new
Column
(
<
Widget
>[
new
Container
(
child:
_buildLabelIcon
(),
margin:
const
EdgeDims
.
only
(
bottom:
10.0
)
),
_buildLabelText
()
],
justifyContent:
FlexJustifyContent
.
center
,
alignItems:
FlexAlignItems
.
center
);
}
Container
centeredLabel
=
new
Container
(
child:
new
Center
(
child:
labelContent
),
constraints:
new
BoxConstraints
(
minWidth:
_kMinTabWidth
),
padding:
_kTabLabelPadding
);
return
new
InkWell
(
child:
centeredLabel
);
}
}
class
_TabsScrollBehavior
extends
BoundedBehavior
{
_TabsScrollBehavior
();
bool
isScrollable
=
true
;
Simulation
release
(
double
position
,
double
velocity
)
{
if
(!
isScrollable
)
return
null
;
double
velocityPerSecond
=
velocity
*
1000.0
;
return
new
BoundedFrictionSimulation
(
_kTabBarScrollDrag
,
position
,
velocityPerSecond
,
minScrollOffset
,
maxScrollOffset
);
}
double
applyCurve
(
double
scrollOffset
,
double
scrollDelta
)
{
return
(
isScrollable
)
?
super
.
applyCurve
(
scrollOffset
,
scrollDelta
)
:
0.0
;
}
}
class
TabBar
extends
Scrollable
{
TabBar
({
Key
key
,
this
.
labels
,
this
.
selectedIndex
:
0
,
this
.
onChanged
,
this
.
isScrollable
:
false
})
:
super
(
key:
key
,
scrollDirection:
ScrollDirection
.
horizontal
);
final
Iterable
<
TabLabel
>
labels
;
final
int
selectedIndex
;
final
SelectedIndexChanged
onChanged
;
final
bool
isScrollable
;
TabBarState
createState
()
=>
new
TabBarState
(
this
);
}
class
TabBarState
extends
ScrollableState
<
TabBar
>
{
TabBarState
(
TabBar
config
)
:
super
(
config
)
{
_indicatorAnimation
=
new
ValueAnimation
<
Rect
>()
..
duration
=
_kTabBarScroll
..
variable
=
new
AnimatedRect
(
null
,
curve:
ease
);
scrollBehavior
.
isScrollable
=
config
.
isScrollable
;
}
Size
_tabBarSize
;
Size
_viewportSize
=
Size
.
zero
;
List
<
double
>
_tabWidths
;
ValueAnimation
<
Rect
>
_indicatorAnimation
;
void
didUpdateConfig
(
TabBar
oldConfig
)
{
super
.
didUpdateConfig
(
oldConfig
);
if
(!
config
.
isScrollable
)
scrollTo
(
0.0
);
}
AnimatedRect
get
_indicatorRect
=>
_indicatorAnimation
.
variable
;
void
_startIndicatorAnimation
(
int
fromTabIndex
,
int
toTabIndex
)
{
_indicatorRect
..
begin
=
(
_indicatorRect
.
value
==
null
?
_tabIndicatorRect
(
fromTabIndex
)
:
_indicatorRect
.
value
)
..
end
=
_tabIndicatorRect
(
toTabIndex
);
_indicatorAnimation
..
progress
=
0.0
..
play
();
}
ScrollBehavior
createScrollBehavior
()
=>
new
_TabsScrollBehavior
();
_TabsScrollBehavior
get
scrollBehavior
=>
super
.
scrollBehavior
;
Rect
_tabRect
(
int
tabIndex
)
{
assert
(
_tabBarSize
!=
null
);
assert
(
_tabWidths
!=
null
);
assert
(
tabIndex
>=
0
&&
tabIndex
<
_tabWidths
.
length
);
double
tabLeft
=
0.0
;
if
(
tabIndex
>
0
)
tabLeft
=
_tabWidths
.
take
(
tabIndex
).
reduce
((
sum
,
width
)
=>
sum
+
width
);
double
tabTop
=
0.0
;
double
tabBottom
=
_tabBarSize
.
height
-
_kTabIndicatorHeight
;
double
tabRight
=
tabLeft
+
_tabWidths
[
tabIndex
];
return
new
Rect
.
fromLTRB
(
tabLeft
,
tabTop
,
tabRight
,
tabBottom
);
}
Rect
_tabIndicatorRect
(
int
tabIndex
)
{
Rect
r
=
_tabRect
(
tabIndex
);
return
new
Rect
.
fromLTRB
(
r
.
left
,
r
.
bottom
,
r
.
right
,
r
.
bottom
+
_kTabIndicatorHeight
);
}
double
_centeredTabScrollOffset
(
int
tabIndex
)
{
double
viewportWidth
=
scrollBehavior
.
containerExtent
;
return
(
_tabRect
(
tabIndex
).
left
+
_tabWidths
[
tabIndex
]
/
2.0
-
viewportWidth
/
2.0
)
.
clamp
(
scrollBehavior
.
minScrollOffset
,
scrollBehavior
.
maxScrollOffset
);
}
void
_handleTap
(
int
tabIndex
)
{
if
(
tabIndex
!=
config
.
selectedIndex
)
{
if
(
_tabWidths
!=
null
)
{
if
(
config
.
isScrollable
)
scrollTo
(
_centeredTabScrollOffset
(
tabIndex
),
duration:
_kTabBarScroll
);
_startIndicatorAnimation
(
config
.
selectedIndex
,
tabIndex
);
}
if
(
config
.
onChanged
!=
null
)
config
.
onChanged
(
tabIndex
);
}
}
Widget
_toTab
(
TabLabel
label
,
int
tabIndex
,
Color
color
,
Color
selectedColor
)
{
return
new
GestureDetector
(
onTap:
()
=>
_handleTap
(
tabIndex
),
child:
new
Tab
(
label:
label
,
color:
color
,
selected:
tabIndex
==
config
.
selectedIndex
,
selectedColor:
selectedColor
)
);
}
void
_updateScrollBehavior
()
{
scrollBehavior
.
updateExtents
(
containerExtent:
config
.
scrollDirection
==
ScrollDirection
.
vertical
?
_viewportSize
.
height
:
_viewportSize
.
width
,
contentExtent:
_tabWidths
.
reduce
((
sum
,
width
)
=>
sum
+
width
)
);
}
void
_layoutChanged
(
Size
tabBarSize
,
List
<
double
>
tabWidths
)
{
setState
(()
{
_tabBarSize
=
tabBarSize
;
_tabWidths
=
tabWidths
;
_updateScrollBehavior
();
});
}
void
_handleViewportSizeChanged
(
Size
newSize
)
{
_viewportSize
=
newSize
;
_updateScrollBehavior
();
}
Widget
buildContent
(
BuildContext
context
)
{
assert
(
config
.
labels
!=
null
&&
config
.
labels
.
isNotEmpty
);
ThemeData
themeData
=
Theme
.
of
(
context
);
Color
backgroundColor
=
themeData
.
primaryColor
;
Color
indicatorColor
=
themeData
.
accentColor
;
if
(
indicatorColor
==
backgroundColor
)
{
indicatorColor
=
Colors
.
white
;
}
TextStyle
textStyle
;
IconThemeColor
iconThemeColor
;
switch
(
themeData
.
primaryColorBrightness
)
{
case
ThemeBrightness
.
light
:
textStyle
=
Typography
.
black
.
body1
;
iconThemeColor
=
IconThemeColor
.
black
;
break
;
case
ThemeBrightness
.
dark
:
textStyle
=
Typography
.
white
.
body1
;
iconThemeColor
=
IconThemeColor
.
white
;
break
;
}
List
<
Widget
>
tabs
=
<
Widget
>[];
bool
textAndIcons
=
false
;
int
tabIndex
=
0
;
for
(
TabLabel
label
in
config
.
labels
)
{
tabs
.
add
(
_toTab
(
label
,
tabIndex
++,
textStyle
.
color
,
indicatorColor
));
if
(
label
.
text
!=
null
&&
label
.
icon
!=
null
)
textAndIcons
=
true
;
}
Widget
tabBar
=
new
IconTheme
(
data:
new
IconThemeData
(
color:
iconThemeColor
),
child:
new
DefaultTextStyle
(
style:
textStyle
,
child:
new
BuilderTransition
(
variables:
[
_indicatorRect
],
performance:
_indicatorAnimation
.
view
,
builder:
(
BuildContext
context
)
{
return
new
_TabBarWrapper
(
children:
tabs
,
selectedIndex:
config
.
selectedIndex
,
backgroundColor:
backgroundColor
,
indicatorColor:
indicatorColor
,
indicatorRect:
_indicatorRect
.
value
,
textAndIcons:
textAndIcons
,
isScrollable:
config
.
isScrollable
,
onLayoutChanged:
_layoutChanged
);
}
)
)
);
if
(!
config
.
isScrollable
)
return
tabBar
;
return
new
SizeObserver
(
callback:
_handleViewportSizeChanged
,
child:
new
Viewport
(
scrollDirection:
ScrollDirection
.
horizontal
,
scrollOffset:
new
Offset
(
scrollOffset
,
0.0
),
child:
tabBar
)
);
}
}
class
TabNavigatorView
{
TabNavigatorView
({
this
.
label
,
this
.
builder
});
final
TabLabel
label
;
final
WidgetBuilder
builder
;
Widget
buildContent
(
BuildContext
context
)
{
assert
(
builder
!=
null
);
Widget
content
=
builder
(
context
);
assert
(
content
!=
null
);
return
content
;
}
}
class
TabNavigator
extends
StatelessComponent
{
TabNavigator
({
Key
key
,
this
.
views
,
this
.
selectedIndex
:
0
,
this
.
onChanged
,
this
.
isScrollable
:
false
})
:
super
(
key:
key
);
final
List
<
TabNavigatorView
>
views
;
final
int
selectedIndex
;
final
SelectedIndexChanged
onChanged
;
final
bool
isScrollable
;
void
_handleSelectedIndexChanged
(
int
tabIndex
)
{
if
(
onChanged
!=
null
)
onChanged
(
tabIndex
);
}
Widget
build
(
BuildContext
context
)
{
assert
(
views
!=
null
&&
views
.
isNotEmpty
);
assert
(
selectedIndex
>=
0
&&
selectedIndex
<
views
.
length
);
TabBar
tabBar
=
new
TabBar
(
labels:
views
.
map
((
view
)
=>
view
.
label
),
onChanged:
_handleSelectedIndexChanged
,
selectedIndex:
selectedIndex
,
isScrollable:
isScrollable
);
Widget
content
=
views
[
selectedIndex
].
buildContent
(
context
);
return
new
Column
([
tabBar
,
new
Flexible
(
child:
content
)]);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment