It takes about 8 minutes to complete the reading.
Waste not to say, look at the picture, there are pictures have **
With three-dimensional depth of the card page effect, a little combination and color changes can be matched to a variety of different styles, such as:
Score board
Card page turning clock
First, design ideas
How to make the numbers more flexible? The 3D depth effect is worth thinking about. In some specific scenes in real life, the change of numbers is realized by turning pages, such as calendars, scoreboards, math teaching AIDS, and so on.
Flexible replacement of the color and digital picture of the flip card, can show a variety of different styles
Second, the implementation scheme
2.1 3D depth effect implementation scheme reference
To achieve 3D depth on Android, consider using OpenGL, which is one of the most detailed and realistic, but also expensive. Using OpenGL for a single control is like nuking a fly — yes, but not necessarily.
The Matrix+Camera combination scheme is considered here.
2.2 introduction of Matrix
Matrix in Android is a 3 by 3 matrix, as shown below
Scale controls scaling, skew controls misalignment, Trans controls displacement, and Persp controls perspective. It’s worth noting that rotation is done through a combination of scale and skew.
The matix package in Android is very perfect, users do not need to pick up the math knowledge taught by the PE teacher, directly call.
2.3 introduction of Camera
This refers to the Camera under the Android.Graphics package, which is used to calculate 3D transformations. The package is also very perfect, the user does not need to think about the control coordinate calculation process with the butt, directly call, and match the corresponding matrix.
Note that the Camera is positioned at the top left corner of the canvas.
The coordinate system is as follows, which is left handed:
Common methods are as follows:
2.4 the UI and dismantling
**2.4.1 Shape Analysis **
It’s not that complicated to see from the shape, three rectangles with rounded corners can be done, including the card being flipped. The digital part is drawn by picture, which can be replaced flexibly. Finally, the picture can be drawn in the specified area of the card.
**2.4.2 Model Design **
In the process of card flipping, there are at most three cards at the same time, so we only need to define the top, middle and bottom three cards according to the position relationship, and the middle card is responsible for the flipping.
2.5 Realization of depth effect
**2.5.1 Card drawing **
Draw the top, middle and bottom cards, and the middle card is the active page, which needs to be drawn last
override fun onDraw(canvas: Canvas?) {super.ondraw (canvas) setLayerType(LAYER_TYPE_SOFTWARE, null) judgeState(curState) Canvas? .let {drawDownCard(it) drawDownCard(it) // drawDownCard(it) drawDownCard(it)Copy the code
**2.5.2 Realizing card page turning **
The page turning effect of the middle card is realized by the Camera roate method, which is rotated around the X-axis
private fun drawMidCard(canvas: Canvas) { if (! isNeedDrawMidCard) return with(canvas) { save() mMatrix.reset() mCamera.save() mCamera.translate(0F, 0F, depthZ) mCamera.rotateX(rotateX) mCamera.rotateY(rotateY) mCamera.getMatrix(mMatrix) mCamera.restore() val scale = resources.displayMetrics.density val mValues = FloatArray(9) mMatrix.getValues(mValues) mValues[6] = mValues[6] / scale mValues[7] = mValues[7] / scale mMatrix.setValues(mValues) mMatrix.preTranslate(-width / 2F, -height / 2F) mMatrix.postTranslate(width / 2F, height / 2F) concat(mMatrix) mPaint.color = Color.WHITE mPaint.setShadowLayer(cardShadowSize, 0F, cardShadowDistance, Color.GRAY) val rectF = RectF(paddingSize, paddingSize + cardHeight, paddingSize + cardWidth, PaddingSize + cardHeight * 2) drawRoundRect(rectF, 20F, 20F, mPaint)Copy the code
Combined with me in [the headlines today loading control, next door products are chan cried “] (https://zhuanlan.zhihu.com/p/228837516), the article mentioned in the coordinates of the computing framework, the distance of moving the fingers of the user on the screen and rotation Angle between card there is a certain functional relation, Save and calculate through IFunc.
The code is as follows:
/ / var cardRotateFunc: IFunc? */ var cardShadowSizeFunc: IFunc? = null /** * shadow distance change function */ var cardShadowDistanceFunc: IFunc? Private fun configFunc() {cardRotateFunc = cardRotateFunc () with(cardRotateFunc!!) { inParamMin = 0F inParamMax = cardHeight * 2 outParamMin = 0F outParamMax = 180F initValue = 45F } cardShadowSizeFunc = CardShadowSizeFunc() with(cardShadowSizeFunc!!) { inParamMin = 0F inParamMax = 180F outParamMax = 50F outParamMin = 0F initValue = 10F } cardShadowDistanceFunc = CardShadowDistanceFunc() with(cardShadowDistanceFunc!!) { inParamMin = 0F inParamMax = 180F outParamMax = 50F outParamMin = 0F initValue = 10F } }Copy the code
**2.5.3 Shadow change **
In order to better simulate the 3D effect, the card shadows are also slightly changed
/** * executeShadowFunc(rotate: Float) {cardShadowSizeFunc? .let { cardShadowSize = it.execute(rotate) } cardShadowDistanceFunc? .let { cardShadowDistance = it.execute(rotate) } }Copy the code
**2.5.4 Digital picture drawing **
The drawing of numbers is the drawing of pictures. It should be noted that when the middle active card is flipped up or down more than 90 degrees, the drawn numbers need to be changed, which involves the horizontal mirror flipping of the picture, which is realized by calling matrix.postscale (-1f, 1F). As an example, the code is:
If (curState == STATE_DOWN_ING) {if (abs(cardRotateFunc!! InitValue -rotatex) >= 90F) {// Draw the previous num. if (curShowNum - 1 >= 0) {tempBm = bitmap. createBitmap(curShowNum -) 1], 0, 0, curNumBm.width, curNumBm.height, matrix, false) } else { tempBm = Bitmap.createBitmap(numBms[0], 0, 0, curNumBm.width, curNumBm.height, matrix, false) } } else { tempBm = Bitmap.createBitmap(numBms[curShowNum], 0, 0, curNumBm.width, curNumBm.height, matrix, false) } } tempBm? .let { drawBitmap(it, Rect(0, it.height / 2, it.width, it.height), rectF, mPaint) }Copy the code
2.6 Interaction
**2.6.1 Flipping up and down **
The logic is mainly in the onTouchEvent method, which determines whether to flip up or down by the initial Angle of the middle card and the current flip Angle
/ finger press the initial coordinate of * * * * / private var downX: Float = 0 f private var downY: Float private var offsetY = 0 f: Float = 0F override fun onTouchEvent(event: MotionEvent?) : Boolean { when (event? .action) {motionEvent.action_down -> {downX = event.x downY = event.y if (downY >= height / 2) {// Draw the mid card below rotateX = 0F curState = STATE_UP_ING } else { rotateX = 180F curState = STATE_DOWN_ING } resetInitValue() postInvalidate() } MotionEvent.ACTION_MOVE -> { offsetY = event.y - downY executeFunc(offsetY) postInvalidate() } ACTION_UP -> {if (rotateX >= 90F) {if (abs(cardRotateFunc!! .initValue - rotateX) >= 90F) { if (curShowNum + 1 <= 9) { startCardUpAnim(curShowNum + 1) } else { curShowNum = 9 startCardDownAnim(9) } } else { startCardUpAnim(curShowNum) } } else { if (abs(cardRotateFunc!! .initValue - rotateX) >= 90F) { if (curShowNum - 1 >= 0) { startCardDownAnim(curShowNum - 1) } else { curShowNum = 0 startCardUpAnim(0) } } else { startCardDownAnim(curShowNum) } } downX = 0F downY = 0F } else -> { } } return true }Copy the code
**2.6.2 Page turning animation and Shadow animation **
Due to the use of the coordinate calculation framework, the implementation of animation becomes very simple, control the offset variable, you can control the middle card flip Angle, shadow distance, shadow size. Take the animation above as an example:
*/ private fun startCardUpAnim(curNum: Int) {cardRotateAnim? .cancel() cardRotateAnim = ValueAnimator.ofFloat(rotateX, 180F) with(cardRotateAnim!!) { duration = 400L addUpdateListener { rotateX = it.animatedValue as Float executeShadowFunc(rotateX) postInvalidate() } addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { super.onAnimationEnd(animation) resetInitValue() curState = STATE_NORMAL curShowNum = curNum } }) start() } }Copy the code
**2.6.3 Display changes in numbers **
When the animation ends, the current display number has really changed, and the value of curShowNum is assigned. It is also convenient to add listening exposure when the number changes to the caller.
2.7 Adding details
**2.7.1 Border Control **
When the current number is 0, you cannot flip down again, so you do not need to draw a card. Similarly, if the current number is 9, there is no need to draw the card
private fun judgeState(state: Int) {
when (state) {
STATE_NORMAL -> {
isNeedDrawMidCard = false
isNeedDrawUpCard = true
isNeedDrawDownCard = true
}
STATE_UP_ING -> {
isNeedDrawMidCard = true
if (curShowNum + 1 > 9) {
isNeedDrawDownCard = false
}
}
STATE_DOWN_ING -> {
isNeedDrawMidCard = true
if (curShowNum - 1 < 0) {
isNeedDrawUpCard = false
}
}
}
}
Copy the code
**2.7.2 Page Turning rebound **
At the boundary number, the user continues to flip and needs to bounce back to the original position.
if (curShowNum + 1 <= 9) {
startCardUpAnim(curShowNum + 1)
} else {
curShowNum = 9
startCardDownAnim(9)
}
Copy the code
threeAfterword,
Camera+Matrix in Android is a good simulation of 3D effects and is much lighter and easier to use than OpenGL in many control design scenarios. Control there are a lot of details of the problem, limited to the length of the article will not expand the details.
The control is placed on Gitee
Order me order me order me
Ah, now that I see here, it’s time to show my public number and personal wechat…… Just strange!
Past highlights:
Stepless knob control
Liquid flow control
Electronic digital display control
Make a switch for the hammer
Seductive scale control