Tweak selected item style

This commit is contained in:
Ming Ming 2022-11-13 16:16:50 +08:00
parent 597961c821
commit bd11aef1db
7 changed files with 137 additions and 11 deletions

View file

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:smooth_corner/smooth_corner.dart';
class AnimatedSmoothClipRRect extends ImplicitlyAnimatedWidget {
const AnimatedSmoothClipRRect({
super.key,
required this.child,
this.smoothness = 0.0,
required this.borderRadius,
this.side = BorderSide.none,
super.curve,
required super.duration,
super.onEnd,
});
@override
ImplicitlyAnimatedWidgetState<AnimatedSmoothClipRRect> createState() =>
_AnimatedSmoothClipRRectState();
final Widget child;
final BorderRadius borderRadius;
final double smoothness;
final BorderSide side;
}
class _AnimatedSmoothClipRRectState
extends AnimatedWidgetBaseState<AnimatedSmoothClipRRect> {
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
_borderRadius = visitor(
_borderRadius,
widget.borderRadius,
(dynamic value) => Tween<BorderRadius>(begin: value as BorderRadius),
) as Tween<BorderRadius>?;
_side = visitor(
_side,
widget.side,
(dynamic value) => BorderSideTween(begin: value as BorderSide),
) as BorderSideTween?;
}
@override
Widget build(BuildContext context) {
return SmoothClipRRect(
smoothness: widget.smoothness,
side: _side?.evaluate(animation) ?? BorderSide.none,
borderRadius: _borderRadius?.evaluate(animation) ?? BorderRadius.zero,
child: widget.child,
);
}
Tween<BorderRadius>? _borderRadius;
BorderSideTween? _side;
}
class BorderSideTween extends Tween<BorderSide?> {
/// Creates an [BorderSide] tween.
///
/// The [begin] and [end] properties must be non-null before the tween is
/// first used, but the arguments can be null if the values are going to be
/// filled in later.
BorderSideTween({BorderSide? begin, BorderSide? end})
: super(begin: begin, end: end);
/// Returns the value this variable has at the given animation clock value.
@override
BorderSide? lerp(double t) => BorderSide.lerp(begin!, end!, t);
}

View file

