Copyright notice: This article is the blogger’s original article, shall not be reproduced without the permission of the blogger

Android Development from Scratch series

Source: AnliaLee/BookPage, welcome star

If you see any mistakes or have any good suggestions, please leave a comment

preface: in the lastAndroid custom View — from scratch to achieve the effect of turning books (2)In the blog, weComplete the page turning effectAnd increasedUnanimate the page turnHow about this videoFill the View with content

This article only focuses on the ideas and implementation steps, some knowledge principles used in it will not be very detailed. If there are unclear APIS or methods, you can search the corresponding information on the Internet, there must be a god to explain very clearly, I will not present the ugly. In the spirit of serious and responsible, I will post the relevant knowledge of the blog links (in fact, is lazy do not want to write so much ha ha), you can send their own. For the benefit of those who are reading this series of blogs for the first time, this post contains some content that has been covered in the previous series of blogs

International convention, go up effect drawing first


Draws the contents of the current page (region A)

Related blog links

Usage of clipping Region.Op parameter in Android

The contents of area A may contain text, patterns, etc., so we need to draw all of these into A Bitmap and then onto the canvas. Of course, remember to crop the content and take the intersection of area A, so that it looks like the content is printed in area A, modify BookPageView

public class BookPageView extends View {
	// omit some code...
    private Paint textPaint;// Draw the text brush
    private void init(Context context, @Nullable AttributeSet attrs){
		// omit some code...
        textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setSubpixelText(true);// Set from pixel. If true, this will help the text appear on the LCD screen.
        textPaint.setTextSize(30);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        bitmap = Bitmap.createBitmap((int) viewWidth, (int) viewHeight, Bitmap.Config.ARGB_8888);
        bitmapCanvas = new Canvas(bitmap);

        if(a.x==-1 && a.y==-1) {// bitmapCanvas.drawPath(getPathDefault(),pathAPaint);
            drawPathAContent(bitmapCanvas,getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0) {// bitmapCanvas.drawPath(getPathAFromTopRight(),pathAPaint);
                drawPathAContent(bitmapCanvas,getPathAFromTopRight(),pathAPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){
// bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
                drawPathAContent(bitmapCanvas,getPathAFromLowerRight(),pathAPaint);
            }

            bitmapCanvas.drawPath(getPathC(),pathCPaint);
            bitmapCanvas.drawPath(getPathB(),pathBPaint);
        }
        canvas.drawBitmap(bitmap,0.0.null);
    }

    /** * Draw the contents of area A *@param canvas
     * @param pathA
     * @param pathPaint
     */
    private void drawPathAContent(Canvas canvas, Path pathA, Paint pathPaint){
        Bitmap contentBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.RGB_565);
        Canvas contentCanvas = new Canvas(contentBitmap);

        // Start drawing the contents of the region...
        contentCanvas.drawPath(pathA,pathPaint);// Draw a background to distinguish the regions
        contentCanvas.drawText("Here's what's in section A... AAAA", viewWidth-260, viewHeight-100, textPaint);

        // Finish drawing the contents of the area...

        canvas.save();
        canvas.clipPath(pathA, Region.Op.INTERSECT);// Clipping the drawing content, take the intersection with A region
        canvas.drawBitmap(contentBitmap, 0.0.null); canvas.restore(); }}Copy the code

The effect is shown in figure


Draw the contents of the next page (region B)

The principle of drawing the content of region B is the same as that of region A, but the difference is that the content of region B takes the part of region B that is different from the complete set of region AC. The code is as follows

