introduce

When browsing Zhihu, I find that when the list of its home page is scrolling, a certain item will display different parts of the picture according to the scrolling position. If some special advertising pictures are combined, it will create a penetrating visual effect.

Roughly as follows:

This is the Demo effect implementedCopy the code

Below is the original picture information to be shown: 1080*1920 size 2.15m because of other research, so the picture deliberately used large volume.Copy the code

This effect is still very interesting, free to have nothing to do, to achieve a try.

implementation

Our widget for displaying images is called DrawImageItem. This is Demo, so sorry for the arbitrary code.Copy the code

Base page structure

The entire page is a listView with 30 items and we insert our DrawImageItem at 5 and 10.

Final GlobalKey key = GlobalKey(); Widget listView(Size size){ return ListView( padding: EdgeInsets.all(0), key: key, controller: controller, children: List.generate(30, (index){ return (index == 10 || index == 5) ? specialOne(size): Container( width: size.width,height: size.height/6, color: index % 2 == 0 ? Colors.blue : Colors.red, ); })); } Widget specialOne(Size size){ return Container( width: size.width,height: size.height/6, child: DrawImageItem(size: size, controller: controller,viewPortHeight: size.height/6, parentKey: key,), ); }Copy the code

Now let’s look at the implementation of DrawImageItem.

DrawImageItem

We passed some values in the page:

Class DrawImageItem extends StatefulWidget{class DrawImageItem extends StatefulWidget{class DrawImageItem extends StatefulWidget{class DrawImageItem extends StatefulWidget{ // The Size of the entire page is used to calculate the proportion of the image. // List View controller final ScrollController controller; // final double viewPortHeight; // List View key final GlobalKey parentKey; const DrawImageItem({Key key, @required this.size,@required this.controller,this.viewPortHeight ,this.parentKey}) : super(key: key); @override State<StatefulWidget> createState() { return DrawImageItemState(size,controller,viewPortHeight,parentKey); }}Copy the code

And then let’s look at its state.

DrawImageItemState

In addition to receiving the value passed in, we also instantiate an image:

// Because this is demo, I will instantiate one directly here. final Image image = Image.asset('assets/lemon.png');Copy the code

Since flutter is logical pixels that are not equal to image pixels, we need to calculate the ratio between them.

/// screen/image double widthRatio; double heightRatio;Copy the code

In addition, the method we used for drawing pictures is as follows:

// canvas.drawImageRect(_image, srcRect, dstRect, myPaint); // In CustomPainter, described laterCopy the code

So we need to create some variables:

//_image ui.Image uiImage; /// screen/image double widthRatio; double heightRatio; ///image Rect srcRect ; ///view Rect dstRect ;Copy the code

Ok, all the required variables are created and we now initialize them in the initState method of DrawImageItemState:

WidgetsBinding.instance.addPostFrameCallback((timeStamp) { final ImageStream newStream = image.image.resolve(createLocalImageConfiguration(context)); newStream.addListener(ImageStreamListener((image,_){ if(image? .image ! = null){ uiImage = image.image; widthRatio = uiImage.width / size.width; heightRatio = uiImage.height / size.height; initRect(); initListener(); setState(() { }); }})); });Copy the code

Using the above method, we get the UI.Image and the corresponding ratio of the Image, and then we look at initRect() :

void initRect(){ final RenderBox renderBox = context.findRenderObject() as RenderBox; Offset globalPos = renderBox.localToGlobal(Offset.zero,ancestor: parentKey.currentContext.findRenderObject()); // The width of the item is equal to the width of the screen. SrcRect = Rect.fromltwh (Globalpos.dx, math.min(Globalpos.dy *heightRatio, uiImage.height.floorToDouble() - (viewPortHeight*widthRatio)), size.width * widthRatio, viewPortHeight * heightRatio); FromLTWH (0, 0, size.width,// The width and height are passed in from the outside. viewPortHeight); }Copy the code

Now that srcRect and dstRect are initialized, look at initListener():

Void initListener(){controller.addListener(() {if(mounted && context! = null){ final RenderBox renderBox = context.findRenderObject() as RenderBox; final Offset dstOffset = renderBox.localToGlobal(Offset.zero,ancestor: parentKey.currentContext.findRenderObject()); Final Offset realOffset = dstoffset. dy <= 0? Offset(dstOffset.dx,0) : (dstOffset.dy * heightRatio) >= uiImage.height ? Offset(dstOffset.dx,uiImage.height.toDouble()) :dstOffset; SrcRect = Rect.fromltwh (realoffset.dx, math.min(realoffset.dy *heightRatio, uiImage.height.floorToDouble() - (viewPortHeight*widthRatio)), size.width * widthRatio, viewPortHeight * heightRatio); setState(() { }); }}); }Copy the code

Now that our initialization is complete, let’s start with the page layout:

  @override
  Widget build(BuildContext context) {
    return uiImage == null ?
    Center(child: Text('loading'),)
        :  CustomPaint(
      painter: MyPaint(uiImage,srcRect,dstRect),
    );
Copy the code

The method is very simple, because the uiImage fetch is asynchronous, so I put a random placeholder widget, and after I get the uiImage, WE use CustomPaint to display the image in MyPaint(uiImage,srcRect,dstRect).

MyPaint

MyPaint(uiImage,srcRect,dstRect) internal code is relatively simple as follows:

It mainly receives three parameters, picture, picture capture area, draw target area.Copy the code
class MyPaint extends CustomPainter{ final ui.Image _image; final Rect srcRect ; final Rect dstRect ; MyPaint(this._image, this.srcRect, this.dstRect); final Paint myPaint = Paint().. isAntiAlias = true; Override void paint(Canvas Canvas, Size Size) { We draw the captured area to the target canvas.drawImageRect(_image, srcRect, dstRect, myPaint); } @override bool shouldRepaint(MyPaint oldDelegate) { // TODO: implement shouldRepaint return srcRect ! = oldDelegate.srcRect || dstRect ! = oldDelegate.dstRect; }}Copy the code

That’s it. Thank you for reading.

Demo

github : Demo

series

Flutter — Imitation of netease Cloud Music App(Basic Version)

Realize netease cloud music sliding conflict processing effect

Flutter custom View — modelled on the Autonavi tertiary linkage Drawer

Flutter custom View – a list of self-selected stocks that mimic a flush

Introduction to Flutter exercise — Evenet&Method Channel collaborative loading