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