public class BookPageView extends View {
    private void init(Context context, @Nullable AttributeSet attrs){
// pathBPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); We don't need to draw the path separately, so comment it out
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        bitmap = Bitmap.createBitmap((int) viewWidth, (int) viewHeight, Bitmap.Config.ARGB_8888);
        bitmapCanvas = new Canvas(bitmap);

        if(a.x==-1 && a.y==-1) {// bitmapCanvas.drawPath(getPathDefault(),pathAPaint);
            drawPathAContent(bitmapCanvas,getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0) {// bitmapCanvas.drawPath(getPathAFromTopRight(),pathAPaint);
                drawPathAContent(bitmapCanvas,getPathAFromTopRight(),pathAPaint);

                bitmapCanvas.drawPath(getPathC(),pathCPaint);
                drawPathBContent(bitmapCanvas,getPathAFromTopRight(),pathBPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){
// bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
                drawPathAContent(bitmapCanvas,getPathAFromLowerRight(),pathAPaint);

                bitmapCanvas.drawPath(getPathC(),pathCPaint);
                drawPathBContent(bitmapCanvas,getPathAFromLowerRight(),pathBPaint);
            }
// bitmapCanvas.drawPath(getPathC(),pathCPaint);
// bitmapCanvas.drawPath(getPathB(),pathBPaint);}}/** * Draw the content of area B *@param canvas
     * @param pathPaint
     * @param pathA
     */
    private void drawPathBContent(Canvas canvas, Path pathA, Paint pathPaint){
        Bitmap contentBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.RGB_565);
        Canvas contentCanvas = new Canvas(contentBitmap);

        // Start drawing the contents of the region...
        contentCanvas.drawPath(getPathB(),pathPaint);
        contentCanvas.drawText("This is the content in section B... BBBB", viewWidth-260, viewHeight-100, textPaint);

        // Finish drawing the contents of the area...

        canvas.save();
        canvas.clipPath(pathA);// Crop out the A area
        canvas.clipPath(getPathC(),Region.Op.UNION);// Crop out the complete set of regions A and C
        canvas.clipPath(getPathB(), Region.Op.REVERSE_DIFFERENCE);// Crop out the parts of the B region that are different from the AC region
        canvas.drawBitmap(contentBitmap, 0.0.null); canvas.restore(); }}Copy the code

The effect is shown in figure


Draws the contents of the back (C) of the current page (region A)

Related blog links

Android custom View advanced -Matrix principle

The content filling of area C is the most complicated part of this installment. The content of AB area is drawn directly in the corresponding area without too much processing, while the content of C area we see, namely the back of the current page, actually comes from this way, as shown in the figure (the figure is from AigeStudio’s blog Android page turning effect principle simulation distortion).

So how do you get the contents of this area? See the figure below and the analysis process

  • We set the current page to rectangle ABCD, and flip rectangle ABCD to get rectangle AB₁CD₁

  • (1) Let Angle ehD be Angle 0, and Angle heD be Angle 1, because eh is the vertical bisector of aD, then Angle ahD is twice the size of Angle 0; ② Because we require AC₂ to be parallel to B4, D4, and AC to be parallel to BD; So Angle C₂AC is equal to Angle ahD, which is twice Angle 0; ④ So the rotation Angle is twice the Angle 0

  • For later calculation purposes, we moved the length of the AB₂C₂D₂ along the negative direction of the X axis and the length of the long side of the rectangle (f.Y or E.Y) along the negative direction of the Y axis to get the final form A₃B₃C₃D₃

  • In the end, the A₃B₃C₃D₃ was used to move the length of e.x along the original X axis of the screen, and the length of e.y along the original Y axis of the screen. The results were A4 B4 C4 D4 (C₃ coincided with C4 and D4 coincided with point A). Because eO is parallel to AC and eC4 is parallel to AC₃, OeC4 is equal to CAC₃ A. (2) because the AC₃ is equal to e.x, the X coordinates of the C₃ are e.x·sinA and the Y coordinates of the C₃ are e.x·cosA. ③ Because rectangle ABCD and rectangle A4, B4, C4 and D4 are axisymmetrical in eh, the length of eC4 is equal to E.x. Similarly, the x-coordinate of C4 is E.x ·sinA+e.x, and the y-coordinate of C4 is e.x·cosA+e.y. (4) The amount of the C₃ to C4 was the same as the amount of the X ₃. The amount of the Y ₃ was e.y (X: (E.x ·sinA+e.x) – (e.x·sinA) = E.x. (e ^ x cosA+e ^ y) – (e ^ x cosA) = e ^ y.

From :з “Angle” _(:з “Angle) _, as for the mathematics knowledge, refer back to the teacher’s classmate to check it on the Internet by yourself haha), So let’s write an algorithm based on our previous analysis using our knowledge of Matrix, so let’s first look at the flip Matrix

Rotation matrix, θ is the Angle to be rotated

According to the previous analysis, we first flipped and then rotated, and the rotation Angle was double Angle 0 to calculate, and the calculation process was

Let’s start modifying our BookPageView

public class BookPageView extends View {
	// omit some code...
    private Paint pathCContentPaint;// Draw the C area content brush
    private void init(Context context, @Nullable AttributeSet attrs){
		// omit some code...
        pathCContentPaint = new Paint();
        pathCContentPaint.setColor(Color.YELLOW);
        pathCContentPaint.setAntiAlias(true);// Set anti-aliasing
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        bitmap = Bitmap.createBitmap((int) viewWidth, (int) viewHeight, Bitmap.Config.ARGB_8888);
        bitmapCanvas = new Canvas(bitmap);

        if(a.x==-1 && a.y==-1){
            drawPathAContent(bitmapCanvas,getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0){
                drawPathAContent(bitmapCanvas,getPathAFromTopRight(),pathAPaint);

                drawPathCContent(bitmapCanvas,getPathAFromTopRight(),pathCContentPaint);
                drawPathBContent(bitmapCanvas,getPathAFromTopRight(),pathBPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){ drawPathAContent(bitmapCanvas,getPathAFromLowerRight(),pathAPaint); drawPathCContent(bitmapCanvas,getPathAFromLowerRight(),pathCContentPaint); drawPathBContent(bitmapCanvas,getPathAFromLowerRight(),pathBPaint); }}}/** * Draw the content of area C *@param canvas
     * @param pathA
     * @param pathPaint
     */
    private void drawPathCContent(Canvas canvas, Path pathA, Paint pathPaint){
        Bitmap contentBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.RGB_565);
        Canvas contentCanvas = new Canvas(contentBitmap);

        // Start drawing the contents of the region...
        contentCanvas.drawPath(getPathB(),pathPaint);// Draw a background with path B
        contentCanvas.drawText("Here's what's in section A... AAAA", viewWidth-260, viewHeight-100, textPaint);

        // Finish drawing the contents of the area...

        canvas.save();
        canvas.clipPath(pathA);
        canvas.clipPath(getPathC(), Region.Op.REVERSE_DIFFERENCE);// Crop out the parts of region C that differ from region A

        float eh = (float) Math.hypot(f.x - e.x,h.y - f.y);
        float sin0 = (f.x - e.x) / eh;
        float cos0 = (h.y - f.y) / eh;
        // Set the rollover and rotation matrices
        float[] mMatrixArray = { 0.0.0.0.0.0.0.0.1.0 f };
        mMatrixArray[0] = - (1-2 * sin0 * sin0);
        mMatrixArray[1] = 2 * sin0 * cos0;
        mMatrixArray[3] = 2 * sin0 * cos0;
        mMatrixArray[4] = 1 - 2 * sin0 * sin0;

        Matrix mMatrix = new Matrix();
        mMatrix.reset();
        mMatrix.setValues(mMatrixArray);// Flip and rotate
        mMatrix.preTranslate(-e.x, -e.y);// A₃B₃C₃D₃ was formed along the negative XY axis
        mMatrix.postTranslate(e.x, e.y);// create a rectangle A4, B4, C4, D4

        canvas.drawBitmap(contentBitmap, mMatrix, null); canvas.restore(); }}Copy the code

The effect is shown in figure

It is not perfect. It can be observed that there are still some blank places on the back of the page before turning. This is because the content of area C is obtained after the rectangle of the current page is flipped, rotated and displaced, so it is also a rectangle, which naturally cannot cover the edge area of the curve. We need to process these edge areas, and different processing methods are required according to the complexity of the background. Since we have a pure color background, it will be easier to process. Here we only give the processing scheme of the pure color background, and you can expand the other background patterns by yourself

public class BookPageView extends View {
	// omit some code...
    @Override
    protected void onDraw(Canvas canvas) {
		// omit some code...
        super.onDraw(canvas);
        if(a.x==-1 && a.y==-1){
            drawPathAContent(bitmapCanvas,getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0){
                drawPathAContent(bitmapCanvas,getPathAFromTopRight(),pathAPaint);

                bitmapCanvas.drawPath(getPathC(),pathCPaint);
                drawPathCContent(bitmapCanvas,getPathAFromTopRight(),pathCContentPaint);
                drawPathBContent(bitmapCanvas,getPathAFromTopRight(),pathBPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){ drawPathAContent(bitmapCanvas,getPathAFromLowerRight(),pathAPaint); bitmapCanvas.drawPath(getPathC(),pathCPaint); drawPathCContent(bitmapCanvas,getPathAFromLowerRight(),pathCContentPaint); drawPathBContent(bitmapCanvas,getPathAFromLowerRight(),pathBPaint); }}}/** * Draw the content of area C *@param canvas
     * @param pathA
     * @param pathPaint
     */
    private void drawPathCContent(Canvas canvas, Path pathA, Paint pathPaint){canvas.save();
		// omit some code...
        canvas.clipPath(pathA);
// canvas.clipPath(getPathC(), Region.Op.REVERSE_DIFFERENCE); // Crop out the parts of region C that differ from region A
        canvas.clipPath(getPathC(),Region.Op.UNION);// Crop out the complete set of regions A and C
        canvas.clipPath(getPathC(), Region.Op.INTERSECT);// take the intersection with the C region}}Copy the code

The effect is shown in figure

That’s the end of this tutorial, and I’ll leave it up to you to customize the contents of the fill. The principle is the same. If you feel good, please give me a thumbs up, your support is my biggest motivation ~