Skip to content

Enable declarative effect variations #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

esDotDev
Copy link
Contributor

@esDotDev esDotDev commented Oct 29, 2022

What this does

Initial pass at a solution for #31 and beginnings of #34.

It sets a core architecture that supports both declarative syntax [ FadeInEffect() ] and imperitive animate().fadeIn() when creating effect variations. The current solution for variations supports only the imperative syntax.

It also adds some plumbing to more easily make variations of other effects. Whether this is composing two or more existing effect together (FadeEffect + SlideEffect), or simply creating a variation of an existing one (FadeIn or BlurX).

NOTE: While this touches many files, that is mostly due to a rename. The bulk of the interesting changes are in the effect.dart class and the new variations themselves.

Architecture

Effects do not always want to expose a public begin/end value.

  • Some may have multiple begin/ends, of different types, for example a FadeInAndUp may have Offset beginOffset and double beginOpacity
  • Some may not semantically make sense at all like Toggle or Swap.

Being forced to inherit begin/end fields in these cases is confusing/undesired, so (imo) we need to make this optional.

To allow this, a new BeginEndEffect<T> class was created, which extends Effect, and the begin/end fields were moved there.

class BeginEndEffect<T> extends Effect {
  const BeginEndEffect({super.delay, super.duration, super.curve, this.begin, this.end});

  /// The begin value for the effect. If null, effects should use a reasonable
  /// default value when appropriate.
  final T? begin;

  /// The end value for the effect. If null, effects should use a reasonable
  /// default value when appropriate.
  final T? end;

  /// Helper method for concrete effects to easily create an Animation<T> from the current begin/end values.
  Animation<T> buildBeginEndAnimation(AnimationController controller, EffectEntry entry) =>
      entry.buildTweenedAnimation(controller, Tween(begin: begin, end: end));
}

Most of the existing 'core' effects were modified to extend BeginEndEffect, the exceptions were Then, Toggle, Callback and Swap where we were able to extend Effect directly and remove the weird Effect<double> and Effect<void> stuff that was being forced. There are also no longer phantom begin/end properties on these effects which is nice.

Finally a CompositeEffectMixin was created, to make it easier to create a new effect from existing ones. The mixin is just a small bit of syntactic sugar, that requires a list of effects, overrides build, and auto-builds all the effects. This allows variations to be created with virtually no boilerplate.

mixin CompositeEffectMixin on Effect {
  // A list of Effects must be provided by any concrete instances of this mixin
  List<Effect> get effects; 

  @override
  /// override build, and call composeEffects(...) so the concrete instances don't have to 
  Widget build(BuildContext context, Widget child, AnimationController controller, EffectEntry entry) =>
      composeEffects(effects, context, child, controller, entry);
}

Importantly, a new effect can both extend BeginEndEffect<T> and use the CompositeEffectMixin if needed so it's quite flexible and easy to use in different configurations.

Example 1,

FadeInEffect, does not want to expose public begin/end values, so it only extends Effect. It uses CompositeEffectMixin to keep boilerplate to a minimum:

class FadeInEffect extends Effect with CompositeEffectMixin {
  const FadeInEffect({super.delay, super.duration, super.curve});

  @override
  List<Effect> get effects => const [FadeEffect(begin: 0, end: 1)];
}

Example 2,

BlurX does want a begin and end, so it extends BeginEndEffect and uses CompositeEffectMixin. This shows how a variation can change the type of its core effect, BlurEffect takes an Offset, but this takes a double, straight inheritence would struggle here but the compositional approach has no problems:

class BlurXEffect extends BeginEndEffect<double> with CompositeEffectMixin {
  const BlurXEffect({super.begin, super.end, super.delay, super.duration, super.curve});

  @override
  List<Effect> get effects => [
        BlurEffect(
          begin: Offset(begin ?? BlurEffect.neutralBlur, 0),
          end: Offset(end ?? (begin == null ? BlurEffect.defaultBlur : BlurEffect.neutralBlur), 0),
        )
      ];
}

Example 3,

FadeInUp composes FadeIn and SlideInUp effects, which themselves are composed of Fade and Slide, showing advanced multi-level composition. It does not use extends BeginEndEffect, because it would be unclear what properties they refer to. Instead it declares it's own beginY for clarity:

class FadeInUpEffect extends Effect with CompositeEffectMixin {
  const FadeInUpEffect({this.beginY, super.delay, super.duration, super.curve});

  final double? beginY;

  @override
  List<Effect> get effects => [
        const FadeInEffect(),
        SlideInUpEffect(beginY: beginY),
      ];
}

NOTE: In this example its been decided that beginY would make sense but the other values do not (beginOpacity, endOpacity and endY). This is debatable, but also not relevant to the example.

What is left?

  • Agree this is a good approach to move forward with
  • Implement remaining low level variations that already exist in the lib
    • FlipH/V
    • MoveX/Y
    • Desaturate
    • ScaleX/Y/XY
    • ShakeX/Y
    • SlideX/Y
    • Untint
    • Show/Hide
  • Add some higher level variations, described in issue Add additional presets #34 (FadeInDown, SlideRight, etc)

…osition / architecture

Add CompositeEffect helper class, and composeEffects helper method
Tweak expectWidgetWithDouble method to be less strict
… a composite tween that changes the underlying type, which is not currently supported cleanly.
# Conflicts:
#	lib/effects/shake_effect.dart
@esDotDev esDotDev marked this pull request as ready for review November 9, 2022 21:56
@esDotDev esDotDev changed the title Feature composite effects Enable declarative effect variations Nov 9, 2022
@esDotDev esDotDev force-pushed the feature-composite-effects branch 2 times, most recently from 089c49a to 24a10c9 Compare November 10, 2022 00:41
@esDotDev esDotDev force-pushed the feature-composite-effects branch from aa33ce0 to eaf8ab8 Compare November 10, 2022 02:22
@esDotDev esDotDev force-pushed the feature-composite-effects branch from 56d4cfb to 45ef6b2 Compare November 10, 2022 17:27
Renamed `buildAnimation` to `buildBeginEndAnimation` to make things a little more clear.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant