Original article, reprint please contact the author.Personal blog

Wutong fall, and also autumn, and also lonely.

Effect drawing, large file, just a moment (●゚ω゚●) :

preface

First of all, first of all! The Demo is just a parody of FliBoard’s stereoscopic straight-board page-turning interaction, just effect, just effect. “Three-dimensional sense is a kind of imitation, in the two-dimensional plane, reasonable use of light and shadow, perspective (far small near big) and other ways to create a kind of approximate realistic three-dimensional world sense jio. Why can produce stereo sense? It’s because the retina of the human eye receives a projection of the world in three dimensions. It’s your brain, your experience, that imagines the world in three dimensions. Take the picture below, would you see it as a curved triangle



Persistence of vision
cheating

Effect of parsing

Before we analyze the effects, let’s mention some of the knowledge points that will be used

1. Knowledge points used

  • Graphics.camera, a class that handles 3D rotations
  • Canvas, Matrix

2. Effect disassembly

Straight plate page turning, the effect is not complicated. Behind the screen of the phone is a three-dimensional coordinate system. Imagine a Bitmap in the XY coordinate system. To turn the page, rotate it around the X axis. Normally, the board (Bitmap) rotates as a whole. If we move the center point of the board to the X axis, the upper and lower parts of the board must move in opposite directions as we rotate about the Z axis. Something like this:

The image above shows the effect of rotating 45 degrees around the X-axis and scaling by 0.5F

As shown above, the upper and lower parts must be drawn separately for effect. You can split a Bitmap or Canvas. In the Demo, I adopted the split Canvas. Use the canvas. ClipRect (left, top, right, bottom) method.

3. Gesture disassembly

There are three states of page turning: static, down and up. No need to go into static, we will analyze flip up and flip down drawing.

3.1 Page down drawing and parsing

To scroll down is to turn the current page and go back to the previous page. In the effect disassembly part, we already know that at 45 degrees, the top part is going to be behind the screen. So we want to flip the top half down. The rotation Angle has to be negative. That is, over a full tumble period, the Angle changes from 0 to -180 degrees. Where 0 to -90 degrees, the current page is turning down, the page changes in the upper half of the area, at this time, you can see the interface: turning down the top half of the previous page, the shadow generated by the current page, the top half of the previous page (remain unchanged). In the -90 to -180 degree stage, the scrolling action is nearly completed, and the page changes to the lower half area. At this time, the interface can be seen: the lower half of the previous page to be turned, the shadow generated by the previous page flipping, and the lower half of the current page.

3.2 Page up drawing and parsing

To turn a page up is to turn the current page to the next. Contrary to flip logic, this is a 0 to 180 degree cycle of activity. 0 to 90 degrees indicates that the page is flipping up and moving to the bottom half of the page. While 90 to 180 degrees, the upturn action is nearly complete, the page changes in the work area, and you will soon see the full next page.

The specific implementation

Use custom View to achieve, here only posted the main code, part of the logic will be expressed in pseudocode, complete code provided at the end.

1, drawing

Because it’s just a copy effect, all the logic is inside a custom View. Let’s start with some of the main member variables.

RotateS // down,0 to -180F private var rotateF // Up,0 to 180F Private var rotateF // rotateS,0 indicates release, 1 indicates down, Private var curPage // Camera class for 3D rotation private val Camera // Bitmap Matrix Private val centerY // private val centerY // private val centerY // private var curBitmap: Bitmap private var lastBitmap: Bitmap private var nextBitmap: BitmapCopy the code

I maintain two variables to control the Angle change of flip and flip respectively. At the same time, there are also two methods to draw the upper part and the lower part respectively.

// Draw fun drawFirstHalf(Canvas: canvas? , bitmap: Bitmap? , rotate: Float) { canvas? .save() // Cut the top part of the canvas? .cliprect (0, 0, width, height / 2) camera.save() Camera.getmatrix (drawMatrix) camera.restore() // Zoom value with rotation Angle, Drawmatrix. preScale(1.0f, scale ratio) // Move the image to the center. -centerY) drawMatrix.postTranslate(centerX, centerY) canvas? .drawBitmap(this, drawMatrix, null) canvas? .restore() }Copy the code
fun drawSecondHalf(canvas: Canvas? , bitmap: Bitmap? , rotate: Float) { canvas? .save() camera.save() // Cut the bottom part of the canvas canvas? .cliprect (0, height / 2, width, height) camera. Camera.getmatrix (drawMatrix) camera.restore() drawmatrix.prescale (1.0f, Drawmatrix. preTranslate(-centerx, -centery) drawmatrix. postTranslate(centerX, centerY) canvas? .drawBitmap(this, drawMatrix, null) canvas? .restore() }Copy the code

2. Gesture processing

Gesture processing is relatively simple. You only need to judge whether the state is up or down when you MOVE. Then when raising the hand UP, according to the distance at this time, to judge whether successful down or successful UP. If the distance falls short of the standard threshold, everything goes back to where it was.

  • StartX and startY are finger drop points
ACTION_MOVE -> {val x = this.x val y = this.y // If y moves at a distance greater than 1.5 times that of x, the move is considered to be verticalif(math.abs (disY) > 1f && math.abs (disY) >= math.abs (x-startx) * 1.5f) {if(statusFlip == 0) {// The slide spacing is positive and not the first page is judged to be down, the slide spacing is negative and not the last page is judged to be up statusFlip =if(disY > 0 && curPage ! = 0) DOWN_FLIPelse if(disY < 0 && curPage ! = girls.lastIndex) UP_FLIPelse 0
                        }
                        val ratio = Math.abs(disY) / centerY
                        if(statusFlip == DOWN_FLIP) {// Scroll down and the current page is not equal to 0 rotateF = ratio * -180f log.d ("cece".": rotateF : " + rotateF);
                            invalidate()
                        } else if(statusFlip == UP_FLIP) {// Scroll up and not the last pageif(curPage ! = girls.lastIndex) { rotateS = ratio * 180f Log.d("cece".": rotateS : " + rotateS);
                                invalidate()
                            }
                        }
                    }
                }
Copy the code
  • When the finger is lifted, first judge the state at this time, and then judge whether the distance moved meets the threshold. If not, return to the current page. If not, continue to perform the unfinished state.
if(statusFlip ! = 0) {drawmatrix. reset() // Animation occurs when releasingifMath.abs(event.y-starty) <= centerY / 2) {// Slide distance less than 1/4 of the screen height, RotateF = 0F rotateS = 0f statusFlip = 0 invalidate()}else{// If the slide distance exceeds the threshold, the current page is skippedif(statusFlip == DOWN_FLIP) {// Automatically finish flipping to the previous pagefor (i in rotateF.toInt() downTo -180 step 6) {
                        invalidate()
                    }
                    curPage--
                } else{// Automatically turn to the next pagefor (i in rotateS.toInt() until 180 step 6) {
                        invalidate()
                    }
                    curPage++
                }
                rotateF = 0f
                rotateS = 0f
                statusFlip = 0
            }
        }
Copy the code

When the distance reaches a threshold, the code is needed to continue the logic of the flip down or flip up. I’m going to use a loop here. For example, if you roll up more than 90 degrees, you loop to 180 degrees and continue to complete the roll.

3. Shaded parts and drawing order

In ontouch (…). When drawing inside a method, be careful about code order. Because in this method, order represents hierarchy. For example, the shadow painting must be written before the page painting. The shaded part is also drawn in two parts.

fun drawFirstShadow(canvas: Canvas? Rotate: Float) {canvas drawSecondShadow(canvas: canvas? Rotate: Float) {canvas rotate: Float) {canvas rotate: Float)Copy the code

In ontouch (…). The drawing order within a method must be distinct

// Draw the layer at the bottom of the current pageifDrawFirstShadow (canvas, lastBitmap, 0f) drawFirstShadow(Canvas, rotateF)} (statusFlip == DOWN_FLIP) {else if(statusFlip == UP_FLIP) { drawSecondHalf(canvas, nextBitmap, 0f) drawSecondShadow(canvas, RotateS)} // Draws current page drawFirstHalf(Canvas, curBitmap, rotateF) drawSecondHalf(Canvas, curBitmap, rotateS) // Draws the layer above the current page, After the page is turnedif (statusFlip == DOWN_FLIP) {
            ifDrawSecondShadow (canvas, rotateF + 180f) drawSecondHalf(Canvas, lastBitmap, RotateF + 180f)} drawFirstColor(canvas, 20)}else if (statusFlip == UP_FLIP) {
            if(rotateS >= 90f) { drawFirstShadow(canvas, rotateS - 180f) drawFirstHalf(canvas, nextBitmap, RotateS -180F)} // Dim transparency shadow layer drawSecondColor(Canvas, 20)}Copy the code

Again, we have to differentiate between states, so when we flip down, we have to draw the top half of the previous page, and it’s static. Then draw the shadow generated by the current page down. Draw the current page again, and then draw another layer of shadow at the top of the current page with a fixed light opacity to make the page hierarchy more obvious.

4. Effect modification

At this point, the main logic is done, but I notice a few minor flaws. It’s the rotation Angle and the scaling ratio, and it doesn’t change very much. It is usually necessary to change the Angle to more than 45 degrees to have a significant zoom effect. At first, I thought it was due to the algorithm of zoom ratio, but later I found it was due to the camera position. The default camera shooting Angle is [0,0,-8], and the change is not obvious when it is very close to the screen. Of course, the camera provides a method to set the camera location setLocation(x, y, z). Finally I adjusted to [0,0,-20] to be satisfied with this effect.

In the figure below, I show the difference between the default and [0,0, -20] positions.

conclusion

The implementation method in the Demo is not unique, but is shared to provide an idea. There are many paths, but the choice is right. The above project code is here, if you like it, you might as well like it

There is a public account, will record some development experience, will also send some of their own learning diary. Welcome to your attention.