extended_image

repository·master·Indexed 24 days ago

https://github.com/fluttercandies/extended_image

A powerful extension library for Flutter's Image component. It provides advanced features including local network image caching via ExtendedImage.network and ExtendedNetworkImageProvider, image editing (crop, rotate, flip) through EditorConfig and ImageEditorController, zoom/pan capabilities with GestureConfig, and customizable load state UI (loading, completed, failed) using ExtendedImageState.

Tokens
21.7K
Snippets
42
Records
78
Agent score
85%

What's inside extended_image

  1. Handle image load states (loading, completed, failed)

    master

    You can customize the UI shown during different stages of an image's lifecycle using the loadStateChanged callback.

    Key Concepts

    • States: The LoadState enum provides loading, completed, and failed states.
    • Enabling States: For network images, enableLoadState is true by default. For other sources (like local assets), you must manually set enableLoadState: true if the image takes time to load.
    • Overriding the Completed State:
      • To add decorations (like animations) without losing gestures or editor functionality, use state.completedWidget.
      • To override size or sourceRect at the completed state, use ExtendedRawImage with the provided state.extendedImageInfo?.image.
    • Reloading: If an image fails to load, you can trigger a retry by calling state.reLoadImage() within the failed state handler.
    ExtendedImage.network(
      url,
      loadStateChanged: (ExtendedImageState state) {
        switch (state.extendedImageLoadState) {
          case LoadState.loading:
            return CircularProgressIndicator();
          case LoadState.completed:
            // Use state.completedWidget to preserve gestures/editor
            return state.completedWidget;
          case LoadState.failed:
            return GestureDetector(
              onTap: () => state.reLoadImage(),
              child: Icon(Icons.error),
            );
        }
      },
    )
  2. Reduce memory usage with ExtendedResizeImage

    master

    You can optimize memory consumption by resizing images during the decoding/caching process using ExtendedResizeImage or the shorthand parameters in ExtendedImage.network.

    Using ExtendedResizeImage

    Wrap your ImageProvider with ExtendedResizeImage to control the decoded size.

    Using ExtendedImage.network shorthand

    Pass resizing parameters directly to the network constructor.

    Parameters:

    • compressionRatio: Resizes the image to original * compressionRatio. Range: (0.0, 1.0).
    • maxBytes: Compresses the image to be smaller than this value (actual bytes of the image, not decoded bytes). Default is 50KB.
    • width / height: The specific dimensions to decode and cache the image at.
        // Option 1: Using shorthand parameters in ExtendedImage.network
        ExtendedImage.network(
          'imageUrl',  
          compressionRatio: 0.1,
          maxBytes: null,
          cacheWidth: null,
          cacheHeight: null,  
        )
    
        // Option 2: Using ExtendedResizeImage wrapper
        ExtendedImage(
          image: ExtendedResizeImage(
            ExtendedNetworkImageProvider(
              'imageUrl',  
            ),
            compressionRatio: 0.1,
            maxBytes: null,
            width: null,
            height: null,
          ),
        )
  3. Configure Zoom and Pan behavior

    master

    To enable interactive zooming and panning, set the mode parameter to ExtendedImageMode.gesture. You can fine-tune the interaction using initGestureConfigHandler which returns a GestureConfig object.

    ExtendedImage Mode Options

    • none: Default mode.
    • gesture: Enables zoom and pan.
    • editor: Enables editor mode.

    GestureConfig Parameters

    ParameterDescriptionDefault
    minScaleMinimum scale factor0.8
    maxScaleMaximum scale factor5.0
    animationMinScaleScale to animate back to when ending a zoom at min scaleminScale
    animationMaxScaleScale to animate back to when ending a zoom at max scalemaxScale
    speedSpeed for zoom/pan1.0
    inertialSpeedInertial speed for zoom/pan100
    cacheGestureSaves gesture state (useful for ExtendedImageGesturePageView)false
    inPageViewIndicates if used within ExtendedImageGesturePageViewfalse
    initialAlignmentInitial alignment when initialScale > 1.0InitialAlignment.center
    hitTestBehaviorHow to behave during hit testsHitTestBehavior.deferToChild
    ExtendedImage.network(
      imageTestUrl,
      fit: BoxFit.contain,
      mode: ExtendedImageMode.gesture,
      initGestureConfigHandler: (state) {
        return GestureConfig(
            minScale: 0.9,
            animationMinScale: 0.7,
            maxScale: 3.0,
            animationMaxScale: 3.5,
            speed: 1.0,
            inertialSpeed: 100.0,
            initialScale: 1.0,
            inPageView: false,
            initialAlignment: InitialAlignment.center,
            );
      },
    )
  4. Extend image loading behavior by overriding ExtendedProvider

    master
    You can create custom loading logic by inheriting from ExtendedProvider and overriding the instantiateImageCodec method. This is useful for implementing global image processing, such as automatic image compression or metadata handling, across your application.
  5. Implement slide-to-exit page effect

    master

    You can implement a "slide-to-exit" effect (similar to WeChat) by wrapping your page in ExtendedImageSlidePage and configuring the ExtendedImage widget.

    Step 1: Configure ExtendedImage

    Set the following properties on your ExtendedImage widget:

    • enableSlideOutPage (bool): Set to true to enable the effect.
    • heroBuilderForSlidingPage: A builder for the Hero animation. The transform must act on the Hero widget to ensure smooth transitions during exit.

    Step 2: Wrap the page with ExtendedImageSlidePage

    Wrap your result widget with ExtendedImageSlidePage.

    Important Implementation Notes:

    • Avoid setState in onSlidingPage: Do not call setState directly inside the onSlidingPage callback, as this will reset the ExtendedImage state. Instead, notify only the specific widgets that need to update (e.g., using a ValueNotifier or a custom stream).
    • Transparency: If using slideType: SlideType.onlyImage, ensure your page has a transparent background.
    • Navigation: Use a transparent route (like a custom TransparentMaterialPageRoute) when pushing the page to ensure the background shows through correctly.

    ExtendedImageSlidePage Parameters

    • child: The widget to wrap.
    • slideAxis: Direction of sliding (SlideAxis.both, SlideAxis.horizontal, or SlideAxis.vertical).
    • slideType: Whether to slide the whole page or just the image (SlideType.wholePage or SlideType.onlyImage).
    • onSlidingPage: Callback triggered during sliding. Use this to update UI elements based on state.offset or state.isSliding.
    • slidePageBackgroundHandler: Custom handler to change the page background color based on Offset.
    • slideScaleHandler: Custom handler to change the page scale based on Offset.
    • slideEndHandler: Custom logic to determine if the page should pop when sliding ends.
    • resetPageDuration: Duration of the rebound animation if the page does not pop.
    return ExtendedImageSlidePage(
      child: result,
      slideAxis: SlideAxis.both,
      slideType: SlideType.onlyImage,
      onSlidingPage: (state) {
        var showSwiper = !state.isSliding;
        if (showSwiper != _showSwiper) {
          // Do NOT use setState() here directly
          _showSwiper = showSwiper;
          rebuildSwiper.add(_showSwiper);
        
      },
    );
  6. Install extended_image

    master

    Add extended_image to your pubspec.yaml dependencies. Choose the version based on your project's null-safety status.

    For Null-Safe projects (SDK >= 2.12.0):

    dependencies:
      extended_image: ^4.0.0

    For Non-Null-Safe projects (SDK < 2.12.0): Use the specific non-null-safety version if you are on Flutter versions between 1.17.0 and 1.22.6.

    dependencies:
      extended_image: ^3.0.0-non-null-safety
    dependencies:
      extended_image: ^4.0.0
  7. Extract crop data and process images using the 'image_editor' native library

    master

    For faster image processing, use the image_editor native library instead of the pure Dart image library.

    Workflow:

    1. Add image_editor: any to your pubspec.yaml.
    2. Get cropRect and rawImageData from ExtendedImageEditorState.
    3. Build an ImageEditorOption by adding RotateOption, FlipOption, and ClipOption.fromRect(cropRect) based on the current editor state.
    4. Call ImageEditor.editImage(image: img, imageEditorOption: option) to perform the edit.

    This method is generally faster as it uses native code.

    // 1. Get crop rect and raw image data from ExtendedImageEditorState
    final Rect cropRect = state.getCropRect();
    var data = state.rawImageData;
    
    // 2. Prepare crop option
    if (action.hasRotateDegrees) {
      final int rotateDegrees = action.rotateDegrees.toInt();
      option.addOption(RotateOption(rotateDegrees));
    }
    if (action.flipY) {
      option.addOption(const FlipOption(horizontal: true, vertical: false));
    }
    
    if (action.needCrop) {
      Rect cropRect = imageEditorController.getCropRect()!;
      option.addOption(ClipOption.fromRect(cropRect));
    }
    
    // 3. Crop with editImage
    final result = await ImageEditor.editImage(
      image: img,
      imageEditorOption: option,
    );
  8. Extract Crop Data using the 'image' Dart library

    master

    To process image data (crop, rotate, flip) using the pure Dart image library:

    1. Add image: any to your pubspec.yaml.
    2. Get the crop rectangle and raw data from ExtendedImageEditorState:
      • state.getCropRect(): Returns the Rect based on the raw image.
      • state.rawImageData: Returns the List<int> of raw image data.
    3. Convert the data to an Image object (use compute or an isolate to avoid blocking the UI).
    4. Apply transformations (rotation, flipping, cropping) using the image library functions like copyRotate, flip, and copyCrop.
    5. Encode the resulting Image back to bytes (e.g., encodeJpg).
    dependencies:
      image: any
      ///crop rect base on raw image
      final Rect cropRect = state.getCropRect();
    
      var data = state.rawImageData;
    
      // ... conversion to Image object using compute/isolate ...
    
      // Example transformations:
      image = bakeOrientation(image);
      if (editAction.hasRotateDegrees) {
        image = copyRotate(image, angle: editAction.rotateDegrees);
      }
    
      if (editAction.flipY) {
        image = flip(image, direction: FlipDirection.horizontal);
      }
    
      if (editAction.needCrop) {
        image = copyCrop(
          image,
          x: cropRect.left.toInt(),
          y: cropRect.top.toInt(),
          width: cropRect.width.toInt(),
          height: cropRect.height.toInt(),
        );
      }
    
      // ... encode back to bytes ...
  9. Extract Crop Data using the 'image_editor' Native library

    master

    For faster image processing, use the native image_editor library:

    1. Add image_editor: any to your pubspec.yaml.
    2. Get the crop rectangle and raw data from ExtendedImageEditorState:
      • state.getCropRect()
      • state.rawImageData
    3. Build an imageEditorOption by adding RotateOption, FlipOption, and ClipOption.fromRect(cropRect) based on the current editor state.
    4. Call ImageEditor.editImage(image: img, imageEditorOption: option) to perform the edits natively.
    dependencies:
      image_editor: any
      ///crop rect base on raw image
      final Rect cropRect = state.getCropRect();
    
      final img = state.rawImageData;
    
      // Prepare options
      if (action.hasRotateDegrees) {
        final int rotateDegrees = action.rotateDegrees.toInt();
        option.addOption(RotateOption(rotateDegrees));
      }
      if (action.flipY) {
        option.addOption(const FlipOption(horizontal: true, vertical: false));
      }
    
      if (action.needCrop) {
        Rect cropRect = imageEditorController.getCropRect()!;
        option.addOption(ClipOption.fromRect(cropRect));
      }
    
      // Execute edit
      final result = await ImageEditor.editImage(
        image: img,
        imageEditorOption: option,
      );
  10. Implement Photo View with ExtendedImageGesturePageView

    master

    Use ExtendedImageGesturePageView to create a page view specifically designed for displaying zoomable and pannable images. This is useful for image galleries where users can swipe between photos while also zooming in on individual images.

    Key Configuration:

    • canMovePage: Determines if the user can swipe to the next/previous page (default: true).
    • allowImplicitScrolling: If true, keeps neighboring pages alive for smoother accessibility scrolling (default: false).
    • GestureConfig.cacheGesture: If set to true, the gesture state (like zoom level) is saved even when the page changes. Important: If you enable this, you must call clearGestureDetailsCache() when the page is disposed to prevent memory leaks.
    • GestureConfig.inPageView: Set this to true when using ExtendedImage inside an ExtendedImageGesturePageView to ensure correct gesture handling.
    ExtendedImageGesturePageView.builder(
      itemBuilder: (BuildContext context, int index) {
        var item = widget.pics[index].picUrl;
        Widget image = ExtendedImage.network(
          item,
          fit: BoxFit.contain,
          mode: ExtendedImageMode.gesture,
          gestureConfig: GestureConfig(
            inPageView: true, 
            initialScale: 1.0,
            cacheGesture: false // If true, call clearGestureDetailsCache() on dispose
          ),
        );
        // ... rest of builder
      },
      itemCount: widget.pics.length,
      // ...
    )
  11. Implement image browsing with ExtendedImageGesturePageView

    master

    To achieve an image viewing experience similar to WeChat or Juejin, use ExtendedImageGesturePageView. This widget is designed to work like a standard PageView but avoids gesture conflicts between page scrolling and image zooming/panning.

    Key Features

    • Gesture State Caching: You can preserve the zoom/pan state of an image when navigating between pages in a PageView. If cacheGesture is set to true in GestureConfig, the image will return to its previous zoom level when the user navigates back to it.
    • Memory Management: If you use gesture caching, you must call clearGestureDetailsCache() (e.g., during page disposal) to prevent memory leaks.

    Configuration Options

    ExtendedImageGesturePageView Parameters

    • canMovePage (bool): Whether to allow page sliding. You can return false if the image scale is > 1.0 to prevent accidental page swipes while zooming.
    • allowImplicitScrolling (bool): Whether to respond to implicit accessibility scroll requests.

    GestureConfig Parameters

    • cacheGesture (bool): Whether to cache the gesture state. Use with ExtendedImageGesturePageView to retain zoom levels.
    • inPageView (bool): Set to true when using the image inside an ExtendedImageGesturePageView to ensure correct gesture handling.
    ExtendedImageGesturePageView.builder(
      itemBuilder: (BuildContext context, int index) {
        var item = widget.pics[index].picUrl;
        Widget image = ExtendedImage.network(
          item,
          fit: BoxFit.contain,
          mode: ExtendedImageMode.gesture,
          gestureConfig: GestureConfig(
            inPageView: true, 
            initialScale: 1.0,
            cacheGesture: false // Set to true to preserve zoom state across pages
          ),
        );
        // ... rest of implementation
      },
      itemCount: widget.pics.length,
      // ...
    )
  12. Customize iOS Launch Screen Assets

    master

    To change the launch screen image for the iOS version of your Flutter application, you can either replace the image files directly in the example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory or use Xcode for a more visual approach.

    Using Xcode:

    1. Open the iOS workspace by running open ios/Runner.xcworkspace from your terminal.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Locate the launch image asset set and drag/drop your desired images into the inspector.
    open ios/Runner.xcworkspace