自定义翻书效果控件

定义翻书效果控件

效果

在这里插入图片描述

源码


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值