├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml └── runConfigurations.xml ├── LICENSE ├── PageFlip ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── eschao │ │ └── android │ │ └── widget │ │ └── pageflip │ │ ├── FoldBackVertexProgram.java │ │ ├── FoldBackVertexes.java │ │ ├── GLPoint.java │ │ ├── GLProgram.java │ │ ├── GLShader.java │ │ ├── GLViewRect.java │ │ ├── OnPageFlipListener.java │ │ ├── Page.java │ │ ├── PageFlip.java │ │ ├── PageFlipException.java │ │ ├── PageFlipState.java │ │ ├── PageFlipUtils.java │ │ ├── ShadowColor.java │ │ ├── ShadowVertexProgram.java │ │ ├── ShadowVertexes.java │ │ ├── ShadowWidth.java │ │ ├── VertexProgram.java │ │ └── Vertexes.java │ └── res │ ├── raw │ ├── fold_back_fragment_shader.glsl │ ├── fold_back_vertex_shader.glsl │ ├── fragment_shader.glsl │ ├── shadow_fragment_shader.glsl │ ├── shadow_vertex_shader.glsl │ └── vertex_shader.glsl │ └── values │ └── strings.xml ├── README.md ├── Sample └── src │ └── main │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── eschao │ │ └── android │ │ └── widget │ │ └── sample │ │ └── pageflip │ │ ├── Constants.java │ │ ├── DoublePagesRender.java │ │ ├── LoadBitmapTask.java │ │ ├── PageFlipView.java │ │ ├── PageRender.java │ │ ├── SampleActivity.java │ │ └── SinglePageRender.java │ └── res │ ├── drawable │ ├── p10_1080.jpg │ ├── p10_480.jpg │ ├── p10_720.jpg │ ├── p1_1080.jpg │ ├── p1_480.jpg │ ├── p1_720.jpg │ ├── p2_1080.jpg │ ├── p2_480.jpg │ ├── p2_720.jpg │ ├── p3_1080.png │ ├── p3_480.png │ ├── p3_720.png │ ├── p4_1080.jpg │ ├── p4_480.jpg │ ├── p4_720.jpg │ ├── p5_1080.jpg │ ├── p5_480.jpg │ ├── p5_720.jpg │ ├── p6_1080.jpg │ ├── p6_480.jpg │ ├── p6_720.jpg │ ├── p7_1080.jpg │ ├── p7_480.jpg │ ├── p7_720.jpg │ ├── p8_1080.jpg │ ├── p8_480.jpg │ ├── p8_720.jpg │ ├── p9_1080.jpg │ ├── p9_480.jpg │ └── p9_720.jpg │ ├── layout │ └── about.xml │ └── menu │ └── optionmenus.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── res │ ├── layout │ └── sample_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.zip 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij 37 | *.iml 38 | .idea/workspace.xml 39 | 40 | # Keystore files 41 | *.jks 42 | 43 | /local.properties 44 | .idea/workspace.xml 45 | .idea/libraries 46 | .idea/misc.xml 47 | .idea/modules.xml 48 | .idea/vcs.xml 49 | .DS_Store 50 | /build 51 | /captures 52 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PageFlip/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /PageFlip/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion '25.0.0' 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 24 10 | versionCode 2 11 | versionName "1.0.2" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | testCompile 'junit:junit:4.12' 24 | compile 'com.android.support:appcompat-v7:24.2.0' 25 | } 26 | -------------------------------------------------------------------------------- /PageFlip/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/chao/Software/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /PageFlip/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/FoldBackVertexProgram.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.content.Context; 19 | 20 | import static android.opengl.GLES20.glGetUniformLocation; 21 | 22 | /** 23 | * FoldBack vertex shader program which is used to load: 24 | * 28 | * 29 | * @author eschao 30 | */ 31 | 32 | public class FoldBackVertexProgram extends VertexProgram { 33 | 34 | final static String VAR_TEXTRUE_OFFSET = "u_texXOffset"; 35 | final static String VAR_MASK_COLOR = "u_maskColor"; 36 | final static String VAR_SHADOW_TEXTURE = "u_shadow"; 37 | 38 | int mShadowLoc; 39 | int mMaskColorLoc; 40 | int mTexXOffsetLoc; 41 | 42 | public FoldBackVertexProgram() { 43 | super(); 44 | 45 | mShadowLoc = INVALID_GL_HANDLE; 46 | mMaskColorLoc = INVALID_GL_HANDLE; 47 | mTexXOffsetLoc = INVALID_GL_HANDLE; 48 | } 49 | 50 | /** 51 | * Initiate shader program 52 | * 53 | * @param context Android app context 54 | * @return self 55 | * @throws PageFlipException if fail to read and compile shader scripts 56 | */ 57 | public FoldBackVertexProgram init(Context context) throws 58 | PageFlipException { 59 | super.init(context, 60 | R.raw.fold_back_vertex_shader, 61 | R.raw.fold_back_fragment_shader); 62 | return this; 63 | } 64 | 65 | /** 66 | * Get variable handles defined in shader script 67 | */ 68 | protected void getVarsLocation() { 69 | super.getVarsLocation(); 70 | 71 | if (mProgramRef != 0) { 72 | mShadowLoc = glGetUniformLocation(mProgramRef, VAR_SHADOW_TEXTURE); 73 | mMaskColorLoc = glGetUniformLocation(mProgramRef, VAR_MASK_COLOR); 74 | mTexXOffsetLoc = glGetUniformLocation(mProgramRef, 75 | VAR_TEXTRUE_OFFSET); 76 | } 77 | } 78 | 79 | /** 80 | * Delete all handles 81 | */ 82 | public void delete() { 83 | super.delete(); 84 | 85 | mShadowLoc = INVALID_GL_HANDLE; 86 | mMaskColorLoc = INVALID_GL_HANDLE; 87 | mTexXOffsetLoc = INVALID_GL_HANDLE; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/FoldBackVertexes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.opengl.GLES20; 19 | 20 | import static android.opengl.GLES20.GL_TEXTURE_2D; 21 | import static android.opengl.GLES20.GL_TRIANGLE_STRIP; 22 | import static android.opengl.GLES20.glActiveTexture; 23 | import static android.opengl.GLES20.glBindTexture; 24 | import static android.opengl.GLES20.glUniform1f; 25 | import static android.opengl.GLES20.glUniform1i; 26 | import static android.opengl.GLES20.glUniform4f; 27 | import static android.opengl.GLES20.glUniformMatrix4fv; 28 | 29 | /** 30 | * Vertex buffer management for back of fold page 31 | * 32 | * @author eschao 33 | */ 34 | final class FoldBackVertexes extends Vertexes { 35 | 36 | private final static String TAG = "FoldBackVertexes"; 37 | 38 | // mask alpha for back of fold page 39 | // mask color is in Page class since it follows the back of first bitmap 40 | float mMaskAlpha; 41 | 42 | public FoldBackVertexes() { 43 | super(); 44 | 45 | mSizeOfPerVex = 4; 46 | mMaskAlpha = 0.6f; 47 | } 48 | 49 | /** 50 | * Set vertex buffer with given mesh count 51 | * 52 | * @param meshCount mesh count 53 | */ 54 | public void set(int meshCount) { 55 | super.set(meshCount << 1, 4, true); 56 | mNext = 0; 57 | } 58 | 59 | /** 60 | * Set mask alpha 61 | * 62 | * @param alpha mask alpha, value is [0 .. 255] 63 | */ 64 | public void setMaskAlpha(int alpha) { 65 | if (alpha < 0 || alpha > 255) { 66 | throw new IllegalArgumentException("Alpha: " + alpha + "is out of " 67 | + "[0 .. 255]!"); 68 | } 69 | 70 | mMaskAlpha = alpha / 255.0f; 71 | } 72 | 73 | /** 74 | * set mask alpha 75 | * 76 | * @param alpha mask alpha, value is [0 .. 1] 77 | */ 78 | public void setMaskAlpha(float alpha) { 79 | if (alpha < 0 || alpha > 1) { 80 | throw new IllegalArgumentException("Alpha: " + alpha + "is out of " 81 | + "[0 .. 1]!"); 82 | } 83 | 84 | mMaskAlpha = alpha; 85 | } 86 | 87 | /** 88 | * Draw fold back and shadow 89 | * 90 | * @param program fold back vertex program 91 | * @param page the current operating page: First Page 92 | * @param hasSecondPage there has second page or not 93 | * @param gradientShadowId gradient shadow id 94 | */ 95 | public void draw(FoldBackVertexProgram program, 96 | Page page, 97 | boolean hasSecondPage, 98 | int gradientShadowId) { 99 | glUniformMatrix4fv(program.mMVPMatrixLoc, 1, false, 100 | VertexProgram.MVPMatrix, 0); 101 | 102 | // load fold back texture 103 | glBindTexture(GL_TEXTURE_2D, page.getBackTextureID()); 104 | glUniform1i(program.mTextureLoc, 0); 105 | 106 | // load gradient shadow texture 107 | glActiveTexture(GLES20.GL_TEXTURE1); 108 | glBindTexture(GL_TEXTURE_2D, gradientShadowId); 109 | glUniform1i(program.mShadowLoc, 1); 110 | 111 | // set x offset of texture coordinate. In single page mode, the value is 112 | // set 0 to draw the back texture with x coordinate inversely against 113 | // the first texture since they are using the same texture, but in 114 | // double page mode, the back texture is different with the first one, 115 | // it is the next page content texture and should be drawn in the same 116 | // order with the first texture, so the value is set 1. For computing 117 | // details, please see the shader script. 118 | glUniform1f(program.mTexXOffsetLoc, hasSecondPage ? 1.0f : 0); 119 | 120 | // set mask color and alpha 121 | glUniform4f(program.mMaskColorLoc, 122 | page.maskColor[0][0], 123 | page.maskColor[0][1], 124 | page.maskColor[0][2], 125 | hasSecondPage ? 0 : mMaskAlpha); 126 | 127 | // draw triangles 128 | drawWith(GL_TRIANGLE_STRIP, 129 | program.mVertexPosLoc, 130 | program.mTexCoordLoc); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/GLPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | /** 19 | * GLPoint includes (x,y,z) in OpenGL coordinate system and its texture 20 | * coordinates (texX, texY) 21 | * 22 | * @author eschao 23 | */ 24 | public final class GLPoint { 25 | // 3D coordinate 26 | float x; 27 | float y; 28 | float z; 29 | 30 | // texutre coordinate 31 | float texX; 32 | float texY; 33 | 34 | /** 35 | * Set GLPoint with given values 36 | * 37 | * @param x x coordinate 38 | * @param y y coordinate 39 | * @param z z coordinate 40 | * @param tX x coordinate of texture 41 | * @param tY y coordinate of texture 42 | */ 43 | public void set(float x, float y, float z, float tX, float tY) { 44 | this.x = x; 45 | this.y = y; 46 | this.z = z; 47 | this.texX = tX; 48 | this.texY = tY; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/GLProgram.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.content.Context; 19 | 20 | import static android.opengl.GLES20.GL_FRAGMENT_SHADER; 21 | import static android.opengl.GLES20.GL_LINK_STATUS; 22 | import static android.opengl.GLES20.GL_VERTEX_SHADER; 23 | import static android.opengl.GLES20.glAttachShader; 24 | import static android.opengl.GLES20.glCreateProgram; 25 | import static android.opengl.GLES20.glDeleteProgram; 26 | import static android.opengl.GLES20.glGetProgramiv; 27 | import static android.opengl.GLES20.glLinkProgram; 28 | import static android.opengl.GLES20.glUseProgram; 29 | 30 | /** 31 | * GLSL program class is used to load, compile and link shader scripts 32 | * 33 | * @author eschao 34 | */ 35 | 36 | public class GLProgram { 37 | 38 | // invalid GL getShaderRef including program reference and variable location 39 | protected final int INVALID_GL_HANDLE = -1; 40 | 41 | // GLSL program reference 42 | protected int mProgramRef; 43 | 44 | // Vertex shader 45 | protected GLShader mVertex; 46 | 47 | // Fragment shader 48 | protected GLShader mFragment; 49 | 50 | public GLProgram() { 51 | mProgramRef = INVALID_GL_HANDLE; 52 | mVertex = new GLShader(); 53 | mFragment = new GLShader(); 54 | } 55 | 56 | /** 57 | * Initiate with given vertex shader and fragment shader 58 | * 59 | * @param context android context 60 | * @param vertexResId vertex shader script id 61 | * @param fragmentResId fragment shader script id 62 | * @return self 63 | * @throws PageFlipException if fail to read or compile shader scripts 64 | */ 65 | public GLProgram init(Context context, int vertexResId, int fragmentResId) 66 | throws PageFlipException { 67 | // 1. init shader 68 | try { 69 | mVertex.compile(context, GL_VERTEX_SHADER, vertexResId); 70 | mFragment.compile(context, GL_FRAGMENT_SHADER, fragmentResId); 71 | } 72 | catch (PageFlipException e) { 73 | mVertex.delete(); 74 | mFragment.delete(); 75 | throw e; 76 | } 77 | 78 | // 2. create texture program and link shader 79 | mProgramRef = glCreateProgram(); 80 | if (mProgramRef == 0) { 81 | mVertex.delete(); 82 | mFragment.delete(); 83 | throw new PageFlipException("Can't create texture program"); 84 | } 85 | 86 | // 3. attach vertex and fragment shader 87 | glAttachShader(mProgramRef, mVertex.getShaderRef()); 88 | glAttachShader(mProgramRef, mFragment.getShaderRef()); 89 | glLinkProgram(mProgramRef); 90 | 91 | // 4. check shader link status 92 | int[] result = new int[1]; 93 | glGetProgramiv(mProgramRef, GL_LINK_STATUS, result, 0); 94 | if (result[0] == 0) { 95 | delete(); 96 | throw new PageFlipException("Can't link program"); 97 | } 98 | 99 | // 5. get all variable handles defined in scripts 100 | // subclass should implement getVarsLocation to be responsible for its 101 | // own variables in script 102 | glUseProgram(mProgramRef); 103 | getVarsLocation(); 104 | return this; 105 | } 106 | 107 | /** 108 | * Delete all handles 109 | */ 110 | public void delete() { 111 | mVertex.delete(); 112 | mFragment.delete(); 113 | 114 | if (mProgramRef != INVALID_GL_HANDLE) { 115 | glDeleteProgram(mProgramRef); 116 | mProgramRef = INVALID_GL_HANDLE; 117 | } 118 | } 119 | 120 | /** 121 | * Get program GL reference 122 | * 123 | * @return program GL reference for program 124 | */ 125 | public int getProgramRef() { 126 | return mProgramRef; 127 | } 128 | 129 | /** 130 | * Subclass should implement it to get its own variable handles which are 131 | * defined in its shader scripts 132 | */ 133 | protected void getVarsLocation() { 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/GLShader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.content.Context; 19 | import android.util.Log; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.IOException; 23 | import java.io.InputStreamReader; 24 | 25 | import static android.opengl.GLES20.GL_COMPILE_STATUS; 26 | import static android.opengl.GLES20.glCompileShader; 27 | import static android.opengl.GLES20.glCreateShader; 28 | import static android.opengl.GLES20.glDeleteShader; 29 | import static android.opengl.GLES20.glGetError; 30 | import static android.opengl.GLES20.glGetShaderInfoLog; 31 | import static android.opengl.GLES20.glGetShaderiv; 32 | import static android.opengl.GLES20.glShaderSource; 33 | 34 | /** 35 | * GLSL shader class is used to load and compile shader script 36 | * 37 | * @author eschao 38 | */ 39 | 40 | public class GLShader { 41 | 42 | private final static String TAG = "GLShader"; 43 | private final int INVALID_GL_HANDLE = -1; 44 | 45 | // shader object reference 46 | int mShaderRef; 47 | 48 | /** 49 | * Default constructor 50 | */ 51 | public GLShader() { 52 | mShaderRef = INVALID_GL_HANDLE; 53 | } 54 | 55 | /** 56 | * Read shader script from resources and compile 57 | * 58 | * @param context android context 59 | * @param type GL_VERTEX_SHADER or GL_FRAGMENT_SHADER 60 | * @param resId script resource id 61 | * @return self 62 | * @throws PageFlipException if fail to compile shader script 63 | */ 64 | public GLShader compile(Context context, int type, int resId) 65 | throws PageFlipException { 66 | // read shader scripts from resource 67 | String codes = readGLSLFromResource(context, resId); 68 | if (codes.length() < 1) { 69 | throw new PageFlipException("Empty GLSL shader for resource id:" 70 | + resId); 71 | } 72 | 73 | // create a shader 74 | mShaderRef = glCreateShader(type); 75 | if (mShaderRef != INVALID_GL_HANDLE) { 76 | // upload shader scripts to GL 77 | glShaderSource(mShaderRef, codes); 78 | 79 | // compile shader scripts 80 | glCompileShader(mShaderRef); 81 | 82 | // get compile results to check if it is successful 83 | final int[] result = new int[1]; 84 | glGetShaderiv(mShaderRef, GL_COMPILE_STATUS, result, 0); 85 | if (result[0] == 0) { 86 | // delete shader if compile is failed 87 | Log.e(TAG, "Can'top compile shader for type: " + type + 88 | "Error: " + glGetError()); 89 | Log.e(TAG, "Compile shader error: " + 90 | glGetShaderInfoLog(mShaderRef)); 91 | glDeleteShader(mShaderRef); 92 | throw new PageFlipException("Can't compile shader for" + 93 | "type: " + type); 94 | } 95 | } else { 96 | throw new PageFlipException("Can't create shader. Error: " + 97 | glGetError()); 98 | } 99 | 100 | return this; 101 | } 102 | 103 | /** 104 | * Delete shader 105 | */ 106 | public void delete() { 107 | if (mShaderRef != INVALID_GL_HANDLE) { 108 | glDeleteShader(mShaderRef); 109 | mShaderRef = INVALID_GL_HANDLE; 110 | } 111 | } 112 | 113 | /** 114 | * Get shader object reference 115 | * 116 | * @return shader object reference in OpenGL 117 | */ 118 | public int getShaderRef() { 119 | return mShaderRef; 120 | } 121 | 122 | /** 123 | * Read shader script from resources 124 | * 125 | * @param context android context 126 | * @param resId script resource id 127 | * @return shader script contents 128 | * @throws PageFlipException if fail to read script from resources 129 | */ 130 | String readGLSLFromResource(Context context, int resId) throws 131 | PageFlipException { 132 | StringBuilder s = new StringBuilder(); 133 | BufferedReader reader = null; 134 | 135 | try { 136 | reader = new BufferedReader(new InputStreamReader( 137 | context.getResources().openRawResource(resId))); 138 | String line; 139 | 140 | while ((line = reader.readLine()) != null) { 141 | s.append(line); 142 | s.append("\n"); 143 | } 144 | } 145 | catch (IOException e) { 146 | throw new PageFlipException("Could not open resource: " 147 | + resId , e); 148 | } 149 | finally { 150 | // close 151 | try { 152 | if (reader != null) { 153 | reader.close(); 154 | } 155 | } 156 | catch (IOException e) { 157 | } 158 | } 159 | 160 | return s.toString(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/GLViewRect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | /** 19 | * View utility 20 | * 21 | * @author eschao 22 | */ 23 | 24 | public final class GLViewRect { 25 | 26 | // view left 27 | float left; 28 | // view right 29 | float right; 30 | // view top 31 | float top; 32 | // view bottom 33 | float bottom; 34 | // view width 35 | float width; 36 | // view height 37 | float height; 38 | // view half width 39 | float halfW; 40 | // view half height 41 | float halfH; 42 | // view margin left 43 | float marginL; 44 | // view margin right 45 | float marginR; 46 | // openGL surface width, it should be >= view width 47 | float surfaceW; 48 | // openGL surface height, it should be >= view height 49 | float surfaceH; 50 | 51 | /** 52 | * Default constructor 53 | */ 54 | public GLViewRect() { 55 | left = 0; 56 | right = 0; 57 | top = 0; 58 | bottom = 0; 59 | width = 0; 60 | height = 0; 61 | halfW = 0; 62 | halfH = 0; 63 | marginL = 0; 64 | marginR = 0; 65 | surfaceW = 0; 66 | surfaceH = 0; 67 | } 68 | 69 | /** 70 | * Construct with surface and margin size 71 | * 72 | * @param surfaceW openGL surface width 73 | * @param surfaceH openGl surface height 74 | * @param marginL margin left 75 | * @param marginR margin right 76 | */ 77 | public GLViewRect(float surfaceW, float surfaceH, 78 | float marginL, float marginR) { 79 | set(surfaceW, surfaceH, marginL, marginR); 80 | } 81 | 82 | /** 83 | * Set margin 84 | * 85 | * @param marginL margin left 86 | * @param marginR margin right 87 | * @return self 88 | */ 89 | public GLViewRect setMargin(float marginL, float marginR) { 90 | return set(this.surfaceW, this.surfaceH, marginL, marginR); 91 | } 92 | 93 | /** 94 | * Set with surface size 95 | * 96 | * @param surfaceW openGL surface width 97 | * @param surfaceH openGl surface height 98 | * @return self 99 | */ 100 | public GLViewRect set(float surfaceW, float surfaceH) { 101 | return set(surfaceW, surfaceH, this.marginL, this.marginR); 102 | } 103 | 104 | /** 105 | * Set with surface size and margin size 106 | * 107 | * @param surfaceW openGL surface width 108 | * @param surfaceH openGl surface height 109 | * @param marginL margin left 110 | * @param marginR margin right 111 | * @return self 112 | */ 113 | public GLViewRect set(float surfaceW, float surfaceH, 114 | float marginL, float marginR) { 115 | this.surfaceW = surfaceW; 116 | this.surfaceH = surfaceH; 117 | this.marginL = marginL; 118 | this.marginR = marginR; 119 | 120 | width = surfaceW - marginL - marginR; 121 | height = surfaceH; 122 | halfW = width * 0.5f; 123 | halfH = height * 0.5f; 124 | left = -halfW + marginL; 125 | right = halfW - marginR; 126 | top = halfH; 127 | bottom = -halfH; 128 | return this; 129 | } 130 | 131 | /** 132 | * Get minimal value between width and height 133 | * 134 | * @return minimal value 135 | */ 136 | public float minOfWH() { 137 | return width > height ? width : height; 138 | } 139 | 140 | /** 141 | * Translate Android coordinate to OpenGL coordinate 142 | *

