Swipe Between Filtered Images

Swipe between filtered images

You should only need 2 image views (the current one and the incoming one, as this is a paginated style scroll), and they switch role after each filter change. And your approach of using a layer mask should work, but not on a scroll view.

So, ensure that your view organisation is something like:

UIView // receives all gestures
UIScrollView // handles the filter name display, touch disabled
UIImageView // incoming in front, but masked out
UIImageView // current behind

Each image view has a mask layer, it's just a simple layer, and you modify the position of the mask layer to change how much of the image is actually visible.

Now, the main view handles the pan gesture, and uses the translation of the gesture to change the incoming image view mask layer position and the scroll view content offset.

As a change completes, the 'current' image view can't be seen any more and the 'incoming' image view takes the whole screen. The 'current' image view now gets moved to the front and becomes the incoming view, its mask gets updated to make it transparent. As the next gesture starts, its image is updated to the next filter and the change process starts over.

You can always be preparing the filtered images in the background as the scrolling is happening so that the image is ready to push into the view as you switch over (for rapid scrolling).

Swipe between filtered images for android

I've worked on something similar myself.

For your specific use case, I would just use a canvas and alpha blend the filters across, on fling, as the top image.

To do the alpha blending, set the alpha paint of the first image (the original) to 255 and the alpha of the second one (the filter) to something like 128.

You just need a filter with the size of the image and then you shift the position of the second image as you draw it. That's it.

It's extremely fast and works a treat on very, very old devices.

Here's a sample implementation:

    Bitmap filter,  // the filter
original, // our original
tempBitmap; // the bitmap which holds the canvas results
// and is then drawn to the imageView
Canvas mCanvas; // our canvas

int x = 0; // The x coordinate of the filter. This variable will be manipulated
// in either onFling or onScroll.
void draw() {
// clear canvas
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

// setup paint
paint0.setAlpha(255); // the original needs to be fully visible
paint1.setAlpha(128); // the filter should be alpha blended into the original.

// enable AA for paint
// filter image
paint1.setAntiAlias(true);
paint1.setFlags(Paint.ANTI_ALIAS_FLAG); // Apply AA to the image. Optional.
paint1.setFlags(Paint.FILTER_BITMAP_FLAG); // In case you scale your image, apple
// bilinear filtering. Optional.
// original image
paint0.setAntiAlias(true);
paint0.setFlags(Paint.ANTI_ALIAS_FLAG);
paint0.setFlags(Paint.FILTER_BITMAP_FLAG);

// draw onto the canvas
mCanvas.save();

mCanvas.drawBitmap(original, 0,0,paint0);
mCanvas.drawBitmap(filter, x,0,paint1);

mCanvas.restore();

// set the new image
imageView.setImageDrawable(new BitmapDrawable(getResources(), tempBitmap));
}

And here are basic onFling and onScroll implementations.

private static final int SWIPE_DISTANCE_THRESHOLD = 125;
private static final int SWIPE_VELOCITY_THRESHOLD = 75;

// make sure to have implemented GestureDetector.OnGestureListener for these to work.
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) >
SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {

// change picture to
if (distanceX > 0) {
// start left increment
}
else { // the left
// start right increment
}
}
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

// checks if we're touching for more than 2f. I like to have this implemented, to prevent
// jerky image motion, when not really moving my finger, but still touching. Optional.
if (Math.abs(distanceY) > 2 || Math.abs(distanceX) > 2) {
if(Math.abs(distanceX) > Math.abs(distanceY)) {
// move the filter left or right
}
}
}

Note: The onScroll/onFling implementations have pseudo code for the x adjustments, as those functions need to be tested. Someone who ends up implementing this in the future, can feel free to edit the answer and provide those functions.

iOS swipe filters over static image

So I can pre-make the image beforehand in code, then can I wipe-reveal the filter image to get the same effect? Using masks?

Yes, but no need for a mask. Pre-make the filtered image and put it in an image view. Let's say this filtered effect is to be swiped in from the left. Then make the image view's content mode be Left, and put it at the left of the real image, with width zero. As the swipe happens, animate the width of the image view to the width of the image. This will cause the filtered image to be revealed from the left side.

Swipe between different images in android fragment

You can use ViewPager to achieve the slider

And for page indicator dots refer this library project

