Created
August 26, 2021 22:07
-
-
Save followthemoney1/257e61588786241b0634a74ffc6dcc2e to your computer and use it in GitHub Desktop.
codepen_example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'dart:developer'; | |
import 'dart:math' as math; | |
import 'dart:ui'; | |
import 'package:flutter/material.dart'; | |
import 'dart:html' as html; | |
import 'dart:ui' as ui; | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
final itemSize = 60.0; | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData.dark(), | |
debugShowCheckedModeBanner: false, | |
home: Scaffold( | |
body: Center( | |
child: AnimatedCategory<String>( | |
childBuilder: (item, String data) { | |
return YoutubePlayer(); | |
}, | |
startSize: itemSize, | |
deltaSizeFirstTap: itemSize / 2, | |
deltaSizeSecondTap: itemSize * 1.8, | |
columnNumber: 2, | |
items: [ | |
'', | |
'', | |
'', | |
'', | |
'' | |
], | |
itemSelected: (SuggestionItem i) {}, | |
), | |
), | |
), | |
); | |
} | |
} | |
class YoutubePlayer extends StatefulWidget { | |
@override | |
State<StatefulWidget> createState() { | |
return YoutubePlayerState(); | |
} | |
} | |
class YoutubePlayerState extends State<YoutubePlayer> { | |
@override | |
Widget build(BuildContext context) { | |
return LayoutBuilder(builder: (BuildContext ctx, BoxConstraints constraints) { | |
Size playerSize = Size( | |
constraints.maxWidth, | |
constraints.maxHeight, | |
); | |
return playerSize.width <= 0 | |
? Container() | |
: Container( | |
width: playerSize.width, | |
height: playerSize.height, | |
child:IframeScreen(), | |
); | |
}); | |
} | |
@override | |
void dispose() { | |
super.dispose(); | |
} | |
} | |
class IframeScreen extends StatefulWidget { | |
@override | |
_IframeScreenState createState() => _IframeScreenState(); | |
} | |
class _IframeScreenState extends State<IframeScreen> { | |
late Widget _iframeWidget; | |
final html.IFrameElement _iframeElement = html.IFrameElement(); | |
@override | |
void initState() { | |
super.initState(); | |
_iframeElement.height = '50'; | |
_iframeElement.width = '50'; | |
_iframeElement.src = 'https://www.youtube.com/embed/liEGSeD3Zt8'; | |
_iframeElement.style.border = 'none'; | |
// ignore: undefined_prefixed_name | |
ui.platformViewRegistry.registerViewFactory( | |
'iframeElement', | |
(int viewId) => _iframeElement, | |
); | |
_iframeWidget = HtmlElementView( | |
key: UniqueKey(), | |
viewType: 'iframeElement', | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
// | |
final _width = 500.0; | |
final _height = 500.0; | |
return Center( | |
child: SizedBox( | |
height: _height, | |
width: _width, | |
child: _iframeWidget, | |
), | |
) | |
; | |
} | |
@override | |
void dispose() { | |
super.dispose(); | |
} | |
} | |
class AnimatedCategory<T> extends StatefulWidget { | |
final List items; | |
final Function itemSelected; | |
final double startSize; | |
final double deltaSizeFirstTap; | |
final double deltaSizeSecondTap; | |
final Widget Function(SuggestionItem item, T data) childBuilder; | |
final bool setClickedItemDelay; | |
final int clickedItemDelay; | |
final int itemAnimationDuration; | |
final Curve itemCurve; | |
final int stackAnimatedDuration; | |
final Curve stackCurve; | |
final Key key; | |
final int columnNumber; | |
const AnimatedCategory( | |
{Key this.key = const ValueKey(101010), | |
required this.childBuilder, | |
required this.items, | |
required this.itemSelected, | |
this.setClickedItemDelay = false, | |
this.clickedItemDelay = 100, | |
this.startSize = 100.0, | |
this.deltaSizeSecondTap = 200.0, | |
this.deltaSizeFirstTap = 50.0, | |
this.itemAnimationDuration = 400, | |
this.itemCurve = Curves.bounceInOut, | |
this.stackAnimatedDuration = 600, | |
this.columnNumber = 4, | |
this.stackCurve = Curves.easeInOutQuint}) | |
: super(key: key); | |
@override | |
_AnimatedCategoryState createState() => _AnimatedCategoryState<T>(childBuilder); | |
} | |
class _AnimatedCategoryState<T> extends State<AnimatedCategory> with TickerProviderStateMixin { | |
Widget Function(SuggestionItem item, T data) childBuilder; | |
_AnimatedCategoryState(this.childBuilder); | |
Map<int, List<SuggestionItem>> suggestionMatrix = {}; | |
var needUpdateScrollWidth = true; | |
var maxWidth = 0; | |
late var startSize; | |
late var deltaSize; | |
late var deltaSizeBig; | |
@override | |
void initState() { | |
super.initState(); | |
startSize = widget.startSize; | |
deltaSize = widget.deltaSizeFirstTap; | |
deltaSizeBig = widget.deltaSizeSecondTap; | |
int rowCount = (widget.items.length / widget.columnNumber).round(); | |
for (int m = 0; m < widget.columnNumber; m++) { | |
suggestionMatrix.addAll({m: []}); | |
print(suggestionMatrix.length); | |
} | |
suggestionMatrix = Map.from(suggestionMatrix.map((key, value) { | |
final endIndex = (rowCount * (key + 1)); | |
return MapEntry( | |
key, | |
widget.items | |
.getRange(rowCount * key, endIndex < widget.items.length ? endIndex : widget.items.length) | |
.toList() | |
.asMap() | |
.entries | |
.map((element) { | |
final val = element.value; | |
final i = element.key; | |
return SuggestionItem( | |
data: val, | |
width: startSize, | |
height: startSize, | |
currentWeight: 1, | |
x: (i) * startSize as double, | |
y: (key) * startSize as double, | |
); | |
}).toList()); | |
})); | |
} | |
@override | |
Widget build(BuildContext context) { | |
updateWidth(); | |
return SingleChildScrollView( | |
scrollDirection: Axis.horizontal, | |
child: Container( | |
width: maxWidth * startSize as double?, | |
height: 4 * deltaSizeBig as double?, | |
child: Stack( | |
fit: StackFit.expand, | |
clipBehavior: Clip.hardEdge, | |
alignment: Alignment.topCenter, | |
children: childerCards(), | |
)), | |
); | |
} | |
updateWidth() { | |
if (needUpdateScrollWidth) { | |
needUpdateScrollWidth = false; | |
// maxWidth = 0; | |
suggestionMatrix.forEach((key, list) { | |
final current = list.where((element) => element.currentWeight == 1).length; | |
final currentExpand = list.where((element) => element.currentWeight == 2).length; | |
final currentExpandMax = list.where((element) => element.currentWeight == 3).length; | |
final all = current + (currentExpand * 2) + (currentExpandMax * 2); | |
maxWidth = maxWidth < all ? all : maxWidth; | |
}); | |
} | |
} | |
List<Widget> childerCards() { | |
List<Widget> cardsMatrixWidgets = []; | |
suggestionMatrix.entries.forEach((columns) { | |
int iColumn = columns.key; | |
List<SuggestionItem> rowsList = columns.value; | |
rowsList.asMap().entries.forEach((rows) { | |
int iRow = rows.key; | |
SuggestionItem currentRow = rows.value; | |
setState(() { | |
currentRow.iColumn = iColumn; | |
currentRow.iRow = iRow; | |
}); | |
///mark: update widgets | |
/// | |
if (widget.setClickedItemDelay) { | |
Future.delayed(Duration(milliseconds: widget.clickedItemDelay), () { | |
_update(currentRow: currentRow, rowsList: rowsList); | |
}); | |
} else { | |
_update(currentRow: currentRow, rowsList: rowsList); | |
} | |
///get all widgets | |
/// | |
/// | |
cardsMatrixWidgets.add(AnimatedPositioned.fromRect( | |
duration: Duration(milliseconds: widget.stackAnimatedDuration), | |
curve: widget.stackCurve, //fastOutSlowIn, | |
child: item(rows.value), | |
rect: currentRow.rect, | |
)); | |
}); | |
}); | |
return cardsMatrixWidgets; | |
} | |
_update({required final currentRow, final rowsList}) { | |
if (currentRow.iRow > 0) { | |
calcOverflowLeft(rowsList.elementAt(currentRow.iRow - 1), currentRow); | |
} | |
setState(() { | |
if (currentRow.iColumn > 0) { | |
calcOverflowTop(suggestionMatrix[currentRow.iColumn - 1]!.elementAt(currentRow.iRow), currentRow); | |
calcOverflowClosestElement(line: suggestionMatrix[currentRow.iColumn - 1]!, current: currentRow); | |
} | |
}); | |
} | |
bool calcOverflowClosestElement( | |
{required List<SuggestionItem> line, required SuggestionItem current, bool check = false}) { | |
for (SuggestionItem element in line) { | |
if (current.rect.intersect(element.rect).height > 0 && current.rect.intersect(element.rect).width > 0) { | |
if (current.rect.intersect(element.rect).height > 0) { | |
if (!check) { | |
current.y += element.rect.intersect(current.rect).height; | |
} | |
} | |
} | |
} | |
return false; | |
} | |
void calcOverflowLeft(SuggestionItem prev, SuggestionItem current, {bool? withGravity}) { | |
if (prev.right > current.left) { | |
current.x += prev.right - current.left; | |
} else if (prev.right < current.left) { | |
current.x -= current.left - prev.right; | |
} | |
} | |
void calcOverflowTop(SuggestionItem prev, SuggestionItem current) { | |
if (current.x == prev.x) current.y += prev.rect.intersect(current.rect).height; | |
} | |
Widget item(SuggestionItem e) { | |
return AnimatedContainer( | |
duration: Duration(milliseconds: widget.itemAnimationDuration), | |
curve: widget.itemCurve, | |
height: e.height, | |
width: e.width, | |
child: Padding(padding: EdgeInsets.all(8), child: childBuilder(e, e.data)).addOnTap( | |
onLongPress: () { | |
onLongPressItem(e); | |
setState(() { | |
needUpdateScrollWidth = true; | |
}); | |
}, | |
onTap: () { | |
onTapItem(e); | |
setState(() { | |
needUpdateScrollWidth = true; | |
}); | |
}, | |
), | |
); | |
} | |
onTapItem(SuggestionItem e) { | |
setState(() { | |
if (e.currentWeight <= 1) { | |
e.currentWeight = e.currentWeight + 1; | |
e.height = e.height + deltaSize; | |
e.width = e.width + deltaSize; | |
} else { | |
e.currentWeight = 1; | |
e.height = startSize; | |
e.width = startSize; | |
} | |
}); | |
widget.itemSelected(e); | |
} | |
onLongPressItem( | |
SuggestionItem e, | |
) { | |
setState(() { | |
if (e.currentWeight < 3) { | |
e.currentWeight = 3; | |
e.height = deltaSizeBig; | |
e.width = deltaSizeBig; | |
} else { | |
e.currentWeight = 1; | |
e.height = startSize; | |
e.width = startSize; | |
} | |
}); | |
widget.itemSelected(e); | |
} | |
} | |
class SuggestionItem<T> { | |
final T data; | |
int currentWeight; | |
double width; | |
double height; | |
double x; | |
double y; | |
int? iRow; | |
int? iColumn; | |
get selected => currentWeight > 1; | |
get superLike => currentWeight == 3; | |
get leftTop => x + y; | |
get rightTop => y + x + width; | |
get leftBottom => x + y + height; | |
get rightBottom => leftTop + width + height; | |
get left => x; | |
get right => x + width; | |
get bottom => y + height; | |
get top => y; | |
get isExpanded => currentWeight > 1; | |
get color => Colors.black; | |
Rect get rect => Rect.fromLTWH(left, top, width, height); | |
SuggestionItem({ | |
required this.data, | |
required this.width, | |
required this.height, | |
this.currentWeight = 1, | |
required this.x, | |
required this.y, | |
}); | |
} | |
class CardItemWidget extends StatefulWidget { | |
final Widget child; | |
final Color? backgroundColor; | |
final IconData? iconData; | |
final int weight; | |
final double? width; | |
final double? height; | |
final String? name; | |
final SuggestionItem? el; | |
final String? localImage; | |
const CardItemWidget( | |
{this.backgroundColor, | |
this.iconData, | |
this.weight = 1, | |
this.width, | |
this.height, | |
this.name, | |
this.localImage, | |
required this.child, | |
this.el}); | |
@override | |
State<StatefulWidget> createState() { | |
return CardItemWidgetState(); | |
} | |
} | |
class CardItemWidgetState extends State<CardItemWidget> | |
with TickerProviderStateMixin { | |
final Shader linearGradient = LinearGradient( | |
colors: <Color>[Color(0xFF9B51E0), Color(0xff5D3DF3)], | |
).createShader(Rect.fromLTWH(0.0, 0.0, 100.0, 70.0)); | |
@override | |
Widget build(BuildContext context) { | |
final unselectedStyle = Theme.of(context).textTheme.headline5!.copyWith( | |
color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold); | |
final selectedStyle = Theme.of(context).textTheme.headline5!.copyWith( | |
foreground: (Paint()..shader = linearGradient), | |
fontSize: 18, | |
fontWeight: FontWeight.bold); | |
final selected = widget.el!.selected; | |
final superLike = widget.el!.superLike; | |
return widget.child; | |
} | |
} | |
//MARK: widget ext | |
extension WidgetExtension on Widget { | |
Widget expand() { | |
return Expanded(child: this); | |
} | |
Widget paddingAll(double value) { | |
return Padding( | |
padding: EdgeInsets.all(value), | |
child: this, | |
); | |
} | |
Widget paddingOnly({double left = 0, double right = 0, double top = 0, double bottom = 0}) { | |
return Container( | |
color: Colors.transparent, | |
child: Padding( | |
padding: EdgeInsets.only( | |
left: left != 0 ? left : 0, | |
right: right != 0 ? right : 0, | |
top: top != 0 ? top : 0, | |
bottom: bottom != 0 ? bottom : 0, | |
), | |
child: this, | |
), | |
); | |
} | |
Widget addOnTap({required Function()? onTap, Function()? onLongPress}) { | |
return GestureDetector( | |
child: Container(child: this), | |
onTap: onTap, | |
onLongPress: onLongPress, | |
); | |
} | |
Widget insideScroll() { | |
return SingleChildScrollView(child: this); | |
} | |
Widget insideScrollAndExpand() { | |
return Expanded(child: SingleChildScrollView(child: this)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment