No update for a long time.
This is about an interesting e-reader.
At present,The function is no prefect.
The main part is the widget be used to display the book content.
The code about this part seems a little long
So in order to look more concise,first delete the detailed code
The widget implements the path calculation according to the position of the finger.
The class pageClipper display the area of the page according to the path.
import 'dart:math';
import 'package:flutter/material.dart';
class BookWidget extends StatefulWidget {
BookWidgetData data;
bool enable;
var callBack;
var onFingerDown;
BookWidget(this.data, this.enable, func(bool isNext, int page),
func2(bool isNext, int page))
: super() {
print(data.bgStr);
print(data.colorpos);
callBack = func;
onFingerDown = func2;
}
State state;
State<StatefulWidget> createState() {
state = BookWidgetState();
return state;
}
void upData(BookWidgetData data) {
if(data.page==-1||data.page==-2){
data.bgStr="images/bg.jpeg";
}
(state as BookWidgetState).upData(data);
}
}
class BookWidgetState extends State<BookWidget> {
void upData(BookWidgetData data) {
// print("更新頁面方法"+ widget.data.content);
setState(() {
widget.data = data;
// widget.color = data.color;
// widget.colorPos = data.colorpos;
reset();
});
// print("更新頁面方法end"+ widget.data.content);
}
@override
void initState() {
// TODO: implement initState
super.initState();
isright = false;
isleft = false;
reset();
}
@override
Widget build(BuildContext context) {
// width=context.size.width;
// height=context.size.height;
return Listener(
child: Stack(
fit: StackFit.expand,
children: [
ClipPath(
clipper: pageCliper(getPathBFromLower),
child: Container(
color: Colors.white,
child: Text(""),
),
),
ClipPath(
clipper: pageCliper(getPathAFromLower),
child: Container(
// color: widget.data.page==-1||widget.data.page==-1
// ? Colors.white
// : null,
child: Padding(
padding: const EdgeInsets.only(top: 30, left: 5, right: 5),
child: Text(
widget.data.content,
style: TextStyle(
color: Colors.black87,
fontSize: 24,
decoration: TextDecoration.none),
),
),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(widget.data.bgStr != null?widget.data.bgStr:"images/bg.jpeg"),
fit: BoxFit.cover)
),
))
],
),
onPointerMove: widget.enable ? onFingerMove : null,
onPointerUp: widget.enable ? onFingerUp : null,
onPointerDown: widget.enable ? onFingerDown : null,
);
}
void reset() {
}
void onFingerDown(PointerDownEvent event) {
}
void onFingerUp(PointerUpEvent event) {
}
void onFingerMove(PointerMoveEvent details) {
}
Path getPathAFromLower(size) {
}
Path getPathAFromLowerLeft(Size size) {
}
Path getPathAFromLowerRight(Size size) {
}
Path getPathBFromLowerRight(Size size) {
}
Path getPathBFromLowerLeft(Size size) {
}
class pageCliper extends CustomClipper<Path> {
var getPath;
pageCliper(fun(Size size)) : super() {
this.getPath = fun;
}
@override
Path getClip(Size size) {
// print("重繪");
return getPath(size);
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
Here is the complete code
The path algorithm draws on a blog about Android's native development of e-book source code
It has been modified and only a few of them are used to achieve the same effect of turning left and right pages
import 'dart:math';
import 'package:flutter/material.dart';
class BookWidgetData {
String content;
int page;
Color color;
String bgStr;
int colorpos;
BookWidgetData();
BookWidgetData.name(this.content, this.page);
Object get colorPos => null;
}
class BookWidget extends StatefulWidget {
BookWidgetData data;
bool enable;
var callBack;
var onFingerDown;
BookWidget(this.data, this.enable, func(bool isNext, int page),
func2(bool isNext, int page))
: super() {
print(data.bgStr);
print(data.colorpos);
callBack = func;
onFingerDown = func2;
}
State state;
State<StatefulWidget> createState() {
state = BookWidgetState();
return state;
}
void upData(BookWidgetData data) {
if(data.page==-1||data.page==-2){
data.bgStr="images/bg.jpeg";
}
(state as BookWidgetState).upData(data);
}
}
bool isright, isleft;
class BookWidgetState extends State<BookWidget> with TickerProviderStateMixin {
Point a, f, g, e, h, c, j, b, k, d, i;
double width, height;
double lPathAShadowDis, rPathAShadowDis;
AnimationController animationController;
AnimationController animationController2;
Animation<double> animation;
void upData(BookWidgetData data) {
// print("更新頁面方法"+ widget.data.content);
setState(() {
widget.data = data;
// widget.color = data.color;
// widget.colorPos = data.colorpos;
reset();
});
// print("更新頁面方法end"+ widget.data.content);
}
@override
void initState() {
// TODO: implement initState
super.initState();
isright = false;
isleft = false;
reset();
}
double startx, starty, endx;
double scale;
void startAnimal() {
// count++;
startx = a.x;
starty = a.y;
if (endx == width) {
scale = (height - a.y) / (width - a.x);
} else {
scale = (height - a.y) / a.x;
}
// print("調用了一次$count");
f.x = width;
f.y = height;
animation = new Tween(begin: startx, end: endx).animate(animationController)
..addListener(() {
setState(() {
a.x = animation.value;
double add = animation.value - startx;
// print(widget.data.content +
// "增長值$add" +
// "比率$scale" +
// "stratX開始值$startx startY開始值");
if (endx == width) {
a.y = starty + add * scale;
} else {
a.y = starty - add * scale;
}
if (isleft) {
a.x = width - a.x;
}
calcPointsXY(a, f);
});
})
..addStatusListener((AnimationStatus state) {
if(state==AnimationStatus.dismissed){
setState(() {
reset();
});
}
if (state == AnimationStatus.completed) {
// animationController.reset();
setState(() {
reset();
});
}
});
animationController.forward();
}
void startAnimal2() {
// count++;
startx = a.x;
starty = a.y;
if (endx == width) {
scale = (height - a.y) / (width - a.x);
} else {
scale = (height - a.y) / a.x;
}
// print("調用了一次$count");
f.x = width;
f.y = height;
animation =
new Tween(begin: startx, end: endx).animate(animationController2)
..addListener(() {
setState(() {
a.x = animation.value;
double add = animation.value - startx;
// print(widget.data.content +
// "增長值$add" +
// "比率$scale" +
// "stratX開始值$startx startY開始值");
if (endx == width) {
a.y = starty + add * scale;
} else {
a.y = starty - add * scale;
}
if (isleft) {
a.x = width - a.x;
}
// print("x值");
// print(a.x);
calcPointsXY(a, f);
});
})
..addStatusListener((AnimationStatus state) {
if(state==AnimationStatus.dismissed){
setState(() {
reset();
});
}
if (state == AnimationStatus.completed) {
// animationController.reset();
setState(() {
reset();
});
if (isright) {
widget.callBack(true, widget.data.page);
} else {
widget.callBack(false, widget.data.page);
}
}
});
animationController2.forward();
}
@override
Widget build(BuildContext context) {
// width=context.size.width;
// height=context.size.height;
animationController = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
animationController2 = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
return Listener(
child: Stack(
fit: StackFit.expand,
children: [
ClipPath(
clipper: pageCliper(getPathBFromLower),
child: Container(
color: Colors.white,
child: Text(""),
),
),
ClipPath(
clipper: pageCliper(getPathAFromLower),
child: Container(
// color: widget.data.page==-1||widget.data.page==-1
// ? Colors.white
// : null,
child: Padding(
padding: const EdgeInsets.only(top: 30, left: 5, right: 5),
child: Text(
widget.data.content,
style: TextStyle(
color: Colors.black87,
fontSize: 24,
decoration: TextDecoration.none),
),
),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(widget.data.bgStr != null?widget.data.bgStr:"images/bg.jpeg"),
fit: BoxFit.cover)
),
))
],
),
onPointerMove: widget.enable ? onFingerMove : null,
onPointerUp: widget.enable ? onFingerUp : null,
onPointerDown: widget.enable ? onFingerDown : null,
);
}
void reset() {
a = Point();
a.x = -1;
a.y = -1;
f = Point();
g = Point();
e = Point();
h = Point();
c = Point();
j = Point();
b = Point();
k = Point();
d = Point();
i = Point();
}
void onFingerDown(PointerDownEvent event) {
isright = false;
isleft = false;
if (event.position.dx > width - 100 && event.position.dy > height - 100) {
isright = true;
widget.onFingerDown(true, widget.data.page);
} else if (event.position.dx < 100 && event.position.dy > height - 100) {
isleft = true;
widget.onFingerDown(false, widget.data.page);
}
}
void onFingerUp(PointerUpEvent event) {
a.x = event.position.dx;
a.y = event.position.dy;
if (isright && a.x < 100) {
// reset();
endx =0;
a.x = event.position.dx;
a.y = event.position.dy;
if(a.x==0){
a.x=1;
}
startAnimal2();
// widget.callBack(true, widget.data.page);
} else if (isleft && a.x > width - 100) {
// reset();
endx = width;
a.x = event.position.dx;
a.y = event.position.dy;
startAnimal2();
// widget.callBack(false, widget.data.page);
} else if(isright||isleft){
reset();
a.x = event.position.dx;
a.y = event.position.dy;
if (isright)
endx = width;
else
endx = 0;
startAnimal();
// setState(() {
// reset();
// });
}
}
void onFingerMove(PointerMoveEvent details) {
if (isleft || isright) {
setState(() {
a.x = details.localPosition.dx;
a.y = details.localPosition.dy;
f.x = width;
f.y = height;
if (isleft) {
a.x = width - a.x;
}
calcPointsXY(a, f);
});
}
}
Path getPathAFromLower(size) {
if (isright) {
return getPathAFromLowerRight(size);
} else {
return getPathAFromLowerLeft(size);
}
}
Path getPathAFromLowerLeft(Size size) {
var pathA = Path();
if (a.x == -1 && a.y == -1||c.x==null) {
width = size.width;
height = size.height;
pathA.lineTo(0, size.height);
pathA.lineTo(size.width, size.height);
pathA.lineTo(size.width, 0);
return pathA;
}
pathA.lineTo(size.width, size.height); //移動到右下角
pathA.lineTo(size.width - c.x, c.y); //移動到c點
pathA.quadraticBezierTo(
size.width - e.x, e.y, size.width - b.x, b.y); //從c到b畫貝塞爾曲線,控制點爲e
pathA.lineTo(width - a.x, a.y); //移動到a點
pathA.lineTo(size.width - k.x, k.y); //移動到k點
pathA.quadraticBezierTo(
size.width - h.x, h.y, size.width - j.x, j.y); //從k到j畫貝塞爾曲線,控制點爲h
pathA.lineTo(0, 0); //移動到左上角
pathA.lineTo(size.width, 0);
pathA.lineTo(size.width, size.height);
return pathA;
}
Path getPathAFromLowerRight(Size size) {
var pathA = Path();
if (a.x == -1 && a.y == -1) {
width = size.width;
height = size.height;
pathA.lineTo(0, size.height);
pathA.lineTo(size.width, size.height);
pathA.lineTo(size.width, 0);
return pathA;
}
pathA.lineTo(0, size.height); //移動到左下角
pathA.lineTo(c.x, c.y); //移動到c點
pathA.quadraticBezierTo(e.x, e.y, b.x, b.y); //從c到b畫貝塞爾曲線,控制點爲e
pathA.lineTo(a.x, a.y); //移動到a點
pathA.lineTo(k.x, k.y); //移動到k點
pathA.quadraticBezierTo(h.x, h.y, j.x, j.y); //從k到j畫貝塞爾曲線,控制點爲h
pathA.lineTo(size.width, 0); //移動到右上角
pathA.close(); //閉合區域
return pathA;
}
Path getPathBFromLower(size) {
if (isright) {
return getPathBFromLowerRight(size);
} else {
return getPathBFromLowerLeft(size);
}
}
Path getPathBFromLowerRight(Size size) {
Path pathB = Path();
if (a.x == -1) {
return pathB;
}
pathB.moveTo(i.x, i.y); //移動到i點
pathB.lineTo(d.x, d.y); //移動到d點
pathB.lineTo(b.x, b.y); //移動到b點
pathB.lineTo(a.x, a.y); //移動到a點
pathB.lineTo(k.x, k.y); //移動到k點
return pathB;
}
Path getPathBFromLowerLeft(Size size) {
Path pathB = Path();
if (a.x == -1) {
return pathB;
}
print(size.width);
pathB.moveTo(size.width - i.x, i.y); //移動到i點
pathB.lineTo(size.width - d.x, d.y); //移動到d點
pathB.lineTo(size.width - b.x, b.y); //移動到b點
pathB.lineTo(size.width - a.x, a.y); //移動到a點
pathB.lineTo(size.width - k.x, k.y); //移動到k點
return pathB;
}
void calcPointAByTouchPoint() {
double w0 = width - c.x;
double w1 = (f.x - a.x).abs();
double w2 = width * w1 / w0;
a.x = (f.x - w2).abs();
double h1 = (f.y - a.y).abs();
double h2 = w2 * h1 / w1;
a.y = (f.y - h2).abs();
}
/**
* 計算各點座標
* @param a
* @param f
*/
void calcPointsXY(Point a, Point f) {
g.x = (a.x + f.x) / 2;
g.y = (a.y + f.y) / 2;
e.x = g.x - (f.y - g.y) * (f.y - g.y) / (f.x - g.x);
e.y = f.y;
h.x = f.x;
h.y = g.y - (f.x - g.x) * (f.x - g.x) / (f.y - g.y);
c.x = e.x - (f.x - e.x) / 2;
c.y = f.y;
j.x = f.x;
j.y = h.y - (f.y - h.y) / 2;
b = getIntersectionPoint(a, e, c, j);
k = getIntersectionPoint(a, h, c, j);
d.x = (c.x + 2 * e.x + b.x) / 4;
d.y = (2 * e.y + c.y + b.y) / 4;
i.x = (j.x + 2 * h.x + k.x) / 4;
i.y = (2 * h.y + j.y + k.y) / 4;
//計算d點到ae的距離
double lA = a.y - e.y;
double lB = e.x - a.x;
double lC = a.x * e.y - e.x * a.y;
lPathAShadowDis =
((lA * d.x + lB * d.y + lC) / sqrt(pow(lA, 2) + pow(lB, 2))).abs();
//計算i點到ah的距離
double rA = a.y - h.y;
double rB = h.x - a.x;
double rC = a.x * h.y - h.x * a.y;
rPathAShadowDis =
((rA * i.x + rB * i.y + rC) / sqrt(pow(rA, 2) + pow(rB, 2))).abs();
}
/**
* 計算兩線段相交點座標
* @param lineOne_My_pointOne
* @param lineOne_My_pointTwo
* @param lineTwo_My_pointOne
* @param lineTwo_My_pointTwo
* @return 返回該點
*/
Point getIntersectionPoint(
Point lineOne_My_pointOne,
Point lineOne_My_pointTwo,
Point lineTwo_My_pointOne,
Point lineTwo_My_pointTwo) {
double x1, y1, x2, y2, x3, y3, x4, y4;
x1 = lineOne_My_pointOne.x;
y1 = lineOne_My_pointOne.y;
x2 = lineOne_My_pointTwo.x;
y2 = lineOne_My_pointTwo.y;
x3 = lineTwo_My_pointOne.x;
y3 = lineTwo_My_pointOne.y;
x4 = lineTwo_My_pointTwo.x;
y4 = lineTwo_My_pointTwo.y;
double pointX =
((x1 - x2) * (x3 * y4 - x4 * y3) - (x3 - x4) * (x1 * y2 - x2 * y1)) /
((x3 - x4) * (y1 - y2) - (x1 - x2) * (y3 - y4));
double pointY =
((y1 - y2) * (x3 * y4 - x4 * y3) - (x1 * y2 - x2 * y1) * (y3 - y4)) /
((y1 - y2) * (x3 - x4) - (x1 - x2) * (y3 - y4));
return new Point.name(pointX, pointY);
}
/**
* 計算C點的X值
* @param a
* @param f
* @return
*/
double calcPointCX(Point a, Point f) {
Point g, e;
g = new Point();
e = new Point();
g.x = (a.x + f.x) / 2;
g.y = (a.y + f.y) / 2;
e.x = g.x - (f.y - g.y) * (f.y - g.y) / (f.x - g.x);
e.y = f.y;
return e.x - (f.x - e.x) / 2;
}
}
class Point {
double x;
double y;
Point();
Point.name(this.x, this.y);
}
class pageCliper extends CustomClipper<Path> {
var getPath;
pageCliper(fun(Size size)) : super() {
this.getPath = fun;
}
@override
Path getClip(Size size) {
// print("重繪");
return getPath(size);
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}