This SO question can help you more link

Apply color filter picture while swipe Flutter

the idea is to move the image in the opposite direction to the page movement - so if PageView is moving the child (ColorFiltered widget) to the right you have to move the grand child (Image widget) to the left so it stays in the same place - while simple in theory in practice it can make some headache - fortunately there is CustomSingleChildLayout widget that save us a lot of work:

class ColorFilteredPageView extends StatefulWidget {
final ImageProvider image;
final List<ColorFilter> filters;
final List<String> filterNames;

ColorFilteredPageView({
required this.image,
required this.filters,
required this.filterNames,
}) : assert(filters.length == filterNames.length);

@override
_ColorFilteredPageViewState createState() => _ColorFilteredPageViewState();
}

class _ColorFilteredPageViewState extends State<ColorFilteredPageView> {
PageController controller = PageController();

@override
Widget build(BuildContext context) {
return PageView.builder(
controller: controller,
itemCount: widget.filters.length,
itemBuilder: (ctx, index) {
return Stack(
fit: StackFit.expand,
children: [
ClipRect(
child: ColorFiltered(
colorFilter: widget.filters[index],
child: CustomSingleChildLayout(
delegate: _ColorFilteredPageViewDelegate(index, controller),
child: Image(image: widget.image, fit: BoxFit.cover),
),
),
),
...outlinedName(widget.filterNames[index]),
],
);
},
);
}
}

/*
// NOTE
// AnimatedBuilder + FractionalTranslation can also be used
// instead of CustomSingleChildLayout but it is kind of workaround imho....
ClipRect(
child: ColorFiltered(
colorFilter: widget.filters[index],
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return FractionalTranslation(
translation: controller.position.haveDimensions?
Offset(controller.page - index, 0) : Offset.zero,
child: child,
);
},
child: Image(image: widget.image, fit: BoxFit.cover),
),
),
),
*/

class _ColorFilteredPageViewDelegate extends SingleChildLayoutDelegate {
final int index;
final PageController controller;

_ColorFilteredPageViewDelegate(this.index, this.controller) : super(relayout: controller);

Offset getPositionForChild(Size size, Size childSize) {
// print('index: $index, dx: ${controller.offset - index * size.width}');
return Offset(controller.offset - index * size.width, 0);
}

@override
bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate) => false;
}

Iterable<Widget> outlinedName(String name) {
final styles = [
TextStyle(
foreground: Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 4
..maskFilter = MaskFilter.blur(BlurStyle.solid, 2),
fontWeight: FontWeight.w500,
),
TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
];
return styles.map((style) => Align(
alignment: Alignment(0, 0.75),
child: Text(name,
textScaleFactor: 2.5,
textAlign: TextAlign.center,
style: style,
),
),);
}

now with some sample filters:

final myFilters = [
ColorFilter.mode(Colors.transparent, BlendMode.dst),
ColorFilter.mode(Colors.teal, BlendMode.softLight),
ColorFilter.mode(Colors.teal, BlendMode.hardLight),
ColorFilter.mode(Colors.deepPurple, BlendMode.hue),
// sepia
ColorFilter.matrix([
0.393, 0.769, 0.189, 0, 0,
0.349, 0.686, 0.168, 0, 0,
0.272, 0.534, 0.131, 0, 0,
0, 0, 0, 1, 0,
]),
// greyscale
ColorFilter.matrix([
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
]),
// invert
ColorFilter.matrix([
-1, 0, 0, 0, 255,
0, -1, 0, 0, 255,
0, 0, -1, 0, 255,
0, 0, 0, 1, 0,
]),
ColorFilter.linearToSrgbGamma(),
ColorFilter.srgbToLinearGamma(),
ColorFilter.mode(Colors.transparent, BlendMode.dst),
];

final myFilterNames = [
'original image', 'teal soft light', 'teal hard light', 'deep purple hue', 'matrix sepia', 'matrix greyscale', 'matrix invert', 'linearToSrgbGamma', 'srgbToLinearGamma', 'original image again',
];

you can use it as follows:

child: ColorFilteredPageView(
image: NetworkImage('https://unsplash.com/photos/3fEzry0pIms/download?force=true&w=640'),
filters: myFilters,
filterNames: myFilterNames,
),


Related Topics



Leave a reply



Submit