The PlanWe are going to build an album-like demo as an application of the new SwipeGesture. If you are a front end web developer, you should be familiar with the construction:Then we are going to wire up a SwipeGesture and a Motion, so that the pictures will shift when they receive a swipe gesture.The OutcomeSwipe to view next or previous photo.The pictures are distributed under Creative Commons license.Original pictures came from Kim Carpenter, sophie, Kate, Chris. P, Kabacchi.Layout and Responsive DesignWe start by laying down the views:final View frame = new View();
frame.style.overflow = "hidden";
frame.profile.location = "center center";
// frame width/height = minimum of [window width] and [window height]
final View frameInner = new View();
// frameInner height = [frame height]
// frameInner width = [frame width] * [photo count]
// frameInner top = 0 (default)
// frameInner left = - [photo index] * [frame width]
frame.addChild(frameInner);
for (int i = 0; i < photoCount; i++) {
View photoBox = new View();
// photoBox width/height = [frame width/height] - 50, capped at 500
// photoBox top = an offset which makes it center-aligned to frame
// photoBox left = [the offset] + [photo index] * [frame width/height]
Image photo = new Image();
photo.classes.add("photo");
photo.profile.text = "location: top left; width: 100%; height: 100%";
photo.src = "res/alpaca-0${i+1}.jpg";
View mask = new View(); // to block browser's default image dragging
mask.classes.add("photo-mask");
mask.profile.text = "location: top left; width: 100%; height: 100%";
photoBox.addChild(photo);
photoBox.addChild(mask);
frameInner.addChild(photoBox);
}
mainView.addChild(frame);
Note that the size settings of some views are purposely left out, as we are going to handle it directly within the layout callback, so they will be responsive automatically:frame.on.preLayout.add((LayoutEvent event) {
final Size msize = new DOMQuery(mainView).innerSize;
frameSize = min(msize.width, msize.height);
final int photoSize = min(frameSize - 50, 500);
final int photoOffset = ((frameSize - photoSize) / 2).toInt();
frame.width = frame.height = frameInner.height = frameSize;
frameInner.width = frameSize * photoCount;
frameInner.left = -_index * frameSize;
for (int i = 0; i < photoCount; i++) {
View photoBox = frameInner.children[i];
photoBox.width = photoBox.height = photoSize;
photoBox.left = photoOffset + i * frameSize;
photoBox.top = photoOffset;
}
});
Business LogicNow we are going to write down the business logic:int _index = 0;
void next() => select(_index + 1);
void previous() => select(_index - 1);
void select(int index) {
if (index < 0 || index >= photoCount) {
// do nothing
return;
}
// TODO: trigger an animation to shift the photos
}
AnimationThe animation part is straightforward:void select(int index) {
if (index < 0 || index >= photoCount) {
// do nothing
return;
}
// trigger an animation to shift the photos
final Offset origin = new Offset(-_index * frameSize, 0);
final Offset dest = new Offset(-index * frameSize, 0);
new LinearPathMotion(frameInner.node, origin, dest, end: (MotionState state) {
_index = index;
}, easing: (num x) => x * x);
}
Swipe GestureHere we are going to capture user's swipe gesture.new SwipeGesture(mainView.node, (SwipeGestureState state) {
final int diff = state.delta.x;
if (diff < -50) // swipe left
next();
else if (diff > 50) // swipe right
previous();
});
But wait! There is an issue with this implementation. Whenever there are multiple gestures/motions in the game, it is necessary to consider whether they will conflict each other. Generally there will be a priority, where one gesture/motion will block or interrupt another.In the previous example we have shown how a gesture interrupts a motion. In this scenario, we are going to make the photo-shifting motion block the incoming swipe gesture:SwipeGesture gesture;
gesture = new SwipeGesture(mainView.node, (SwipeGestureState state) {
gesture.disable();
final int diff = state.delta.x;
if (diff < -50) // swipe left
next();
else if (diff > 50) // swipe right
previous();
else
gesture.enable();
});
void select(int index) {
if (index < 0 || index >= photoCount) {
gesture.enable();
return;
}
final Offset origin = new Offset(-_index * frameSize, 0);
final Offset dest = new Offset(-index * frameSize, 0);
new LinearPathMotion(frameInner.node, origin, dest, end: (MotionState state) {
_index = index;
gesture.enable();
}, easing: (num x) => x * x);
}
The gestures come with enable/disable APIs, allowing us to easily turn it on and off.ConclusionAt the end we added a few more decorations to the demo to make it fancier. They are quite straightforward to figure out from the source code.