定义翻书效果控件
效果
源码
public class BookPageView extends View implements ValueAnimator.AnimatorUpdateListener {
private Paint borderPaint;
private Paint paint;
private int measuredHeight;
private int measuredWidth;
private float ratio = 0.5f;
private boolean isReset = true;
private PointF f;//触摸的页角(页面右下角)
private PointF a;//触摸点(页面右下角)
private PointF e;//af连线的垂直平分线与页面底边交汇点
private PointF h;//af连线的垂直平分线与页面右边交汇点
private PointF c;//eh连线的水平线与页面底边交汇点(过af连线1/4位置)
private PointF j;//eh连线的水平线与页面右边交汇点(过af连线1/4位置)
private PointF m;//cj连线与ae连线的交汇点
private PointF n;//cj连线与ah连线的交汇点
/**
* 二阶贝塞尔曲线 (起始点p0,控制点p1、p2(结束点))生成规则
* 连接p0-p1,p1-p2;分别取中点p3,p4并连接
* 分别取p0-p3、p3-p4、p4-p2中点p5,p6,p7
* 连接p5-p6,p6-p7,并分别取中点p8,p9并连接
* p0-p5-p8-p9-p7便形成弧形,重复以上动作就可以得到二阶贝塞尔曲线,同时可发现二阶贝塞尔曲线顶点是p0-p2连线的中点到p1的连线的中点
*/
private PointF p;//cea三点二阶贝塞尔曲线的顶点
private PointF q;//jha三点二阶贝塞尔曲线的顶点
private Path path;
private Path clipPath;
private RectF touchTop;//下方触摸区域
private RectF touchBottom;//上方触摸区域
private RectF goNextTopRectF;//顶部翻页区域
private RectF goNextBottomRectF;//底部翻页区域
private ValueAnimator pageAnim;
private float startX;
private float startY;
private boolean isHandleTouchEvent = false;
private boolean isTop;
private Bitmap bitmap1;
private Canvas canvas1;
private Bitmap bitmap2;
private Canvas canvas2;
private Bitmap bitmap3;
private Canvas canvas3;
public BookPageView(Context context) {
this(context, null);
}
public BookPageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BookPageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(30);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
borderPaint = new Paint();
borderPaint.setColor(Color.GRAY);
borderPaint.setStrokeWidth(1);
borderPaint.setDither(true);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setAntiAlias(true);
f = new PointF();
a = new PointF();
e = new PointF();
h = new PointF();
c = new PointF();
j = new PointF();
p = new PointF();
q = new PointF();
m = new PointF();
n = new PointF();
path = new Path();
clipPath = new Path();
touchTop = new RectF();
touchBottom = new RectF();
goNextTopRectF = new RectF();
goNextBottomRectF = new RectF();
pageAnim = ValueAnimator.ofInt(0, 100);
pageAnim.addUpdateListener(this);
pageAnim.setInterpolator(new AccelerateInterpolator());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
measuredHeight = getMeasuredHeight();
measuredWidth = getMeasuredWidth();
touchTop.top = 0;
touchTop.left = measuredWidth * (2 / 3f);
touchTop.right = measuredWidth;
touchTop.bottom = measuredHeight / 2f;
touchBottom.top = measuredHeight / 2f;
touchBottom.left = measuredWidth * (2 / 3f);
touchBottom.right = measuredWidth;
touchBottom.bottom = measuredHeight;
goNextTopRectF.top = 0;
goNextTopRectF.left = measuredWidth / 2f;
goNextTopRectF.right = measuredWidth;
goNextTopRectF.bottom = measuredHeight / 2f;
goNextBottomRectF.top = measuredHeight / 2f;
goNextBottomRectF.left = measuredWidth / 2f;
goNextBottomRectF.right = measuredWidth;
goNextBottomRectF.bottom = measuredHeight;
bitmap1 = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
canvas1 = new Canvas(bitmap1);
canvas1.drawColor(Color.GREEN);
bitmap2 = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
canvas2 = new Canvas(bitmap2);
canvas2.drawColor(Color.YELLOW);
bitmap3 = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
canvas3 = new Canvas(bitmap3);
canvas3.drawColor(Color.BLUE);
}
/**
* 根据A点和F点计算剩余所需点
*/
private void setPointF(float ax, float ay) {
// ay = ay >= measuredHeight ? measuredHeight - 1 : ay <= 0 ? 1 : ay;
if (!isTop) {
int min = measuredHeight - measuredWidth;
ay = Math.max(min, ay);
} else {
ay = Math.min(measuredWidth, ay);
}
float maxAx = (float) Math.sqrt(measuredWidth * measuredWidth - Math.pow(isTop ? ay : (measuredHeight - ay), 2));
a.set(Math.min(ax, maxAx), ay);
// a.set(ax, ay);
//eh为af连线的垂直平分线
float mf = (f.x - a.x) / 2;
float gm = (f.y - a.y) / 2;
e.set(Math.max(f.x - ((gm * gm / mf + mf)), 0), f.y);
h.set(f.x, (f.y - (mf * mf / gm + gm)));
//cj为eh的水平线,在a点到eh连线之间
c.set(Math.max(f.x - ((f.x - e.x) * (1 + ratio)), 0), f.y);
j.set(f.x, f.y - ((f.y - h.y) * (1 + ratio)));
setIntersectionPoint(a, e, c, j, m);
setIntersectionPoint(a, h, c, j, n);
p.set(Math.max(0, ((c.x + m.x) / 2 + e.x) / 2), ((m.y + c.y) / 2 + e.y) / 2);
q.set(((j.x + n.x) / 2 + h.x) / 2, ((j.y + n.y) / 2 + h.y) / 2);
}
/**
* 计算两条直线的交点
*
* @param p1 直线1上的点
* @param p2 直线1上的点
* @param p3 直线2上的点
* @param p4 直线2上的点
* @param intersectionPoint 交点
*/
private void setIntersectionPoint(PointF p1, PointF p2, PointF p3, PointF p4, PointF intersectionPoint) {
float a1 = (p2.y - p1.y) / (p2.x - p1.x);//直线1: y1=a1x1+b1中a1
float b1 = (p1.y * p2.x - p2.y * p1.x) / (p2.x - p1.x);//直线1: y2=a1x2+b1中b1
float a2 = (p4.y - p3.y) / (p4.x - p3.x);//直线2: y3=a2x3+b2中a2
float b2 = (p3.y * p4.x - p4.y * p3.x) / (p4.x - p3.x);//直线2: y4=a2x4+b2中b2
intersectionPoint.x = (b2 - b1) / (a1 - a2);
intersectionPoint.y = intersectionPoint.x * a1 + b1;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap1, 0, 0, null);
path.reset();
path.moveTo(0, isTop ? 0 : measuredHeight);
path.lineTo(c.x, c.y);
path.quadTo(e.x, e.y, m.x, m.y);
path.lineTo(a.x, a.y);
path.lineTo(n.x, n.y);
path.quadTo(h.x, h.y, j.x, j.y);
path.lineTo(measuredWidth, isTop ? measuredHeight : 0);
canvas.drawPath(path, borderPaint);
path.lineTo(f.x, f.y);
path.lineTo(c.x, c.y);
path.close();
canvas.clipPath(path);
canvas.drawBitmap(bitmap3, 0, 0, null);
clipPath.reset();
clipPath.moveTo(q.x, q.y);
clipPath.lineTo(p.x, p.y);
canvas.drawPath(clipPath, borderPaint);
clipPath.lineTo(a.x, a.y);
clipPath.lineTo(q.x, q.y);
clipPath.close();
canvas.clipPath(clipPath, Region.Op.INTERSECT);
canvas.drawBitmap(bitmap2, 0, 0, null);
}
private static final String TAG = "BookPageView";
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event != null) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
startY = y;
isTop = isTouchRect(touchTop, x, y);
if ((isTop || isTouchRect(touchBottom, x, y))) {
f.set(measuredWidth, isTop ? 0 : measuredHeight);
setPointF(x, y);
isHandleTouchEvent = true;
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
if (isHandleTouchEvent) {
if (isPointAScope(x, y)) {
setPointF(a.x + (x - startX), (y - startY) + a.y);
startX = x;
startY = y;
invalidate();
} else {
isReset = true;
playAnim();
isHandleTouchEvent = false;
}
}
break;
case MotionEvent.ACTION_UP:
if (isHandleTouchEvent) {
isReset = ((isTop && isTouchRect(goNextTopRectF, x, y))) || (!isTop && isTouchRect(goNextBottomRectF, x, y));
playAnim();
}
isHandleTouchEvent = false;
break;
}
}
return isHandleTouchEvent || super.onTouchEvent(event);
}
private void playAnim() {
isFinished = false;
pageAnim.setDuration(800);
pageAnim.start();
}
/**
* 判断A点界限
*/
public boolean isPointAScope(float ax, float ay) {
return ((isTop && ay <= measuredWidth) || (!isTop && ay >= measuredHeight - measuredWidth)) &&
ax <= Math.sqrt(measuredWidth * measuredWidth - Math.pow(isTop ? ay : (measuredHeight - ay), 2));
}
public boolean isTouchRect(RectF f, float x, float y) {
return f != null && x >= f.left && x <= f.right && y >= f.top && y <= f.bottom;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = (int) animation.getAnimatedValue() / 100f;
if (t == 1) {
isFinished = true;
finish();
}
if (isReset || t < 1) {
setPointF((isReset ? f.x : -measuredWidth-1 - a.x) * t + a.x, (f.y - a.y) * t + a.y);
invalidate();
}
}
private int count = 1;
private boolean isFinished = false;
public void finish() {
// if (!isReset) {
// canvas1.drawColor(count % 2 == 1 ? Color.BLUE : Color.GREEN);
// canvas3.drawColor(count % 2 == 1 ? Color.GREEN : Color.BLUE);
// count++;
// }
}
}
贝塞尔曲线工具类:https://blog.csdn.net/TomCat0916/article/details/105095137