143 | * Android screen coordinate: 144 | * *------------> X[0..Width] 145 | * | 146 | * | 147 | * | 148 | * V 149 | * Y[0..Height] 150 | * 151 | * OpenGL screen coordinate: 152 | * Y[0..Height/2] 153 | * ^ 154 | * | 155 | * | 156 | * +-----------> X[0..Width/2] 157 | * / 158 | * / 159 | * / 160 | * Z[0..1] 161 | * 162 | */ 163 | public float toOpenGLX(float x) { 164 | return x - halfW; 165 | } 166 | 167 | public float toOpenGLY(float y) { 168 | return halfH - y; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/OnPageFlipListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | /** 19 | * Listener for page flipping 20 | * 21 | * @author eschao 22 | */ 23 | 24 | public interface OnPageFlipListener { 25 | 26 | /** 27 | * Can page flip forward? 28 | * 29 | * @return true if page can flip forward 30 | */ 31 | boolean canFlipForward(); 32 | 33 | /** 34 | * Can page flip backward? 35 | * 36 | * @return true if page can flip backward 37 | */ 38 | boolean canFlipBackward(); 39 | } 40 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/Page.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.graphics.Bitmap; 19 | import android.graphics.Color; 20 | import android.graphics.PointF; 21 | import android.opengl.GLUtils; 22 | 23 | import java.nio.ByteBuffer; 24 | import java.nio.ByteOrder; 25 | import java.nio.FloatBuffer; 26 | 27 | import static android.opengl.GLES20.GL_FLOAT; 28 | import static android.opengl.GLES20.GL_LINEAR; 29 | import static android.opengl.GLES20.GL_TEXTURE0; 30 | import static android.opengl.GLES20.GL_TEXTURE_2D; 31 | import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER; 32 | import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER; 33 | import static android.opengl.GLES20.GL_TRIANGLE_FAN; 34 | import static android.opengl.GLES20.GL_TRIANGLE_STRIP; 35 | import static android.opengl.GLES20.glActiveTexture; 36 | import static android.opengl.GLES20.glBindTexture; 37 | import static android.opengl.GLES20.glDeleteTextures; 38 | import static android.opengl.GLES20.glDrawArrays; 39 | import static android.opengl.GLES20.glEnableVertexAttribArray; 40 | import static android.opengl.GLES20.glGenTextures; 41 | import static android.opengl.GLES20.glTexParameterf; 42 | import static android.opengl.GLES20.glUniform1i; 43 | import static android.opengl.GLES20.glUniformMatrix4fv; 44 | import static android.opengl.GLES20.glVertexAttribPointer; 45 | 46 | /** 47 | * Page class 48 | *

49 | * Page holds content textures and show them on screen. In single page mode, a 50 | * page represents the whole screen area. But in double pages mode, there are 51 | * two pages to depict the entire screen size, in the left part is called left 52 | * page and the right part is called right page. 53 | * Every page has the below properties: 54 | *

55 | *
    56 | *
  • Page size: left/right/top/bottom and width/height
  • 57 | *
  • Holding 3 content textures for drawing: 58 | *
      59 | *
    • The first texture: which is showing on screen when page is 60 | * stationary, we can relatively call it as the first 'Page' at 61 | * some extend
    • 62 | *
    • The second texture: normally it can be called the second 63 | * 'Page' against the first texture. It will be appeared when page 64 | * is flipping or flip is over, in the later, the second texture 65 | * will eventually become the first one
    • 66 | *
    • The back texture: in single page mode, the back texture is 67 | * always same with the first texture, thus, the caller shouldn't 68 | * set it before drawing. But in double pages mode, it should be 69 | * set with a different texture and can be called the second 'Page' 70 | * , at this time, the second texture will be called the third 71 | * 'Page' as like we're reading a book
    • 72 | *
    • Every texture should be set with a bitmap by outside caller 73 | *
    • 74 | *
    75 | *
  • 76 | *
77 | * 78 | * @author eschao 79 | */ 80 | 81 | public class Page { 82 | 83 | private final static int TEXTURE_SIZE = 3; 84 | private final static int FIRST_TEXTURE_ID = 0; 85 | private final static int SECOND_TEXTURE_ID = 1; 86 | private final static int BACK_TEXTURE_ID = 2; 87 | private final static int INVALID_TEXTURE_ID = -1; 88 | 89 | /** 90 | *

91 | * 4 apexes of page has different permutation order according to original 92 | * point since original point will be changed when user click to curl page 93 | * from different direction. There are 4 kinds of order: 94 | *

 95 |      *   A           B           C           D
 96 |      * 2    1      3    0      0    3      1    2
 97 |      * +----+      +----+      +----+      +----+
 98 |      * |    |      |    |      |    |      |    |
 99 |      * +----+      +----+      +----+      +----+
100 |      * 3    0      2    1      1    2      0    3
101 |      *             From A      From A      From A
102 |      *             0 <-> 1     0 <-> 2     0 <-> 3
103 |      *             3 <-> 2     3 <-> 1     1 <-> 2
104 |      * 
105 | *
    106 | *
  • 0 always represents the origin point, accordingly 2 is diagonal 107 | * point
  • 108 | *
  • Case A is default order: 0 -> 1 -> 2 -> 3
  • 109 | *
  • Every apex data is stored in mApexes following the case A order 110 | * and never changed
  • 111 | *
  • This array is mapping apex order (case A - D) to real apex data 112 | * stored in mApexes. For example: 113 | *
      114 | *
    • Case A has same order with storing sequence of apex data in 115 | * mApexes
    • 116 | *
    • Case B: the 0 apex is stored in 1 position in mApexes
    • 117 | *
  • 118 | *
119 | */ 120 | private final static int[][] mPageApexOrders = new int[][] { 121 | new int[] {0, 1, 2, 3}, // for case A 122 | new int[] {1, 0, 3, 2}, // for case B 123 | new int[] {2, 3, 0, 1}, // for case C 124 | new int[] {3, 2, 1, 0}, // for case D 125 | }; 126 | 127 | /** 128 | *

When page is curled, there are 4 kinds of vertexes orders for drawing 129 | * first texture and second texture with TRIANGLE_STRIP way

130 |      *     A             B              C              D
131 |      * 2       1     2     X 1      2 X     1      2       1
132 |      * +-------+     +-----.-+      +-.-----+      +-------+
133 |      * |       |     | F  /  |      |/      |      |   F   |
134 |      * |   F   .Y    |   /   |     Y.   S   |     X.-------.Y
135 |      * |      /|     |  /    |      |       |      |   S   |
136 |      * +-----.-+     +-.-----+      +-------+      +-------+
137 |      * 3    X  0     3 Y     0      3       0      3       0
138 |      * 
139 | *
    140 | *
  • All cases are based on the apex order case A(0 -> 1 -> 2 -> 3) 141 | *
  • 142 | *
  • F means the first texture area, S means the second texture area 143 | *
  • 144 | *
  • X is xFoldX point, Y is yFoldY point
  • 145 | *
  • Case A means: xFoldX and yFoldY are both in page
  • 146 | *
  • Case B means: xFoldX is in page, but yFoldY is the intersecting 147 | * point with line 1->2 since yFoldY is outside the page
  • 148 | *
  • Case C means: xFoldX and yFoldY are both outside the page
  • 149 | *
  • Case D means: xFoldX outside page but yFoldY is in the page
  • 150 | *
  • Combining {@link #mPageApexOrders} with this array, we can get 151 | * the right apex data from mApexes array which will help us quickly 152 | * organizing triangle data for openGL drawing
  • 153 | *
  • The last array(Case E) in this array means: xFoldX and yFoldY 154 | * are both outside the page and the whole page will be draw with 155 | * second texture
  • 156 | *
157 | */ 158 | private final static int[][] mFoldVexOrders = new int[][] { 159 | new int[] {4, 3, 1, 2, 0}, // Case A 160 | new int[] {3, 3, 2, 0, 1}, // Case B 161 | new int[] {3, 2, 1, 3, 0}, // Case C 162 | new int[] {2, 2, 3, 1, 0}, // Case D 163 | new int[] {1, 0, 1, 3, 2}, // Case E 164 | }; 165 | 166 | // page size 167 | float left; 168 | float right; 169 | float top; 170 | float bottom; 171 | float width; 172 | float height; 173 | 174 | // texture size for rendering page, normally they are same with page width 175 | // and height 176 | float texWidth; 177 | float texHeight; 178 | 179 | /** 180 | *

origin point and diagonal point

181 | *
182 |      * 0-----+
183 |      * |     |
184 |      * |     |
185 |      * +-----1
186 |      * 
187 | *

if origin(x, y) is 1, the diagonal(x, y) is 0

188 | */ 189 | GLPoint originP; 190 | GLPoint diagonalP; 191 | 192 | private GLPoint mXFoldP; 193 | private GLPoint mYFoldP; 194 | 195 | // vertexes and texture coordinates buffer for full page 196 | private FloatBuffer mFullPageVexBuf; 197 | private FloatBuffer mFullPageTexCoordsBuf; 198 | 199 | // storing 4 apexes data of page 200 | private float[] mApexes; 201 | // texture coordinates for page apex 202 | private float[] mApexTexCoords; 203 | // vertex size of front of fold page and unfold page 204 | private int mFrontVertexSize; 205 | // index of apex order array for current original point 206 | private int mApexOrderIndex; 207 | 208 | // mask color of back texture 209 | float[][] maskColor; 210 | 211 | // texture(front, back and second) ids allocated by OpenGL 212 | private int[] mTexIDs; 213 | // unused texture ids, will be deleted when next OpenGL drawing 214 | private int[] mUnusedTexIDs; 215 | // actual size of mUnusedTexIDs 216 | private int mUnusedTexSize; 217 | 218 | /** 219 | * Constructor 220 | */ 221 | public Page() { 222 | init(0, 0, 0, 0); 223 | } 224 | 225 | /** 226 | * Constructor with page size 227 | */ 228 | public Page(float l, float r, float t, float b) { 229 | init(l, r, t, b); 230 | } 231 | 232 | private void init(float l, float r, float t, float b) { 233 | top = t; 234 | left = l; 235 | right = r; 236 | bottom = b; 237 | width = right - left; 238 | height = top - bottom; 239 | texWidth = width; 240 | texHeight = height; 241 | mFrontVertexSize = 0; 242 | mApexOrderIndex = 0; 243 | 244 | mXFoldP = new GLPoint(); 245 | mYFoldP = new GLPoint(); 246 | originP = new GLPoint(); 247 | diagonalP = new GLPoint(); 248 | 249 | maskColor = new float[][] {new float[] {0, 0, 0}, 250 | new float[] {0, 0, 0}, 251 | new float[] {0, 0, 0}}; 252 | 253 | mTexIDs = new int[] {INVALID_TEXTURE_ID, 254 | INVALID_TEXTURE_ID, 255 | INVALID_TEXTURE_ID}; 256 | mUnusedTexSize = 0; 257 | mUnusedTexIDs = new int[] {INVALID_TEXTURE_ID, 258 | INVALID_TEXTURE_ID, 259 | INVALID_TEXTURE_ID}; 260 | 261 | createVertexesBuffer(); 262 | buildVertexesOfFullPage(); 263 | } 264 | 265 | /** 266 | * Is the left page? 267 | *

Left page represents the left screen in double pages mode

268 | * 269 | * @return true if current page is left page 270 | */ 271 | public boolean isLeftPage() { 272 | return right <= 0; 273 | } 274 | 275 | /** 276 | * Is the right page? 277 | *

Right page represents the right screen in double pages mode

278 | * 279 | * @return true if current page is right page 280 | */ 281 | public boolean isRightPage() { 282 | return left >= 0; 283 | } 284 | 285 | /** 286 | * Get page width 287 | * 288 | * @return page width 289 | */ 290 | public float width() { 291 | return width; 292 | } 293 | 294 | /** 295 | * Gets page height 296 | * 297 | * @return page height 298 | */ 299 | public float height() { 300 | return height; 301 | } 302 | 303 | /** 304 | * Is the first texture set? 305 | * 306 | * @return true if the first texture is set 307 | */ 308 | public boolean isFirstTextureSet() { 309 | return mTexIDs[FIRST_TEXTURE_ID] != INVALID_TEXTURE_ID; 310 | } 311 | 312 | /** 313 | * Is the second texture set ? 314 | * 315 | * @return true if the second texture is set 316 | */ 317 | public boolean isSecondTextureSet() { 318 | return mTexIDs[SECOND_TEXTURE_ID] != INVALID_TEXTURE_ID; 319 | } 320 | 321 | /** 322 | * Is the back texture set ? 323 | * 324 | * @return true if the back texture is set 325 | */ 326 | public boolean isBackTextureSet() { 327 | return mTexIDs[BACK_TEXTURE_ID] != INVALID_TEXTURE_ID; 328 | } 329 | 330 | /** 331 | * Deletes unused texture ids 332 | *

It should be called in OpenGL thread

333 | */ 334 | public void deleteUnusedTextures() { 335 | if (mUnusedTexSize > 0) { 336 | glDeleteTextures(mUnusedTexSize, mUnusedTexIDs, 0); 337 | mUnusedTexSize = 0; 338 | } 339 | } 340 | 341 | /** 342 | * Recycle the first texture id and set it with the second texture 343 | *

Manually call this function to set the first texture with the second 344 | * one after page forward flipped over in single page mode.

345 | * 346 | * @return self 347 | */ 348 | public Page setFirstTextureWithSecond() { 349 | if (mTexIDs[FIRST_TEXTURE_ID] > INVALID_TEXTURE_ID) { 350 | mUnusedTexIDs[mUnusedTexSize++] = mTexIDs[FIRST_TEXTURE_ID]; 351 | } 352 | 353 | maskColor[FIRST_TEXTURE_ID][0] = maskColor[SECOND_TEXTURE_ID][0]; 354 | maskColor[FIRST_TEXTURE_ID][1] = maskColor[SECOND_TEXTURE_ID][1]; 355 | maskColor[FIRST_TEXTURE_ID][2] = maskColor[SECOND_TEXTURE_ID][2]; 356 | mTexIDs[FIRST_TEXTURE_ID] = mTexIDs[SECOND_TEXTURE_ID]; 357 | mTexIDs[SECOND_TEXTURE_ID] = INVALID_TEXTURE_ID; 358 | return this; 359 | } 360 | 361 | /** 362 | * Recycle the second texture id and set it with the first texture 363 | *

Manually call this function to set the second texture with the first 364 | * one when page is backward flipping in single page mode.

365 | * 366 | * @return self 367 | */ 368 | public Page setSecondTextureWithFirst() { 369 | if (mTexIDs[SECOND_TEXTURE_ID] > INVALID_TEXTURE_ID) { 370 | mUnusedTexIDs[mUnusedTexSize++] = mTexIDs[SECOND_TEXTURE_ID]; 371 | } 372 | 373 | maskColor[SECOND_TEXTURE_ID][0] = maskColor[FIRST_TEXTURE_ID][0]; 374 | maskColor[SECOND_TEXTURE_ID][1] = maskColor[FIRST_TEXTURE_ID][1]; 375 | maskColor[SECOND_TEXTURE_ID][2] = maskColor[FIRST_TEXTURE_ID][2]; 376 | mTexIDs[SECOND_TEXTURE_ID] = mTexIDs[FIRST_TEXTURE_ID]; 377 | mTexIDs[FIRST_TEXTURE_ID] = INVALID_TEXTURE_ID; 378 | return this; 379 | 380 | } 381 | 382 | /** 383 | * Swap textures of two pages and recycle unused texture ids 384 | *

Call this function when page is flipped over in double pages mode

385 | * 386 | * @param page another page 387 | * @return self 388 | */ 389 | public Page swapTexturesWithPage(Page page) { 390 | // [second page]: second -> first 391 | mUnusedTexIDs[mUnusedTexSize++] = mTexIDs[SECOND_TEXTURE_ID]; 392 | mTexIDs[SECOND_TEXTURE_ID] = mTexIDs[FIRST_TEXTURE_ID]; 393 | 394 | // [first page] first -> [second page] back of first 395 | mUnusedTexIDs[mUnusedTexSize++] = mTexIDs[BACK_TEXTURE_ID]; 396 | mTexIDs[BACK_TEXTURE_ID] = page.mTexIDs[FIRST_TEXTURE_ID]; 397 | 398 | // [first page] back of first -> [second page] first 399 | mTexIDs[FIRST_TEXTURE_ID] = page.mTexIDs[BACK_TEXTURE_ID]; 400 | page.mTexIDs[BACK_TEXTURE_ID] = INVALID_TEXTURE_ID; 401 | 402 | // [first page] second -> [first page] first 403 | page.mTexIDs[FIRST_TEXTURE_ID] = page.mTexIDs[SECOND_TEXTURE_ID]; 404 | page.mTexIDs[SECOND_TEXTURE_ID] = INVALID_TEXTURE_ID; 405 | return this; 406 | } 407 | 408 | /** 409 | * Get back texture ID 410 | * 411 | * @return back texture id, If it is not set, return the first texture id 412 | */ 413 | int getBackTextureID() { 414 | // In single page mode, the back texture is same with the first texture 415 | if (mTexIDs[BACK_TEXTURE_ID] == INVALID_TEXTURE_ID) { 416 | return mTexIDs[FIRST_TEXTURE_ID]; 417 | } 418 | else { 419 | return mTexIDs[BACK_TEXTURE_ID]; 420 | } 421 | } 422 | 423 | /** 424 | * Is given point(x, y) in page? 425 | * 426 | * @param x x coordinate 427 | * @param y y coordinate 428 | * @return true if the point is in page 429 | */ 430 | boolean contains(float x, float y) { 431 | return left < right && bottom < top && 432 | left <= x && x < right && 433 | bottom <= y && y < top; 434 | } 435 | 436 | /** 437 | * Is given x coordinate in specified page range? 438 | * 439 | * @param x x coordinate 440 | * @param ratio range ratio based on page width, start from OriginP.x 441 | * @return True if x is in specified range 442 | */ 443 | boolean isXInRange(float x, float ratio) { 444 | final float w = width * ratio; 445 | return originP.x < 0 ? x < (originP.x + w) : x > (originP.x - w); 446 | } 447 | 448 | /** 449 | * Is given x coordinate outside page width? 450 | * 451 | * @param x x coordinate 452 | * @return true if given x is not in page 453 | */ 454 | boolean isXOutsidePage(float x) { 455 | return originP.x < 0 ? x > diagonalP.x : x < diagonalP.x; 456 | } 457 | 458 | /** 459 | * Compute index of page apexes order for current original point 460 | */ 461 | private void computeIndexOfApexOrder() { 462 | mApexOrderIndex = 0; 463 | if (originP.x < right && originP.y < 0) { 464 | mApexOrderIndex = 3; 465 | } 466 | else { 467 | if (originP.y > 0) { 468 | mApexOrderIndex++; 469 | } 470 | if (originP.x < right) { 471 | mApexOrderIndex++; 472 | } 473 | } 474 | } 475 | 476 | /** 477 | * Set original point and diagonal point 478 | * 479 | * @param hasSecondPage has the second page in double pages mode? 480 | * @param dy relative finger movement on Y axis 481 | * @return self 482 | */ 483 | Page setOriginAndDiagonalPoints(boolean hasSecondPage, float dy) { 484 | if (hasSecondPage && left < 0) { 485 | originP.x = left; 486 | diagonalP.x = right; 487 | } 488 | else { 489 | originP.x = right; 490 | diagonalP.x = left; 491 | } 492 | 493 | if (dy > 0) { 494 | originP.y = bottom; 495 | diagonalP.y = top; 496 | } 497 | else { 498 | originP.y = top; 499 | diagonalP.y = bottom; 500 | } 501 | 502 | computeIndexOfApexOrder(); 503 | 504 | // set texture coordinates 505 | originP.texX = (originP.x - left) / texWidth; 506 | originP.texY = (top - originP.y) / texHeight; 507 | diagonalP.texX = (diagonalP.x - left) / texWidth; 508 | diagonalP.texY = (top - diagonalP.y) / texHeight; 509 | return this; 510 | } 511 | 512 | /** 513 | * Invert Y coordinate of original point and diagonal point 514 | */ 515 | void invertYOfOriginPoint() { 516 | float t = originP.y; 517 | originP.y = diagonalP.y; 518 | diagonalP.y = t; 519 | 520 | t = originP.texY; 521 | originP.texY = diagonalP.texY; 522 | diagonalP.texY = t; 523 | 524 | // re-compute index for apex order since original point is changed 525 | computeIndexOfApexOrder(); 526 | } 527 | 528 | /** 529 | * Compute X coordinate of texture 530 | * 531 | * @param x x coordinate 532 | * @return x coordinate of texture, value is in [0 .. 1] 533 | */ 534 | public float textureX(float x) { 535 | return (x - left) / texWidth; 536 | } 537 | 538 | /** 539 | * Compute Y coordinate of texture 540 | * 541 | * @param y y coordinate 542 | * @return y coordinate of texture, value is in [0 .. 1] 543 | */ 544 | public float textureY(float y) { 545 | return (top - y) / texHeight; 546 | } 547 | 548 | /** 549 | * Delete all textures 550 | */ 551 | public void deleteAllTextures() { 552 | glDeleteTextures(TEXTURE_SIZE, mTexIDs, 0); 553 | mTexIDs[FIRST_TEXTURE_ID] = INVALID_TEXTURE_ID; 554 | mTexIDs[SECOND_TEXTURE_ID] = INVALID_TEXTURE_ID; 555 | mTexIDs[BACK_TEXTURE_ID] = INVALID_TEXTURE_ID; 556 | } 557 | 558 | /** 559 | * Set the first texture with given bitmap 560 | * 561 | * @param b Bitmap object for creating texture 562 | */ 563 | public void setFirstTexture(Bitmap b) { 564 | // compute mask color 565 | int color = PageFlipUtils.computeAverageColor(b, 30); 566 | maskColor[FIRST_TEXTURE_ID][0] = Color.red(color) / 255.0f; 567 | maskColor[FIRST_TEXTURE_ID][1] = Color.green(color) / 255.0f; 568 | maskColor[FIRST_TEXTURE_ID][2] = Color.blue(color) / 255.0f; 569 | 570 | glGenTextures(1, mTexIDs, FIRST_TEXTURE_ID); 571 | glActiveTexture(GL_TEXTURE0); 572 | glBindTexture(GL_TEXTURE_2D, mTexIDs[FIRST_TEXTURE_ID]); 573 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 574 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 575 | GLUtils.texImage2D(GL_TEXTURE_2D, 0, b, 0); 576 | } 577 | 578 | /** 579 | * Set the second texture with given bitmap 580 | * 581 | * @param b Bitmap object for creating texture 582 | */ 583 | public void setSecondTexture(Bitmap b) { 584 | // compute mask color 585 | int color = PageFlipUtils.computeAverageColor(b, 30); 586 | maskColor[SECOND_TEXTURE_ID][0] = Color.red(color) / 255.0f; 587 | maskColor[SECOND_TEXTURE_ID][1] = Color.green(color) / 255.0f; 588 | maskColor[SECOND_TEXTURE_ID][2] = Color.blue(color) / 255.0f; 589 | 590 | glGenTextures(1, mTexIDs, SECOND_TEXTURE_ID); 591 | glActiveTexture(GL_TEXTURE0); 592 | glBindTexture(GL_TEXTURE_2D, mTexIDs[SECOND_TEXTURE_ID]); 593 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 594 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 595 | GLUtils.texImage2D(GL_TEXTURE_2D, 0, b, 0); 596 | } 597 | 598 | /** 599 | * Set the back texture with given bitmap 600 | *

If given bitmap is null, the back texture will be same with the first 601 | * texture

602 | * 603 | * @param b Bitmap object for creating back texture 604 | */ 605 | public void setBackTexture(Bitmap b) { 606 | if (b == null) { 607 | // back texture is same with the first texture 608 | if (mTexIDs[BACK_TEXTURE_ID] != INVALID_TEXTURE_ID) { 609 | mUnusedTexIDs[mUnusedTexSize++] = mTexIDs[BACK_TEXTURE_ID]; 610 | } 611 | mTexIDs[BACK_TEXTURE_ID] = INVALID_TEXTURE_ID; 612 | } 613 | else { 614 | // compute mask color 615 | int color = PageFlipUtils.computeAverageColor(b, 50); 616 | maskColor[BACK_TEXTURE_ID][0] = Color.red(color) / 255.0f; 617 | maskColor[BACK_TEXTURE_ID][1] = Color.green(color) / 255.0f; 618 | maskColor[BACK_TEXTURE_ID][2] = Color.blue(color) / 255.0f; 619 | 620 | glGenTextures(1, mTexIDs, BACK_TEXTURE_ID); 621 | glBindTexture(GL_TEXTURE_2D, mTexIDs[BACK_TEXTURE_ID]); 622 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 623 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 624 | GLUtils.texImage2D(GL_TEXTURE_2D, 0, b, 0); 625 | } 626 | } 627 | 628 | /** 629 | * Draw front page when page is flipping 630 | * 631 | * @param program GL shader program 632 | * @param vertexes Vertexes of the curled front page 633 | */ 634 | public void drawFrontPage(VertexProgram program, 635 | Vertexes vertexes) { 636 | // 1. draw unfold part and curled part with the first texture 637 | glUniformMatrix4fv(program.mMVPMatrixLoc, 1, false, 638 | VertexProgram.MVPMatrix, 0); 639 | glBindTexture(GL_TEXTURE_2D, mTexIDs[FIRST_TEXTURE_ID]); 640 | glUniform1i(program.mTextureLoc, 0); 641 | vertexes.drawWith(GL_TRIANGLE_STRIP, 642 | program.mVertexPosLoc, 643 | program.mTexCoordLoc, 644 | 0, mFrontVertexSize); 645 | 646 | // 2. draw the second texture 647 | glBindTexture(GL_TEXTURE_2D, mTexIDs[SECOND_TEXTURE_ID]); 648 | glUniform1i(program.mTextureLoc, 0); 649 | glDrawArrays(GL_TRIANGLE_STRIP, 650 | mFrontVertexSize, 651 | vertexes.mVertexesSize - mFrontVertexSize); 652 | } 653 | 654 | /** 655 | * Draw full page 656 | * 657 | * @param program GL shader program 658 | * @param isFirst use the first or second texture to draw 659 | */ 660 | public void drawFullPage(VertexProgram program, boolean isFirst) { 661 | if (isFirst) { 662 | drawFullPage(program, mTexIDs[FIRST_TEXTURE_ID]); 663 | } 664 | else { 665 | drawFullPage(program, mTexIDs[SECOND_TEXTURE_ID]); 666 | } 667 | } 668 | 669 | /** 670 | * Draw full page with given texture id 671 | */ 672 | private void drawFullPage(VertexProgram program, int textureID) { 673 | glBindTexture(GL_TEXTURE_2D, textureID); 674 | glUniform1i(program.mTextureLoc, 0); 675 | 676 | glVertexAttribPointer(program.mVertexPosLoc, 3, GL_FLOAT, false, 0, 677 | mFullPageVexBuf); 678 | glEnableVertexAttribArray(program.mVertexPosLoc); 679 | 680 | glVertexAttribPointer(program.mTexCoordLoc, 2, GL_FLOAT, false, 0, 681 | mFullPageTexCoordsBuf); 682 | glEnableVertexAttribArray(program.mTexCoordLoc); 683 | 684 | glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 685 | } 686 | 687 | /** 688 | * Create vertexes buffer 689 | */ 690 | private void createVertexesBuffer() { 691 | // 4 vertexes for full page 692 | mFullPageVexBuf = ByteBuffer.allocateDirect(48) 693 | .order(ByteOrder.nativeOrder()) 694 | .asFloatBuffer(); 695 | 696 | mFullPageTexCoordsBuf = ByteBuffer.allocateDirect(32) 697 | .order(ByteOrder.nativeOrder()) 698 | .asFloatBuffer(); 699 | 700 | mApexes = new float[12]; 701 | mApexTexCoords = new float[8]; 702 | } 703 | 704 | /** 705 | * Build vertexes of page when page is flipping vertically 706 | *
707 |      *        <---- flip
708 |      *     1        fY    2
709 |      *     +--------#-----+
710 |      *     |        |     |
711 |      *     |        |     |
712 |      *     |        |     |
713 |      *     +--------#-----+
714 |      *     4        fX    3
715 |      * 
716 | *

717 | * There is only one case to draw when page is flipping vertically 718 | *

719 | *
    720 | *
  • Page is flipping from right -> left
  • 721 | *
  • Origin point: 3
  • 722 | *
  • Diagonal point: 1
  • 723 | *
  • xFoldP1.y: fY, xFoldP2.x: fX
  • 724 | *
  • Drawing front part with the first texture(GL_TRIANGLE_STRIP): 725 | * fX -> fY -> 4 -> 1
  • 726 | *
  • Drawing back part with the second texture(GL_TRIANGLE_STRIP): 727 | * 3 -> 2 -> fX -> fY
  • 728 | *
729 | * 730 | * @param frontVertexes vertexes for drawing font part of page 731 | * @param xFoldP1 fold point on X axis 732 | */ 733 | public void buildVertexesOfPageWhenVertical(Vertexes frontVertexes, 734 | PointF xFoldP1) { 735 | // if xFoldX and yFoldY are both outside the page, use the last vertex 736 | // order to draw page 737 | int index = 4; 738 | 739 | // compute xFoldX and yFoldY points 740 | if (!isXOutsidePage(xFoldP1.x)) { 741 | // use the case B of vertex order to draw page 742 | index = 1; 743 | float cx = textureX(xFoldP1.x); 744 | mXFoldP.set(xFoldP1.x, originP.y, 0, cx, originP.texY); 745 | mYFoldP.set(xFoldP1.x, diagonalP.y, 0, cx, diagonalP.texY); 746 | } 747 | 748 | // get apex order and fold vertex order 749 | final int[] apexOrder = mPageApexOrders[mApexOrderIndex]; 750 | final int[] vexOrder = mFoldVexOrders[index]; 751 | 752 | // need to draw first texture, add xFoldX and yFoldY first. Remember 753 | // the adding order of vertex in float buffer is X point prior to Y 754 | // point 755 | if (vexOrder[0] > 1) { 756 | frontVertexes.addVertex(mXFoldP).addVertex(mYFoldP); 757 | } 758 | 759 | // add the leftover vertexes for the first texture 760 | for (int i = 1; i < vexOrder[0]; ++i) { 761 | int k = apexOrder[vexOrder[i]]; 762 | int m = k * 3; 763 | int n = k << 1; 764 | frontVertexes.addVertex(mApexes[m], mApexes[m + 1], 0, 765 | mApexTexCoords[n], mApexTexCoords[n + 1]); 766 | } 767 | 768 | // the vertex size for drawing front of fold page and first texture 769 | mFrontVertexSize = frontVertexes.mNext / 3; 770 | 771 | // if xFoldX and yFoldY are in the page, need add them for drawing the 772 | // second texture 773 | if (vexOrder[0] > 1) { 774 | mXFoldP.z = mYFoldP.z = -1; 775 | frontVertexes.addVertex(mXFoldP).addVertex(mYFoldP); 776 | } 777 | 778 | // add the remaining vertexes for the second texture 779 | for (int i = vexOrder[0]; i < vexOrder.length; ++i) { 780 | int k = apexOrder[vexOrder[i]]; 781 | int m = k * 3; 782 | int n = k << 1; 783 | frontVertexes.addVertex(mApexes[m], mApexes[m + 1], -1, 784 | mApexTexCoords[n], mApexTexCoords[n + 1]); 785 | } 786 | } 787 | 788 | /** 789 | * Build vertexes of page when page flip is slope 790 | *

See {@link #mApexOrderIndex} and {@link #mFoldVexOrders} to get more 791 | * details

792 | * 793 | * @param frontVertexes vertexes for drawing front part of page 794 | * @param xFoldP1 fold point on X axis 795 | * @param yFoldP1 fold point on Y axis 796 | * @param kValue tan value of page curling angle 797 | */ 798 | public void buildVertexesOfPageWhenSlope(Vertexes frontVertexes, 799 | PointF xFoldP1, 800 | PointF yFoldP1, 801 | float kValue) { 802 | // compute xFoldX point 803 | float halfH = height * 0.5f; 804 | int index = 0; 805 | mXFoldP.set(xFoldP1.x, originP.y, 0, textureX(xFoldP1.x), originP.texY); 806 | if (isXOutsidePage(xFoldP1.x)) { 807 | index = 2; 808 | mXFoldP.x = diagonalP.x; 809 | mXFoldP.y = originP.y + (xFoldP1.x - diagonalP.x) / kValue; 810 | mXFoldP.texX = diagonalP.texX; 811 | mXFoldP.texY = textureY(mXFoldP.y); 812 | } 813 | 814 | // compute yFoldY point 815 | mYFoldP.set(originP.x, yFoldP1.y, 0, originP.texX, textureY(yFoldP1.y)); 816 | if (Math.abs(yFoldP1.y) > halfH) { 817 | index++; 818 | mYFoldP.x = originP.x + kValue * (yFoldP1.y - diagonalP.y); 819 | if (isXOutsidePage(mYFoldP.x)) { 820 | index++; 821 | } 822 | else { 823 | mYFoldP.y = diagonalP.y; 824 | mYFoldP.texX = textureX(mYFoldP.x); 825 | mYFoldP.texY = diagonalP.texY; 826 | } 827 | } 828 | 829 | // get apex order and fold vertex order 830 | final int[] apexOrder = mPageApexOrders[mApexOrderIndex]; 831 | final int[] vexOrder = mFoldVexOrders[index]; 832 | 833 | // need to draw first texture, add xFoldX and yFoldY first. Remember 834 | // the adding order of vertex in float buffer is X point prior to Y 835 | // point 836 | if (vexOrder[0] > 1) { 837 | frontVertexes.addVertex(mXFoldP).addVertex(mYFoldP); 838 | } 839 | 840 | // add the leftover vertexes for the first texture 841 | for (int i = 1; i < vexOrder[0]; ++i) { 842 | int k = apexOrder[vexOrder[i]]; 843 | int m = k * 3; 844 | int n = k << 1; 845 | frontVertexes.addVertex(mApexes[m], mApexes[m + 1], 0, 846 | mApexTexCoords[n], mApexTexCoords[n + 1]); 847 | } 848 | 849 | // the vertex size for drawing front of fold page and first texture 850 | mFrontVertexSize = frontVertexes.mNext / 3; 851 | 852 | // if xFoldX and yFoldY are in the page, need add them for drawing the 853 | // second texture 854 | if (vexOrder[0] > 1) { 855 | mXFoldP.z = mYFoldP.z = -1; 856 | frontVertexes.addVertex(mXFoldP).addVertex(mYFoldP); 857 | } 858 | 859 | // add the remaining vertexes for the second texture 860 | for (int i = vexOrder[0]; i < vexOrder.length; ++i) { 861 | int k = apexOrder[vexOrder[i]]; 862 | int m = k * 3; 863 | int n = k << 1; 864 | frontVertexes.addVertex(mApexes[m], mApexes[m + 1], -1, 865 | mApexTexCoords[n], mApexTexCoords[n + 1]); 866 | } 867 | } 868 | 869 | /** 870 | * Build vertexes of full page 871 | *
872 |      *        <---- flip
873 |      *     3              2
874 |      *     +--------------+
875 |      *     |              |
876 |      *     |              |
877 |      *     |              |
878 |      *     |              |
879 |      *     +--------------+
880 |      *     4              1
881 |      * 
882 | *
    883 | *
  • Page is flipping from right -> left
  • 884 | *
  • Origin point: 3
  • 885 | *
  • Diagonal point: 1
  • 886 | *
  • xFoldP1.y: fY, xFoldP2.x: fX
  • 887 | *
  • Drawing order: 3 -> 2 -> 4 -> 1
  • 888 | *
889 | */ 890 | private void buildVertexesOfFullPage() { 891 | int i = 0; 892 | int j = 0; 893 | 894 | mApexes[i++] = right; 895 | mApexes[i++] = bottom; 896 | mApexes[i++] = 0; 897 | mApexTexCoords[j++] = textureX(right); 898 | mApexTexCoords[j++] = textureY(bottom); 899 | 900 | mApexes[i++] = right; 901 | mApexes[i++] = top; 902 | mApexes[i++] = 0; 903 | mApexTexCoords[j++] = textureX(right); 904 | mApexTexCoords[j++] = textureY(top); 905 | 906 | mApexes[i++] = left; 907 | mApexes[i++] = top; 908 | mApexes[i++] = 0; 909 | mApexTexCoords[j++] = textureX(left); 910 | mApexTexCoords[j++] = textureY(top); 911 | 912 | mApexes[i++] = left; 913 | mApexes[i++] = bottom; 914 | mApexes[i] = 0; 915 | mApexTexCoords[j++] = textureX(left); 916 | mApexTexCoords[j] = textureY(bottom); 917 | 918 | mFullPageVexBuf.put(mApexes, 0, 12).position(0); 919 | mFullPageTexCoordsBuf.put(mApexTexCoords, 0, 8).position(0); 920 | } 921 | } 922 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/PageFlipException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | /** 19 | * PageFlip exception class 20 | * 21 | * @author eschao 22 | */ 23 | public class PageFlipException extends Exception { 24 | 25 | public PageFlipException() { 26 | super(); 27 | } 28 | 29 | public PageFlipException(String message) { 30 | super(message); 31 | } 32 | 33 | public PageFlipException(String message, Throwable cause) { 34 | super(message, cause); 35 | } 36 | 37 | public PageFlipException(Throwable cause) { 38 | super(cause); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/PageFlipState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | /** 19 | * Animation state of page flip 20 | * 21 | * @author eschao 22 | */ 23 | public enum PageFlipState { 24 | BEGIN_FLIP, 25 | FORWARD_FLIP, 26 | BACKWARD_FLIP, 27 | RESTORE_FLIP, 28 | END_FLIP, 29 | END_WITH_FORWARD, 30 | END_WITH_BACKWARD, 31 | END_WITH_RESTORE, 32 | } 33 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/PageFlipUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.graphics.Bitmap; 19 | import android.graphics.Canvas; 20 | import android.graphics.Color; 21 | import android.graphics.LinearGradient; 22 | import android.graphics.Paint; 23 | import android.graphics.Shader; 24 | 25 | /** 26 | * Utilities of page flip 27 | * 28 | * @author eschao 29 | */ 30 | public class PageFlipUtils { 31 | 32 | /** 33 | * Compute average color for given bitmap 34 | * 35 | * @param bitmap bitmap object 36 | * @param pixels how many sample pixels are used to compute 37 | * @return Average color 38 | */ 39 | public static int computeAverageColor(Bitmap bitmap, int pixels) { 40 | int red = 0; 41 | int green = 0; 42 | int blue = 0; 43 | int alpha = 0; 44 | int width = bitmap.getWidth(); 45 | int height = bitmap.getHeight(); 46 | int maxWPixels = width / 3; 47 | int maxHPixels = height / 3; 48 | 49 | if (pixels > maxWPixels) { 50 | pixels = maxWPixels; 51 | } 52 | 53 | if (pixels > maxHPixels) { 54 | pixels = maxHPixels; 55 | } 56 | 57 | int right = width - pixels; 58 | int bottom = height - pixels; 59 | int centerLeft = right / 2; 60 | int centerTop = bottom / 2; 61 | 62 | for (int i = 0; i < pixels; ++i) { 63 | // left-top 64 | int color = bitmap.getPixel(i, i); 65 | red += Color.red(color); 66 | blue += Color.blue(color); 67 | green += Color.green(color); 68 | alpha += Color.alpha(color); 69 | 70 | // center 71 | color = bitmap.getPixel(centerLeft + i, centerTop + i); 72 | red += Color.red(color); 73 | blue += Color.blue(color); 74 | green += Color.green(color); 75 | alpha += Color.alpha(color); 76 | 77 | // right-top 78 | color = bitmap.getPixel(right + i, i); 79 | red += Color.red(color); 80 | blue += Color.blue(color); 81 | green += Color.green(color); 82 | alpha += Color.alpha(color); 83 | 84 | // left-bottom 85 | color = bitmap.getPixel(i, bottom + i); 86 | red += Color.red(color); 87 | blue += Color.blue(color); 88 | green += Color.green(color); 89 | alpha += Color.alpha(color); 90 | 91 | // right-bottom 92 | color = bitmap.getPixel(right + i, bottom + i); 93 | red += Color.red(color); 94 | blue += Color.blue(color); 95 | green += Color.green(color); 96 | alpha += Color.alpha(color); 97 | } 98 | 99 | int count = pixels * 5; 100 | red /= count; 101 | blue /= count; 102 | green /= count; 103 | alpha /= count; 104 | return Color.argb(alpha, red, green, blue); 105 | } 106 | 107 | /** 108 | * Create gradient bitmap for drawing lighting effect on back of fold page 109 | * 110 | * @return gradient bitmap object 111 | */ 112 | public static Bitmap createGradientBitmap() { 113 | Canvas c = new Canvas(); 114 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 115 | Bitmap bitmap = Bitmap.createBitmap(256, 1, Bitmap.Config.ARGB_8888); 116 | 117 | c.setBitmap(bitmap); 118 | int[] colors = new int[]{0x00FFFFFF, 119 | 0x24000000, 120 | 0x24101010, 121 | 0x48000000}; 122 | float[] positions = new float[]{0.5f, 0.9f, 0.94f, 1.0f}; 123 | LinearGradient shader = new LinearGradient(0, 0, 256, 0, colors, 124 | positions, 125 | Shader.TileMode.CLAMP); 126 | paint.setShader(shader); 127 | c.drawRect(0, 0, 256, 1, paint); 128 | return bitmap; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/ShadowColor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | /** 19 | * Shadow color 20 | * 21 | * @author eschao 22 | */ 23 | 24 | public final class ShadowColor { 25 | 26 | float startColor; 27 | float startAlpha; 28 | float endColor; 29 | float endAlpha; 30 | 31 | /** 32 | * Default constructor 33 | */ 34 | public ShadowColor() { 35 | startColor = 0; 36 | startAlpha = 0; 37 | endColor = 0; 38 | endAlpha = 0; 39 | } 40 | 41 | /** 42 | * Constructor 43 | * 44 | * @param startColor start color, range is [0 .. 1] 45 | * @param startAlpha start alpha, range is [0 .. 1] 46 | * @param endColor end color, range is [0 .. 1] 47 | * @param endAlpha end alpha, range is [0 .. 1] 48 | */ 49 | public ShadowColor(float startColor, float startAlpha, 50 | float endColor, float endAlpha) { 51 | set(startColor, startAlpha, endColor, endAlpha); 52 | } 53 | 54 | /** 55 | * Set color and alpha 56 | * 57 | * @param startColor start color, range is [0 .. 1] 58 | * @param startAlpha start alpha, range is [0 .. 1] 59 | * @param endColor end color, range is [0 .. 1] 60 | * @param endAlpha end alpha, range is [0 .. 1] 61 | */ 62 | public void set(float startColor, float startAlpha, 63 | float endColor, float endAlpha) { 64 | if (startColor < 0 || startColor > 1 || 65 | startAlpha < 0 || startAlpha > 1 || 66 | endColor < 0 || endColor > 1 || 67 | endAlpha < 0 || endAlpha > 1) { 68 | throw new IllegalArgumentException("Illegal color or alpha value!"); 69 | } 70 | 71 | this.startColor = startColor; 72 | this.startAlpha = startAlpha; 73 | this.endColor = endColor; 74 | this.endAlpha = endAlpha; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/ShadowVertexProgram.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.content.Context; 19 | 20 | import static android.opengl.GLES20.glGetAttribLocation; 21 | import static android.opengl.GLES20.glGetUniformLocation; 22 | 23 | /** 24 | * Shadow vertex shader program which is used to load: 25 | *
    26 | *
  • shadow_vertex_shader.glsl
  • 27 | *
  • shadow_fragment_shader.glsl
  • 28 | *
29 | * 30 | * @author eschao 31 | */ 32 | public class ShadowVertexProgram extends GLProgram { 33 | 34 | // variable names defined in shader scripts 35 | final static String VAR_MVP_MATRIX = "u_MVPMatrix"; 36 | final static String VAR_VERTEX_Z = "u_vexZ"; 37 | final static String VAR_VERTEX_POS = "a_vexPosition"; 38 | 39 | int mMVPMatrixLoc; 40 | int mVertexZLoc; 41 | int mVertexPosLoc; 42 | 43 | /** 44 | * Constructor 45 | */ 46 | public ShadowVertexProgram() { 47 | super(); 48 | 49 | mMVPMatrixLoc = INVALID_GL_HANDLE; 50 | mVertexZLoc = INVALID_GL_HANDLE; 51 | mVertexPosLoc = INVALID_GL_HANDLE; 52 | } 53 | 54 | /** 55 | * Initiate shader program 56 | * 57 | * @param context android context 58 | * @return self 59 | * @throws PageFlipException raise exception if fail to compile & link 60 | * program 61 | */ 62 | public ShadowVertexProgram init(Context context) throws 63 | PageFlipException { 64 | super.init(context, 65 | R.raw.shadow_vertex_shader, 66 | R.raw.shadow_fragment_shader); 67 | return this; 68 | } 69 | 70 | /** 71 | * Get variable handles from linked shader program 72 | */ 73 | protected void getVarsLocation() { 74 | if (mProgramRef != 0) { 75 | mVertexZLoc = glGetUniformLocation(mProgramRef, VAR_VERTEX_Z); 76 | mVertexPosLoc = glGetAttribLocation(mProgramRef, VAR_VERTEX_POS); 77 | mMVPMatrixLoc = glGetUniformLocation(mProgramRef, VAR_MVP_MATRIX); 78 | } 79 | } 80 | 81 | /** 82 | * Delete shader resources 83 | */ 84 | public void delete() { 85 | super.delete(); 86 | 87 | mMVPMatrixLoc = INVALID_GL_HANDLE; 88 | mVertexZLoc = INVALID_GL_HANDLE; 89 | mVertexPosLoc = INVALID_GL_HANDLE; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/ShadowVertexes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import java.nio.ByteBuffer; 19 | import java.nio.ByteOrder; 20 | import java.nio.FloatBuffer; 21 | 22 | import static android.opengl.GLES10.GL_SRC_ALPHA; 23 | import static android.opengl.GLES20.GL_BLEND; 24 | import static android.opengl.GLES20.GL_FLOAT; 25 | import static android.opengl.GLES20.GL_ONE_MINUS_SRC_ALPHA; 26 | import static android.opengl.GLES20.GL_TEXTURE_2D; 27 | import static android.opengl.GLES20.GL_TRIANGLE_STRIP; 28 | import static android.opengl.GLES20.glBlendFunc; 29 | import static android.opengl.GLES20.glDisable; 30 | import static android.opengl.GLES20.glDrawArrays; 31 | import static android.opengl.GLES20.glEnable; 32 | import static android.opengl.GLES20.glEnableVertexAttribArray; 33 | import static android.opengl.GLES20.glUniform1f; 34 | import static android.opengl.GLES20.glUniformMatrix4fv; 35 | import static android.opengl.GLES20.glVertexAttribPointer; 36 | 37 | /** 38 | * Shadow vertex which is used to store vertex data of fold shadow and draw 39 | * shadow with openGL 40 | *

Every vertex has 4 float data which are:

41 | *
    42 | *
  • x coordinate
  • 43 | *
  • y coordinate
  • 44 | *
  • color (start color or end color)
  • 45 | *
  • alpha (start alpha or end alpha)
  • 46 | *
47 | * 48 | * @author eschao 49 | */ 50 | class ShadowVertexes { 51 | 52 | // how many vertexes in vertex float buffer will be drawn on screen 53 | int mVertexesSize; 54 | 55 | // universal Z coordinate for all shadow vertex 56 | // we will enable DEPTH_TEST while drawing fold shadow to avoid some drawing 57 | // issue 58 | float vertexZ; 59 | 60 | // float array and float buffer for storing vertexes 61 | float[] mVertexes; 62 | FloatBuffer mVertexesBuffer; 63 | 64 | // shadow color 65 | ShadowColor mColor; 66 | 67 | // the start position of backward vertexes 68 | int mMaxBackward; 69 | 70 | // reserve space between backward and forward index 71 | // need to preserver space for fold top edge shadow when compute fold edge 72 | // shadow since the top edge shadow will be computed at last 73 | // 74 | // +--------------------+------------+--------------------+ 75 | // | <-- mBackward | reserved | mForward --> | 76 | // +--------------------+------------+--------------------+ 77 | private int mSpaceOfFrontRear; 78 | 79 | // forward and backward index for adding vertex 80 | private int mBackward; 81 | private int mForward; 82 | 83 | /** 84 | * Default constructor 85 | */ 86 | public ShadowVertexes() { 87 | release(); 88 | mColor = new ShadowColor(); 89 | } 90 | 91 | /** 92 | * Constructor 93 | * 94 | * @param spaceOfFrontRear reserve space for special usage 95 | * @param startColor shadow start color, range is [0 .. 1] 96 | * @param startAlpha shadow alpha, range is [0 .. 1] 97 | * @param endColor shadow end color, range is [0 .. 1] 98 | * @param endAlpha shadow end alpah, range is [0 .. 1] 99 | */ 100 | public ShadowVertexes(int spaceOfFrontRear, 101 | float startColor, float startAlpha, 102 | float endColor, float endAlpha) { 103 | release(); 104 | mSpaceOfFrontRear = spaceOfFrontRear; 105 | mColor = new ShadowColor(startColor, startAlpha, endColor, endAlpha); 106 | } 107 | 108 | /** 109 | * Set with vertex count 110 | * 111 | * @param meshCount mesh count 112 | * @return self 113 | */ 114 | public ShadowVertexes set(int meshCount) { 115 | // every mesh need two vertexes: 116 | // (startX, startY , startColor, startAlpha) and 117 | // (endX, endY, endColor, endAlpha), that is why it is meshCount * 8 118 | mMaxBackward = meshCount << 3; 119 | 120 | // double meshCount since fold shadow has two sides, for example: 121 | // fold edge shadow has left and right edge along the fold triangle 122 | int size = (meshCount << 4) + (mSpaceOfFrontRear << 2); 123 | mVertexes = new float[size]; 124 | mVertexesBuffer = ByteBuffer.allocateDirect(size << 2) 125 | .order(ByteOrder.nativeOrder()) 126 | .asFloatBuffer(); 127 | reset(); 128 | return this; 129 | } 130 | 131 | /** 132 | * Release all resources 133 | */ 134 | public void release() { 135 | mBackward = 0; 136 | mForward = 0; 137 | mMaxBackward = 0; 138 | mSpaceOfFrontRear = 0; 139 | mVertexes = null; 140 | mVertexesBuffer = null; 141 | } 142 | 143 | /** 144 | * Reset index of float array before adding vertex to buffer 145 | *

There are two index: forward and backward, all of them have to be 146 | * reset to middle position(exclude reserved space) before adding vertexes 147 | *

148 | */ 149 | public void reset() { 150 | vertexZ = 0; 151 | mBackward = mMaxBackward; 152 | mForward = mMaxBackward + (mSpaceOfFrontRear << 2); 153 | } 154 | 155 | /** 156 | * Set vertex in given offset 157 | * 158 | * @param offset where to start saving vertex 159 | * @param startX start x coordinate 160 | * @param startY start y coordinate 161 | * @param endX end x coordinate 162 | * @param endY end y coordinate 163 | * @return self 164 | */ 165 | public ShadowVertexes setVertexes(int offset, 166 | float startX, float startY, 167 | float endX, float endY) { 168 | mVertexes[offset++] = startX; 169 | mVertexes[offset++] = startY; 170 | mVertexes[offset++] = mColor.startColor; 171 | mVertexes[offset++] = mColor.startAlpha; 172 | mVertexes[offset++] = endX; 173 | mVertexes[offset++] = endY; 174 | mVertexes[offset++] = mColor.endColor; 175 | mVertexes[offset] = mColor.endAlpha; 176 | return this; 177 | } 178 | 179 | /** 180 | * Backward add vertex to float buffer 181 | *

Call {@link #reset()} before start calling any add operations

182 | * 183 | * @param startX start x coordinate 184 | * @param startY start y coordinate 185 | * @param endX end x coordinate 186 | * @param endY end y coordinate 187 | * @return self 188 | */ 189 | public ShadowVertexes addVertexesBackward(float startX, float startY, 190 | float endX, float endY) { 191 | mVertexes[--mBackward] = mColor.endAlpha; 192 | mVertexes[--mBackward] = mColor.endColor; 193 | mVertexes[--mBackward] = endY; 194 | mVertexes[--mBackward] = endX; 195 | mVertexes[--mBackward] = mColor.startAlpha; 196 | mVertexes[--mBackward] = mColor.startColor; 197 | mVertexes[--mBackward] = startY; 198 | mVertexes[--mBackward] = startX; 199 | return this; 200 | } 201 | 202 | /** 203 | * Forward add vertex to float buffer 204 | *

Call {@link #reset()} before start calling any add operations

205 | * 206 | * @param startX start x coordinate 207 | * @param startY start y coordinate 208 | * @param endX end x coordinate 209 | * @param endY end y coordinate 210 | * @return self 211 | */ 212 | public ShadowVertexes addVertexesForward(float startX, float startY, 213 | float endX, float endY) { 214 | mVertexes[mForward++] = startX; 215 | mVertexes[mForward++] = startY; 216 | mVertexes[mForward++] = mColor.startColor; 217 | mVertexes[mForward++] = mColor.startAlpha; 218 | mVertexes[mForward++] = endX; 219 | mVertexes[mForward++] = endY; 220 | mVertexes[mForward++] = mColor.endColor; 221 | mVertexes[mForward++] = mColor.endAlpha; 222 | return this; 223 | } 224 | 225 | /** 226 | * Add vertex to float buffer 227 | * Call {@link #reset()} before calling any add operations 228 | * 229 | * @param isForward is backward or forward adding 230 | * @param startX start x coordinate 231 | * @param startY start y coordinate 232 | * @param endX end x coordinate 233 | * @param endY end y coordinate 234 | * @return self 235 | */ 236 | public ShadowVertexes addVertexes(boolean isForward, 237 | float startX, float startY, 238 | float endX, float endY) { 239 | return isForward ? 240 | addVertexesForward(startX, startY, endX, endY) : 241 | addVertexesBackward(startX, startY, endX, endY); 242 | } 243 | 244 | /** 245 | * Put data from float array to float buffer 246 | */ 247 | public void toFloatBuffer() { 248 | mVertexesSize = (mForward - mBackward) / 4; 249 | mVertexesBuffer.put(mVertexes, mBackward, mForward - mBackward) 250 | .position(0); 251 | } 252 | 253 | /** 254 | * put given length data from float array to float buffer 255 | * 256 | * @param length data length 257 | */ 258 | public void toFloatBuffer(int length) { 259 | mVertexesBuffer.put(mVertexes, 0, length).position(0); 260 | mVertexesSize = length / 4; 261 | } 262 | 263 | /** 264 | * Draw shadow 265 | * 266 | * @param program shadow vertex shader program 267 | */ 268 | public void draw(ShadowVertexProgram program) { 269 | if (mVertexesSize > 0) { 270 | glUniformMatrix4fv(program.mMVPMatrixLoc, 1, false, 271 | VertexProgram.MVPMatrix, 0); 272 | 273 | glUniform1f(program.mVertexZLoc, vertexZ); 274 | 275 | // disable texture, and enable blend 276 | glDisable(GL_TEXTURE_2D); 277 | glEnable(GL_BLEND); 278 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 279 | 280 | // draw shadow 281 | glVertexAttribPointer(program.mVertexPosLoc, 4, GL_FLOAT, false, 0, 282 | mVertexesBuffer); 283 | glEnableVertexAttribArray(program.mVertexPosLoc); 284 | glDrawArrays(GL_TRIANGLE_STRIP, 0, mVertexesSize); 285 | 286 | glDisable(GL_BLEND); 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/ShadowWidth.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | /** 19 | * Shadow width 20 | * 21 | * @author eschao 22 | */ 23 | 24 | final class ShadowWidth { 25 | 26 | // minimal shadow width 27 | float mMin; 28 | 29 | // maximal shadow width 30 | float mMax; 31 | 32 | // the shadow width ratio based on fold cylinder radius 33 | // shadow width will be dynamically computed upon fold cylinder radius 34 | float mRatio; 35 | 36 | public ShadowWidth(float min, float max, float ratio) { 37 | set(min, max, ratio); 38 | } 39 | 40 | /** 41 | * Set minimal, maximal and ratio value of shadow width 42 | * 43 | * @param min minimal value 44 | * @param max maximal value 45 | * @param ratio width ratio based on fold cylinder radius 46 | */ 47 | public void set(float min, float max, float ratio) { 48 | if (min < 0 || max < 0 || min > max || 49 | ratio <= 0 || ratio > 1) { 50 | throw new IllegalArgumentException("One of Min(" + min + ") Max(" + 51 | max + ") Ration(" + ratio + ")" + 52 | "is invalid!"); 53 | } 54 | 55 | mMin = min; 56 | mMax = max; 57 | mRatio = ratio; 58 | } 59 | 60 | /** 61 | * Compute shadow width upon fold cylinder radius 62 | *

if width is out of (min, max), one of them will be returned

63 | * 64 | * @param r fold cylinder radius 65 | * @return shadow width 66 | */ 67 | public float width(float r) { 68 | float w = r * mRatio; 69 | if (w < mMin) { 70 | return mMin; 71 | } 72 | else if (w > mMax) { 73 | return mMax; 74 | } 75 | else { 76 | return w; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/VertexProgram.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.content.Context; 19 | import android.opengl.Matrix; 20 | 21 | import static android.opengl.GLES20.glGetAttribLocation; 22 | import static android.opengl.GLES20.glGetUniformLocation; 23 | 24 | /** 25 | * Vertex shader program which is used to load: 26 | *
    27 | *
  • vertex_shader.glsl
  • 28 | *
  • fragment_shader.glsl
  • 29 | *
30 | * 31 | * @author eschao 32 | */ 33 | class VertexProgram extends GLProgram { 34 | 35 | // variable names defined in GLSL scripts 36 | final static String VAR_MVP_MATRIX = "u_MVPMatrix"; 37 | final static String VAR_VERTEX_POS = "a_vexPosition"; 38 | final static String VAR_TEXTURE_COORD = "a_texCoord"; 39 | final static String VAR_TEXTURE = "u_texture"; 40 | 41 | // universal model-view matrix 42 | final static float[] MVMatrix = new float[16]; 43 | // universal model-view-project matrix 44 | final static float[] MVPMatrix = new float[16]; 45 | 46 | // variable handles after compiled & linked shader scripts 47 | int mMVPMatrixLoc; 48 | int mVertexPosLoc; 49 | int mTexCoordLoc; 50 | int mTextureLoc; 51 | 52 | public VertexProgram() { 53 | super(); 54 | 55 | // init with invalid value 56 | mTextureLoc = INVALID_GL_HANDLE; 57 | mMVPMatrixLoc = INVALID_GL_HANDLE; 58 | mTexCoordLoc = INVALID_GL_HANDLE; 59 | mVertexPosLoc = INVALID_GL_HANDLE; 60 | } 61 | 62 | /** 63 | * Initiate vertex shader program 64 | * 65 | * @param context Android app context 66 | * @return self object 67 | * @throws PageFlipException raise exception if fail to initiate program 68 | */ 69 | public VertexProgram init(Context context) throws PageFlipException { 70 | super.init(context, R.raw.vertex_shader, R.raw.fragment_shader); 71 | return this; 72 | } 73 | 74 | /** 75 | * Get variable handles after linked shader program 76 | */ 77 | protected void getVarsLocation() { 78 | if (mProgramRef != 0) { 79 | mVertexPosLoc = glGetAttribLocation(mProgramRef, VAR_VERTEX_POS); 80 | mTexCoordLoc = glGetAttribLocation(mProgramRef, VAR_TEXTURE_COORD); 81 | mMVPMatrixLoc = glGetUniformLocation(mProgramRef, VAR_MVP_MATRIX); 82 | mTextureLoc = glGetUniformLocation(mProgramRef, VAR_TEXTURE); 83 | } 84 | } 85 | 86 | /** 87 | * Delete shader and program handlers 88 | */ 89 | public void delete() { 90 | super.delete(); 91 | 92 | mTextureLoc = INVALID_GL_HANDLE; 93 | mMVPMatrixLoc = INVALID_GL_HANDLE; 94 | mTexCoordLoc = INVALID_GL_HANDLE; 95 | mVertexPosLoc = INVALID_GL_HANDLE; 96 | } 97 | 98 | /** 99 | * Initiate matrix with view size 100 | * 101 | * @param left view left 102 | * @param right view right 103 | * @param bottom view bottom 104 | * @param top view top 105 | */ 106 | public void initMatrix(float left, float right, float bottom, float top) { 107 | float[] projectMatrix = new float[16]; 108 | Matrix.orthoM(projectMatrix, 0, left, right, bottom, top, 0, 6000); 109 | Matrix.setIdentityM(MVMatrix, 0); 110 | Matrix.setLookAtM(MVMatrix, 0, 0, 0, 3000, 0, 0, 0, 0, 1, 0); 111 | Matrix.setIdentityM(MVPMatrix, 0); 112 | Matrix.multiplyMM(MVPMatrix, 0, projectMatrix, 0, MVMatrix, 0); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /PageFlip/src/main/java/com/eschao/android/widget/pageflip/Vertexes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.pageflip; 17 | 18 | import android.util.Log; 19 | 20 | import java.nio.ByteBuffer; 21 | import java.nio.ByteOrder; 22 | import java.nio.FloatBuffer; 23 | 24 | import static android.opengl.GLES20.GL_FLOAT; 25 | import static android.opengl.GLES20.glDrawArrays; 26 | import static android.opengl.GLES20.glEnableVertexAttribArray; 27 | import static android.opengl.GLES20.glVertexAttribPointer; 28 | 29 | /** 30 | * Vertexes is used to manage vertex and texture data for openGL drawing 31 | * 32 | * @author eschao 33 | */ 34 | 35 | class Vertexes { 36 | 37 | private static final String TAG = "Vertexes"; 38 | 39 | // how many vertexes in vertex float buffer will be drawn on screen 40 | int mVertexesSize; 41 | 42 | // how many float data is used for every vertex 43 | int mSizeOfPerVex; 44 | 45 | // vertex data array 46 | float[] mVertexes; 47 | 48 | // texture coordinates array 49 | float[] mTextureCoords; 50 | 51 | // float buffer for vertexes data and texture coordinates data 52 | FloatBuffer mVertexesBuf; 53 | FloatBuffer mTextureCoordsBuf; 54 | 55 | // next index when add vertex to float array 56 | int mNext; 57 | 58 | 59 | /** 60 | * Default constructor 61 | */ 62 | public Vertexes() { 63 | mNext = 0; 64 | mVertexesSize = 0; 65 | mSizeOfPerVex = 0; 66 | mVertexes = null; 67 | mVertexesBuf = null; 68 | mTextureCoords = null; 69 | mTextureCoordsBuf = null; 70 | } 71 | 72 | /** 73 | * Constructor with given vertex amount 74 | * 75 | * @param capacity vertex max amount 76 | * @param sizeOfPerVex how many float data is used for a vertex 77 | */ 78 | public Vertexes(int capacity, int sizeOfPerVex) { 79 | set(capacity, sizeOfPerVex, true); 80 | } 81 | 82 | /** 83 | * Constructor with given vertex max amount and texture 84 | * 85 | * @param capacity vertex amount 86 | * @param sizeOfPerVex how many float data is used for a vertex 87 | * @param hasTexture if need texture buffer for texture coordinates 88 | */ 89 | public Vertexes(int capacity, int sizeOfPerVex, boolean hasTexture) { 90 | set(capacity, sizeOfPerVex, hasTexture); 91 | } 92 | 93 | /** 94 | * Set max vertex amount and create buffer for vertex and texture 95 | * 96 | * @param capacity vertex amount 97 | * @param sizeOfPerVex how many float data is used for a vertex 98 | * @param hasTexture True if need texture buffer for texture coordinates 99 | * @return self 100 | */ 101 | public Vertexes set(int capacity, int sizeOfPerVex, boolean hasTexture) { 102 | if (sizeOfPerVex < 2) { 103 | Log.w(TAG, "sizeOfPerVex is invalid: " + sizeOfPerVex); 104 | throw new IllegalArgumentException("sizeOfPerVex:" + sizeOfPerVex + 105 | "is less than 2!"); 106 | } 107 | 108 | // reset all 109 | mNext = 0; 110 | mVertexes = null; 111 | mVertexesBuf = null; 112 | mTextureCoords = null; 113 | mTextureCoordsBuf = null; 114 | 115 | // create vertexes buffer 116 | mSizeOfPerVex = sizeOfPerVex; 117 | mVertexes = new float[capacity * sizeOfPerVex]; 118 | mVertexesBuf = ByteBuffer.allocateDirect(capacity * sizeOfPerVex * 4) 119 | .order(ByteOrder.nativeOrder()) 120 | .asFloatBuffer(); 121 | 122 | // if need, create texture buffer 123 | if (hasTexture) { 124 | mTextureCoords = new float[capacity << 1]; 125 | mTextureCoordsBuf = ByteBuffer.allocateDirect(capacity << 3) 126 | .order(ByteOrder.nativeOrder()) 127 | .asFloatBuffer(); 128 | } 129 | 130 | return this; 131 | } 132 | 133 | /** 134 | * Release all resources 135 | * 136 | * @return self 137 | */ 138 | public Vertexes release() { 139 | mNext = 0; 140 | mVertexesSize = 0; 141 | mSizeOfPerVex = 0; 142 | mVertexes = null; 143 | mVertexesBuf = null; 144 | mTextureCoords = null; 145 | mTextureCoordsBuf = null; 146 | return this; 147 | } 148 | 149 | /** 150 | * Get max vertex amount 151 | * 152 | * @return max vertex amount 153 | */ 154 | public int capacity() { 155 | return mVertexes == null ? 0 : mVertexes.length / mSizeOfPerVex; 156 | } 157 | 158 | /** 159 | * Reset index of float array before adding vertex to buffer 160 | */ 161 | public void reset() { 162 | mNext = 0; 163 | } 164 | 165 | 166 | /** 167 | * Get float data with given index 168 | * 169 | * @param index float data position index 170 | * @return float data 171 | */ 172 | public float getFloatAt(int index) { 173 | if (index >= 0 && index < mNext) { 174 | return mVertexes[index]; 175 | } 176 | 177 | return 0; 178 | } 179 | 180 | /** 181 | * Set vertex coordinate(x, y, z) in given buffer position 182 | * 183 | * @param i where to start saving vertex data 184 | * @param x x value of vertex coordinate 185 | * @param y y value of vertex coordinate 186 | * @param z z value of vertex coordinate 187 | * @return self 188 | */ 189 | public Vertexes setVertex(int i, float x, float y, float z) { 190 | assert(i+2 < mVertexes.length); 191 | 192 | mVertexes[i] = x; 193 | mVertexes[i + 1] = y; 194 | mVertexes[i + 2] = z; 195 | return this; 196 | } 197 | 198 | /** 199 | * Set vertex coordinate(x, y, z, width) in given buffer position 200 | * 201 | * @param i where to start saving vertex data 202 | * @param x x value of vertex coordinate 203 | * @param y y value of vertex coordinate 204 | * @param z z value of vertex coordinate 205 | * @param w width value which is normally used to pass other value to shader 206 | * @return self 207 | */ 208 | public Vertexes setVertex(int i, float x, float y, float z, float w) { 209 | assert(i+3 < mVertexes.length); 210 | 211 | mVertexes[i] = x; 212 | mVertexes[i + 1] = y; 213 | mVertexes[i + 2] = z; 214 | mVertexes[i + 3] = w; 215 | return this; 216 | } 217 | 218 | /** 219 | * Set texture coordinate(x, y) in given buffer position 220 | * 221 | * @param i where to start saving texture coordinate 222 | * @param x x value of texture coordinate 223 | * @param y y value of texture coordinate 224 | * @return self 225 | */ 226 | public Vertexes setTextureCoord(int i, float x, float y) { 227 | assert(i+1 < mTextureCoords.length); 228 | 229 | mTextureCoords[i] = x; 230 | mTextureCoords[i + 1] = y; 231 | return this; 232 | } 233 | 234 | /** 235 | * Add vertex coordinate to buffer 236 | * 237 | * @param x x value of vertex coordinate 238 | * @param y y value of vertex coordinate 239 | * @param z z value of vertex coordinate 240 | * @return self 241 | */ 242 | public Vertexes addVertex(float x, float y, float z) { 243 | mVertexes[mNext++] = x; 244 | mVertexes[mNext++] = y; 245 | mVertexes[mNext++] = z; 246 | return this; 247 | } 248 | 249 | /** 250 | * Add vertex and texture coordinates 251 | * 252 | * @param x x value of vertex coordinate 253 | * @param y y value of vertex coordinate 254 | * @param z z value of vertex coordinate 255 | * @param coordX x value of texture coordinate 256 | * @param coordY y value of texture coordinate 257 | * @return self 258 | */ 259 | public Vertexes addVertex(float x, float y, float z, 260 | float coordX, float coordY) { 261 | int j = mNext / mSizeOfPerVex * 2; 262 | mVertexes[mNext++] = x; 263 | mVertexes[mNext++] = y; 264 | mVertexes[mNext++] = z; 265 | 266 | mTextureCoords[j++] = coordX; 267 | mTextureCoords[j] = coordY; 268 | return this; 269 | } 270 | 271 | /** 272 | * Add vertex coordinate to buffer 273 | * 274 | * @param x x value of vertex coordinate 275 | * @param y y value of vertex coordinate 276 | * @param z z value of vertex coordinate 277 | * @param w width value which is normally used to pass other value to shader 278 | * @return self 279 | */ 280 | public Vertexes addVertex(float x, float y, float z, float w) { 281 | mVertexes[mNext++] = x; 282 | mVertexes[mNext++] = y; 283 | mVertexes[mNext++] = z; 284 | mVertexes[mNext++] = w; 285 | return this; 286 | } 287 | 288 | /** 289 | * Add vertex and texture coordinates 290 | * 291 | * @param x x value of vertex coordinate 292 | * @param y y value of vertex coordinate 293 | * @param z z value of vertex coordinate 294 | * @param w width value which is normally used to pass other value to shader 295 | * @param coordX x value of texture coordinate 296 | * @param coordY y value of texture coordinate 297 | * @return self 298 | */ 299 | public Vertexes addVertex(float x, float y, float z, float w, 300 | float coordX, float coordY) { 301 | int j = mNext / mSizeOfPerVex * 2; 302 | mVertexes[mNext++] = x; 303 | mVertexes[mNext++] = y; 304 | mVertexes[mNext++] = z; 305 | mVertexes[mNext++] = w; 306 | 307 | mTextureCoords[j++] = coordX; 308 | mTextureCoords[j] = coordY; 309 | return this; 310 | } 311 | 312 | /** 313 | * Add GLPoint to float buffer 314 | * 315 | * @param point GLPoint object 316 | * @return self 317 | */ 318 | public Vertexes addVertex(GLPoint point) { 319 | int j = mNext / mSizeOfPerVex * 2; 320 | mVertexes[mNext++] = point.x; 321 | mVertexes[mNext++] = point.y; 322 | mVertexes[mNext++] = point.z; 323 | 324 | mTextureCoords[j++] = point.texX; 325 | mTextureCoords[j] = point.texY; 326 | return this; 327 | } 328 | 329 | /** 330 | * Put data from float array to float buffer 331 | * 332 | * @param offset data start offset in float array 333 | * @param length data length to be put 334 | */ 335 | public void toFloatBuffer(int offset, int length) { 336 | mVertexesBuf.put(mVertexes, offset, length).position(0); 337 | mVertexesSize = length / mSizeOfPerVex; 338 | 339 | // has texture? put again 340 | if (mTextureCoords != null) { 341 | final int o = offset / mSizeOfPerVex * 2; 342 | final int l = mVertexesSize * 2; 343 | mTextureCoordsBuf.put(mTextureCoords, o, l).position(0); 344 | } 345 | } 346 | 347 | /** 348 | * Put all data from float array to float buffer 349 | *

350 | * The offset is 0 and the length is determined by mNext which is increased 351 | * after calling {@link #addVertex} 352 | *

353 | */ 354 | public void toFloatBuffer() { 355 | mVertexesBuf.put(mVertexes, 0, mNext).position(0); 356 | mVertexesSize = mNext / mSizeOfPerVex; 357 | 358 | if (mTextureCoords != null) { 359 | mTextureCoordsBuf.put(mTextureCoords, 0, mVertexesSize << 1) 360 | .position(0); 361 | } 362 | } 363 | 364 | /** 365 | * Draw vertexes 366 | * 367 | * @param type openGL drawing type: TRIANGLE, STRIP, FAN 368 | * @param hVertexPos vertex position var in shader program 369 | * @param hTextureCoord texture var in shader program 370 | */ 371 | public void drawWith(int type, int hVertexPos, int hTextureCoord) { 372 | // pass vertex data 373 | glVertexAttribPointer(hVertexPos, mSizeOfPerVex, GL_FLOAT, false, 0, 374 | mVertexesBuf); 375 | glEnableVertexAttribArray(hVertexPos); 376 | 377 | // pass texture data 378 | glVertexAttribPointer(hTextureCoord, 2, GL_FLOAT, false, 0, 379 | mTextureCoordsBuf); 380 | glEnableVertexAttribArray(hTextureCoord); 381 | 382 | // draw triangles 383 | glDrawArrays(type, 0, mVertexesSize); 384 | } 385 | 386 | /** 387 | * Draw vertexes with given offset and length 388 | * 389 | * @param type openGL drawing type: TRIANGLE, STRIP, FAN 390 | * @param hVertexPos vertex var in shader program 391 | * @param hTextureCoord texture var in shader program 392 | * @param offset vertex start offset in buffer 393 | * @param length vertex length to be drawn 394 | */ 395 | public void drawWith(int type, int hVertexPos, int hTextureCoord, 396 | int offset, int length) { 397 | glVertexAttribPointer(hVertexPos, mSizeOfPerVex, GL_FLOAT, false, 0, 398 | mVertexesBuf); 399 | glEnableVertexAttribArray(hVertexPos); 400 | 401 | glVertexAttribPointer(hTextureCoord, 2, GL_FLOAT, false, 0, 402 | mTextureCoordsBuf); 403 | glEnableVertexAttribArray(hTextureCoord); 404 | 405 | glDrawArrays(type, offset, length); 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /PageFlip/src/main/res/raw/fold_back_fragment_shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform sampler2D u_texture; 3 | uniform sampler2D u_shadow; 4 | uniform vec4 u_maskColor; 5 | varying vec2 v_texCoord; 6 | varying float v_shadowX; 7 | 8 | void main() { 9 | vec4 texture = texture2D(u_texture, v_texCoord); 10 | vec2 shadowCoord = vec2(v_shadowX, 0); 11 | vec4 shadow = texture2D(u_shadow, shadowCoord); 12 | vec4 maskedTexture = vec4(mix(texture.rgb, u_maskColor.rgb, u_maskColor.a), 1.0); 13 | gl_FragColor = vec4(maskedTexture.rgb * (1.0 - shadow.a) + shadow.rgb, maskedTexture.a); 14 | } 15 | -------------------------------------------------------------------------------- /PageFlip/src/main/res/raw/fold_back_vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform mat4 u_MVPMatrix; 3 | uniform float u_texXOffset; 4 | attribute vec4 a_vexPosition; 5 | attribute vec2 a_texCoord; 6 | varying vec2 v_texCoord; 7 | varying float v_shadowX; 8 | 9 | void main() { 10 | v_texCoord = vec2(abs(a_texCoord.x - u_texXOffset), a_texCoord.y); 11 | v_shadowX = clamp(abs(a_vexPosition.w), 0.01, 1.0); 12 | vec4 vertex = vec4(a_vexPosition.xyz, 1.0); 13 | gl_Position = u_MVPMatrix * vertex; 14 | } -------------------------------------------------------------------------------- /PageFlip/src/main/res/raw/fragment_shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform sampler2D u_texture; 3 | varying vec2 v_texCoord; 4 | 5 | void main() { 6 | gl_FragColor = texture2D(u_texture, v_texCoord); 7 | } 8 | -------------------------------------------------------------------------------- /PageFlip/src/main/res/raw/shadow_fragment_shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | varying vec4 v_texColor; 3 | 4 | void main() 5 | { 6 | gl_FragColor = v_texColor; 7 | } -------------------------------------------------------------------------------- /PageFlip/src/main/res/raw/shadow_vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform mat4 u_MVPMatrix; 3 | uniform float u_vexZ; 4 | attribute vec4 a_vexPosition; 5 | varying vec4 v_texColor; 6 | 7 | void main() { 8 | vec4 vexPos = vec4(a_vexPosition.xy, u_vexZ, 1.0); 9 | v_texColor = vec4(a_vexPosition.z, a_vexPosition.z, a_vexPosition.z, a_vexPosition.w); 10 | gl_Position = u_MVPMatrix * vexPos; 11 | } -------------------------------------------------------------------------------- /PageFlip/src/main/res/raw/vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform mat4 u_MVPMatrix; 3 | attribute vec4 a_vexPosition; 4 | attribute vec2 a_texCoord; 5 | varying vec2 v_texCoord; 6 | 7 | void main() { 8 | gl_Position = u_MVPMatrix * a_vexPosition; 9 | v_texCoord = a_texCoord; 10 | } -------------------------------------------------------------------------------- /PageFlip/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PageFlip 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-3D%20Style%20PageFlip-brightgreen.svg?style=flat)]() 2 | 3 | # PageFlip 4 | This project is aimed to implement 3D style page flip on Android system based on OpenGL 2.0. 5 | 6 | For **JNI** version, please visit: [**android-PageFlip-JNI**](https://github.com/eschao/android-PageFlip-JNI) 7 | 8 | ## Table of Contents 9 | 10 | * [Preview](#preview) 11 | * [Installation](#installation) 12 | - [Gradle](#gradle) 13 | * [Android Version Support](#android-version-support) 14 | * [Usage](#usage) 15 | - [Introduce PageFlip Into Your Project](#i-simple-steps-for-introducing-pageflip-into-your-project) 16 | - [Configure PageFilp](#ii-configure-pageflip) 17 | + [Page Mode](#1-page-mode) 18 | + [Click Screen To Flip](#2-click-screen-to-flip) 19 | + [Area Of Clicking To Flip](#3-area-of-clicking-to-flip) 20 | + [PageFlip Listener](#4-pageflip-listener) 21 | + [Mesh Pixels](#5-mesh-pixels) 22 | + [Ratio Of Semi-peremeter](#6-ratio-of-semi-peremeter) 23 | + [Mask Alpha For The Back Of Fold Page](#7-mask-alpha-for-the-back-of-fold-page) 24 | + [Edge Shadow Color/Alpha Of Fold Page](#8-edge-shadow-coloralpha-of-fold-page) 25 | + [Base Shadow Color/Alpha Of Fold Page](#9-base-shadow-coloralpha-of-fold-page) 26 | + [Edge Shadow Width Of Fold Page](#10-edge-shadow-width-of-fold-page) 27 | + [Base Shadow Width Of Fold Page](#11-base-shadow-width-of-fold-page) 28 | + [Duration Of Flip Animating](#12-duration-of-flip-animating) 29 | 30 | * [License](#license) 31 | 32 | ## Preview 33 | 34 | ![SinglePage](https://cloud.githubusercontent.com/assets/20178358/20646678/df7c6ba4-b4ba-11e6-8753-6f764f825cc2.png) ![DoublePages](https://cloud.githubusercontent.com/assets/20178358/20646731/20f6ebc6-b4bc-11e6-9857-efd8367db80c.png) 35 | 36 | ## Installation 37 | 38 | #### Gradle 39 | 40 | Add it to your build.gradle with: 41 | ```gradle 42 | allprojects { 43 | repositories { 44 | maven { url "https://jitpack.io" } 45 | } 46 | } 47 | ``` 48 | and: 49 | 50 | ```gradle 51 | dependencies { 52 | compile 'com.github.eschao:android-PageFlip:1.0.2' 53 | } 54 | ``` 55 | ## Android Version Support 56 | 57 | The following versions have been tested on emulator: 58 | 59 | Android version | API version | Support | 60 | ----------------|-------------|---------| 61 | 3.2 | API 13 | x | 62 | 4.1 | API 16 | √ | 63 | 4.2 | API 17 | √ | 64 | 4.3 | API 18 | √ | 65 | 4.4 | API 19 | √ | 66 | 5.0 | API 21 | √ | 67 | 5.1 | API 22 | √ | 68 | 6.0 | API 23 | √ | 69 | 7.0 | API 24 | √ | 70 | 7.1.1 | API 25 | √ | 71 | 7.+ | API 26 | √ | 72 | 73 | ## Usage 74 | 75 | ### I. Simple steps for introducing PageFlip into your project 76 | 77 | * Creates a surface view class extending from **GLSurfaceView** 78 | * Implements android **Renderer** interface to draw your content on a bitmap and set it as a texture of **PageFlip** 79 | * Instanitiates a **PageFlip** object in the constructor of your surface view 80 | * Configures **PageFlip**, For example: set animating duration, page mode or mesh pixels 81 | * Handles the below android events: 82 | 83 | * **onFingerDown**: notify *PageFlip* object to prepare flip 84 | * **onFingerMove**: notify *PageFlip* object to compute data for drawing flip frame 85 | * **onFingerUp**: notify *PageFlip* object to determine whether or not launching a flip animation 86 | * **onSurfaceCreated**: notify *PageFlip* object to handle usreface creating event 87 | * **onSurfaceChanged**: notify *PageFlip* object to handle surface changing event 88 | 89 | * You may need a message handler to send/receive an drawing message. Please refer to **PageFlipView** in sample application. 90 | * You may need a lock to avoid conflicts between main thread and OpenGL rendering thread. Please refer to **PageFlipView** in sample application. 91 | 92 | More details, please take a look **PageFlipView** in sample application. 93 | 94 | ### II. Configure PageFlip 95 | 96 | **PageFlip** library provides some configurations for customizing its behaviors. For example: shadow color and alpha, mesh pixels and page mode. 97 | 98 | #### 1. Page Mode 99 | 100 | There are two page modes provided by **PageFlip**: 101 | 102 | * **Auto Page Mode**: In this mode, **PageFlip** will automatically decide to use single page or double pages to present content on screen. That means single page is used for portrait mode and double pages is used for lanscape mode. 103 | * **Single Page Mode**: No matter screen is portait or landscape mode, **PageFlip** always use single page to show content 104 | 105 | 106 | You can use **enableAutoPage** to enable auto page mode or disable it(equally enable single page mode). 107 | 108 | Example: 109 | ```java 110 | // enable auto page mode 111 | mPageFlip.enableAutopage(true); 112 | ``` 113 | 114 | #### 2. Click screen to flip 115 | 116 | You can enable/disable clicking screen to flip 117 | 118 | Example: 119 | ```java 120 | // enable clicking to flip 121 | mPageFlip.enableClickToFlip(true); 122 | ``` 123 | 124 | #### 3. Area of clicking to flip 125 | 126 | You can give a ratio of page width from 0 to 0.5f to set an area for reponsing click event to trigger a page flip. The default value is **0.5f**, which means the backfward flip will happen if you click the left half of screen and forward flip will start if you click the right half of screen in single page mode. 127 | 128 | Example: 129 | ```java 130 | // set ratio with 0.3 131 | mPageFlip.setWidthRatioOfClickToFlip(0.3f); 132 | ``` 133 | 134 | #### 4. PageFlip listener 135 | 136 | You can set a listener to tell **PageFlip** if the forward flip or backward flip could happen. 137 | 138 | Example: 139 | ```java 140 | mPageFlip.setListener(mListener); 141 | ``` 142 | 143 | #### 5. Mesh pixels 144 | 145 | Set how many pixels are used for a mesh. The less pxiels the mesh uses, the more fine the drawing is and the lower the performance is. The default value is 10 pixels. 146 | 147 | Example: 148 | ```java 149 | mPageFlip.setPixelsOfMesh(5); 150 | ``` 151 | 152 | #### 6. Ratio of semi-peremeter 153 | 154 | When page is curled, it is actually tackled as a semi-cylinder by **PageFlip**. You can set size of the semi-cylinder to change the flip shap. Since the semi-cylinder dependeds on the line length from the touch point to original point(see the below illustration), you need to provide a ratio of this line length to tell **PageFlip** the peremeter of the semi-cylinder. The default value is 0.8f. 155 | 156 | ``` 157 | +----------------+ 158 | | touchP | 159 | | . | 160 | | \ | 161 | | + p0 | 162 | | \ | 163 | | \ | 164 | | p1 + | 165 | | \ | 166 | +----------------+ 167 | original point, that means you drag the page from here to touch point(touchP) 168 | 169 | The length from p0 to p1 is peremeter of semi-cylinder and determined by ratio your giving 170 | ``` 171 | 172 | Example: 173 | ```java 174 | mPageFlip.setSemiPerimeterRatio(0.6f); 175 | ``` 176 | 177 | #### 7. Mask alpha for the back of fold page 178 | 179 | You can set the mask alpha for the back of fold page when page is curled in single page mode. The default value is 0.6f. 180 | 181 | Example: 182 | ```java 183 | mPageFlip.setMaskAlphaOfFold(0.5f); 184 | ``` 185 | 186 | #### 8. Edge shadow color/alpha of fold page 187 | 188 | You can set start/end color and start/end alpha for edge shadow of fold page. 189 | 190 | Example: 191 | ```java 192 | // set start color with 0.1f, start alpha with 0.2f, end color with 0.5f 193 | // and end alpha with 1f 194 | mPageFlip.setShadowColorOfFoldBase(0.1f, 0.2f, 0.5f, 1f); 195 | ``` 196 | 197 | #### 9. Base shadow color/alpha of fold page 198 | 199 | You can set start/end color and start/end alpha for base shadow of fold page. 200 | 201 | Example: 202 | ```java 203 | mPageFlip.setShadowColorOfFoldBase(0.05f, 0.2f, 0.5f, 1f); 204 | ``` 205 | 206 | #### 10. Edge shadow width of fold page 207 | 208 | When page is curled, the size of fold page will follow the finger movement to be changed and its edge shadow width should be changed accordingly. You can set an appropriate width range for shadow width. 209 | 210 | Example: 211 | ```java 212 | // set the minimal width is 5 pixels and maximum width is 40 pixels. 213 | // set the ratio is 0.3f which means the width will be firstly computed by formula: 214 | // width = diameter of semi-cylinder * 0.3f, and then compare it with minimal 215 | // and maximal value to make sure the width is in range. 216 | mPageFlip.setShadowWidthOfFoldEdges(5, 40, 0.3f); 217 | ``` 218 | 219 | #### 11. Base shadow width of fold page 220 | 221 | Like **[Edge shadow width of fold page](10-edge-shadow-width-of-fold-page)**, You can set an appropriate width range for base shadow of fold page. 222 | 223 | Example: 224 | ```java 225 | // see {@link #setShadowWidthOfFoldEdges} function 226 | mPageFlip.setShadowWidthOfFoldBase(5, 40, 0.4f); 227 | ``` 228 | 229 | #### 12. Duration of flip animating 230 | 231 | You can give a duration for flip animating when you call **onFingerUp** function to handle the finger up event. 232 | 233 | Example: 234 | ```java 235 | // the last parameter is duration with millisecond unit, here we set it with 2 seconds. 236 | mPageFlip.onFingerUp(x, y, 2000); 237 | ``` 238 | 239 | ## License 240 | This project is licensed under the Apache License Version 2.0. 241 | -------------------------------------------------------------------------------- /Sample/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /Sample/src/main/java/com/eschao/android/widget/sample/pageflip/Constants.java: -------------------------------------------------------------------------------- 1 | package com.eschao.android.widget.sample.pageflip; 2 | 3 | /** 4 | * Created by chao on 20/11/2016. 5 | */ 6 | 7 | public class Constants { 8 | public final static String PREF_MESH_PIXELS = "MeshPixels"; 9 | public final static String PREF_DURATION = "Duration"; 10 | public final static String PREF_PAGE_MODE = "PageMode"; 11 | } 12 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/eschao/android/widget/sample/pageflip/DoublePagesRender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.sample.pageflip; 17 | 18 | import android.content.Context; 19 | import android.graphics.Bitmap; 20 | import android.graphics.Color; 21 | import android.graphics.Paint; 22 | import android.graphics.Rect; 23 | import android.os.Handler; 24 | import android.os.Message; 25 | import android.widget.Toast; 26 | 27 | import com.eschao.android.widget.pageflip.Page; 28 | import com.eschao.android.widget.pageflip.PageFlip; 29 | import com.eschao.android.widget.pageflip.PageFlipState; 30 | 31 | /** 32 | * Double pages render 33 | *

34 | * Some key points here: 35 | *

    36 | *
  • First page is which page user is clicking on or moving by finger 37 | * Sometimes it is left page on screen, sometimes it is right page. 38 | * Second page is leftover page against the first page 39 | *
  • 40 | *
  • mPageNo is always the number of left page instead of first page
  • 41 | *
42 | *

43 | *

44 | * Every screen 'Page' contains 3 page contents, so it need 3 textures: 45 | *

    46 | *
  • First texture: first page content of this 'Page'
  • 47 | *
  • Back texture: the second page content of this 'Page'
  • 48 | *
  • Second texture: the third page content of this 'Page'
  • 49 | *
50 | *

51 | * 52 | * @author eschao 53 | */ 54 | 55 | public class DoublePagesRender extends PageRender { 56 | 57 | /** 58 | * Constructor 59 | * @see {@link #PageRender(Context, PageFlip, Handler, int)} 60 | */ 61 | public DoublePagesRender(Context context, PageFlip pageFlip, 62 | Handler handler, int pageNo) { 63 | super(context, pageFlip, handler, pageNo); 64 | } 65 | 66 | /** 67 | * Draw page frame 68 | */ 69 | public void onDrawFrame() { 70 | // 1. delete unused textures to save memory 71 | mPageFlip.deleteUnusedTextures(); 72 | 73 | // 2. there are two pages for representing the whole screen, we need to 74 | // draw them one by one 75 | final Page first = mPageFlip.getFirstPage(); 76 | final Page second = mPageFlip.getSecondPage(); 77 | 78 | // 3. check if the first texture is valid for first page, if not, 79 | // create it with relative content 80 | if (!first.isFirstTextureSet()) { 81 | drawPage(first.isLeftPage() ? mPageNo : mPageNo + 1); 82 | first.setFirstTexture(mBitmap); 83 | } 84 | 85 | // 4. check if the first texture is valid for second page 86 | if (!second.isFirstTextureSet()) { 87 | drawPage(second.isLeftPage() ? mPageNo : mPageNo + 1); 88 | second.setFirstTexture(mBitmap); 89 | } 90 | 91 | // 5. handle drawing command triggered from finger moving and animating 92 | if (mDrawCommand == DRAW_MOVING_FRAME || 93 | mDrawCommand == DRAW_ANIMATING_FRAME) { 94 | // before drawing, check if back texture of first page is valid 95 | // Remember: the first page is always the fold page 96 | if (!first.isBackTextureSet()) { 97 | drawPage(first.isLeftPage() ? mPageNo - 1 : mPageNo + 2); 98 | first.setBackTexture(mBitmap); 99 | } 100 | 101 | // check the second texture of first page is valid. 102 | if (!first.isSecondTextureSet()) { 103 | drawPage(first.isLeftPage() ? mPageNo - 2 : mPageNo + 3); 104 | first.setSecondTexture(mBitmap); 105 | } 106 | 107 | // draw frame for page flip 108 | mPageFlip.drawFlipFrame(); 109 | } 110 | // draw stationary page without flipping 111 | else if (mDrawCommand == DRAW_FULL_PAGE){ 112 | mPageFlip.drawPageFrame(); 113 | } 114 | 115 | // 6. send message to main thread to notify drawing is ended so that 116 | // we can continue to calculate next animation frame if need. 117 | // Remember: the drawing operation is always in GL thread instead of 118 | // main thread 119 | Message msg = Message.obtain(); 120 | msg.what = MSG_ENDED_DRAWING_FRAME; 121 | msg.arg1 = mDrawCommand; 122 | mHandler.sendMessage(msg); 123 | } 124 | 125 | /** 126 | * Handle GL surface is changed 127 | * 128 | * @param width surface width 129 | * @param height surface height 130 | */ 131 | public void onSurfaceChanged(int width, int height) { 132 | // recycle bitmap resources if need 133 | if (mBackgroundBitmap != null) { 134 | mBackgroundBitmap.recycle(); 135 | } 136 | 137 | if (mBitmap != null) { 138 | mBitmap.recycle(); 139 | } 140 | 141 | // create bitmap and canvas for page 142 | //mBackgroundBitmap = background; 143 | Page page = mPageFlip.getFirstPage(); 144 | int pageW = (int)page.width(); 145 | int pageH = (int)page.height(); 146 | mBitmap = Bitmap.createBitmap(pageW, pageH, Bitmap.Config.ARGB_8888); 147 | mCanvas.setBitmap(mBitmap); 148 | LoadBitmapTask.get(mContext).set(pageW, pageH, 2); 149 | } 150 | 151 | /** 152 | * Handle ended drawing event 153 | * In here, we only tackle the animation drawing event, If we need to 154 | * continue requesting render, please return true. Remember this function 155 | * will be called in main thread 156 | * 157 | * @param what event type 158 | * @return ture if need render again 159 | */ 160 | public boolean onEndedDrawing(int what) { 161 | if (what == DRAW_ANIMATING_FRAME) { 162 | boolean isAnimating = mPageFlip.animating(); 163 | // continue animating 164 | if (isAnimating) { 165 | mDrawCommand = DRAW_ANIMATING_FRAME; 166 | return true; 167 | } 168 | // animation is finished 169 | else { 170 | // should handle forward flip to update page number and exchange 171 | // textures between first and second pages. Don't have to handle 172 | // backward flip since there is no such state happened in double 173 | // page mode 174 | if (mPageFlip.getFlipState() == PageFlipState.END_WITH_FORWARD) 175 | { 176 | final Page first = mPageFlip.getFirstPage(); 177 | final Page second = mPageFlip.getSecondPage(); 178 | second.swapTexturesWithPage(first); 179 | 180 | // update page number for left page 181 | if (first.isLeftPage()) { 182 | mPageNo -= 2; 183 | } 184 | else { 185 | mPageNo += 2; 186 | } 187 | } 188 | 189 | mDrawCommand = DRAW_FULL_PAGE; 190 | return true; 191 | } 192 | } 193 | return false; 194 | } 195 | 196 | /** 197 | * Draw page content 198 | * 199 | * @param number page number 200 | */ 201 | private void drawPage(int number) { 202 | final int width = mCanvas.getWidth(); 203 | final int height = mCanvas.getHeight(); 204 | Paint p = new Paint(); 205 | p.setFilterBitmap(true); 206 | 207 | // 1. draw background bitmap 208 | Bitmap background = LoadBitmapTask.get(mContext).getBitmap(); 209 | Rect rect = new Rect(0, 0, width, height); 210 | // if (width > height) { 211 | // mCanvas.rotate(90); 212 | // mCanvas.drawBitmap(background, null, rect, p); 213 | // mCanvas.rotate(-90); 214 | // } 215 | // else { 216 | mCanvas.drawBitmap(background, null, rect, p); 217 | // } 218 | 219 | background.recycle(); 220 | background = null; 221 | 222 | // 2. draw page number 223 | int fontSize = (int)(80 * mContext.getResources().getDisplayMetrics() 224 | .scaledDensity); 225 | p.setColor(Color.WHITE); 226 | p.setStrokeWidth(1); 227 | p.setAntiAlias(true); 228 | p.setShadowLayer(5.0f, 8.0f, 8.0f, Color.BLACK); 229 | p.setTextSize(fontSize); 230 | 231 | String text = String.valueOf(number); 232 | if (number < 1) { 233 | text = "Preface"; 234 | } 235 | else if (number > MAX_PAGES) { 236 | text = "End"; 237 | } 238 | float textWidth = p.measureText(text); 239 | float y = height - p.getTextSize() - 20; 240 | mCanvas.drawText(text, (width - textWidth) / 2, y, p); 241 | 242 | if (number == 1) { 243 | String firstPage = "The First Page"; 244 | p.setTextSize(calcFontSize(16)); 245 | float w = p.measureText(firstPage); 246 | float h = p.getTextSize(); 247 | mCanvas.drawText(firstPage, (width - w) / 2, y + 5 + h, p); 248 | } 249 | else if (number == MAX_PAGES) { 250 | String lastPage = "The Last Page"; 251 | p.setTextSize(calcFontSize(16)); 252 | float w = p.measureText(lastPage); 253 | float h = p.getTextSize(); 254 | mCanvas.drawText(lastPage, (width - w) / 2, y + 5 + h, p); 255 | } 256 | } 257 | 258 | /** 259 | * If page can flip forward 260 | * 261 | * @return true if it can flip forward 262 | */ 263 | public boolean canFlipForward() { 264 | final Page page = mPageFlip.getFirstPage(); 265 | // current page is left page 266 | if (page.isLeftPage()) { 267 | return (mPageNo > 1); 268 | } 269 | 270 | // current page is right page 271 | return (mPageNo + 2 <= MAX_PAGES); 272 | } 273 | 274 | /** 275 | * Don't need to handle backward flip 276 | * 277 | * @return always false 278 | */ 279 | public boolean canFlipBackward() { 280 | return false; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/eschao/android/widget/sample/pageflip/LoadBitmapTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.sample.pageflip; 17 | 18 | import android.content.Context; 19 | import android.content.res.Resources; 20 | import android.graphics.Bitmap; 21 | import android.graphics.BitmapFactory; 22 | import android.graphics.Matrix; 23 | import android.util.Log; 24 | 25 | import java.util.LinkedList; 26 | import java.util.Random; 27 | 28 | /** 29 | * A singleton thread task to load bitmap 30 | *

Attempt to load bitmap in separate thread to get better performance.

31 | * 32 | * @author eschao 33 | */ 34 | 35 | public final class LoadBitmapTask implements Runnable { 36 | 37 | private final static String TAG = "LoadBitmapTask"; 38 | private static LoadBitmapTask __object; 39 | 40 | final static int SMALL_BG = 0; 41 | final static int MEDIUM_BG = 1; 42 | final static int LARGE_BG = 2; 43 | final static int BG_COUNT = 10; 44 | 45 | int mBGSizeIndex; 46 | int mQueueMaxSize; 47 | int mPreRandomNo; 48 | boolean mIsLandscape; 49 | boolean mStop; 50 | Random mBGRandom; 51 | Resources mResources; 52 | Thread mThread; 53 | LinkedList mQueue; 54 | int[][] mPortraitBGs; 55 | 56 | /** 57 | * Get an unique task object 58 | * 59 | * @param context Android context 60 | * @return unique task object 61 | */ 62 | public static LoadBitmapTask get(Context context) { 63 | if (__object == null) { 64 | __object = new LoadBitmapTask(context); 65 | } 66 | return __object; 67 | } 68 | 69 | /** 70 | * Constructor 71 | * 72 | * @param context Android context 73 | */ 74 | private LoadBitmapTask(Context context) { 75 | mResources = context.getResources(); 76 | mBGRandom = new Random(); 77 | mBGSizeIndex = SMALL_BG; 78 | mStop = false; 79 | mThread = null; 80 | mPreRandomNo = 0; 81 | mIsLandscape = false; 82 | mQueueMaxSize = 1; 83 | mQueue = new LinkedList(); 84 | 85 | // init all available bitmaps 86 | mPortraitBGs = new int[][] { 87 | new int[] {R.drawable.p1_480, R.drawable.p2_480, R.drawable.p3_480, 88 | R.drawable.p4_480, R.drawable.p5_480, R.drawable.p6_480, 89 | R.drawable.p7_480, R.drawable.p8_480, R.drawable.p9_480, 90 | R.drawable.p10_480}, 91 | new int[] {R.drawable.p1_720, R.drawable.p2_720, R.drawable.p3_720, 92 | R.drawable.p4_720, R.drawable.p5_720, R.drawable.p6_720, 93 | R.drawable.p7_720, R.drawable.p8_720, R.drawable.p9_720, 94 | R.drawable.p10_720}, 95 | new int[] {R.drawable.p1_1080, R.drawable.p2_1080, 96 | R.drawable.p3_1080, R.drawable.p4_1080, 97 | R.drawable.p5_1080, R.drawable.p6_1080, 98 | R.drawable.p7_1080, R.drawable.p8_1080, 99 | R.drawable.p9_1080, R.drawable.p10_1080} 100 | }; 101 | } 102 | 103 | /** 104 | * Acquire a bitmap to show 105 | *

If there is no cached bitmap, it will load one immediately

106 | * 107 | * @return bitmap 108 | */ 109 | public Bitmap getBitmap() { 110 | Bitmap b = null; 111 | synchronized (this) { 112 | if (mQueue.size() > 0) { 113 | b = mQueue.pop(); 114 | } 115 | 116 | notify(); 117 | } 118 | 119 | if (b == null) { 120 | Log.d(TAG, "Load bitmap instantly!"); 121 | b = getRandomBitmap(); 122 | } 123 | 124 | return b; 125 | } 126 | 127 | /** 128 | * Is task running? 129 | * 130 | * @return true if task is running 131 | */ 132 | public boolean isRunning() { 133 | return mThread != null && mThread.isAlive(); 134 | } 135 | 136 | /** 137 | * Start task 138 | */ 139 | public synchronized void start() { 140 | if (mThread == null || !mThread.isAlive()) { 141 | mStop = false; 142 | mThread = new Thread(this); 143 | mThread.start(); 144 | } 145 | } 146 | 147 | /** 148 | * Stop task 149 | *

Set mStop flag with true and notify task thread, at last, it will 150 | * check if task is alive every 500ms with 3 times to make sure the thread 151 | * stop

152 | */ 153 | public void stop() { 154 | synchronized (this) { 155 | mStop = true; 156 | notify(); 157 | } 158 | 159 | // wait for thread stopping 160 | for (int i = 0; i < 3 && mThread.isAlive(); ++i) { 161 | Log.d(TAG, "Waiting thread to stop ..."); 162 | try { 163 | Thread.sleep(500); 164 | } 165 | catch (InterruptedException e) { 166 | 167 | } 168 | } 169 | 170 | if (mThread.isAlive()) { 171 | Log.d(TAG, "Thread is still alive after waited 1.5s!"); 172 | } 173 | } 174 | 175 | /** 176 | * Set bitmap width , height and maximum size of cache queue 177 | * 178 | * @param w width of bitmap 179 | * @param h height of bitmap 180 | * @param maxCached maximum size of cache queue 181 | */ 182 | public void set(int w, int h, int maxCached) { 183 | int newIndex = LARGE_BG; 184 | if ((w <= 480 && h <= 854) || 185 | (w <= 854 && h <= 480)) { 186 | newIndex = SMALL_BG; 187 | } 188 | else if ((w <= 800 && h <= 1280) || 189 | (h <= 800 && w <= 1280)) { 190 | newIndex = MEDIUM_BG; 191 | } 192 | 193 | mIsLandscape = w > h; 194 | 195 | if (maxCached != mQueueMaxSize) { 196 | mQueueMaxSize = maxCached; 197 | } 198 | 199 | if (newIndex != mBGSizeIndex) { 200 | mBGSizeIndex = newIndex; 201 | synchronized (this) { 202 | cleanQueue(); 203 | notify(); 204 | } 205 | } 206 | } 207 | 208 | /** 209 | * Load bitmap from resources randomly 210 | * 211 | * @return bitmap object 212 | */ 213 | private Bitmap getRandomBitmap() { 214 | int newNo = mPreRandomNo; 215 | while (newNo == mPreRandomNo) { 216 | newNo = mBGRandom.nextInt(BG_COUNT); 217 | } 218 | 219 | mPreRandomNo = newNo; 220 | int resId = mPortraitBGs[mBGSizeIndex][newNo]; 221 | Bitmap b = BitmapFactory.decodeResource(mResources, resId); 222 | // if (mIsLandscape) { 223 | // Matrix matrix = new Matrix(); 224 | // matrix.postRotate(90); 225 | // Bitmap lb = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), 226 | // matrix, true); 227 | // b.recycle(); 228 | // return lb; 229 | // } 230 | 231 | return b; 232 | } 233 | 234 | /** 235 | * Clear cache queue 236 | */ 237 | private void cleanQueue() { 238 | for (int i = 0; i < mQueue.size(); ++i) { 239 | mQueue.get(i).recycle(); 240 | } 241 | mQueue.clear(); 242 | } 243 | 244 | public void run() { 245 | while (true) { 246 | synchronized (this) { 247 | // check if ask thread stopping 248 | if (mStop) { 249 | cleanQueue(); 250 | break; 251 | } 252 | 253 | // load bitmap only when no cached bitmap in queue 254 | int size = mQueue.size(); 255 | if (size < 1) { 256 | for (int i = 0; i < mQueueMaxSize; ++i) { 257 | Log.d(TAG, "Load Queue:" + i + " in background!"); 258 | mQueue.push(getRandomBitmap()); 259 | } 260 | } 261 | 262 | // wait to be awaken 263 | try { 264 | wait(); 265 | } 266 | catch (InterruptedException e) { 267 | } 268 | } 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/eschao/android/widget/sample/pageflip/PageFlipView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.sample.pageflip; 17 | 18 | import android.content.Context; 19 | import android.content.SharedPreferences; 20 | import android.opengl.GLSurfaceView; 21 | import android.opengl.GLSurfaceView.Renderer; 22 | import android.os.Handler; 23 | import android.os.Message; 24 | import android.preference.PreferenceManager; 25 | import android.util.AttributeSet; 26 | import android.util.Log; 27 | 28 | import com.eschao.android.widget.pageflip.PageFlip; 29 | import com.eschao.android.widget.pageflip.PageFlipException; 30 | 31 | import java.util.concurrent.locks.ReentrantLock; 32 | 33 | import javax.microedition.khronos.egl.EGLConfig; 34 | import javax.microedition.khronos.opengles.GL10; 35 | 36 | /** 37 | * Page flip view 38 | * 39 | * @author eschao 40 | */ 41 | 42 | public class PageFlipView extends GLSurfaceView implements Renderer { 43 | 44 | private final static String TAG = "PageFlipView"; 45 | 46 | int mPageNo; 47 | int mDuration; 48 | Handler mHandler; 49 | PageFlip mPageFlip; 50 | PageRender mPageRender; 51 | ReentrantLock mDrawLock; 52 | 53 | public PageFlipView(Context context) { 54 | super(context); 55 | init(context); 56 | } 57 | 58 | public PageFlipView(Context context, AttributeSet attrs) { 59 | super(context, attrs); 60 | init(context); 61 | } 62 | private void init(Context context) { 63 | // create handler to tackle message 64 | newHandler(); 65 | 66 | // load preferences 67 | SharedPreferences pref = PreferenceManager 68 | .getDefaultSharedPreferences(context); 69 | mDuration = pref.getInt(Constants.PREF_DURATION, 1000); 70 | int pixelsOfMesh = pref.getInt(Constants.PREF_MESH_PIXELS, 10); 71 | boolean isAuto = pref.getBoolean(Constants.PREF_PAGE_MODE, true); 72 | 73 | // create PageFlip 74 | mPageFlip = new PageFlip(context); 75 | mPageFlip.setSemiPerimeterRatio(0.8f) 76 | .setShadowWidthOfFoldEdges(5, 60, 0.3f) 77 | .setShadowWidthOfFoldBase(5, 80, 0.4f) 78 | .setPixelsOfMesh(pixelsOfMesh) 79 | .enableAutoPage(isAuto); 80 | setEGLContextClientVersion(2); 81 | 82 | // init others 83 | mPageNo = 1; 84 | mDrawLock = new ReentrantLock(); 85 | mPageRender = new SinglePageRender(context, mPageFlip, 86 | mHandler, mPageNo); 87 | // configure render 88 | setRenderer(this); 89 | setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 90 | } 91 | 92 | 93 | /** 94 | * Is auto page mode enabled? 95 | * 96 | * @return true if auto page mode enabled 97 | */ 98 | public boolean isAutoPageEnabled() { 99 | return mPageFlip.isAutoPageEnabled(); 100 | } 101 | 102 | /** 103 | * Enable/Disable auto page mode 104 | * 105 | * @param enable true is enable 106 | */ 107 | public void enableAutoPage(boolean enable) { 108 | if (mPageFlip.enableAutoPage(enable)) { 109 | try { 110 | mDrawLock.lock(); 111 | if (mPageFlip.getSecondPage() != null && 112 | mPageRender instanceof SinglePageRender) { 113 | mPageRender = new DoublePagesRender(getContext(), 114 | mPageFlip, 115 | mHandler, 116 | mPageNo); 117 | mPageRender.onSurfaceChanged(mPageFlip.getSurfaceWidth(), 118 | mPageFlip.getSurfaceHeight()); 119 | } 120 | else if (mPageFlip.getSecondPage() == null && 121 | mPageRender instanceof DoublePagesRender) { 122 | mPageRender = new SinglePageRender(getContext(), 123 | mPageFlip, 124 | mHandler, 125 | mPageNo); 126 | mPageRender.onSurfaceChanged(mPageFlip.getSurfaceWidth(), 127 | mPageFlip.getSurfaceHeight()); 128 | } 129 | requestRender(); 130 | } 131 | finally { 132 | mDrawLock.unlock(); 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Get duration of animating 139 | * 140 | * @return duration of animating 141 | */ 142 | public int getAnimateDuration() { 143 | return mDuration; 144 | } 145 | 146 | /** 147 | * Set animate duration 148 | * 149 | * @param duration duration of animating 150 | */ 151 | public void setAnimateDuration(int duration) { 152 | mDuration = duration; 153 | } 154 | 155 | /** 156 | * Get pixels of mesh 157 | * 158 | * @return pixels of mesh 159 | */ 160 | public int getPixelsOfMesh() { 161 | return mPageFlip.getPixelsOfMesh(); 162 | } 163 | 164 | /** 165 | * Handle finger down event 166 | * 167 | * @param x finger x coordinate 168 | * @param y finger y coordinate 169 | */ 170 | public void onFingerDown(float x, float y) { 171 | // if the animation is going, we should ignore this event to avoid 172 | // mess drawing on screen 173 | if (!mPageFlip.isAnimating() && 174 | mPageFlip.getFirstPage() != null) { 175 | mPageFlip.onFingerDown(x, y); 176 | } 177 | } 178 | 179 | /** 180 | * Handle finger moving event 181 | * 182 | * @param x finger x coordinate 183 | * @param y finger y coordinate 184 | */ 185 | public void onFingerMove(float x, float y) { 186 | if (mPageFlip.isAnimating()) { 187 | // nothing to do during animating 188 | } 189 | else if (mPageFlip.canAnimate(x, y)) { 190 | // if the point is out of current page, try to start animating 191 | onFingerUp(x, y); 192 | } 193 | // move page by finger 194 | else if (mPageFlip.onFingerMove(x, y)) { 195 | try { 196 | mDrawLock.lock(); 197 | if (mPageRender != null && 198 | mPageRender.onFingerMove(x, y)) { 199 | requestRender(); 200 | } 201 | } 202 | finally { 203 | mDrawLock.unlock(); 204 | } 205 | } 206 | } 207 | 208 | /** 209 | * Handle finger up event and start animating if need 210 | * 211 | * @param x finger x coordinate 212 | * @param y finger y coordinate 213 | */ 214 | public void onFingerUp(float x, float y) { 215 | if (!mPageFlip.isAnimating()) { 216 | mPageFlip.onFingerUp(x, y, mDuration); 217 | try { 218 | mDrawLock.lock(); 219 | if (mPageRender != null && 220 | mPageRender.onFingerUp(x, y)) { 221 | requestRender(); 222 | } 223 | } 224 | finally { 225 | mDrawLock.unlock(); 226 | } 227 | } 228 | } 229 | 230 | /** 231 | * Draw frame 232 | * 233 | * @param gl OpenGL handle 234 | */ 235 | @Override 236 | public void onDrawFrame(GL10 gl) { 237 | try { 238 | mDrawLock.lock(); 239 | if (mPageRender != null) { 240 | mPageRender.onDrawFrame(); 241 | } 242 | } 243 | finally { 244 | mDrawLock.unlock(); 245 | } 246 | } 247 | 248 | /** 249 | * Handle surface is changed 250 | * 251 | * @param gl OpenGL handle 252 | * @param width new width of surface 253 | * @param height new height of surface 254 | */ 255 | @Override 256 | public void onSurfaceChanged(GL10 gl, int width, int height) { 257 | try { 258 | mPageFlip.onSurfaceChanged(width, height); 259 | 260 | // if there is the second page, create double page render when need 261 | int pageNo = mPageRender.getPageNo(); 262 | if (mPageFlip.getSecondPage() != null && width > height) { 263 | if (!(mPageRender instanceof DoublePagesRender)) { 264 | mPageRender.release(); 265 | mPageRender = new DoublePagesRender(getContext(), 266 | mPageFlip, 267 | mHandler, 268 | pageNo); 269 | } 270 | } 271 | // if there is only one page, create single page render when need 272 | else if(!(mPageRender instanceof SinglePageRender)) { 273 | mPageRender.release(); 274 | mPageRender = new SinglePageRender(getContext(), 275 | mPageFlip, 276 | mHandler, 277 | pageNo); 278 | } 279 | 280 | // let page render handle surface change 281 | mPageRender.onSurfaceChanged(width, height); 282 | } 283 | catch (PageFlipException e) { 284 | Log.e(TAG, "Failed to run PageFlipFlipRender:onSurfaceChanged"); 285 | } 286 | } 287 | 288 | /** 289 | * Handle surface is created 290 | * 291 | * @param gl OpenGL handle 292 | * @param config EGLConfig object 293 | */ 294 | @Override 295 | public void onSurfaceCreated(GL10 gl, EGLConfig config) { 296 | try { 297 | mPageFlip.onSurfaceCreated(); 298 | } 299 | catch (PageFlipException e) { 300 | Log.e(TAG, "Failed to run PageFlipFlipRender:onSurfaceCreated"); 301 | } 302 | } 303 | 304 | /** 305 | * Create message handler to cope with messages from page render, 306 | * Page render will send message in GL thread, but we want to handle those 307 | * messages in main thread that why we need handler here 308 | */ 309 | private void newHandler() { 310 | mHandler = new Handler() { 311 | public void handleMessage(Message msg) { 312 | switch (msg.what) { 313 | case PageRender.MSG_ENDED_DRAWING_FRAME: 314 | try { 315 | mDrawLock.lock(); 316 | // notify page render to handle ended drawing 317 | // message 318 | if (mPageRender != null && 319 | mPageRender.onEndedDrawing(msg.arg1)) { 320 | requestRender(); 321 | } 322 | } 323 | finally { 324 | mDrawLock.unlock(); 325 | } 326 | break; 327 | 328 | default: 329 | break; 330 | } 331 | } 332 | }; 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/eschao/android/widget/sample/pageflip/PageRender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.sample.pageflip; 17 | 18 | import android.content.Context; 19 | import android.graphics.Bitmap; 20 | import android.graphics.Canvas; 21 | import android.os.Handler; 22 | 23 | import com.eschao.android.widget.pageflip.OnPageFlipListener; 24 | import com.eschao.android.widget.pageflip.PageFlip; 25 | 26 | /** 27 | * Abstract Page Render 28 | * 29 | * @author eschao 30 | */ 31 | 32 | public abstract class PageRender implements OnPageFlipListener { 33 | 34 | public final static int MSG_ENDED_DRAWING_FRAME = 1; 35 | private final static String TAG = "PageRender"; 36 | 37 | final static int DRAW_MOVING_FRAME = 0; 38 | final static int DRAW_ANIMATING_FRAME = 1; 39 | final static int DRAW_FULL_PAGE = 2; 40 | 41 | final static int MAX_PAGES = 30; 42 | 43 | int mPageNo; 44 | int mDrawCommand; 45 | Bitmap mBitmap; 46 | Canvas mCanvas; 47 | Bitmap mBackgroundBitmap; 48 | Context mContext; 49 | Handler mHandler; 50 | PageFlip mPageFlip; 51 | 52 | public PageRender(Context context, PageFlip pageFlip, 53 | Handler handler, int pageNo) { 54 | mContext = context; 55 | mPageFlip = pageFlip; 56 | mPageNo = pageNo; 57 | mDrawCommand = DRAW_FULL_PAGE; 58 | mCanvas = new Canvas(); 59 | mPageFlip.setListener(this); 60 | mHandler = handler; 61 | } 62 | 63 | /** 64 | * Get page number 65 | * 66 | * @return page number 67 | */ 68 | public int getPageNo() { 69 | return mPageNo; 70 | } 71 | 72 | /** 73 | * Release resources 74 | */ 75 | public void release() { 76 | if (mBitmap != null) { 77 | mBitmap.recycle(); 78 | mBitmap = null; 79 | } 80 | 81 | mPageFlip.setListener(null); 82 | mCanvas = null; 83 | mBackgroundBitmap = null; 84 | } 85 | 86 | /** 87 | * Handle finger moving event 88 | * 89 | * @param x x coordinate of finger moving 90 | * @param y y coordinate of finger moving 91 | * @return true if event is handled 92 | */ 93 | public boolean onFingerMove(float x, float y) { 94 | mDrawCommand = DRAW_MOVING_FRAME; 95 | return true; 96 | } 97 | 98 | /** 99 | * Handle finger up event 100 | * 101 | * @param x x coordinate of finger up 102 | * @param y y coordinate of inger up 103 | * @return true if event is handled 104 | */ 105 | public boolean onFingerUp(float x, float y) { 106 | if (mPageFlip.animating()) { 107 | mDrawCommand = DRAW_ANIMATING_FRAME; 108 | return true; 109 | } 110 | 111 | return false; 112 | } 113 | 114 | /** 115 | * Calculate font size by given SP unit 116 | */ 117 | protected int calcFontSize(int size) { 118 | return (int)(size * mContext.getResources().getDisplayMetrics() 119 | .scaledDensity); 120 | } 121 | 122 | /** 123 | * Render page frame 124 | */ 125 | abstract void onDrawFrame(); 126 | 127 | /** 128 | * Handle surface changing event 129 | * 130 | * @param width surface width 131 | * @param height surface height 132 | */ 133 | abstract void onSurfaceChanged(int width, int height); 134 | 135 | /** 136 | * Handle drawing ended event 137 | * 138 | * @param what draw command 139 | * @return true if render is needed 140 | */ 141 | abstract boolean onEndedDrawing(int what); 142 | } 143 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/eschao/android/widget/sample/pageflip/SampleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.sample.pageflip; 17 | 18 | import android.app.Activity; 19 | import android.app.AlertDialog; 20 | import android.content.SharedPreferences; 21 | import android.content.SharedPreferences.Editor; 22 | import android.os.Build; 23 | import android.os.Bundle; 24 | 25 | import android.preference.PreferenceManager; 26 | import android.text.Html; 27 | import android.text.method.LinkMovementMethod; 28 | import android.view.GestureDetector; 29 | 30 | import android.view.GestureDetector.OnGestureListener; 31 | import android.view.Menu; 32 | import android.view.MenuInflater; 33 | import android.view.MenuItem; 34 | import android.view.MotionEvent; 35 | import android.view.View; 36 | import android.view.WindowManager; 37 | import android.widget.TextView; 38 | 39 | /** 40 | * Sample Activity 41 | * 42 | * @author eschao 43 | */ 44 | public class SampleActivity extends Activity implements OnGestureListener { 45 | 46 | PageFlipView mPageFlipView; 47 | GestureDetector mGestureDetector; 48 | 49 | @Override 50 | public void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | 53 | mPageFlipView = new PageFlipView(this); 54 | setContentView(mPageFlipView); 55 | mGestureDetector = new GestureDetector(this, this); 56 | 57 | if (Build.VERSION.SDK_INT < 16) { 58 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 59 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 60 | } else { 61 | mPageFlipView.setSystemUiVisibility( 62 | View.SYSTEM_UI_FLAG_FULLSCREEN | 63 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | 64 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | 65 | View.SYSTEM_UI_FLAG_IMMERSIVE | 66 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); 67 | } 68 | } 69 | 70 | @Override 71 | protected void onResume() { 72 | super.onResume(); 73 | 74 | LoadBitmapTask.get(this).start(); 75 | mPageFlipView.onResume(); 76 | } 77 | 78 | @Override 79 | protected void onPause() { 80 | super.onPause(); 81 | 82 | mPageFlipView.onPause(); 83 | LoadBitmapTask.get(this).stop(); 84 | } 85 | 86 | @Override 87 | public boolean onCreateOptionsMenu(Menu menu) { 88 | MenuInflater inflater = getMenuInflater(); 89 | inflater.inflate(R.menu.optionmenus, menu); 90 | 91 | int duration = mPageFlipView.getAnimateDuration(); 92 | if (duration == 1000) { 93 | menu.findItem(R.id.animation_1s).setChecked(true); 94 | } 95 | else if (duration == 2000) { 96 | menu.findItem(R.id.animation_2s).setChecked(true); 97 | } 98 | else if (duration == 5000) { 99 | menu.findItem(R.id.animation_5s).setChecked(true); 100 | } 101 | 102 | if (mPageFlipView.isAutoPageEnabled()) { 103 | menu.findItem(R.id.auoto_page).setChecked(true); 104 | } 105 | else { 106 | menu.findItem(R.id.single_page).setChecked(true); 107 | } 108 | 109 | SharedPreferences pref = PreferenceManager 110 | .getDefaultSharedPreferences(this); 111 | int pixels = pref.getInt("MeshPixels", mPageFlipView.getPixelsOfMesh()); 112 | switch (pixels) { 113 | case 2: 114 | menu.findItem(R.id.mesh_2p).setChecked(true); 115 | break; 116 | case 5: 117 | menu.findItem(R.id.mesh_5p).setChecked(true); 118 | break; 119 | case 10: 120 | menu.findItem(R.id.mesh_10p).setChecked(true); 121 | break; 122 | case 20: 123 | menu.findItem(R.id.mesh_20p).setChecked(true); 124 | break; 125 | default: 126 | break; 127 | } 128 | 129 | return true; 130 | } 131 | 132 | @Override 133 | public boolean onOptionsItemSelected(MenuItem item) { 134 | boolean isHandled = true; 135 | SharedPreferences pref = PreferenceManager 136 | .getDefaultSharedPreferences(this); 137 | Editor editor = pref.edit(); 138 | switch (item.getItemId()) { 139 | case R.id.animation_1s: 140 | mPageFlipView.setAnimateDuration(1000); 141 | editor.putInt(Constants.PREF_DURATION, 1000); 142 | break; 143 | case R.id.animation_2s: 144 | mPageFlipView.setAnimateDuration(2000); 145 | editor.putInt(Constants.PREF_DURATION, 2000); 146 | break; 147 | case R.id.animation_5s: 148 | mPageFlipView.setAnimateDuration(5000); 149 | editor.putInt(Constants.PREF_DURATION, 5000); 150 | break; 151 | case R.id.auoto_page: 152 | mPageFlipView.enableAutoPage(true); 153 | editor.putBoolean(Constants.PREF_PAGE_MODE, true); 154 | break; 155 | case R.id.single_page: 156 | mPageFlipView.enableAutoPage(false); 157 | editor.putBoolean(Constants.PREF_PAGE_MODE, false); 158 | break; 159 | case R.id.mesh_2p: 160 | editor.putInt(Constants.PREF_MESH_PIXELS, 2); 161 | break; 162 | case R.id.mesh_5p: 163 | editor.putInt(Constants.PREF_MESH_PIXELS, 5); 164 | break; 165 | case R.id.mesh_10p: 166 | editor.putInt(Constants.PREF_MESH_PIXELS, 10); 167 | break; 168 | case R.id.mesh_20p: 169 | editor.putInt(Constants.PREF_MESH_PIXELS, 20); 170 | break; 171 | case R.id.about_menu: 172 | showAbout(); 173 | return true; 174 | default: 175 | isHandled = false; 176 | break; 177 | } 178 | 179 | if (isHandled) { 180 | item.setChecked(true); 181 | editor.apply(); 182 | return true; 183 | } 184 | return super.onOptionsItemSelected(item); 185 | } 186 | 187 | @Override 188 | public boolean onTouchEvent(MotionEvent event) { 189 | if (event.getAction() == MotionEvent.ACTION_UP) { 190 | mPageFlipView.onFingerUp(event.getX(), event.getY()); 191 | return true; 192 | } 193 | 194 | return mGestureDetector.onTouchEvent(event); 195 | } 196 | 197 | @Override 198 | public boolean onDown(MotionEvent e) { 199 | mPageFlipView.onFingerDown(e.getX(), e.getY()); 200 | return true; 201 | } 202 | 203 | @Override 204 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 205 | float velocityY) { 206 | return false; 207 | } 208 | 209 | 210 | @Override 211 | public void onLongPress(MotionEvent e) { 212 | } 213 | 214 | @Override 215 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, 216 | float distanceY) { 217 | mPageFlipView.onFingerMove(e2.getX(), e2.getY()); 218 | return true; 219 | } 220 | 221 | @Override 222 | public void onShowPress(MotionEvent e) { 223 | } 224 | 225 | public boolean onSingleTapUp(MotionEvent e) { 226 | return false; 227 | } 228 | 229 | private void showAbout() { 230 | View aboutView = getLayoutInflater().inflate(R.layout.about, null, 231 | false); 232 | 233 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 234 | builder.setIcon(R.mipmap.ic_launcher); 235 | builder.setTitle(R.string.app_name); 236 | builder.setView(aboutView); 237 | builder.create(); 238 | builder.show(); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/eschao/android/widget/sample/pageflip/SinglePageRender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 eschao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.eschao.android.widget.sample.pageflip; 17 | 18 | import android.content.Context; 19 | import android.graphics.Bitmap; 20 | import android.graphics.Color; 21 | import android.graphics.Paint; 22 | import android.graphics.Rect; 23 | import android.os.Handler; 24 | import android.os.Message; 25 | import android.widget.Toast; 26 | 27 | import com.eschao.android.widget.pageflip.Page; 28 | import com.eschao.android.widget.pageflip.PageFlip; 29 | import com.eschao.android.widget.pageflip.PageFlipState; 30 | 31 | /** 32 | * Single page render 33 | *

34 | * Every page need 2 texture in single page mode: 35 | *

    36 | *
  • First texture: current page content
  • 37 | *
  • Back texture: back of front content, it is same with first texture 38 | *
  • 39 | *
  • Second texture: next page content
  • 40 | *
41 | *

42 | * 43 | * @author eschao 44 | */ 45 | 46 | public class SinglePageRender extends PageRender { 47 | 48 | /** 49 | * Constructor 50 | * @see {@link #PageRender(Context, PageFlip, Handler, int)} 51 | */ 52 | public SinglePageRender(Context context, PageFlip pageFlip, 53 | Handler handler, int pageNo) { 54 | super(context, pageFlip, handler, pageNo); 55 | } 56 | 57 | /** 58 | * Draw frame 59 | */ 60 | public void onDrawFrame() { 61 | // 1. delete unused textures 62 | mPageFlip.deleteUnusedTextures(); 63 | Page page = mPageFlip.getFirstPage(); 64 | 65 | // 2. handle drawing command triggered from finger moving and animating 66 | if (mDrawCommand == DRAW_MOVING_FRAME || 67 | mDrawCommand == DRAW_ANIMATING_FRAME) { 68 | // is forward flip 69 | if (mPageFlip.getFlipState() == PageFlipState.FORWARD_FLIP) { 70 | // check if second texture of first page is valid, if not, 71 | // create new one 72 | if (!page.isSecondTextureSet()) { 73 | drawPage(mPageNo + 1); 74 | page.setSecondTexture(mBitmap); 75 | } 76 | } 77 | // in backward flip, check first texture of first page is valid 78 | else if (!page.isFirstTextureSet()) { 79 | drawPage(--mPageNo); 80 | page.setFirstTexture(mBitmap); 81 | } 82 | 83 | // draw frame for page flip 84 | mPageFlip.drawFlipFrame(); 85 | } 86 | // draw stationary page without flipping 87 | else if (mDrawCommand == DRAW_FULL_PAGE) { 88 | if (!page.isFirstTextureSet()) { 89 | drawPage(mPageNo); 90 | page.setFirstTexture(mBitmap); 91 | } 92 | 93 | mPageFlip.drawPageFrame(); 94 | } 95 | 96 | // 3. send message to main thread to notify drawing is ended so that 97 | // we can continue to calculate next animation frame if need. 98 | // Remember: the drawing operation is always in GL thread instead of 99 | // main thread 100 | Message msg = Message.obtain(); 101 | msg.what = MSG_ENDED_DRAWING_FRAME; 102 | msg.arg1 = mDrawCommand; 103 | mHandler.sendMessage(msg); 104 | } 105 | 106 | /** 107 | * Handle GL surface is changed 108 | * 109 | * @param width surface width 110 | * @param height surface height 111 | */ 112 | public void onSurfaceChanged(int width, int height) { 113 | // recycle bitmap resources if need 114 | if (mBackgroundBitmap != null) { 115 | mBackgroundBitmap.recycle(); 116 | } 117 | 118 | if (mBitmap != null) { 119 | mBitmap.recycle(); 120 | } 121 | 122 | // create bitmap and canvas for page 123 | //mBackgroundBitmap = background; 124 | Page page = mPageFlip.getFirstPage(); 125 | mBitmap = Bitmap.createBitmap((int)page.width(), (int)page.height(), 126 | Bitmap.Config.ARGB_8888); 127 | mCanvas.setBitmap(mBitmap); 128 | LoadBitmapTask.get(mContext).set(width, height, 1); 129 | } 130 | 131 | /** 132 | * Handle ended drawing event 133 | * In here, we only tackle the animation drawing event, If we need to 134 | * continue requesting render, please return true. Remember this function 135 | * will be called in main thread 136 | * 137 | * @param what event type 138 | * @return ture if need render again 139 | */ 140 | public boolean onEndedDrawing(int what) { 141 | if (what == DRAW_ANIMATING_FRAME) { 142 | boolean isAnimating = mPageFlip.animating(); 143 | // continue animating 144 | if (isAnimating) { 145 | mDrawCommand = DRAW_ANIMATING_FRAME; 146 | return true; 147 | } 148 | // animation is finished 149 | else { 150 | final PageFlipState state = mPageFlip.getFlipState(); 151 | // update page number for backward flip 152 | if (state == PageFlipState.END_WITH_BACKWARD) { 153 | // don't do anything on page number since mPageNo is always 154 | // represents the FIRST_TEXTURE no; 155 | } 156 | // update page number and switch textures for forward flip 157 | else if (state == PageFlipState.END_WITH_FORWARD) { 158 | mPageFlip.getFirstPage().setFirstTextureWithSecond(); 159 | mPageNo++; 160 | } 161 | 162 | mDrawCommand = DRAW_FULL_PAGE; 163 | return true; 164 | } 165 | } 166 | return false; 167 | } 168 | 169 | /** 170 | * Draw page content 171 | * 172 | * @param number page number 173 | */ 174 | private void drawPage(int number) { 175 | final int width = mCanvas.getWidth(); 176 | final int height = mCanvas.getHeight(); 177 | Paint p = new Paint(); 178 | p.setFilterBitmap(true); 179 | 180 | // 1. draw background bitmap 181 | Bitmap background = LoadBitmapTask.get(mContext).getBitmap(); 182 | Rect rect = new Rect(0, 0, width, height); 183 | mCanvas.drawBitmap(background, null, rect, p); 184 | background.recycle(); 185 | background = null; 186 | 187 | // 2. draw page number 188 | int fontSize = calcFontSize(80); 189 | p.setColor(Color.WHITE); 190 | p.setStrokeWidth(1); 191 | p.setAntiAlias(true); 192 | p.setShadowLayer(5.0f, 8.0f, 8.0f, Color.BLACK); 193 | p.setTextSize(fontSize); 194 | String text = String.valueOf(number); 195 | float textWidth = p.measureText(text); 196 | float y = height - p.getTextSize() - 20; 197 | mCanvas.drawText(text, (width - textWidth) / 2, y, p); 198 | 199 | if (number <= 1) { 200 | String firstPage = "The First Page"; 201 | p.setTextSize(calcFontSize(16)); 202 | float w = p.measureText(firstPage); 203 | float h = p.getTextSize(); 204 | mCanvas.drawText(firstPage, (width - w) / 2, y + 5 + h, p); 205 | } 206 | else if (number >= MAX_PAGES) { 207 | String lastPage = "The Last Page"; 208 | p.setTextSize(calcFontSize(16)); 209 | float w = p.measureText(lastPage); 210 | float h = p.getTextSize(); 211 | mCanvas.drawText(lastPage, (width - w) / 2, y + 5 + h, p); 212 | } 213 | } 214 | 215 | /** 216 | * If page can flip forward 217 | * 218 | * @return true if it can flip forward 219 | */ 220 | public boolean canFlipForward() { 221 | return (mPageNo < MAX_PAGES); 222 | } 223 | 224 | /** 225 | * If page can flip backward 226 | * 227 | * @return true if it can flip backward 228 | */ 229 | public boolean canFlipBackward() { 230 | if (mPageNo > 1) { 231 | mPageFlip.getFirstPage().setSecondTextureWithFirst(); 232 | return true; 233 | } 234 | else { 235 | return false; 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p10_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p10_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p10_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p10_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p10_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p10_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p1_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p1_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p1_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p1_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p1_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p1_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p2_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p2_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p2_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p2_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p2_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p2_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p3_1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p3_1080.png -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p3_480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p3_480.png -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p3_720.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p3_720.png -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p4_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p4_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p4_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p4_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p4_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p4_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p5_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p5_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p5_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p5_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p5_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p5_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p6_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p6_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p6_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p6_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p6_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p6_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p7_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p7_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p7_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p7_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p7_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p7_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p8_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p8_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p8_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p8_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p8_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p8_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p9_1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p9_1080.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p9_480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p9_480.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/p9_720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/Sample/src/main/res/drawable/p9_720.jpg -------------------------------------------------------------------------------- /Sample/src/main/res/layout/about.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 19 | 20 | 31 | 32 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Sample/src/main/res/menu/optionmenus.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.2' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Sat Dec 24 22:22:36 CST 2016 16 | systemProp.http.proxyHost=127.0.0.1 17 | systemProp.http.proxyPort=1080 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 13 13:58:50 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion '25.0.0' 6 | 7 | defaultConfig { 8 | applicationId "com.eschao.android.widget.sample" 9 | minSdkVersion 15 10 | targetSdkVersion 24 11 | versionCode 2 12 | versionName "1.0.2" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:24.2.0' 26 | compile project(':PageFlip') 27 | } 28 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/chao/Software/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/sample_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschao/android-PageFlip/6c361e0932c80c360fdf5f7de06963031e8c26d2/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PageFlip 3 | PageFlip Demo 4 | 5 | 1. Changing "Mesh Pixels" will only take effect in next time! 6 | 7 | 8 | 2. The codes are under Apache License 2.0. You can get it from 9 | https://github.com/eschao/android-PageFlip and use freely. 10 | 11 | 12 | 3. Any question please mailto: esc.chao@gmail.com or file issue in github. 13 | 14 | 15 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':Sample', ':PageFlip' 2 | --------------------------------------------------------------------------------