@ -127,6 +127,8 @@ class _HomeAlbumsState extends State<HomeAlbums>
sliver: buildItemStreamList(
maxCrossAxisExtent: 256,
mainAxisSpacing: 6,
childBorderRadius: BorderRadius.zero,
indicatorAlignment: const Alignment(-.92, -.92),
),
),
SliverToBoxAdapter(

View file

@ -965,6 +965,7 @@ class _StylePickerState extends State<_StylePicker> {
child: Selectable(
isSelected: _selected == index,
iconSize: 24,
indicatorAlignment: Alignment.center,
child: child,
onTap: () {
setState(() {

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/widget/animated_smooth_clip_r_rect.dart';
// Overlay a check mark if an item is selected
class Selectable extends StatelessWidget {
@ -9,6 +10,8 @@ class Selectable extends StatelessWidget {
this.isSelected = false,
required this.iconSize,
this.borderRadius,
this.childBorderRadius = BorderRadius.zero,
this.indicatorAlignment = Alignment.topLeft,
this.onTap,
this.onLongPress,
}) : super(key: key);
@ -31,24 +34,43 @@ class Selectable extends StatelessWidget {
scale: isSelected ? .85 : 1,
curve: Curves.easeInOut,
duration: k.animationDurationNormal,
child: child,
child: childBorderRadius != BorderRadius.zero
? AnimatedSmoothClipRRect(
smoothness: 1,
borderRadius:
isSelected ? childBorderRadius : BorderRadius.zero,
side: BorderSide(
color: isSelected
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0),
width: isSelected ? 4 : 0,
),
Positioned.fill(
curve: Curves.easeInOut,
duration: k.animationDurationNormal,
child: child,
)
: child,
),
Align(
alignment: indicatorAlignment,
child: AnimatedOpacity(
opacity: isSelected ? 1 : 0,
duration: k.animationDurationNormal,
child: Stack(
alignment: AlignmentDirectional.center,
alignment: Alignment.center,
children: [
Icon(
Icons.circle,
size: iconSize,
color: Theme.of(context).colorScheme.primaryContainer,
size: iconSize - 2,
color: Theme.of(context).colorScheme.primary,
),
Icon(
Icons.check_circle_outlined,
size: iconSize,
color: Theme.of(context).colorScheme.onPrimaryContainer,
color: Theme.of(context).colorScheme.primaryContainer,
),
],
),
@ -74,6 +96,10 @@ class Selectable extends StatelessWidget {
final double iconSize;
final BorderRadius? borderRadius;
/// Border radius used to clip the child widget when selected
final BorderRadius childBorderRadius;
final Alignment indicatorAlignment;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
}

View file

@ -64,15 +64,24 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
double mainAxisSpacing = 0,
ValueChanged<double?>? onMaxExtentChanged,
bool isEnableVisibilityCallback = false,
BorderRadius? childBorderRadius,
Alignment indicatorAlignment = Alignment.topLeft,
}) {
childBorderRadius ??= BorderRadius.circular(24);
final Widget content;
if (onMaxExtentChanged != null) {
content = MeasurableItemList(
key: _listKey,
maxCrossAxisExtent: maxCrossAxisExtent,
itemCount: _items.length,
itemBuilder: (context, i) =>
_buildItem(context, i, isEnableVisibilityCallback),
itemBuilder: (context, i) => _buildItem(
context,
i,
isEnableVisibilityCallback: isEnableVisibilityCallback,
childBorderRadius: childBorderRadius!,
indicatorAlignment: indicatorAlignment,
),
staggeredTileBuilder: (index) => _items[index].staggeredTile,
mainAxisSpacing: mainAxisSpacing,
onMaxExtentChanged: onMaxExtentChanged,
@ -82,8 +91,13 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
key: ObjectKey(maxCrossAxisExtent),
maxCrossAxisExtent: maxCrossAxisExtent,
itemCount: _items.length,
itemBuilder: (context, i) =>
_buildItem(context, i, isEnableVisibilityCallback),
itemBuilder: (context, i) => _buildItem(
context,
i,
isEnableVisibilityCallback: isEnableVisibilityCallback,
childBorderRadius: childBorderRadius!,
indicatorAlignment: indicatorAlignment,
),
staggeredTileBuilder: (index) => _items[index].staggeredTile,
mainAxisSpacing: mainAxisSpacing,
);
@ -149,13 +163,20 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
}
Widget _buildItem(
BuildContext context, int index, bool isEnableVisibilityCallback) {
BuildContext context,
int index, {
required bool isEnableVisibilityCallback,
required BorderRadius childBorderRadius,
required Alignment indicatorAlignment,
}) {
final item = _items[index];
Widget content = item.buildWidget(context);
if (item.isSelectable) {
content = Selectable(
isSelected: _selectedItems.contains(item),
iconSize: 32,
childBorderRadius: childBorderRadius,
indicatorAlignment: indicatorAlignment,
onTap: () => _onItemTap(item, index),
onLongPress: isSelectionMode && platform_k.isWeb
? null

View file

@ -1134,6 +1134,13 @@ packages:
description: flutter
source: sdk
version: "0.0.99"
smooth_corner:
dependency: "direct main"
description:
name: smooth_corner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
source_gen:
dependency: transitive
description:

View file

@ -95,6 +95,7 @@ dependencies:
quiver: ^3.1.0
screen_brightness: ^0.2.1
shared_preferences: ^2.0.8
smooth_corner: ^1.1.0
sqlite3: any
sqlite3_flutter_libs: ^0.5.8
synchronized: ^3.0.0