├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ebook │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── fonts │ │ │ ├── hkshaonv.ttf │ │ │ ├── hwzhongsong.ttf │ │ │ ├── kaiti.ttf │ │ │ └── youyuan.ttf │ │ ├── image │ │ │ ├── 01.jpg │ │ │ ├── 02.jpg │ │ │ ├── 03.jpg │ │ │ ├── 04.jpg │ │ │ ├── 05.jpg │ │ │ ├── 06.jpg │ │ │ ├── 07.jpg │ │ │ ├── 08.jpg │ │ │ ├── 09.jpg │ │ │ ├── 10.jpg │ │ │ ├── 11.jpg │ │ │ ├── 12.jpg │ │ │ ├── 13.jpg │ │ │ └── 14.jpg │ │ ├── litepal.xml │ │ └── text │ │ │ ├── 01_第一卷 西凉风暴.txt │ │ │ ├── 02_第二卷 乱世文章.txt │ │ │ ├── 03_第三卷 京城之会.txt │ │ │ ├── 04_第四卷 神鬼亭外.txt │ │ │ ├── 05_第五卷 西出阳关.txt │ │ │ ├── 06_第六卷 一代真龙.txt │ │ │ ├── 07_第七卷 天下第一.txt │ │ │ ├── 08_第八卷 金榜题名.txt │ │ │ ├── 09_第九卷 神剑擒龙.txt │ │ │ ├── 10_第十卷 忠义孤臣.txt │ │ │ ├── 11_第十一卷 重建怒苍.txt │ │ │ ├── 12_第十二卷 十面埋伏.txt │ │ │ ├── 13_第十三卷 海上孤鸿.txt │ │ │ └── 14_第十四卷 正统王朝.txt │ ├── java │ │ └── com │ │ │ └── ebook │ │ │ ├── ReadingActivity.java │ │ │ ├── ReadingFragment.java │ │ │ ├── ShelfActivity.java │ │ │ ├── ShelfFragment.java │ │ │ ├── SingleFragmentActivity.java │ │ │ ├── model │ │ │ ├── Book.java │ │ │ └── BookLab.java │ │ │ ├── util │ │ │ ├── SaveHelper.java │ │ │ ├── ScreenBrightnessHelper.java │ │ │ └── bookPageUtil │ │ │ │ ├── BookPageFactory.java │ │ │ │ ├── Label.java │ │ │ │ ├── PaintInfo.java │ │ │ │ └── ReadInfo.java │ │ │ └── view │ │ │ ├── FlipView.java │ │ │ ├── SwitchView.java │ │ │ └── popupWindow │ │ │ ├── BasePopupWindow.java │ │ │ ├── ContentPopup.java │ │ │ ├── FontPopup.java │ │ │ ├── LabelPopup.java │ │ │ └── SettingPopup.java │ └── res │ │ ├── anim │ │ ├── pop_hide_anim.xml │ │ └── pop_show_anim.xml │ │ ├── drawable │ │ ├── btn_bottom_bar.xml │ │ ├── btn_filp_mode.xml │ │ ├── btn_font.xml │ │ ├── btn_theme.xml │ │ ├── clear.png │ │ ├── light_big.png │ │ ├── light_small.png │ │ ├── seek_bar_progress.xml │ │ ├── seek_bar_thumb.xml │ │ ├── size_add.png │ │ └── size_small.png │ │ ├── layout │ │ ├── activity_fragment.xml │ │ ├── bottom_bar.xml │ │ ├── fragment_reading_layout.xml │ │ ├── fragment_shelf_layout.xml │ │ ├── item_recycler_view_label.xml │ │ ├── item_recycler_view_shelf.xml │ │ ├── popup_content_layout.xml │ │ ├── popup_font_layout.xml │ │ ├── popup_label_layout.xml │ │ └── popup_setting_layout.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── icon.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── ebook │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── flip.gif ├── function_1.gif └── function_2.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 因为个人兴趣开发的Android电子书应用 2 | ### 一款界面简洁、风格清新、功能丰富的Android电子书应用: 3 | 1.具有仿真翻页、覆盖翻页、直接翻页三种翻页风格 4 | 5 | 2.常规、复古、护眼、夜间四种主题,可调节亮度,能根据光线自动切换夜间模式 6 | 7 | 3.支持文字大小、字体、颜色设置 8 | 9 | 4.能自动保存阅读进度,支持目录和书签功能 10 | 11 | 5.使用了自定义控件、持久化存储、Assets文件系统、单例设计模式等Android必备基础技术 12 | 13 | ![翻页](https://github.com/yuyangXu0222/eBook/blob/master/screenshot/flip.gif) 14 | 15 | 16 | ![设置1](https://github.com/yuyangXu0222/eBook/blob/master/screenshot/function_1.gif) 17 | 18 | ![设置2](https://github.com/yuyangXu0222/eBook/blob/master/screenshot/function_2.gif) 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.ebook" 8 | minSdkVersion 21 9 | targetSdkVersion 22 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 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(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:24.2.1' 28 | testCompile 'junit:junit:4.12' 29 | compile 'com.android.support:recyclerview-v7:24.2.1' 30 | compile 'com.android.support:design:24.2.1' 31 | compile 'com.android.support:cardview-v7:24.2.1' 32 | compile 'com.android.support:design:24.2.1' 33 | compile 'org.litepal.android:core:1.3.2' 34 | } 35 | -------------------------------------------------------------------------------- /app/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 C:\E\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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ebook/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.ebook; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.ebook", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/assets/fonts/hkshaonv.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/fonts/hkshaonv.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/hwzhongsong.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/fonts/hwzhongsong.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/kaiti.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/fonts/kaiti.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/youyuan.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/fonts/youyuan.ttf -------------------------------------------------------------------------------- /app/src/main/assets/image/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/01.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/02.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/03.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/04.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/05.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/06.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/07.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/08.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/09.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/10.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/11.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/12.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/13.jpg -------------------------------------------------------------------------------- /app/src/main/assets/image/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyangXu0222/eBook/36faacdc7f617cdd7ddd53db43c217f3539f5541/app/src/main/assets/image/14.jpg -------------------------------------------------------------------------------- /app/src/main/assets/litepal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/ReadingActivity.java: -------------------------------------------------------------------------------- 1 | package com.ebook; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.support.v4.app.Fragment; 6 | import android.view.WindowManager; 7 | 8 | 9 | public class ReadingActivity extends SingleFragmentActivity { 10 | public static final String EXTRA_BOOK_ID = "EXTRA_BOOK_ID"; 11 | 12 | public static Intent newIntent(Context context, int bookId) { 13 | Intent intent = new Intent(context, ReadingActivity.class); 14 | intent.putExtra(EXTRA_BOOK_ID, bookId); 15 | return intent; 16 | } 17 | 18 | @Override 19 | protected void setScreen() { 20 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, //全屏 21 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 22 | } 23 | 24 | @Override 25 | protected Fragment createFragment() { 26 | int bookId = getIntent().getIntExtra(EXTRA_BOOK_ID, 0); 27 | return ReadingFragment.newInstance(bookId); 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/ReadingFragment.java: -------------------------------------------------------------------------------- 1 | package com.ebook; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.graphics.Bitmap; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.os.Message; 11 | import android.support.annotation.Nullable; 12 | import android.support.v4.app.Fragment; 13 | import android.text.format.Time; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.view.WindowManager; 18 | import android.widget.Button; 19 | import android.widget.LinearLayout; 20 | import android.widget.PopupWindow; 21 | import android.widget.Toast; 22 | 23 | 24 | import com.ebook.model.Book; 25 | import com.ebook.model.BookLab; 26 | import com.ebook.util.ScreenBrightnessHelper; 27 | import com.ebook.util.bookPageUtil.BookPageFactory; 28 | import com.ebook.util.bookPageUtil.Label; 29 | import com.ebook.util.bookPageUtil.ReadInfo; 30 | import com.ebook.util.SaveHelper; 31 | import com.ebook.view.FlipView; 32 | import com.ebook.view.popupWindow.ContentPopup; 33 | import com.ebook.view.popupWindow.FontPopup; 34 | import com.ebook.view.popupWindow.LabelPopup; 35 | import com.ebook.view.popupWindow.SettingPopup; 36 | 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | 40 | /** 41 | * Created by Mum on 2017/2/6. 42 | */ 43 | 44 | public class ReadingFragment extends Fragment implements View.OnClickListener { 45 | public static final String ARG_FLIP_BOOK_ID = "ARG_FLIP_BOOK_ID "; 46 | public static final int TEXT_SIZE_DELTA = 50; 47 | 48 | private Context mContext; 49 | private int mBookId; 50 | private Book mBook; 51 | private BookPageFactory mBookPageFactory; 52 | 53 | private Bitmap mPrePage; 54 | private Bitmap mNextPage; 55 | private List mPageList = new ArrayList<>(); 56 | 57 | private int[] mBgColors; 58 | 59 | private FlipView mFlipView; 60 | 61 | private LinearLayout mBottomBar; 62 | private Button[] mBottomBtns; 63 | 64 | private SettingPopup mSettingPopup; 65 | private ContentPopup mContentPopup; 66 | private FontPopup mFontPopup; 67 | private LabelPopup mLabelPopup; 68 | 69 | private boolean isBottomBarShow = true; 70 | private boolean isFirstRead = true;//是否是第一次进入 71 | 72 | private float mBackgroundAlpha = 1.0f; 73 | private float mPowerPercent; //当前电池电量百分比 74 | 75 | private BatteryPowerReceiver mBatteryReceiver;//电池电量广播接收者 76 | 77 | private Handler mHandler = new Handler() { 78 | @Override 79 | public void handleMessage(Message msg) { 80 | 81 | WindowManager.LayoutParams layoutParams = getActivity().getWindow().getAttributes(); 82 | layoutParams.alpha = (Float) msg.obj; 83 | getActivity().getWindow().setAttributes(layoutParams); 84 | 85 | } 86 | }; 87 | 88 | 89 | public static ReadingFragment newInstance(int bookId) { 90 | 91 | Bundle args = new Bundle(); 92 | args.putInt(ARG_FLIP_BOOK_ID, bookId); 93 | ReadingFragment fragment = new ReadingFragment(); 94 | fragment.setArguments(args); 95 | return fragment; 96 | } 97 | 98 | @Override 99 | public void onCreate(@Nullable Bundle savedInstanceState) { 100 | super.onCreate(savedInstanceState); 101 | initDatas(); 102 | } 103 | 104 | 105 | @Nullable 106 | @Override 107 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 108 | View v = inflater.inflate(R.layout.fragment_reading_layout, container, false); 109 | initViews(v); 110 | initEvents(); 111 | 112 | return v; 113 | } 114 | 115 | @Override 116 | public void onStart() { 117 | super.onStart(); 118 | 119 | //注册电量变化广播接收者 120 | IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 121 | mBatteryReceiver = new BatteryPowerReceiver(); 122 | mContext.registerReceiver(mBatteryReceiver, filter); 123 | 124 | } 125 | 126 | 127 | @Override 128 | public void onStop() { 129 | super.onStop(); 130 | mContext.unregisterReceiver(mBatteryReceiver); //取消广播接收者 131 | 132 | //SettingPopup 133 | SaveHelper.save(mContext, SaveHelper.THEME, mSettingPopup.getTheme()); 134 | SaveHelper.save(mContext, SaveHelper.FLIP_STYLE, mSettingPopup.getFlipStyle()); 135 | 136 | //FlipView 137 | SaveHelper.save(mContext, SaveHelper.IS_PRE_PAGE_OVER, mFlipView.isPrePageOver()); 138 | 139 | //BookPageFactory 140 | SaveHelper.saveObject(mContext, mBookId + SaveHelper.DRAW_INFO, mBookPageFactory.getReadInfo()); 141 | SaveHelper.saveObject(mContext, SaveHelper.PAINT_INFO, mBookPageFactory.getPaintInfo()); 142 | 143 | 144 | } 145 | 146 | @Override 147 | public void onClick(View v) { 148 | switch (v.getId()) { 149 | 150 | case R.id.button_content: 151 | //设置出现动画和位置 152 | 153 | mContentPopup.setAnimationStyle(R.style.pop_window_anim_style); 154 | mContentPopup.showAsDropDown(mBottomBar, 0, -mContentPopup.getHeight()); 155 | 156 | lightOff(); 157 | 158 | 159 | break; 160 | case R.id.button_setting: 161 | 162 | int xOff = (mBottomBar.getWidth() - mSettingPopup.getWidth()) / 2; 163 | int yOff = -mSettingPopup.getHeight() - mBottomBar.getHeight() / 6; 164 | 165 | mSettingPopup.setAnimationStyle(R.style.pop_window_anim_style); 166 | mSettingPopup.showAsDropDown(mBottomBar, xOff, yOff); 167 | 168 | break; 169 | 170 | case R.id.button_font: 171 | 172 | mFontPopup.setAnimationStyle(R.style.pop_window_anim_style); 173 | mFontPopup.showAsDropDown(mBottomBar, 0, -mFontPopup.getHeight()); 174 | 175 | lightOff(); 176 | 177 | break; 178 | 179 | case R.id.button_label: 180 | 181 | saveLabel(); 182 | Toast.makeText(mContext, "书签已添加,长按显示书签列表", Toast.LENGTH_SHORT).show(); 183 | 184 | break; 185 | } 186 | 187 | } 188 | 189 | //书签存入数据库 190 | private void saveLabel() { 191 | Time time = new Time(); 192 | time.setToNow(); // 取得系统时间。 193 | String timeStr = time.year + "/" + time.month + "/" + time.monthDay; 194 | 195 | ReadInfo readInfo = mBookPageFactory.getReadInfo(); 196 | String objectStr = SaveHelper.serObject(readInfo); 197 | 198 | Label label = new Label(); 199 | label.setBookId(mBookId); 200 | label.setDetails(mBookPageFactory.getCurContent()); 201 | label.setProgress(mBookPageFactory.getPercentStr()); 202 | label.setTime(timeStr); 203 | label.setPrePageOver(mFlipView.isPrePageOver()); 204 | label.setReadInfoStr(objectStr); 205 | 206 | label.save(); 207 | } 208 | 209 | private void initDatas() { 210 | mContext = getActivity(); 211 | mBookId = getArguments().getInt(ARG_FLIP_BOOK_ID); 212 | mBook = BookLab.newInstance(mContext).getBookList().get(mBookId); 213 | mBookPageFactory = new BookPageFactory(mContext, mBookId); 214 | 215 | mBgColors = new int[]{ 216 | 0xffe7dcbe, //复古 217 | 0xffffffff, // 常规 218 | 0xffcbe1cf, //护眼 219 | 0xff333232 //夜间 220 | }; 221 | 222 | } 223 | 224 | private void initEvents() { 225 | 226 | if (isBottomBarShow) 227 | hideBottomBar(); 228 | 229 | int theme = SaveHelper.getInt(mContext, SaveHelper.THEME); 230 | setTheme(theme); 231 | 232 | mFlipView.setOnPageFlippedListener(new FlipView.OnPageFlippedListener() { 233 | @Override 234 | public List onNextPageFlipped() { 235 | //向后读一页 236 | 237 | mNextPage = mBookPageFactory.drawNextPage(mPowerPercent); 238 | 239 | if (mNextPage == null) 240 | return null; 241 | 242 | mPageList.remove(0); 243 | mPageList.add(mNextPage); 244 | 245 | return mPageList; 246 | } 247 | 248 | @Override 249 | public List onPrePageFlipped() { 250 | mPrePage = mBookPageFactory.drawPrePage(mPowerPercent); 251 | if (mPrePage == null) 252 | return null; 253 | 254 | mPageList.remove(1); 255 | mPageList.add(0, mPrePage); 256 | 257 | return mPageList; 258 | } 259 | 260 | @Override 261 | public void onFlipStarted() { 262 | if (isBottomBarShow) 263 | hideBottomBar(); 264 | 265 | 266 | } 267 | 268 | @Override 269 | public void onFoldViewClicked() { 270 | if (isBottomBarShow) 271 | hideBottomBar(); 272 | else 273 | showBottomBar(); 274 | 275 | } 276 | 277 | 278 | }); 279 | 280 | for (Button button : mBottomBtns) { 281 | button.setOnClickListener(this); 282 | } 283 | 284 | mBottomBtns[3].setOnLongClickListener(new View.OnLongClickListener() { 285 | @Override 286 | public boolean onLongClick(View v) { 287 | 288 | mLabelPopup.updateUI(); //刷新书签列表 289 | 290 | mLabelPopup.setAnimationStyle(R.style.pop_window_anim_style); 291 | mLabelPopup.showAsDropDown(mBottomBar, 0, -mLabelPopup.getHeight()); 292 | 293 | lightOff(); 294 | 295 | return true; 296 | } 297 | }); 298 | 299 | setPopupWindowListener(); 300 | } 301 | 302 | 303 | private void setPopupWindowListener() { 304 | 305 | mSettingPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { 306 | @Override 307 | public void onDismiss() { 308 | hideBottomBar(); 309 | } 310 | }); 311 | mContentPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { 312 | @Override 313 | public void onDismiss() { 314 | lightOn(); 315 | hideBottomBar(); 316 | 317 | } 318 | }); 319 | 320 | mFontPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { 321 | @Override 322 | public void onDismiss() { 323 | lightOn(); 324 | hideBottomBar(); 325 | } 326 | }); 327 | 328 | mLabelPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { 329 | @Override 330 | public void onDismiss() { 331 | lightOn(); 332 | hideBottomBar(); 333 | } 334 | }); 335 | 336 | 337 | mSettingPopup.setOnSettingChangedListener(new SettingPopup.OnSettingChangedListener() { 338 | @Override 339 | public void onSizeChanged(int progress) { 340 | mPageList = mBookPageFactory.updateTextSize(progress + TEXT_SIZE_DELTA, mPowerPercent); 341 | mFlipView.updateBitmapList(mPageList); 342 | 343 | } 344 | 345 | @Override 346 | public void onThemeChanged(int theme) { 347 | setTheme(theme); 348 | mPageList = mBookPageFactory.updateTheme(theme, mPowerPercent); 349 | mFlipView.updateBitmapList(mPageList); 350 | } 351 | 352 | @Override 353 | public void onFlipStyleChanged(int style) { 354 | mFlipView.setFlipStyle(style); 355 | 356 | } 357 | 358 | 359 | }); 360 | 361 | 362 | mContentPopup.setOnContentClicked(new ContentPopup.OnContentSelectedListener() { 363 | 364 | @Override 365 | public void OnContentClicked(int paraIndex) { 366 | mPageList = mBookPageFactory.updatePagesByContent(paraIndex, mPowerPercent); 367 | mFlipView.setPageByContent(mPageList); 368 | 369 | mContentPopup.dismiss(); 370 | } 371 | }); 372 | 373 | mFontPopup.setOnFontSelectedListener(new FontPopup.OnFontSelectedListener() { 374 | 375 | @Override 376 | public void onTypefaceSelected(int typeIndex) { 377 | mPageList = mBookPageFactory.updateTypeface(typeIndex, mPowerPercent); 378 | mFlipView.updateBitmapList(mPageList); 379 | } 380 | 381 | @Override 382 | public void onColorSelected(int color) { 383 | 384 | mPageList = mBookPageFactory.updateTextColor(color, mPowerPercent); 385 | mFlipView.updateBitmapList(mPageList); 386 | 387 | 388 | } 389 | }); 390 | 391 | 392 | mLabelPopup.setOnLabelClicked(new LabelPopup.OnLabelSelectedListener() { 393 | @Override 394 | public void OnLabelClicked(Label label) { 395 | 396 | String objectStr = label.getReadInfoStr(); 397 | ReadInfo readInfo = SaveHelper.deserObject(objectStr); 398 | boolean isPrePageOver = label.isPrePageOver(); 399 | 400 | mBookPageFactory.setReadInfo(readInfo); 401 | mPageList = mBookPageFactory.drawCurTwoPages(mPowerPercent); 402 | 403 | mFlipView.setPrePageOver(isPrePageOver); 404 | mFlipView.updateBitmapList(mPageList); 405 | 406 | } 407 | }); 408 | 409 | 410 | } 411 | 412 | private void setTheme(int theme) { 413 | mBottomBar.setBackgroundColor(mBgColors[theme]); 414 | mContentPopup.setBackgroundColor(mBgColors[theme]); 415 | mLabelPopup.setBackgroundColor(mBgColors[theme]); 416 | } 417 | 418 | 419 | private void initViews(View v) { 420 | mFlipView = (FlipView) v.findViewById(R.id.flip_view); 421 | mBottomBar = (LinearLayout) v.findViewById(R.id.bottom_bar_layout); 422 | 423 | mBottomBtns = new Button[]{ 424 | (Button) v.findViewById(R.id.button_content), 425 | (Button) v.findViewById(R.id.button_setting), 426 | (Button) v.findViewById(R.id.button_font), 427 | (Button) v.findViewById(R.id.button_label) 428 | }; 429 | 430 | mContentPopup = new ContentPopup(mContext, mBook); 431 | mSettingPopup = new SettingPopup(mContext); 432 | mFontPopup = new FontPopup(mContext); 433 | mLabelPopup = new LabelPopup(mContext, mBookId); 434 | 435 | } 436 | 437 | 438 | private void showBottomBar() { 439 | mBottomBar.setVisibility(View.VISIBLE); 440 | isBottomBarShow = true; 441 | } 442 | 443 | private void hideBottomBar() { 444 | mBottomBar.setVisibility(View.INVISIBLE); 445 | isBottomBarShow = false; 446 | } 447 | 448 | private void lightOff() { 449 | //开启一个线程,使背景内容逐渐变暗 450 | new Thread(new Runnable() { 451 | @Override 452 | public void run() { 453 | while (mBackgroundAlpha > 0.4f) { 454 | try { 455 | Thread.sleep(8); 456 | } catch (InterruptedException e) { 457 | e.printStackTrace(); 458 | } 459 | mBackgroundAlpha -= 0.01f; 460 | Message message = mHandler.obtainMessage(); 461 | message.obj = mBackgroundAlpha; 462 | mHandler.sendMessage(message); 463 | 464 | } 465 | } 466 | }).start(); 467 | } 468 | 469 | private void lightOn() { 470 | //开启一个线程,使背景内容逐渐变暗 471 | new Thread(new Runnable() { 472 | @Override 473 | public void run() { 474 | while (mBackgroundAlpha < 1.0f) { 475 | try { 476 | Thread.sleep(8); 477 | } catch (InterruptedException e) { 478 | e.printStackTrace(); 479 | } 480 | mBackgroundAlpha += 0.01f; 481 | Message message = mHandler.obtainMessage(); 482 | message.obj = mBackgroundAlpha; 483 | mHandler.sendMessage(message); 484 | 485 | } 486 | } 487 | }).start(); 488 | } 489 | 490 | 491 | private class BatteryPowerReceiver extends BroadcastReceiver { 492 | @Override 493 | public void onReceive(Context context, Intent intent) { 494 | int current = intent.getExtras().getInt("level");// 获得当前电量 495 | int total = intent.getExtras().getInt("scale");// 获得总电量 496 | mPowerPercent = current * 1f / total; 497 | 498 | //首次获取电量后初始化flipView 499 | if (isFirstRead) { 500 | 501 | ReadInfo readInfo = SaveHelper.getObject(mContext, mBookId + SaveHelper.DRAW_INFO); 502 | 503 | if (readInfo != null) { 504 | mPageList = mBookPageFactory.drawCurTwoPages(mPowerPercent); 505 | mFlipView.updateBitmapList(mPageList); 506 | 507 | } else { 508 | mPageList.add(mBookPageFactory.drawNextPage(mPowerPercent)); 509 | mPageList.add(mBookPageFactory.drawNextPage(mPowerPercent)); 510 | mFlipView.setPrePageOver(false); 511 | mFlipView.updateBitmapList(mPageList); 512 | } 513 | 514 | isFirstRead = false; 515 | } 516 | 517 | 518 | } 519 | } 520 | 521 | 522 | } 523 | 524 | 525 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/ShelfActivity.java: -------------------------------------------------------------------------------- 1 | package com.ebook; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | public class ShelfActivity extends SingleFragmentActivity { 6 | 7 | @Override 8 | protected Fragment createFragment() { 9 | return new ShelfFragment(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/ShelfFragment.java: -------------------------------------------------------------------------------- 1 | package com.ebook; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.ImageView; 14 | 15 | import com.ebook.model.Book; 16 | import com.ebook.model.BookLab; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | 22 | /** 23 | * Created by Administrator on 2016/12/14. 24 | */ 25 | 26 | public class ShelfFragment extends Fragment { 27 | private Context mContext; 28 | private List mBookList; 29 | 30 | @Nullable 31 | @Override 32 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 33 | View v = inflater.inflate(R.layout.fragment_shelf_layout, container, false); 34 | initEvents(v); 35 | return v; 36 | 37 | } 38 | 39 | private void initEvents(View v) { 40 | mContext = getActivity(); 41 | mBookList = BookLab.newInstance(mContext).getBookList(); 42 | 43 | RecyclerView recyclerView = (RecyclerView) v.findViewById(R.id.fragment_book_shelf_recycler_view); 44 | recyclerView.setLayoutManager(new GridLayoutManager(mContext, 3)); 45 | recyclerView.setAdapter(new BookAdapter(mBookList)); 46 | 47 | } 48 | 49 | 50 | private class BookHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 51 | private ImageView mBookCover; 52 | private Book mBook; 53 | 54 | public BookHolder(View itemView) { 55 | super(itemView); 56 | mBookCover = (ImageView) itemView.findViewById(R.id.item_recycler_view_image_view); 57 | itemView.setOnClickListener(this); 58 | } 59 | 60 | public void bind(Book book) { 61 | mBook = book; 62 | mBookCover.setImageBitmap(mBook.getBookCover()); 63 | } 64 | 65 | @Override 66 | public void onClick(View v) { 67 | Intent intent = ReadingActivity.newIntent(mContext, mBookList.indexOf(mBook)); 68 | startActivity(intent); 69 | } 70 | 71 | 72 | } 73 | 74 | private class BookAdapter extends RecyclerView.Adapter { 75 | private List bookList = new ArrayList<>(); 76 | 77 | public BookAdapter(List bookList) { 78 | this.bookList = bookList; 79 | } 80 | 81 | @Override 82 | public BookHolder onCreateViewHolder(ViewGroup parent, int viewType) { 83 | LayoutInflater inflater = LayoutInflater.from(mContext); 84 | View view = inflater.inflate(R.layout.item_recycler_view_shelf, parent, false); 85 | 86 | return new BookHolder(view); 87 | } 88 | 89 | @Override 90 | public void onBindViewHolder(BookHolder holder, int position) { 91 | holder.bind(bookList.get(position)); 92 | } 93 | 94 | @Override 95 | public int getItemCount() { 96 | return bookList.size(); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/SingleFragmentActivity.java: -------------------------------------------------------------------------------- 1 | package com.ebook; 2 | 3 | import android.os.Bundle; 4 | import android.os.PersistableBundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.WindowManager; 10 | 11 | /** 12 | * Created by xyy on 2017/3/31. 13 | */ 14 | 15 | public abstract class SingleFragmentActivity extends AppCompatActivity { 16 | 17 | 18 | /** 19 | * @function 返回托管的fragment 20 | */ 21 | protected abstract Fragment createFragment(); 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setScreen(); 27 | setContentView(R.layout.activity_fragment); 28 | 29 | FragmentManager fm = getSupportFragmentManager(); 30 | Fragment fragment = fm.findFragmentById(R.id.fragment_container); 31 | if (fragment == null) { 32 | fragment = createFragment(); 33 | fm.beginTransaction().add(R.id.fragment_container, fragment).commit(); 34 | } 35 | } 36 | 37 | /** 38 | * @function 设置屏幕显示状态 39 | */ 40 | protected void setScreen() { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/model/Book.java: -------------------------------------------------------------------------------- 1 | package com.ebook.model; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Created by Mum on 2017/1/24. 12 | */ 13 | public class Book { 14 | private static final String TAG = "Book"; 15 | private String mBookTitle; 16 | private Bitmap mBookCover; 17 | //格式化文本,将文本以段落为单位保存 18 | private List mParagraphList; 19 | 20 | //目录集合(卷/章/回/集等) 21 | private List mBookContents; 22 | //目录对应的在段落集合中的索引 23 | private List mContentParaIndexs; 24 | 25 | //空两格 26 | private String mSpace = "\t\t\t\t\t\t"; 27 | 28 | public Book(String bookTitle, Bitmap bookCover, String fullText) { 29 | mParagraphList = new ArrayList<>(); 30 | mBookContents = new ArrayList<>(); 31 | mContentParaIndexs=new ArrayList<>(); 32 | 33 | mBookTitle = bookTitle; 34 | mBookCover = bookCover; 35 | 36 | formatText(fullText); 37 | 38 | findContents(mParagraphList); 39 | } 40 | 41 | //格式化文本,将文本以段落为单位保存 42 | private void formatText(String text) { 43 | boolean isFirstParas = true; 44 | String paragraph = ""; 45 | 46 | //按段落切分文本 47 | String[] paragraphs = text.split("\\s{2,}"); 48 | //格式化段落 49 | for (int i = 0; i < paragraphs.length; i++) { 50 | if (paragraphs[i].isEmpty()) { 51 | continue; 52 | } 53 | if (isFirstParas) { 54 | paragraph = mSpace + paragraphs[i]; 55 | isFirstParas = false; 56 | } else { 57 | paragraph = "\n" + mSpace + paragraphs[i]; 58 | 59 | } 60 | 61 | mParagraphList.add(paragraph); 62 | 63 | } 64 | 65 | } 66 | 67 | 68 | 69 | private void findContents(List paraList) { 70 | //字符串匹配模式 71 | String patternString = "第\\S{2,4}\\s\\S{2,}"; 72 | Pattern pattern = Pattern.compile(patternString); 73 | 74 | for (String para:paraList) { 75 | 76 | Matcher matcher = pattern.matcher(para); 77 | 78 | if (matcher.find()){ 79 | 80 | //除去段首多余空格 81 | int start = matcher.start(); 82 | int end = matcher.end(); 83 | String subString = para.substring(start, end); 84 | 85 | mBookContents.add(subString); //目录 86 | mContentParaIndexs.add(paraList.indexOf(para)); //目录对应的在段落集合中的索引 87 | 88 | } 89 | 90 | } 91 | 92 | } 93 | 94 | 95 | public String getBookTitle() { 96 | return mBookTitle; 97 | } 98 | 99 | public Bitmap getBookCover() { 100 | return mBookCover; 101 | } 102 | 103 | public List getParagraphList() { 104 | return mParagraphList; 105 | } 106 | 107 | public List getBookContents() { 108 | return mBookContents; 109 | } 110 | 111 | public List getContentParaIndexs() { 112 | return mContentParaIndexs; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/model/BookLab.java: -------------------------------------------------------------------------------- 1 | package com.ebook.model; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by Mum on 2017/1/24. 17 | */ 18 | 19 | public class BookLab { 20 | public static final String TEXT = "text"; 21 | public static final String IMAGE = "image"; 22 | private static BookLab sBookLab; 23 | 24 | private AssetManager mAssetManager; 25 | private List mBookList; 26 | //assets中的文件名清单 27 | private String[] mAssetsImageList; 28 | private String[] mAssetsTextList; 29 | 30 | 31 | private BookLab(Context context) { 32 | mAssetManager = context.getAssets(); 33 | loadAssetsFiles(); 34 | } 35 | 36 | 37 | public static BookLab newInstance(Context context) { 38 | if (sBookLab == null) { 39 | sBookLab = new BookLab(context); 40 | } 41 | return sBookLab; 42 | } 43 | 44 | //加载assets中的文件 45 | private void loadAssetsFiles() { 46 | mBookList = new ArrayList<>(); 47 | //获取image、text中的文件名清单 48 | try { 49 | mAssetsImageList = mAssetManager.list(IMAGE); 50 | mAssetsTextList = mAssetManager.list(TEXT); 51 | } catch (IOException e) { 52 | e.printStackTrace(); 53 | } 54 | 55 | 56 | for (int i = 0; i < mAssetsTextList.length; i++) { 57 | //获取书名 58 | String[] nameSplit = mAssetsTextList[i].split("_"); 59 | String nameSecond = nameSplit[nameSplit.length - 1]; 60 | String bookTitle = nameSecond.replace(".txt", ""); 61 | 62 | //获取封面 63 | String imagePath = IMAGE + "/" + mAssetsImageList[i]; 64 | Bitmap bookCover = loadImage(imagePath); 65 | 66 | //获取文本 67 | String textPath = TEXT + "/" + mAssetsTextList[i]; 68 | String bodyText = loadText(textPath); 69 | 70 | 71 | Book book = new Book(bookTitle, bookCover, bodyText); 72 | mBookList.add(book); 73 | 74 | } 75 | 76 | } 77 | 78 | 79 | //从assets中读取文本 80 | private String loadText(String path) { 81 | InputStream in = null; 82 | BufferedReader reader = null; 83 | StringBuilder stringBuilder = new StringBuilder(); 84 | 85 | try { 86 | in = mAssetManager.open(path); 87 | reader = new BufferedReader(new InputStreamReader(in)); 88 | 89 | String line = ""; 90 | while ((line = reader.readLine()) != null) { 91 | stringBuilder.append(line); 92 | } 93 | } catch (IOException e) { 94 | e.printStackTrace(); 95 | } finally { 96 | if (reader != null) { 97 | try { 98 | reader.close(); 99 | } catch (IOException e) { 100 | e.printStackTrace(); 101 | } 102 | } 103 | } 104 | 105 | return stringBuilder.toString(); 106 | 107 | } 108 | 109 | //从assets中读取图片 110 | private Bitmap loadImage(String path) { 111 | Bitmap image = null; 112 | InputStream in = null; 113 | try { 114 | in = mAssetManager.open(path); 115 | image = BitmapFactory.decodeStream(in); 116 | 117 | } catch (IOException e) { 118 | e.printStackTrace(); 119 | } finally { 120 | try { 121 | in.close(); 122 | } catch (IOException e) { 123 | e.printStackTrace(); 124 | } 125 | 126 | } 127 | return image; 128 | } 129 | 130 | public List getBookList() { 131 | return mBookList; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/util/SaveHelper.java: -------------------------------------------------------------------------------- 1 | package com.ebook.util; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | import android.util.Base64; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.ObjectInputStream; 12 | import java.io.ObjectOutputStream; 13 | import java.io.StreamCorruptedException; 14 | 15 | /** 16 | * Created by Administrator on 2016/12/16. 17 | */ 18 | 19 | public class SaveHelper { 20 | public static final String THEME = "SettingPopup_Theme"; 21 | public static final String FLIP_STYLE = "SettingPopup_FlipStyle"; 22 | 23 | public static final String DRAW_INFO = "BookPageFactory_draw_info"; 24 | public static final String PAINT_INFO = "BookPageFactory_paint_info"; 25 | 26 | public static final String IS_PRE_PAGE_OVER = "FlipView_IsPrePageOver"; 27 | 28 | 29 | public static void save(Context context, String key, int value) { 30 | PreferenceManager.getDefaultSharedPreferences(context) 31 | .edit() 32 | .putInt(key, value) 33 | .apply(); 34 | 35 | } 36 | 37 | public static void save(Context context, String key, float value) { 38 | PreferenceManager.getDefaultSharedPreferences(context) 39 | .edit() 40 | .putFloat(key, value) 41 | .apply(); 42 | 43 | } 44 | 45 | public static void save(Context context, String key, boolean is) { 46 | PreferenceManager.getDefaultSharedPreferences(context) 47 | .edit() 48 | .putBoolean(key, is) 49 | .apply(); 50 | 51 | } 52 | 53 | 54 | public static void saveObject(Context context, String key, Object object) { 55 | //序列化对象,编码成String 56 | String objectStr = serObject(object); 57 | 58 | PreferenceManager.getDefaultSharedPreferences(context) 59 | .edit() 60 | .putString(key, objectStr) 61 | .apply(); 62 | 63 | } 64 | 65 | 66 | public static int getInt(Context context, String key) { 67 | return PreferenceManager.getDefaultSharedPreferences(context).getInt(key, 0); 68 | 69 | } 70 | 71 | public static float getFloat(Context context, String key) { 72 | return PreferenceManager.getDefaultSharedPreferences(context).getFloat(key, 0f); 73 | 74 | } 75 | 76 | public static boolean getBoolean(Context context, String key) { 77 | return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(key, false); 78 | 79 | } 80 | 81 | 82 | 83 | public static T getObject(Context context, String key) { 84 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); 85 | if (sp.contains(key)){ 86 | String objectStr =sp.getString(key, null); 87 | T t = deserObject(objectStr); 88 | return t; 89 | } 90 | return null; 91 | 92 | } 93 | 94 | //序列化对象 95 | public static String serObject(Object object) { 96 | String objectStr = ""; 97 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 98 | ObjectOutputStream out = null; 99 | try { 100 | out = new ObjectOutputStream(baos); 101 | out.writeObject(object); 102 | objectStr = new String(Base64.encode(baos.toByteArray(), Base64.DEFAULT)); 103 | 104 | } catch (IOException e) { 105 | e.printStackTrace(); 106 | } finally { 107 | try { 108 | if (baos != null) { 109 | baos.close(); 110 | } 111 | if (out != null) { 112 | out.close(); 113 | } 114 | } catch (IOException e) { 115 | e.printStackTrace(); 116 | } 117 | } 118 | return objectStr; 119 | } 120 | 121 | //反序列化获得对象 122 | public static T deserObject(String objectStr) { 123 | 124 | byte[] buffer = Base64.decode(objectStr, Base64.DEFAULT); 125 | ByteArrayInputStream bais = new ByteArrayInputStream(buffer); 126 | ObjectInputStream ois = null; 127 | try { 128 | ois = new ObjectInputStream(bais); 129 | T t = (T) ois.readObject(); 130 | return t; 131 | 132 | } catch (StreamCorruptedException e) { 133 | e.printStackTrace(); 134 | } catch (IOException e) { 135 | e.printStackTrace(); 136 | } catch (ClassNotFoundException e) { 137 | e.printStackTrace(); 138 | } finally { 139 | try { 140 | if (bais != null) { 141 | bais.close(); 142 | } 143 | if (ois != null) { 144 | ois.close(); 145 | } 146 | } catch (IOException e) { 147 | e.printStackTrace(); 148 | } 149 | } 150 | return null; 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/util/ScreenBrightnessHelper.java: -------------------------------------------------------------------------------- 1 | package com.ebook.util; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.provider.Settings; 6 | 7 | /** 8 | * Created by Administrator on 2017/1/13. 9 | */ 10 | 11 | public class ScreenBrightnessHelper { 12 | 13 | //设置系统屏幕亮度值,在设置之前先关闭亮度自动调节,设为手动模式 14 | public static void setBrightness(Context context, int value) { 15 | if (isAutoBrightness(context)){ 16 | closeAutoBrightness(context); 17 | } 18 | ContentResolver contentResolver = context.getContentResolver(); 19 | Settings.System.putInt(contentResolver, 20 | Settings.System.SCREEN_BRIGHTNESS, value); 21 | } 22 | 23 | 24 | //获取当前系统屏幕亮度,获取失败返回-1 25 | public static int getBrightness(Context context) 26 | { 27 | int brightnessValue = -1; 28 | try 29 | { 30 | brightnessValue = Settings.System. 31 | getInt(context.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS); 32 | } 33 | catch (Exception e) 34 | { 35 | e.printStackTrace(); 36 | } 37 | return brightnessValue; 38 | } 39 | 40 | 41 | //判断系统是否打开了自动调节亮度 42 | public static boolean isAutoBrightness(Context context) 43 | { 44 | boolean autoBrightness = false; 45 | try 46 | { 47 | autoBrightness = Settings.System.getInt(context.getContentResolver() , Settings.System.SCREEN_BRIGHTNESS_MODE) 48 | == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; 49 | } 50 | catch (Exception e) 51 | { 52 | e.printStackTrace(); 53 | } 54 | return autoBrightness; 55 | } 56 | 57 | 58 | //关闭自动调节亮度,设为手动模式 59 | public static void closeAutoBrightness(Context context) 60 | { 61 | Settings.System.putInt(context.getContentResolver(), 62 | Settings.System.SCREEN_BRIGHTNESS_MODE, 63 | Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); 64 | } 65 | 66 | //打开自动调节亮度 67 | public static void openAutoBrightness(Context context) 68 | { 69 | Settings.System.putInt(context.getContentResolver(), 70 | Settings.System.SCREEN_BRIGHTNESS_MODE, 71 | Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); 72 | } 73 | 74 | 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/util/bookPageUtil/BookPageFactory.java: -------------------------------------------------------------------------------- 1 | package com.ebook.util.bookPageUtil; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.RectF; 9 | import android.graphics.Typeface; 10 | import android.text.format.Time; 11 | import android.util.DisplayMetrics; 12 | import android.view.WindowManager; 13 | 14 | import com.ebook.model.Book; 15 | import com.ebook.model.BookLab; 16 | import com.ebook.util.SaveHelper; 17 | import com.ebook.view.popupWindow.FontPopup; 18 | 19 | import java.io.IOException; 20 | import java.text.DecimalFormat; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | /** 25 | * Created by Mum on 2017/2/6. 26 | */ 27 | 28 | public class BookPageFactory { 29 | private Context mContext; 30 | private int mWidth; 31 | private int mHeight; 32 | private int marginWidth; 33 | private int marginHeight; 34 | private int mBookId; 35 | 36 | //绘制正文区域 37 | private float mVisibleWidth; 38 | private float mVisibleHeight; 39 | 40 | private float mLineHeight; //行高 41 | private int mLineCount; //一页能容纳的行数 42 | 43 | private List mParaList; //文本段落集合 44 | private List mContents; //目录集合(卷/章/回/集等) 45 | private List mContentParaIndex; //目录对应的在段落集合中的索引 46 | private int mParaListSize; 47 | 48 | private List mPageLines = new ArrayList<>(); 49 | private String mCurContent;//当前page对应的目录 50 | private Paint mPaint; 51 | 52 | private int[] mBgColors; 53 | private int[] mTextColors; 54 | 55 | private List mTypefaceList = new ArrayList<>(); 56 | 57 | private PaintInfo mPaintInfo; 58 | private ReadInfo mReadInfo; 59 | private String percentStr; 60 | 61 | 62 | public BookPageFactory(Context context, int bookId) { 63 | mContext = context; 64 | mBookId = bookId; 65 | calWidthAndHeight(); 66 | getFontFromAssets(); 67 | 68 | initDatas(); 69 | } 70 | 71 | private void initDatas() { 72 | Book book = BookLab.newInstance(mContext).getBookList().get(mBookId); 73 | mParaList = book.getParagraphList(); 74 | mParaListSize = mParaList.size(); 75 | mContents = book.getBookContents(); 76 | mContentParaIndex = book.getContentParaIndexs(); 77 | 78 | marginWidth = (int) (mWidth / 30f); 79 | marginHeight = (int) (mHeight / 60f); 80 | mVisibleWidth = mWidth - marginWidth * 2; 81 | mVisibleHeight = mHeight - marginHeight * 2; 82 | 83 | mBgColors = new int[]{ 84 | 0xffe7dcbe, //复古 85 | 0xffffffff, // 常规 86 | 0xffcbe1cf, //护眼 87 | 0xff333232 //夜间 88 | }; 89 | 90 | mTextColors = new int[]{ 91 | 0x8A000000, 92 | 0x8A000000, 93 | 0x8A000000, 94 | 0xffa9a8a8 //夜间 95 | }; 96 | 97 | 98 | PaintInfo paintInfo = SaveHelper.getObject(mContext, SaveHelper.PAINT_INFO); 99 | if (paintInfo != null) 100 | mPaintInfo = paintInfo; 101 | else 102 | mPaintInfo = new PaintInfo(); 103 | 104 | mLineHeight = mPaintInfo.textSize * 1.5f; 105 | mLineCount = (int) (mVisibleHeight / mLineHeight) - 1; 106 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 107 | mPaint.setTextAlign(Paint.Align.LEFT); 108 | 109 | mPaint.setColor(mPaintInfo.textColor); 110 | mPaint.setTextSize(mPaintInfo.textSize); 111 | mPaint.setTypeface(mTypefaceList.get(mPaintInfo.typeIndex)); 112 | 113 | ReadInfo info = SaveHelper.getObject(mContext, mBookId + SaveHelper.DRAW_INFO); 114 | if (info != null) 115 | mReadInfo = info; 116 | else 117 | mReadInfo = new ReadInfo(); 118 | } 119 | 120 | public Bitmap drawNextPage(float powerPercent) { 121 | if (!mReadInfo.isLastNext) { 122 | pageDown(); 123 | mReadInfo.isLastNext = true; 124 | 125 | } 126 | 127 | Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 128 | Canvas canvas = new Canvas(bitmap); 129 | canvas.drawColor(mPaintInfo.bgColor); 130 | 131 | //下一页 132 | mPageLines = getNextPageLines(); 133 | //已经到最后一页了 134 | if (mPageLines.size() == 0 || mPageLines == null) { 135 | return null; 136 | } 137 | 138 | float y = mPaintInfo.textSize; 139 | 140 | for (String strLine : mPageLines) { 141 | y += mLineHeight; 142 | canvas.drawText(strLine, marginWidth, y, mPaint); 143 | } 144 | 145 | //绘制显示在底部的信息 146 | drawInfo(canvas, powerPercent); 147 | 148 | return bitmap; 149 | 150 | } 151 | 152 | 153 | public Bitmap drawPrePage(float powerPercent) { 154 | if (mReadInfo.isLastNext) { 155 | pageUp(); 156 | 157 | mReadInfo.isLastNext = false; 158 | } 159 | 160 | Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 161 | Canvas canvas = new Canvas(bitmap); 162 | canvas.drawColor(mPaintInfo.bgColor); 163 | 164 | //下一页 165 | mPageLines = getPrePageLines(); 166 | //已经到第一页了 167 | if (mPageLines.size() == 0 || mPageLines == null) { 168 | return null; 169 | } 170 | 171 | float y = mPaintInfo.textSize; 172 | 173 | for (String strLine : mPageLines) { 174 | y += mLineHeight; 175 | canvas.drawText(strLine, marginWidth, y, mPaint); 176 | } 177 | 178 | //绘制显示的信息 179 | drawInfo(canvas, powerPercent); 180 | 181 | return bitmap; 182 | 183 | } 184 | 185 | 186 | public List updatePagesByContent(int nextParaIndex, float powerPercent) { 187 | mReadInfo.nextParaIndex = nextParaIndex; 188 | 189 | if (mReadInfo.nextParaIndex == 1) //第一章和卷名一起处理 190 | mReadInfo.nextParaIndex = 0; 191 | reset(); 192 | 193 | mReadInfo.isLastNext = true;//设置为直接往后读 194 | List bitmaps = new ArrayList<>(); 195 | bitmaps.add(drawNextPage(powerPercent)); 196 | bitmaps.add(drawNextPage(powerPercent)); 197 | 198 | return bitmaps; 199 | 200 | } 201 | 202 | 203 | public List updateTheme(int theme, float powerPercent) { 204 | mPaintInfo.bgColor = mBgColors[theme]; 205 | mPaintInfo.textColor = mTextColors[theme]; 206 | return drawCurTwoPages(powerPercent); 207 | } 208 | 209 | public List updateTypeface(int typeIndex, float powerPercent) { 210 | mPaintInfo.typeIndex = typeIndex; 211 | return drawCurTwoPages(powerPercent); 212 | } 213 | 214 | public List updateTextSize(int textSize, float powerPercent) { 215 | mPaintInfo.textSize = textSize; 216 | mLineHeight = textSize * 1.5f; 217 | mLineCount = (int) (mVisibleHeight / mLineHeight) - 1; 218 | return drawCurTwoPages(powerPercent); 219 | } 220 | 221 | public List updateTextColor(int textColor, float powerPercent) { 222 | mPaintInfo.textColor = textColor; 223 | return drawCurTwoPages(powerPercent); 224 | } 225 | 226 | public List drawCurTwoPages(float powerPercent) { 227 | 228 | setIndexToCurStart(); 229 | 230 | mPaint.setColor(mPaintInfo.textColor); 231 | mPaint.setTextSize(mPaintInfo.textSize); 232 | mPaint.setTypeface(mTypefaceList.get(mPaintInfo.typeIndex)); 233 | 234 | List bitmaps = new ArrayList<>(); 235 | if (mReadInfo.isLastNext) { 236 | bitmaps.add(drawNextPage(powerPercent)); 237 | bitmaps.add(drawNextPage(powerPercent)); 238 | } else { 239 | bitmaps.add(drawPrePage(powerPercent)); 240 | bitmaps.add(0, drawPrePage(powerPercent)); 241 | } 242 | 243 | return bitmaps; 244 | } 245 | 246 | private void setIndexToCurStart() { 247 | 248 | if (mReadInfo.isLastNext) { 249 | pageUp(); 250 | mReadInfo.nextParaIndex += 1; 251 | 252 | if (!mReadInfo.isPreRes) 253 | return; 254 | 255 | String string = mParaList.get(mReadInfo.nextParaIndex); 256 | 257 | while (string.length() > 0) { 258 | //检测一行能够显示多少字 259 | int size = mPaint.breakText(string, true, mVisibleWidth, null); 260 | 261 | mReadInfo.nextResLines.add(string.substring(0, size)); 262 | 263 | string = string.substring(size); 264 | 265 | } 266 | 267 | mReadInfo.nextResLines.clear(); 268 | mReadInfo.isNextRes = true; 269 | mReadInfo.nextParaIndex += 1; 270 | 271 | mReadInfo.preResLines.clear(); 272 | mReadInfo.isPreRes = false; 273 | 274 | 275 | } else { 276 | pageDown(); 277 | mReadInfo.nextParaIndex -= 1; 278 | 279 | if (!mReadInfo.isNextRes) 280 | return; 281 | 282 | String string = mParaList.get(mReadInfo.nextParaIndex); 283 | 284 | while (string.length() > 0) { 285 | //检测一行能够显示多少字 286 | int size = mPaint.breakText(string, true, mVisibleWidth, null); 287 | 288 | mReadInfo.preResLines.add(string.substring(0, size)); 289 | 290 | string = string.substring(size); 291 | 292 | } 293 | 294 | mReadInfo.preResLines.removeAll(mReadInfo.nextResLines); 295 | mReadInfo.isPreRes = true; 296 | mReadInfo.nextParaIndex -= 1; 297 | 298 | mReadInfo.nextResLines.removeAll(mReadInfo.preResLines); 299 | mReadInfo.isNextRes = false; 300 | 301 | 302 | } 303 | 304 | 305 | } 306 | 307 | private String findContent(int paraIndex) { //找到当前page对应的目录 308 | for (int i = 0; i < mContentParaIndex.size() - 1; i++) { 309 | if (paraIndex >= mContentParaIndex.get(i) && paraIndex < mContentParaIndex.get(i + 1)) { 310 | if (i == 0) 311 | i = 1; //合并卷名和第一章 312 | 313 | return mContents.get(i); 314 | } 315 | 316 | } 317 | 318 | return mContents.get(mContentParaIndex.size() - 1); 319 | 320 | } 321 | 322 | private void drawInfo(Canvas canvas, float powerPercent) { 323 | 324 | Paint infoPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 325 | infoPaint.setTextAlign(Paint.Align.LEFT); 326 | infoPaint.setTextSize(32); 327 | infoPaint.setColor(0xff5c5c5c); 328 | 329 | float offsetY = mHeight - marginHeight; 330 | 331 | //当前page对应的目录 332 | canvas.drawText(mCurContent, marginWidth, marginHeight, infoPaint); 333 | 334 | //阅读进度 335 | float percent = mReadInfo.nextParaIndex * 1.0f / mParaListSize; 336 | DecimalFormat df = new DecimalFormat("#0.00"); 337 | percentStr = df.format(percent * 100) + "%"; 338 | canvas.drawText(percentStr, marginWidth, offsetY, infoPaint); 339 | 340 | //当前系统时间 341 | Time time = new Time(); 342 | time.setToNow(); // 取得系统时间。 343 | int hour = time.hour; // 0-23 344 | int minute = time.minute; 345 | String timeStr = ""; 346 | if (minute < 10) { 347 | timeStr = hour + ":0" + minute; 348 | } else { 349 | timeStr = hour + ":" + minute; 350 | } 351 | canvas.drawText(timeStr, mWidth - 7f * marginWidth, offsetY, infoPaint); 352 | 353 | //电池电量 354 | infoPaint.reset(); 355 | infoPaint.setStyle(Paint.Style.STROKE); 356 | infoPaint.setStrokeWidth(1); 357 | infoPaint.setColor(0xff5c5c5c); 358 | 359 | float left = mWidth - 3.8f * marginWidth; 360 | float right = mWidth - 2.2f * marginWidth; 361 | float height = 0.8f * marginHeight; 362 | 363 | //电池左边部分外框 364 | RectF rectF = new RectF(left, offsetY - height, right, offsetY); 365 | canvas.drawRect(rectF, infoPaint); 366 | 367 | //电池左边部分内部电量区域 368 | infoPaint.setStyle(Paint.Style.FILL); 369 | 370 | float width = (right - left) * powerPercent; 371 | rectF = new RectF(left + 1.5f, offsetY - height + 1.5f, left + width - 1.5f, offsetY - 1.5f); 372 | canvas.drawRect(rectF, infoPaint); 373 | 374 | //电池右边小矩形 375 | rectF = new RectF(right, offsetY - 0.7f * height, right + 0.2f * marginWidth, offsetY - 0.3f * height); 376 | canvas.drawRect(rectF, infoPaint); 377 | } 378 | 379 | private List getNextPageLines() { 380 | 381 | String string = ""; 382 | 383 | List lines = new ArrayList<>(); 384 | 385 | if (mReadInfo.isNextRes) { 386 | lines.addAll(mReadInfo.nextResLines); 387 | 388 | mReadInfo.nextResLines.clear(); 389 | 390 | mReadInfo.isNextRes = false; 391 | } 392 | 393 | if (mReadInfo.nextParaIndex >= mParaListSize) { 394 | return lines; 395 | } 396 | 397 | mCurContent = findContent(mReadInfo.nextParaIndex); 398 | 399 | while (lines.size() < mLineCount && mReadInfo.nextParaIndex < mParaListSize) { 400 | 401 | string = mParaList.get(mReadInfo.nextParaIndex); 402 | 403 | mReadInfo.nextParaIndex++; 404 | 405 | while (string.length() > 0) { 406 | //检测一行能够显示多少字 407 | int size = mPaint.breakText(string, true, mVisibleWidth, null); 408 | 409 | lines.add(string.substring(0, size)); 410 | 411 | string = string.substring(size); 412 | 413 | } 414 | 415 | } 416 | 417 | while (lines.size() > mLineCount) { 418 | mReadInfo.isNextRes = true; 419 | 420 | int end = lines.size() - 1; 421 | 422 | mReadInfo.nextResLines.add(0, lines.get(end)); 423 | 424 | lines.remove(end); 425 | } 426 | 427 | return lines; 428 | } 429 | 430 | 431 | private List getPrePageLines() { 432 | 433 | String string = ""; 434 | List lines = new ArrayList<>(); 435 | 436 | if (mReadInfo.isPreRes) { 437 | 438 | lines.addAll(mReadInfo.preResLines); 439 | 440 | mReadInfo.preResLines.clear(); 441 | 442 | mReadInfo.isPreRes = false; 443 | } 444 | 445 | if (mReadInfo.nextParaIndex < 0) { 446 | 447 | return lines; 448 | } 449 | 450 | mCurContent = findContent(mReadInfo.nextParaIndex); 451 | 452 | while (lines.size() < mLineCount && mReadInfo.nextParaIndex >= 0) { 453 | 454 | List paraLines = new ArrayList<>(); 455 | 456 | string = mParaList.get(mReadInfo.nextParaIndex); 457 | 458 | mReadInfo.nextParaIndex--; 459 | 460 | while (string.length() > 0) { 461 | //检测一行能够显示多少字 462 | int size = mPaint.breakText(string, true, mVisibleWidth, null); 463 | 464 | paraLines.add(string.substring(0, size)); 465 | 466 | string = string.substring(size); 467 | 468 | } 469 | 470 | lines.addAll(0, paraLines); 471 | 472 | } 473 | 474 | while (lines.size() > mLineCount) { 475 | mReadInfo.isPreRes = true; 476 | 477 | mReadInfo.preResLines.add(lines.get(0)); 478 | 479 | lines.remove(0); 480 | } 481 | 482 | return lines; 483 | 484 | } 485 | 486 | //向后移动两页的距离 487 | private void pageDown() { 488 | mReadInfo.nextParaIndex += 1;//移动到最后已读的段落 489 | 490 | String string = ""; 491 | 492 | List lines = new ArrayList<>(); 493 | 494 | int totalLines = 2 * mLineCount + mReadInfo.preResLines.size(); 495 | 496 | reset(); 497 | 498 | while (lines.size() < totalLines && mReadInfo.nextParaIndex < mParaListSize) { 499 | 500 | string = mParaList.get(mReadInfo.nextParaIndex); 501 | 502 | mReadInfo.nextParaIndex++; 503 | 504 | while (string.length() > 0) { 505 | //检测一行能够显示多少字 506 | int size = mPaint.breakText(string, true, mVisibleWidth, null); 507 | 508 | lines.add(string.substring(0, size)); 509 | 510 | string = string.substring(size); 511 | 512 | } 513 | 514 | } 515 | 516 | while (lines.size() > totalLines) { 517 | mReadInfo.isNextRes = true; 518 | 519 | int end = lines.size() - 1; 520 | 521 | mReadInfo.nextResLines.add(0, lines.get(end)); 522 | 523 | lines.remove(end); 524 | } 525 | 526 | 527 | } 528 | 529 | //向前移动两页的距离 530 | private void pageUp() { 531 | mReadInfo.nextParaIndex -= 1; //移动到最后已读的段落 532 | 533 | String string = ""; 534 | 535 | List lines = new ArrayList<>(); 536 | 537 | int totalLines = 2 * mLineCount + mReadInfo.nextResLines.size(); 538 | 539 | reset(); 540 | 541 | while (lines.size() < totalLines && mReadInfo.nextParaIndex >= 0) { 542 | 543 | List paraLines = new ArrayList<>(); 544 | 545 | string = mParaList.get(mReadInfo.nextParaIndex); 546 | 547 | mReadInfo.nextParaIndex--; 548 | 549 | while (string.length() > 0) { 550 | //检测一行能够显示多少字 551 | int size = mPaint.breakText(string, true, mVisibleWidth, null); 552 | 553 | paraLines.add(string.substring(0, size)); 554 | 555 | string = string.substring(size); 556 | 557 | } 558 | 559 | lines.addAll(0, paraLines); 560 | 561 | } 562 | 563 | while (lines.size() > totalLines) { 564 | mReadInfo.isPreRes = true; 565 | 566 | mReadInfo.preResLines.add(lines.get(0)); 567 | 568 | lines.remove(0); 569 | } 570 | 571 | } 572 | 573 | private void reset() { 574 | mReadInfo.preResLines.clear(); 575 | mReadInfo.isPreRes = false; 576 | 577 | mReadInfo.nextResLines.clear(); 578 | mReadInfo.isNextRes = false; 579 | } 580 | 581 | //获取屏幕的宽高 582 | private void calWidthAndHeight() { 583 | WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 584 | DisplayMetrics metrics = new DisplayMetrics(); 585 | windowManager.getDefaultDisplay().getMetrics(metrics); 586 | 587 | mWidth = metrics.widthPixels; 588 | mHeight = metrics.heightPixels; 589 | 590 | } 591 | 592 | private void getFontFromAssets() { 593 | mTypefaceList.add(Typeface.DEFAULT); 594 | 595 | String[] fontNameList = null; 596 | AssetManager assetManager = mContext.getAssets(); 597 | try { 598 | fontNameList = assetManager.list(FontPopup.FONTS); 599 | } catch (IOException e) { 600 | e.printStackTrace(); 601 | } 602 | 603 | for (int i = 0; i < fontNameList.length; i++) { 604 | 605 | String fontPath = FontPopup.FONTS + "/" + fontNameList[i]; 606 | Typeface typeface = Typeface.createFromAsset(assetManager, fontPath);//根据路径得到Typeface 607 | mTypefaceList.add(typeface); 608 | } 609 | 610 | } 611 | 612 | public ReadInfo getReadInfo() { 613 | return mReadInfo; 614 | } 615 | 616 | 617 | public PaintInfo getPaintInfo() { 618 | return mPaintInfo; 619 | } 620 | 621 | public void setReadInfo(ReadInfo readInfo) { 622 | mReadInfo = readInfo; 623 | } 624 | 625 | public String getCurContent() { 626 | return mCurContent; 627 | } 628 | 629 | public String getPercentStr() { 630 | return percentStr; 631 | } 632 | } 633 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/util/bookPageUtil/Label.java: -------------------------------------------------------------------------------- 1 | package com.ebook.util.bookPageUtil; 2 | 3 | 4 | import org.litepal.crud.DataSupport; 5 | 6 | /** 7 | * Created by Administrator on 2017/2/27. 8 | */ 9 | 10 | public class Label extends DataSupport{ 11 | private int mBookId; 12 | private String mDetails; 13 | private String mProgress; 14 | private String mTime; 15 | private boolean isPrePageOver; 16 | private String readInfoStr; //readInfo对象序列化编码后的String 17 | 18 | 19 | public int getBookId() { 20 | return mBookId; 21 | } 22 | 23 | 24 | public boolean isPrePageOver() { 25 | return isPrePageOver; 26 | } 27 | 28 | public String getTime() { 29 | return mTime; 30 | } 31 | 32 | public String getProgress() { 33 | return mProgress; 34 | } 35 | 36 | public String getDetails() { 37 | return mDetails; 38 | } 39 | 40 | public String getReadInfoStr() { 41 | return readInfoStr; 42 | } 43 | 44 | 45 | public void setBookId(int bookId) { 46 | mBookId = bookId; 47 | } 48 | 49 | public void setDetails(String details) { 50 | mDetails = details; 51 | } 52 | 53 | public void setProgress(String progress) { 54 | mProgress = progress; 55 | } 56 | 57 | public void setTime(String time) { 58 | mTime = time; 59 | } 60 | 61 | public void setPrePageOver(boolean prePageOver) { 62 | isPrePageOver = prePageOver; 63 | } 64 | 65 | 66 | public void setReadInfoStr(String readInfoStr) { 67 | this.readInfoStr = readInfoStr; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/util/bookPageUtil/PaintInfo.java: -------------------------------------------------------------------------------- 1 | package com.ebook.util.bookPageUtil; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by Administrator on 2017/2/24. 7 | */ 8 | 9 | public class PaintInfo implements Serializable { 10 | public int bgColor = 0xffe7dcbe; //背景颜色 11 | public int textColor = 0x8A000000; //字体颜色 12 | public int textSize = 55; //字体大小 13 | public int typeIndex; //字体索引 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/util/bookPageUtil/ReadInfo.java: -------------------------------------------------------------------------------- 1 | package com.ebook.util.bookPageUtil; 2 | 3 | 4 | import java.io.Serializable; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by Administrator on 2017/2/23. 10 | */ 11 | 12 | public class ReadInfo implements Serializable { 13 | public int nextParaIndex; //即将读取的段落的索引 14 | public boolean isLastNext = true; //上一次阅读是向后阅读还是向前阅读 15 | public boolean isNextRes; //往后读是否剩余字符串 16 | public boolean isPreRes; //往前读是否剩余字符串 17 | public List preResLines = new ArrayList<>(); //上一次向前读剩余的line 18 | public List nextResLines = new ArrayList<>(); //上一次向后读剩余的line 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/view/FlipView.java: -------------------------------------------------------------------------------- 1 | package com.ebook.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PointF; 9 | import android.graphics.RectF; 10 | import android.graphics.Region; 11 | import android.graphics.drawable.GradientDrawable; 12 | import android.os.Handler; 13 | import android.os.Message; 14 | import android.util.AttributeSet; 15 | import android.util.Log; 16 | import android.view.MotionEvent; 17 | import android.view.View; 18 | import android.widget.Toast; 19 | 20 | import com.ebook.util.SaveHelper; 21 | import com.ebook.view.popupWindow.SettingPopup; 22 | 23 | import java.util.List; 24 | 25 | /** 26 | * Created by Mum on 2017/2/5. 27 | */ 28 | 29 | public class FlipView extends View { 30 | private static final String TAG = "FoldView"; 31 | private static final float CURVATURE = 1 / 4f;//假定贝塞尔曲线从原这折线的1/4f处开始 32 | private Context mContext; 33 | private List mBitmapList; 34 | 35 | private int mViewWidth, mViewHeight;// 控件宽高 36 | float mDiagonalLength;//对角线长度 37 | 38 | //折叠三角形的底部直角边、左侧直角边 39 | private float mFoldEdgeBtm; 40 | private float mFoldEdgeLeft; 41 | 42 | private float mFoldBuffArea; //翻页时的缓冲,防止折叠区域无法封闭 43 | 44 | //触摸点 45 | private PointF mTouch; 46 | //底部贝塞尔曲线起始点、控制点、终点、顶点 47 | private PointF mBezierStart1; 48 | private PointF mBezierControl1; 49 | private PointF mBezierEnd1; 50 | private PointF mBezierVertex1; 51 | //右侧贝塞尔曲线起始点、控制点、终点、顶点 52 | private PointF mBezierStart2; 53 | private PointF mBezierControl2; 54 | private PointF mBezierEnd2; 55 | private PointF mBezierVertex2; 56 | //自滑直线起点 57 | private PointF mAutoSlideStart; 58 | //当前翻页模式对应的边角(右下角,右上角) 59 | private PointF mCorner; 60 | //手指落下时的触摸点 61 | private PointF mLastDownPoint; 62 | 63 | 64 | private Path mFoldPath;// 折叠区域path 65 | private Path mFoldAndNextPath;// 包含折叠和下一页区域的Path 66 | private Paint mFoldPaint; //折叠区域的画笔 67 | 68 | //渐变阴影 69 | private GradientDrawable mNextShadowRL; 70 | private GradientDrawable mNextShadowLR; 71 | 72 | private GradientDrawable mFoldShadowRL; 73 | private GradientDrawable mFoldShadowLR; 74 | 75 | private GradientDrawable mCurShadowRL; 76 | private GradientDrawable mCurShadowBT; 77 | private GradientDrawable mCurShadowTB; 78 | 79 | 80 | private float mSlideSpeedLeft;// 滑动速度 81 | private float mSlideSpeedRight; 82 | 83 | private float mAutoAreaBound;// 自滑区域分界 84 | 85 | private static final int SUB_WIDTH = 19, SUB_HEIGHT = 19;// 细分值横竖各19个网格 86 | private final float[] mCoordinates;//扭曲之后的坐标点的数组集合 87 | 88 | private boolean isSlide;// 是否滑动 89 | private boolean isPrePageOver;//前一页是否已经翻完 90 | private boolean isDrawOnMove; //移动的时候是否已经开始重绘了 91 | private boolean isFlipNext;//翻页时翻前一页还是后一页 92 | 93 | private int mPrePage = 0; //前一页索引 94 | private int mNextPage = 1;//后一页索引 95 | 96 | private Slide mSlide; 97 | private FlipMode mFlipMode; 98 | private FlipStyle mFlipStyle; 99 | 100 | private OnPageFlippedListener mListener; 101 | 102 | private SlideHandler mSlideHandler; // 滑动处理Handler 103 | 104 | // 滑动方式:往左滑,往右滑 105 | private enum Slide { 106 | LEFT, RIGHT 107 | } 108 | 109 | //翻页方式:右上角,右边中部,右下角 110 | private enum FlipMode { 111 | RIGHT_TOP, RIGHT_MIDDLE, RIGHT_BOTTOM 112 | } 113 | 114 | //翻页风格:仿真、覆盖、无效果 115 | public enum FlipStyle { 116 | STYLE_PAGE_LIKE, STYLE_COVER, STYLE_NO_EFFECT 117 | } 118 | 119 | 120 | public void setOnPageFlippedListener(OnPageFlippedListener listener) { 121 | mListener = listener; 122 | } 123 | 124 | public interface OnPageFlippedListener { 125 | 126 | List onNextPageFlipped(); 127 | 128 | List onPrePageFlipped(); 129 | 130 | void onFlipStarted(); 131 | 132 | void onFoldViewClicked(); 133 | } 134 | 135 | 136 | //处理滑动的Handler 137 | private class SlideHandler extends Handler { 138 | @Override 139 | public void handleMessage(Message msg) { 140 | Log.i(TAG, "handleMessage: 7"); 141 | // 重绘视图 142 | FlipView.this.invalidate(); 143 | // 循环调用滑动计算 144 | FlipView.this.slide(); 145 | } 146 | 147 | //延迟向Handler发送消息实现时间间隔 148 | public void sleep(long delayMillis) { 149 | this.removeMessages(0); 150 | sendMessageDelayed(obtainMessage(0), delayMillis); 151 | } 152 | } 153 | 154 | 155 | public FlipView(Context context) { 156 | this(context, null); 157 | } 158 | 159 | public FlipView(Context context, AttributeSet attrs) { 160 | super(context, attrs); 161 | mContext = context; 162 | mCoordinates = new float[(SUB_WIDTH + 1) * (SUB_HEIGHT + 1) * 2]; 163 | initObjects(); 164 | } 165 | 166 | 167 | @Override 168 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 169 | mViewWidth = w; 170 | mViewHeight = h; 171 | initDatas(); 172 | } 173 | 174 | 175 | @Override 176 | protected void onDraw(Canvas canvas) { 177 | 178 | if (mBitmapList == null || mBitmapList.size() == 0) { 179 | return; 180 | } 181 | 182 | // 重绘时清除上次路径 183 | mFoldPath.reset(); 184 | mFoldAndNextPath.reset(); 185 | 186 | //首次进入绘制前一页 187 | if (!isPrePageOver && mTouch.x == 0 && mTouch.y == 0) { 188 | canvas.drawBitmap(mBitmapList.get(mPrePage), 0, 0, null); 189 | return; 190 | } 191 | 192 | 193 | if (!isPrePageOver) { 194 | 195 | //仿真翻页 196 | if (mFlipStyle == FlipStyle.STYLE_PAGE_LIKE) { 197 | 198 | if (mFlipMode == FlipMode.RIGHT_MIDDLE) 199 | 200 | flipPageFromMiddle(canvas); //右侧中部翻页 201 | else 202 | flipPageFromCorner(canvas); //右上角和右下角翻页 203 | 204 | } 205 | 206 | //覆盖翻页 207 | if (mFlipStyle == FlipStyle.STYLE_COVER) 208 | flipCover(canvas); 209 | 210 | 211 | //无效果翻页 212 | if (mFlipStyle == FlipStyle.STYLE_NO_EFFECT) 213 | flipNoEffect(canvas); 214 | 215 | 216 | } else { 217 | //前一页已经完全翻完,直接绘制下一页区域 218 | canvas.save(); 219 | canvas.drawBitmap(mBitmapList.get(mNextPage), 0, 0, null); 220 | canvas.restore(); 221 | 222 | } 223 | 224 | } 225 | 226 | 227 | @Override 228 | public boolean onTouchEvent(MotionEvent event) { 229 | if (isSlide) 230 | return true; //自动滑动过程中不响应touch事件 231 | 232 | mTouch.x = event.getX(); 233 | mTouch.y = event.getY(); 234 | 235 | float width = mDiagonalLength / 100f; //判断是翻页还是点击事件的距离 236 | 237 | switch (event.getAction()) { 238 | case MotionEvent.ACTION_DOWN: 239 | mLastDownPoint.x = mTouch.x;//保存下手指落下时刻的触摸点 240 | mLastDownPoint.y = mTouch.y; 241 | 242 | isDrawOnMove = false; 243 | 244 | //仿真翻页风格确定翻页方式 245 | if (mFlipStyle == FlipStyle.STYLE_PAGE_LIKE) 246 | getFlipPageMode(); 247 | 248 | break; 249 | 250 | case MotionEvent.ACTION_MOVE: 251 | 252 | if (!isDrawOnMove) { 253 | 254 | //翻前一页 255 | if (mTouch.x - mLastDownPoint.x > width) { 256 | 257 | isFlipNext = false; 258 | 259 | if (mFlipStyle == FlipStyle.STYLE_PAGE_LIKE) 260 | mFlipMode = FlipMode.RIGHT_MIDDLE; 261 | 262 | if (!isPrePageOver) { 263 | //回调获得前一页page 264 | 265 | List temp = null; 266 | 267 | if (mListener != null) 268 | temp = mListener.onPrePageFlipped(); 269 | 270 | if (temp == null) { 271 | Toast.makeText(mContext, "已经是第一页了", Toast.LENGTH_SHORT).show(); 272 | return true; 273 | } 274 | 275 | mBitmapList = temp; 276 | isDrawOnMove = true; 277 | 278 | 279 | } else { 280 | 281 | isPrePageOver = false; 282 | isDrawOnMove = true; 283 | } 284 | 285 | } 286 | 287 | 288 | //翻下一页 289 | if (mTouch.x - mLastDownPoint.x < -width) { 290 | isFlipNext = true; 291 | 292 | if (isPrePageOver) { 293 | //回调获得后一页page 294 | 295 | List temp = null; 296 | 297 | if (mListener != null) 298 | temp = mListener.onNextPageFlipped(); 299 | 300 | if (temp == null) { 301 | Toast.makeText(mContext, "已经是最后一页了", Toast.LENGTH_SHORT).show(); 302 | return true; 303 | } 304 | 305 | mBitmapList = temp; 306 | isPrePageOver = false; 307 | isDrawOnMove = true; 308 | 309 | } else { 310 | 311 | isDrawOnMove = true; 312 | } 313 | } 314 | 315 | 316 | } else { 317 | 318 | if (mListener != null) 319 | mListener.onFlipStarted(); 320 | 321 | invalidate(); 322 | } 323 | 324 | break; 325 | 326 | 327 | case MotionEvent.ACTION_UP: 328 | 329 | float dis = (float) Math.hypot(mTouch.x - mLastDownPoint.x, mTouch.y - mLastDownPoint.y); 330 | if (dis < width) { //没有触发翻页效果,认为是点击事件 331 | 332 | mTouch.x = mCorner.x; //强制设置touch点坐标,防止因设置visibility而重绘导致的画面突变 333 | mTouch.y = mCorner.y; 334 | 335 | if (mListener != null) 336 | mListener.onFoldViewClicked(); 337 | 338 | return true; 339 | } 340 | 341 | if (!isDrawOnMove) { 342 | return true; 343 | } 344 | 345 | if (mFlipStyle == FlipStyle.STYLE_PAGE_LIKE) { 346 | 347 | //抬起的时候强制限制触摸点,防止滑动时因为取消限制产生的突变 348 | if (mFlipMode == FlipMode.RIGHT_BOTTOM || mFlipMode == FlipMode.RIGHT_TOP) { 349 | PointF pointF = limitTouchPoints(mTouch.x, mTouch.y); 350 | mTouch.x = pointF.x; 351 | mTouch.y = pointF.y; 352 | } 353 | 354 | } 355 | 356 | 357 | if (mFlipStyle != FlipStyle.STYLE_NO_EFFECT) { 358 | 359 | if (mTouch.x < mAutoAreaBound) { 360 | 361 | mSlide = Slide.LEFT;// 当前为往左滑 362 | startSlide(mTouch.x, mTouch.y);// 开始滑动 363 | } else { 364 | 365 | mSlide = Slide.RIGHT; 366 | startSlide(mTouch.x, mTouch.y); 367 | 368 | } 369 | } 370 | 371 | 372 | break; 373 | 374 | } 375 | 376 | return true; 377 | } 378 | 379 | private void getFlipPageMode() { 380 | float height = mViewHeight * 3 / 10f; 381 | if (mTouch.y > mViewHeight - height) { 382 | 383 | mFlipMode = FlipMode.RIGHT_BOTTOM; 384 | 385 | mCorner.x = mViewWidth; 386 | mCorner.y = mViewHeight; 387 | 388 | } else if (mTouch.y > height) { 389 | 390 | mFlipMode = FlipMode.RIGHT_MIDDLE; 391 | 392 | mCorner.x = mViewWidth; 393 | mCorner.y = mViewHeight / 2f; 394 | 395 | } else { 396 | 397 | mFlipMode = FlipMode.RIGHT_TOP; 398 | 399 | mCorner.x = mViewWidth; 400 | mCorner.y = 0; 401 | } 402 | } 403 | 404 | private void flipCover(Canvas canvas) { 405 | //绘制下层page 406 | canvas.save(); 407 | canvas.drawBitmap(mBitmapList.get(mNextPage), 0, 0, null); 408 | canvas.restore(); 409 | 410 | //绘制上层page 411 | if (isFlipNext) { //向后翻 412 | float moveDis = mLastDownPoint.x - mTouch.x; 413 | if (moveDis < 0) moveDis = 0; 414 | canvas.save(); 415 | canvas.drawBitmap(mBitmapList.get(mPrePage), -moveDis, 0, null); 416 | canvas.restore(); 417 | 418 | //阴影 419 | int left = (int) (mViewWidth - moveDis) - 1; 420 | int right = (int) (left + mDiagonalLength / 30f); 421 | 422 | canvas.save(); 423 | mFoldShadowRL.setBounds(left, 0, right, mViewHeight); 424 | mFoldShadowRL.draw(canvas); 425 | canvas.restore(); 426 | 427 | } else { //向前翻 428 | 429 | float moveDis = mTouch.x; 430 | canvas.save(); 431 | canvas.drawBitmap(mBitmapList.get(mPrePage), -(mViewWidth - moveDis), 0, null); 432 | canvas.restore(); 433 | 434 | //阴影 435 | int left = (int) mTouch.x - 1; 436 | int right = (int) (left + mDiagonalLength / 30f); 437 | canvas.save(); 438 | mFoldShadowRL.setBounds(left, 0, right, mViewHeight); 439 | mFoldShadowRL.draw(canvas); 440 | canvas.restore(); 441 | 442 | } 443 | } 444 | 445 | private void flipNoEffect(Canvas canvas) { 446 | if (isFlipNext) { 447 | canvas.drawBitmap(mBitmapList.get(mNextPage), 0, 0, null); 448 | isPrePageOver = true; 449 | 450 | } else { 451 | canvas.drawBitmap(mBitmapList.get(mPrePage), 0, 0, null); 452 | } 453 | } 454 | 455 | private void flipPageFromCorner(Canvas canvas) { 456 | //手动拖拽时限制触摸点范围,自动滑动时放开限制 457 | if (!isSlide) { 458 | mTouch = limitTouchPoints(mTouch.x, mTouch.y); 459 | } 460 | 461 | calPoints(); //计算坐标点 462 | 463 | calPaths(); //计算路径 464 | 465 | drawCurrentAreaAndShadow(canvas); //当前页区域填充及阴影绘制 466 | 467 | drawFoldAreaAndShadow(canvas); //折叠区域填充及阴影绘制 468 | 469 | drawNextAreaAndShadow(canvas); //下一页区域填充及阴影绘制 470 | } 471 | 472 | private void flipPageFromMiddle(Canvas canvas) { 473 | if (!isPrePageOver) { 474 | //前一页还没完全翻完,要绘制三个区域 475 | 476 | float foldWidth = (mViewWidth - mTouch.x) * 4 / 10f; //折叠区域宽度 477 | 478 | //当前页区域 479 | canvas.save(); 480 | canvas.clipRect(new RectF(0, 0, mTouch.x, mViewHeight)); 481 | canvas.drawBitmap(mBitmapList.get(mPrePage), 0, 0, null); 482 | 483 | mCurShadowRL.setBounds((int) (mTouch.x - foldWidth / 10f), 0, (int) (mTouch.x + 1), mViewHeight); 484 | mCurShadowRL.draw(canvas); 485 | canvas.restore(); 486 | 487 | //折叠区域 488 | canvas.save(); 489 | canvas.clipRect(new RectF(mTouch.x, 0, mTouch.x + foldWidth, mViewHeight)); 490 | canvas.scale(-1, 1);//x方向镜像 491 | canvas.translate(-(mViewWidth + mTouch.x), 0); 492 | canvas.drawBitmap(mBitmapList.get(mPrePage), 0, 0, mFoldPaint); 493 | canvas.restore(); 494 | 495 | canvas.save();//还原坐标系,绘制折叠区域阴影 496 | mFoldShadowLR.setBounds((int) (mTouch.x + foldWidth * 7 / 10f), 0, (int) (mTouch.x + foldWidth + 1), mViewHeight); 497 | mFoldShadowLR.draw(canvas); 498 | canvas.restore(); 499 | 500 | 501 | //下一页区域 502 | canvas.save(); 503 | canvas.clipRect(new RectF(mTouch.x + foldWidth, 0, mViewWidth, mViewHeight)); 504 | canvas.drawBitmap(mBitmapList.get(mNextPage), 0, 0, null); 505 | mNextShadowLR.setBounds((int) (mTouch.x + foldWidth * 7 / 10), 0, (int) (mTouch.x + foldWidth + foldWidth * 1 / 2), mViewHeight); 506 | mNextShadowLR.draw(canvas); 507 | canvas.restore(); 508 | 509 | } else { 510 | 511 | //前一页已经完全翻完,直接绘制下一页区域 512 | canvas.save(); 513 | canvas.drawBitmap(mBitmapList.get(mNextPage), 0, 0, null); 514 | canvas.restore(); 515 | } 516 | 517 | } 518 | 519 | private void startSlide(float x, float y) { 520 | // 获取并设置直线方程的起点 521 | mAutoSlideStart.x = x; 522 | mAutoSlideStart.y = y; 523 | // 开始滑动 524 | isSlide = true; 525 | 526 | // 滑动 527 | slide(); 528 | } 529 | 530 | private void slide() { 531 | if (mFlipStyle == FlipStyle.STYLE_PAGE_LIKE) { 532 | 533 | //前一页已经向左完全翻完 534 | if (mSlide == Slide.LEFT && mTouch.x <= -mViewWidth + mFoldBuffArea) { 535 | isPrePageOver = true; 536 | isSlide=false; 537 | invalidate(); 538 | } 539 | 540 | //往右边滑结束 541 | if (mSlide == Slide.RIGHT && mTouch.x >= mViewWidth) { 542 | isSlide=false; 543 | } 544 | 545 | 546 | 547 | //往左边滑 548 | if (mSlide == Slide.LEFT && mTouch.x > -mViewWidth + mFoldBuffArea) { 549 | // 则让x坐标自减,右侧侧边翻页不用设定y值 550 | mTouch.x -= mSlideSpeedLeft; 551 | 552 | // 角翻页需要设定y值 553 | if (mFlipMode == FlipMode.RIGHT_BOTTOM || mFlipMode == FlipMode.RIGHT_TOP) { 554 | mTouch.y = mAutoSlideStart.y + ((mTouch.x - mAutoSlideStart.x) * (mCorner.y - mAutoSlideStart.y)) / (-mCorner.x - mAutoSlideStart.x); 555 | } 556 | 557 | // 让SlideHandler处理重绘 558 | mSlideHandler.sleep(25); 559 | } 560 | 561 | //往右边滑 562 | if (mSlide == Slide.RIGHT && mTouch.x < mViewWidth) { 563 | 564 | // 则让x坐标自加,右侧侧边翻页不用设定y值 565 | mTouch.x += mSlideSpeedRight; 566 | 567 | // 角翻页需要设定y值 568 | if (mFlipMode == FlipMode.RIGHT_BOTTOM || mFlipMode == FlipMode.RIGHT_TOP) { 569 | mTouch.y = mAutoSlideStart.y + ((mTouch.x - mAutoSlideStart.x) * (mCorner.y - mAutoSlideStart.y)) / (mCorner.x - mAutoSlideStart.x); 570 | } 571 | 572 | // 让SlideHandler处理重绘 573 | mSlideHandler.sleep(25); 574 | 575 | } 576 | 577 | } 578 | 579 | if (mFlipStyle == FlipStyle.STYLE_COVER) { 580 | 581 | //前一页已经向左完全翻完 582 | if (mSlide == Slide.LEFT && mTouch.x <= -(mViewWidth - mLastDownPoint.x)) { 583 | isPrePageOver = true; 584 | isSlide=false; 585 | invalidate(); 586 | 587 | } 588 | 589 | //往右边滑 590 | if (mSlide == Slide.RIGHT && mTouch.x >= mViewWidth) { 591 | isSlide=false; 592 | 593 | } 594 | 595 | //往左边滑 596 | if (mSlide == Slide.LEFT && mTouch.x > -(mViewWidth - mLastDownPoint.x)) { 597 | mTouch.x -= mSlideSpeedLeft; 598 | mSlideHandler.sleep(25); 599 | } 600 | 601 | //往右边滑 602 | if (mSlide == Slide.RIGHT && mTouch.x < mViewWidth) { 603 | mTouch.x += mSlideSpeedRight; 604 | mSlideHandler.sleep(25); 605 | 606 | } 607 | 608 | 609 | } 610 | 611 | } 612 | 613 | //更新当前page 614 | public void updateBitmapList(List bitmapList) { 615 | 616 | mBitmapList = bitmapList; 617 | 618 | invalidate(); 619 | } 620 | 621 | 622 | public void setPageByContent(List bitmapList) { 623 | 624 | mBitmapList = bitmapList; 625 | 626 | isPrePageOver = false; 627 | 628 | mTouch.x = 0; 629 | mTouch.y = 0; 630 | 631 | invalidate(); 632 | } 633 | 634 | 635 | private PointF limitTouchPoints(float touchX, float touchY) { 636 | PointF effectPoint = new PointF(mViewWidth * 2 / 10f, mCorner.y); 637 | float effectR = mCorner.x - effectPoint.x; 638 | float distance = (float) Math.hypot(touchX - effectPoint.x, touchY - effectPoint.y); 639 | if (distance > effectR) { 640 | float radio = (distance - effectR) / distance; 641 | touchX = touchX - (touchX - effectPoint.x) * radio; 642 | touchY = touchY + (mCorner.y - touchY) * radio; 643 | } 644 | 645 | return new PointF(touchX, touchY); 646 | 647 | } 648 | 649 | 650 | //根据图来计算坐标点 651 | private void calPoints() { 652 | 653 | //为了兼容右上角翻页和右下角翻页增加的变量 654 | int operator = 1; 655 | if (mFlipMode == FlipMode.RIGHT_TOP) { 656 | operator = -1; 657 | } 658 | 659 | float toCornerX = mCorner.x - mTouch.x; 660 | float toCornerY = Math.abs(mCorner.y - mTouch.y); 661 | float temp = (float) (Math.pow(toCornerY, 2) + Math.pow(toCornerX, 2)); 662 | 663 | //折叠三角形的底部直角边和左侧直角边 664 | mFoldEdgeBtm = temp / (2F * toCornerX); 665 | mFoldEdgeLeft = temp / (2F * toCornerY); 666 | 667 | 668 | //底部贝塞尔曲线 669 | mBezierControl1.x = mCorner.x - mFoldEdgeBtm; 670 | mBezierControl1.y = mCorner.y; 671 | 672 | mBezierStart1.x = mBezierControl1.x - CURVATURE * mFoldEdgeBtm; 673 | mBezierStart1.y = mCorner.y; 674 | 675 | mBezierEnd1.x = mTouch.x + (1 - CURVATURE) * (toCornerX - mFoldEdgeBtm); 676 | mBezierEnd1.y = mTouch.y + operator * (1 - CURVATURE) * toCornerY; 677 | 678 | mBezierVertex1.x = 0.25F * mBezierStart1.x + 0.5F * mBezierControl1.x + 0.25F * mBezierEnd1.x; 679 | mBezierVertex1.y = 0.25F * mBezierStart1.y + 0.5F * mBezierControl1.y + 0.25F * mBezierEnd1.y; 680 | 681 | //右侧贝塞尔曲线 682 | mBezierControl2.x = mCorner.x; 683 | mBezierControl2.y = mCorner.y - operator * mFoldEdgeLeft; 684 | 685 | mBezierStart2.x = mCorner.x; 686 | mBezierStart2.y = mCorner.y - operator * mFoldEdgeLeft - operator * CURVATURE * mFoldEdgeLeft;// 687 | 688 | mBezierEnd2.x = mTouch.x + (1 - CURVATURE) * toCornerX; 689 | mBezierEnd2.y = mTouch.y + operator * (1 - CURVATURE) * (toCornerY - mFoldEdgeLeft);// 690 | 691 | 692 | mBezierVertex2.x = 0.25F * mBezierStart2.x + 0.5F * mBezierControl2.x + 0.25F * mBezierEnd2.x; 693 | mBezierVertex2.y = 0.25F * mBezierStart2.y + 0.5F * mBezierControl2.y + 0.25F * mBezierEnd2.y; 694 | 695 | } 696 | 697 | 698 | private void calPaths() { 699 | 700 | //折叠区域path 701 | mFoldPath.moveTo(mBezierStart1.x, mBezierStart1.y); 702 | mFoldPath.quadTo(mBezierControl1.x, mBezierControl1.y, mBezierEnd1.x, mBezierEnd1.y); 703 | mFoldPath.lineTo(mTouch.x, mTouch.y); 704 | mFoldPath.lineTo(mBezierEnd2.x, mBezierEnd2.y); 705 | mFoldPath.quadTo(mBezierControl2.x, mBezierControl2.y, mBezierStart2.x, mBezierStart2.y); 706 | mFoldPath.lineTo(mBezierVertex2.x, mBezierVertex2.y); 707 | mFoldPath.lineTo(mBezierVertex1.x, mBezierVertex1.y); 708 | 709 | //包含折叠区域和下一页的path 710 | mFoldAndNextPath.moveTo(mBezierStart1.x, mBezierStart1.y); 711 | mFoldAndNextPath.quadTo(mBezierControl1.x, mBezierControl1.y, mBezierEnd1.x, mBezierEnd1.y); 712 | mFoldAndNextPath.lineTo(mTouch.x, mTouch.y); 713 | mFoldAndNextPath.lineTo(mBezierEnd2.x, mBezierEnd2.y); 714 | mFoldAndNextPath.quadTo(mBezierControl2.x, mBezierControl2.y, mBezierStart2.x, mBezierStart2.y); 715 | mFoldAndNextPath.lineTo(mCorner.x, mCorner.y); 716 | mFoldAndNextPath.close(); 717 | 718 | 719 | } 720 | 721 | 722 | private void drawCurrentAreaAndShadow(Canvas canvas) { 723 | //当前页bitmap填充 724 | canvas.save(); 725 | canvas.clipRect(new RectF(0, 0, mViewWidth, mViewHeight)); 726 | canvas.clipPath(mFoldAndNextPath, Region.Op.DIFFERENCE); 727 | 728 | canvas.drawBitmap(mBitmapList.get(mPrePage), 0, 0, null); 729 | canvas.restore(); 730 | 731 | if (mFlipMode == FlipMode.RIGHT_BOTTOM) { 732 | 733 | //折叠三角形底部直角边阴影 734 | Path tempPath = new Path(); 735 | double degree = Math.PI / 4 736 | + Math.atan2(mTouch.x - mBezierControl1.x, mBezierControl1.y - mTouch.y); 737 | 738 | float rotateDegrees = (float) Math.toDegrees(Math.atan2(mTouch.x 739 | - mBezierControl1.x, mBezierControl1.y - mTouch.y)); 740 | 741 | double d1 = (float) 25 * 1.414 * Math.cos(degree); 742 | double d2 = (float) 25 * 1.414 * Math.sin(degree); 743 | 744 | //阴影的起点 745 | PointF pointF = new PointF((float) (mTouch.x - d1), (float) (mTouch.y - d2)); 746 | float width = (float) Math.hypot((mTouch.x - mCorner.x), (mTouch.y - mCorner.y))/40f; 747 | int left = (int) (mBezierControl1.x - width); 748 | int right = (int) mBezierControl1.x + 1; 749 | 750 | tempPath.reset(); 751 | tempPath.moveTo(pointF.x, pointF.y); 752 | tempPath.lineTo(mTouch.x, mTouch.y); 753 | tempPath.lineTo(mBezierControl1.x, mBezierControl1.y); 754 | tempPath.lineTo(mBezierStart1.x, mBezierStart1.y); 755 | tempPath.close(); 756 | 757 | canvas.save(); 758 | canvas.clipRect(new RectF(0, 0, mViewWidth, mViewHeight)); 759 | canvas.clipPath(mFoldAndNextPath, Region.Op.DIFFERENCE); 760 | canvas.clipPath(tempPath); 761 | 762 | mCurShadowRL.setBounds(left, (int) (mBezierControl1.y - mDiagonalLength), 763 | right, (int) (mBezierControl1.y)); 764 | canvas.rotate(rotateDegrees, mBezierControl1.x, mBezierControl1.y); 765 | mCurShadowRL.draw(canvas); 766 | canvas.restore(); 767 | 768 | 769 | //还原坐标系,绘制折叠三角形左侧直角边阴影 770 | 771 | int btmY = (int) (mTouch.y + 1); 772 | int topY = (int) (mTouch.y - width); 773 | rotateDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl2.y - mTouch.y, 774 | mBezierControl2.x - mTouch.x)); 775 | 776 | tempPath.reset(); 777 | tempPath.moveTo(pointF.x, pointF.y); 778 | tempPath.lineTo(mTouch.x, mTouch.y); 779 | tempPath.lineTo(mBezierControl2.x, mBezierControl2.y); 780 | tempPath.lineTo(mBezierStart2.x, mBezierStart2.y); 781 | tempPath.close(); 782 | 783 | canvas.save(); 784 | canvas.clipRect(new RectF(0, 0, mViewWidth, mViewHeight)); 785 | canvas.clipPath(mFoldAndNextPath, Region.Op.DIFFERENCE); 786 | canvas.clipPath(tempPath); 787 | 788 | mCurShadowBT.setBounds((int) (mTouch.x - 2f*width), topY, 789 | (int) (mTouch.x + mDiagonalLength), btmY); 790 | canvas.rotate(rotateDegrees, mTouch.x, mTouch.y); 791 | mCurShadowBT.draw(canvas); 792 | canvas.restore(); 793 | 794 | 795 | } else if (mFlipMode == FlipMode.RIGHT_TOP) { 796 | 797 | 798 | //折叠三角形底部直角边阴影 799 | Path tempPath = new Path(); 800 | double degree = Math.PI / 4 801 | + Math.atan2(mBezierControl1.x - mTouch.x, mTouch.y - mBezierControl1.y); 802 | 803 | float rotateDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl1.x - mTouch.x, 804 | mTouch.y - mBezierControl1.y)); 805 | 806 | double d1 = (float) 25 * 1.414 * Math.sin(degree); 807 | double d2 = (float) 25 * 1.414 * Math.cos(degree); 808 | 809 | //阴影的起点 810 | PointF pointF = new PointF((float) (mTouch.x - d1), (float) (mTouch.y + d2)); 811 | 812 | float width = (float) Math.hypot((mTouch.x - mCorner.x), (mTouch.y - mCorner.y))/40f; 813 | int left = (int) (mBezierControl1.x - width); 814 | int right = (int) mBezierControl1.x + 1; 815 | 816 | tempPath.reset(); 817 | tempPath.moveTo(pointF.x, pointF.y); 818 | tempPath.lineTo(mTouch.x, mTouch.y); 819 | tempPath.lineTo(mBezierControl1.x, mBezierControl1.y); 820 | tempPath.lineTo(mBezierStart1.x, mBezierStart1.y); 821 | tempPath.close(); 822 | 823 | canvas.save(); 824 | canvas.clipRect(new RectF(0, 0, mViewWidth, mViewHeight)); 825 | canvas.clipPath(mFoldAndNextPath, Region.Op.DIFFERENCE); 826 | canvas.clipPath(tempPath); 827 | 828 | mCurShadowRL.setBounds(left, (int) mBezierControl1.y, 829 | right, (int) (mBezierControl1.y + mDiagonalLength)); 830 | canvas.rotate(rotateDegrees, mBezierControl1.x, mBezierControl1.y); 831 | mCurShadowRL.draw(canvas); 832 | canvas.restore(); 833 | 834 | 835 | //还原坐标系,绘制折叠三角形左侧直角边阴影 836 | 837 | int topY = (int) (mTouch.y - 1); 838 | int btmY = (int) (mTouch.y + width); 839 | rotateDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl2.y - mTouch.y, 840 | mBezierControl2.x - mTouch.x)); 841 | 842 | tempPath.reset(); 843 | tempPath.moveTo(pointF.x, pointF.y); 844 | tempPath.lineTo(mTouch.x, mTouch.y); 845 | tempPath.lineTo(mBezierControl2.x, mBezierControl2.y); 846 | tempPath.lineTo(mBezierStart2.x, mBezierStart2.y); 847 | tempPath.close(); 848 | 849 | canvas.save(); 850 | canvas.clipRect(new RectF(0, 0, mViewWidth, mViewHeight)); 851 | canvas.clipPath(mFoldAndNextPath, Region.Op.DIFFERENCE); 852 | canvas.clipPath(tempPath); 853 | 854 | mCurShadowTB.setBounds((int) (mTouch.x - 2f*width), topY, 855 | (int) (mTouch.x + mDiagonalLength), btmY); 856 | canvas.rotate(rotateDegrees, mTouch.x, mTouch.y); 857 | mCurShadowTB.draw(canvas); 858 | canvas.restore(); 859 | 860 | } 861 | 862 | } 863 | 864 | 865 | private void drawFoldAreaAndShadow(Canvas canvas) { 866 | 867 | if (mFlipMode == FlipMode.RIGHT_BOTTOM) { 868 | 869 | //计算填充折叠区域时需要的画布坐标旋转角度 870 | float rotate = (float) Math.toDegrees(Math.PI / 2f 871 | - Math.atan2(mTouch.y - mBezierControl2.y, mViewWidth - mTouch.x)); 872 | 873 | canvas.save(); 874 | canvas.clipPath(mFoldAndNextPath); 875 | canvas.clipPath(mFoldPath, Region.Op.INTERSECT); 876 | canvas.scale(-1, 1);//x方向镜像 877 | canvas.translate(-(mViewWidth + mTouch.x), -(mViewHeight - mTouch.y)); 878 | 879 | //mTouch在新坐标系下的坐标 880 | float x = -mTouch.x + mViewWidth + mTouch.x; 881 | float y = mTouch.y + mViewHeight - mTouch.y; 882 | 883 | canvas.rotate(-rotate, x, y); 884 | 885 | //计算bitmap扭曲后的坐标 886 | wrapCoordinates(); 887 | //位图的扭曲填充 888 | canvas.drawBitmapMesh(mBitmapList.get(mPrePage), SUB_WIDTH, SUB_HEIGHT, mCoordinates, 0, null, 0, mFoldPaint); 889 | 890 | canvas.restore(); 891 | 892 | 893 | //绘制阴影 894 | float width = mFoldEdgeBtm / 8f; 895 | int left = (int) (mBezierStart1.x - width - 1); 896 | int right = (int) (mBezierStart1.x + 1); 897 | float rotateDegrees = (float) Math.toDegrees(Math.PI / 2f 898 | + Math.atan2(mViewHeight - mBezierStart2.y, mViewWidth - mBezierStart1.x)); 899 | 900 | //还原坐标轴,绘制阴影 901 | canvas.save(); 902 | canvas.clipPath(mFoldAndNextPath); 903 | canvas.clipPath(mFoldPath, Region.Op.INTERSECT); 904 | canvas.rotate(-rotateDegrees, mBezierStart1.x, mBezierStart1.y); 905 | mFoldShadowRL.setBounds(left, (int) mBezierStart1.y, right, 906 | (int) (mBezierStart1.y + mDiagonalLength)); 907 | mFoldShadowRL.draw(canvas); 908 | canvas.restore(); 909 | 910 | 911 | } else if (mFlipMode == FlipMode.RIGHT_TOP) { 912 | 913 | 914 | //计算填充折叠区域时需要的画布坐标旋转角度 915 | float rotate = (float) Math.toDegrees(Math.PI / 2f 916 | - Math.atan2(mBezierControl2.y - mTouch.y, mBezierControl2.x - mTouch.x)); 917 | 918 | canvas.save(); 919 | canvas.clipPath(mFoldAndNextPath); 920 | canvas.clipPath(mFoldPath, Region.Op.INTERSECT); 921 | canvas.scale(-1, 1);//x方向镜像 922 | canvas.translate(-(mViewWidth + mTouch.x), -(0 - mTouch.y)); 923 | 924 | //mTouch在新坐标系下的坐标 925 | float x = -mTouch.x + mViewWidth + mTouch.x; 926 | float y = mTouch.y + (0 - mTouch.y); 927 | 928 | canvas.rotate(rotate, x, y); 929 | 930 | //计算bitmap扭曲后的坐标 931 | wrapCoordinates(); 932 | 933 | //位图的扭曲填充 934 | canvas.drawBitmapMesh(mBitmapList.get(mPrePage), SUB_WIDTH, SUB_HEIGHT, mCoordinates, 0, null, 0, mFoldPaint); 935 | 936 | canvas.restore(); 937 | 938 | 939 | //绘制阴影 940 | float width = mFoldEdgeBtm / 8f; 941 | int left = (int) (mBezierStart1.x - width - 1); 942 | int right = (int) (mBezierStart1.x + 1); 943 | float rotateDegrees = (float) Math.toDegrees(Math.PI / 2f 944 | + Math.atan2(mBezierStart2.y, mViewWidth - mBezierStart1.x)); 945 | 946 | //还原坐标轴,绘制阴影 947 | canvas.save(); 948 | canvas.clipPath(mFoldAndNextPath); 949 | canvas.clipPath(mFoldPath, Region.Op.INTERSECT); 950 | canvas.rotate(rotateDegrees, mBezierStart1.x, mBezierStart1.y); 951 | mFoldShadowRL.setBounds(left, (int) (mBezierStart1.y - mDiagonalLength), right, 952 | (int) mBezierStart1.y); 953 | mFoldShadowRL.draw(canvas); 954 | canvas.restore(); 955 | 956 | } 957 | 958 | } 959 | 960 | 961 | private void drawNextAreaAndShadow(Canvas canvas) { 962 | 963 | // 下一页区域bitmap填充 964 | canvas.save(); 965 | canvas.clipPath(mFoldAndNextPath); 966 | canvas.clipPath(mFoldPath, Region.Op.DIFFERENCE); 967 | canvas.drawBitmap(mBitmapList.get(mNextPage), 0, 0, null); 968 | 969 | //阴影绘制 970 | float touchToCornerDis = (float) Math.hypot((mTouch.x - mCorner.x), (mTouch.y - mCorner.y)); 971 | int left = (int) (mBezierStart1.x - touchToCornerDis / 5); 972 | int right = (int) mBezierStart1.x; 973 | 974 | if (mFlipMode == FlipMode.RIGHT_BOTTOM) { 975 | 976 | float rotateDegrees = (float) Math.toDegrees(Math.PI / 2f 977 | + Math.atan2(mCorner.y - mBezierStart2.y, mCorner.x - mBezierStart1.x)); 978 | 979 | canvas.rotate(-rotateDegrees, mBezierStart1.x, mBezierStart1.y); 980 | 981 | mNextShadowRL.setBounds(left, (int) mBezierStart1.y, right, 982 | (int) (mDiagonalLength + mBezierStart1.y)); 983 | 984 | } else if (mFlipMode == FlipMode.RIGHT_TOP) { 985 | 986 | float rotateDegrees = (float) Math.toDegrees(Math.PI / 2f 987 | + Math.atan2(mCorner.y + mBezierStart2.y, mCorner.x - mBezierStart1.x)); 988 | 989 | canvas.rotate(rotateDegrees, mBezierStart1.x, mBezierStart1.y); 990 | 991 | mNextShadowRL.setBounds(left, (int) (mBezierStart1.y - mDiagonalLength), right, 992 | (int) mBezierStart1.y); 993 | 994 | } 995 | 996 | mNextShadowRL.draw(canvas); 997 | canvas.restore(); 998 | 999 | 1000 | } 1001 | 1002 | 1003 | public void setFlipStyle(int style) { 1004 | 1005 | switch (style) { 1006 | case SettingPopup.FLIP_PAGE_LIKE: 1007 | mFlipStyle = FlipStyle.STYLE_PAGE_LIKE; 1008 | break; 1009 | case SettingPopup.FLIP_COVER: 1010 | mFlipStyle = FlipStyle.STYLE_COVER; 1011 | break; 1012 | case SettingPopup.FLIP_NO_EFFECT: 1013 | mFlipStyle = FlipStyle.STYLE_NO_EFFECT; 1014 | break; 1015 | 1016 | } 1017 | 1018 | 1019 | } 1020 | 1021 | //生成折叠区域的扭曲坐标 1022 | private void wrapCoordinates() { 1023 | //每一个网格的宽高 1024 | float subMinWidth = mViewWidth / (SUB_WIDTH + 1); 1025 | float subMinHeight = mViewHeight / (SUB_HEIGHT + 1); 1026 | 1027 | int index = 0; 1028 | 1029 | // 长边偏移 1030 | float offsetLong = CURVATURE / 2F * mFoldEdgeLeft; 1031 | 1032 | // 长边偏移倍增 1033 | float mulOffsetLong = 1.0F; 1034 | 1035 | // 短边偏移 1036 | float offsetShort = CURVATURE / 2F * mFoldEdgeBtm; 1037 | 1038 | // 短边偏移倍增 1039 | float mulOffsetShort = 1.0F; 1040 | 1041 | 1042 | // 计算底部扭曲的起始细分下标 1043 | float subWidthStart = Math.round((mBezierControl1.x / subMinWidth)) - 1; 1044 | float subWidthEnd = Math.round(((mBezierControl1.x + CURVATURE * mFoldEdgeBtm) / subMinWidth)) + 1; 1045 | 1046 | 1047 | if (mFlipMode == FlipMode.RIGHT_BOTTOM) { 1048 | 1049 | // 计算右侧扭曲的起始细分下标 1050 | float subHeightStart = Math.round((mBezierControl2.y / subMinHeight)) - 1; 1051 | float subHeightEnd = Math.round(((mBezierControl2.y + CURVATURE * mFoldEdgeLeft) / subMinHeight)) + 1; 1052 | 1053 | for (int y = 0; y <= SUB_HEIGHT; y++) { 1054 | float fy = mViewHeight * y / SUB_HEIGHT; 1055 | for (int x = 0; x <= SUB_WIDTH; x++) { 1056 | 1057 | float fx = mViewWidth * x / SUB_WIDTH; 1058 | //右侧扭曲 1059 | if (x == SUB_WIDTH) { 1060 | if (y >= subHeightStart && y <= subHeightEnd) { 1061 | fx = mViewWidth * x / SUB_WIDTH + offsetLong * mulOffsetLong; 1062 | mulOffsetLong = mulOffsetLong / 1.5F; 1063 | 1064 | } 1065 | } 1066 | 1067 | //底部扭曲 1068 | if (y == SUB_HEIGHT) { 1069 | if (x >= subWidthStart && x <= subWidthEnd) { 1070 | fy = mViewHeight * y / SUB_HEIGHT + offsetShort * mulOffsetShort; 1071 | mulOffsetShort = mulOffsetShort / 1.5F; 1072 | } 1073 | } 1074 | 1075 | mCoordinates[index * 2 + 0] = fx; 1076 | mCoordinates[index * 2 + 1] = fy; 1077 | 1078 | index += 1; 1079 | } 1080 | } 1081 | } else if (mFlipMode == FlipMode.RIGHT_TOP) { 1082 | 1083 | // 计算右侧扭曲的起始细分下标 1084 | float subHeightStart = Math.round(((mBezierControl2.y - CURVATURE * mFoldEdgeLeft) / subMinHeight)) - 1; 1085 | float subHeightEnd = Math.round((mBezierControl2.y / subMinHeight)) + 1; 1086 | 1087 | 1088 | for (int y = 0; y <= SUB_HEIGHT; y++) { 1089 | float fy = mViewHeight * y / SUB_HEIGHT; 1090 | for (int x = 0; x <= SUB_WIDTH; x++) { 1091 | 1092 | float fx = mViewWidth * x / SUB_WIDTH; 1093 | //右侧扭曲 1094 | if (x == SUB_WIDTH) { 1095 | if (y >= subHeightStart && y <= subHeightEnd) { 1096 | fx = mViewWidth * x / SUB_WIDTH + offsetLong * mulOffsetLong; 1097 | mulOffsetLong = mulOffsetLong * 1.5F; 1098 | 1099 | } 1100 | } 1101 | 1102 | //底部扭曲 1103 | if (y == 0) { 1104 | if (x >= subWidthStart && x <= subWidthEnd) { 1105 | fy = mViewHeight * y / SUB_HEIGHT - offsetShort * mulOffsetShort; 1106 | mulOffsetShort = mulOffsetShort / 1.5F; 1107 | } 1108 | } 1109 | 1110 | mCoordinates[index * 2 + 0] = fx; 1111 | mCoordinates[index * 2 + 1] = fy; 1112 | 1113 | index += 1; 1114 | } 1115 | } 1116 | 1117 | } 1118 | 1119 | } 1120 | 1121 | 1122 | private void initDatas() { 1123 | //控件对角线长度 1124 | mDiagonalLength = (float) Math.hypot(mViewWidth, mViewHeight); 1125 | 1126 | 1127 | //翻折时x的缓冲,防止完全翻页时折叠区域的曲线无法封闭 1128 | mFoldBuffArea = mViewWidth / 20f; 1129 | 1130 | //计算自滑界限位置 1131 | mAutoAreaBound = mViewWidth * 6 / 10f; 1132 | 1133 | //滑动速度 1134 | mSlideSpeedLeft = mViewWidth / 25f; 1135 | mSlideSpeedRight = mViewWidth / 80f; 1136 | 1137 | isPrePageOver = SaveHelper.getBoolean(mContext, SaveHelper.IS_PRE_PAGE_OVER); 1138 | int style = SaveHelper.getInt(mContext, SaveHelper.FLIP_STYLE); 1139 | setFlipStyle(style); 1140 | 1141 | } 1142 | 1143 | 1144 | private void initObjects() { 1145 | 1146 | mTouch = new PointF(); 1147 | mCorner = new PointF(); 1148 | mLastDownPoint = new PointF(); 1149 | 1150 | mBezierStart1 = new PointF(); 1151 | mBezierControl1 = new PointF(); 1152 | mBezierEnd1 = new PointF(); 1153 | mBezierVertex1 = new PointF(); 1154 | 1155 | mBezierStart2 = new PointF(); 1156 | mBezierControl2 = new PointF(); 1157 | mBezierEnd2 = new PointF(); 1158 | mBezierVertex2 = new PointF(); 1159 | 1160 | mAutoSlideStart = new PointF(); 1161 | 1162 | mFoldPaint = new Paint(); 1163 | mFoldPaint.setAntiAlias(true); 1164 | mFoldPaint.setAlpha(0x70); 1165 | 1166 | mFoldPath = new Path(); 1167 | mFoldAndNextPath = new Path(); 1168 | 1169 | mSlideHandler = new SlideHandler(); 1170 | 1171 | //初始化阴影GradientDrawable 1172 | int[] frontShadowColors = new int[]{0x80111111, 0x00111111};//从深到浅 1173 | mCurShadowRL = new GradientDrawable( 1174 | GradientDrawable.Orientation.RIGHT_LEFT, frontShadowColors); 1175 | mCurShadowRL.setGradientType(GradientDrawable.LINEAR_GRADIENT); 1176 | 1177 | mCurShadowBT = new GradientDrawable( 1178 | GradientDrawable.Orientation.BOTTOM_TOP, frontShadowColors); 1179 | mCurShadowBT.setGradientType(GradientDrawable.LINEAR_GRADIENT); 1180 | 1181 | mCurShadowTB = new GradientDrawable( 1182 | GradientDrawable.Orientation.TOP_BOTTOM, frontShadowColors); 1183 | mCurShadowTB.setGradientType(GradientDrawable.LINEAR_GRADIENT); 1184 | 1185 | 1186 | int[] color = {0x00333333, 0xb0333333}; //从浅到深 1187 | mFoldShadowRL = new GradientDrawable( 1188 | GradientDrawable.Orientation.RIGHT_LEFT, color); 1189 | mFoldShadowRL.setGradientType(GradientDrawable.LINEAR_GRADIENT); 1190 | 1191 | mFoldShadowLR = new GradientDrawable( 1192 | GradientDrawable.Orientation.LEFT_RIGHT, color); 1193 | mFoldShadowLR.setGradientType(GradientDrawable.LINEAR_GRADIENT); 1194 | 1195 | 1196 | int[] nextShadowColors = new int[]{0xff111111, 0x00111111}; //从深到浅 1197 | mNextShadowRL = new GradientDrawable( 1198 | GradientDrawable.Orientation.RIGHT_LEFT, nextShadowColors); 1199 | mNextShadowRL.setGradientType(GradientDrawable.LINEAR_GRADIENT); 1200 | 1201 | 1202 | mNextShadowLR = new GradientDrawable( 1203 | GradientDrawable.Orientation.LEFT_RIGHT, nextShadowColors); 1204 | mNextShadowLR.setGradientType(GradientDrawable.LINEAR_GRADIENT); 1205 | } 1206 | 1207 | public void setPrePageOver(boolean prePageOver) { 1208 | isPrePageOver = prePageOver; 1209 | } 1210 | 1211 | public boolean isPrePageOver() { 1212 | return isPrePageOver; 1213 | } 1214 | 1215 | } 1216 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/view/SwitchView.java: -------------------------------------------------------------------------------- 1 | package com.ebook.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Paint.Style; 9 | import android.graphics.Path; 10 | import android.graphics.RectF; 11 | import android.os.Handler; 12 | import android.os.Message; 13 | import android.util.AttributeSet; 14 | import android.view.MotionEvent; 15 | import android.view.View; 16 | 17 | import com.ebook.R; 18 | 19 | 20 | /** 21 | * Created by Administrator on 2017/1/16. 22 | * Custom SwitchView 23 | */ 24 | 25 | public class SwitchView extends View { 26 | 27 | private int mWidth, mHeight; 28 | private float mLeft, mTop, mRight, mBottom; 29 | private float mButtonRadius; 30 | private float mButtonWidth; 31 | private float mButtonLeft; 32 | private float mButtonRight; 33 | private float mButtonX; 34 | private float mButtonY; 35 | private float mStrokeWidth; 36 | private float mSlidePercent; 37 | 38 | private int mStrokeColor; 39 | private int mMaskColor; 40 | private int mButtonColor; 41 | 42 | private Paint mStrokePaint; 43 | private Paint mMaskPaint; 44 | private Paint mButtonPaint; 45 | 46 | private Path mPath; 47 | 48 | private boolean isChecked; 49 | private SlideHandler mSlideHandler; 50 | 51 | private OnCheckedChangeListener mListener; 52 | 53 | 54 | public interface OnCheckedChangeListener { 55 | void onCheckedChange(boolean isChecked, View view); 56 | } 57 | 58 | public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { 59 | mListener = listener; 60 | } 61 | 62 | public SwitchView(Context context) { 63 | this(context, null); 64 | } 65 | 66 | public SwitchView(Context context, AttributeSet attrs) { 67 | super(context, attrs); 68 | //获取自定义属性 69 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MySwitchView); 70 | mButtonColor = ta.getColor(R.styleable.MySwitchView_buttonColor, 0xffbab9b9); 71 | mStrokeColor = ta.getColor(R.styleable.MySwitchView_strokeColor, 0xffbab9b9); 72 | mMaskColor = ta.getColor(R.styleable.MySwitchView_maskColor, 0xff20a9c7); 73 | isChecked = ta.getBoolean(R.styleable.MySwitchView_state, false); 74 | 75 | ta.recycle(); 76 | 77 | initObjects(); 78 | 79 | } 80 | 81 | private void initObjects() { 82 | mPath = new Path(); 83 | mSlideHandler = new SlideHandler(); 84 | 85 | mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 86 | mStrokePaint.setStyle(Style.STROKE); 87 | mStrokePaint.setColor(mStrokeColor); 88 | 89 | mMaskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 90 | mMaskPaint.setStyle(Style.FILL); 91 | mMaskPaint.setColor(mMaskColor); 92 | 93 | mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 94 | mButtonPaint.setStyle(Style.FILL); 95 | mButtonPaint.setColor(mButtonColor); 96 | 97 | 98 | } 99 | 100 | @Override 101 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 102 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 103 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 104 | int heightSize = (int) (widthSize * 0.55f); 105 | setMeasuredDimension(widthSize, heightSize); 106 | 107 | } 108 | 109 | @Override 110 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 111 | super.onSizeChanged(w, h, oldw, oldh); 112 | mWidth = w; 113 | mHeight = h; 114 | 115 | //背景path的外部矩形,预留一定空间给边框 116 | mStrokeWidth = mHeight / 20f; 117 | mLeft = mStrokeWidth * 2; 118 | mTop = mStrokeWidth * 2; 119 | mRight = mWidth - mStrokeWidth * 2; 120 | mBottom = mHeight - mStrokeWidth * 2; 121 | 122 | RectF rectF = new RectF(mLeft, mTop, mBottom, mBottom); 123 | mPath.arcTo(rectF, 90, 180); 124 | rectF.left = mRight - mBottom; 125 | rectF.right = mRight; 126 | mPath.arcTo(rectF, 270, 180); 127 | 128 | mPath.close(); 129 | 130 | //滑动button外部正方形范围 131 | mButtonLeft = mLeft; 132 | mButtonRight = mBottom; 133 | mButtonWidth = mButtonRight - mButtonLeft; 134 | mButtonRadius = mButtonWidth / 6f; 135 | mButtonX = mButtonWidth / 2 + mStrokeWidth * 2; 136 | mButtonY = mButtonWidth / 2 + mStrokeWidth * 2; 137 | 138 | } 139 | 140 | @Override 141 | protected void onDraw(Canvas canvas) { 142 | super.onDraw(canvas); 143 | 144 | if (mSlidePercent > 0) 145 | mSlidePercent -= 0.1f; 146 | else 147 | mSlidePercent = 0; 148 | 149 | 150 | //绘制背景边框 151 | canvas.save(); 152 | mStrokePaint.setStrokeWidth(mStrokeWidth); 153 | canvas.drawPath(mPath, mStrokePaint); 154 | canvas.restore(); 155 | 156 | 157 | //绘制遮盖层 158 | float scale = (mStrokeWidth * 2 + mWidth) / mWidth * (isChecked ? 1 - mSlidePercent : mSlidePercent); 159 | canvas.save(); 160 | canvas.scale(scale, scale, mButtonWidth / 2, (mTop + mBottom) / 2); 161 | canvas.drawPath(mPath, mMaskPaint); 162 | canvas.restore(); 163 | 164 | //绘制滑动button 165 | final float translate = (mWidth - mStrokeWidth * 4 - mButtonWidth) * (isChecked ? 1 - mSlidePercent : mSlidePercent); // 平移距离参数随sAnim变化而变化 166 | canvas.save(); 167 | canvas.translate(translate, 0); 168 | 169 | //在滑动过程中根据状态改变button半径 170 | if (mSlidePercent > 0 && isChecked) { 171 | mButtonRadius += mButtonWidth / 50f; 172 | } else if (mSlidePercent > 0 && !isChecked) { 173 | mButtonRadius -= mButtonWidth / 50f; 174 | } 175 | 176 | // 根据状态改变paint颜色 177 | if (isChecked) 178 | mButtonPaint.setColor(Color.WHITE); 179 | else 180 | mButtonPaint.setColor(mButtonColor); 181 | 182 | canvas.drawCircle(mButtonX, mButtonY, mButtonRadius, mButtonPaint); 183 | canvas.restore(); 184 | 185 | } 186 | 187 | @Override 188 | public boolean onTouchEvent(MotionEvent event) { 189 | 190 | if (event.getAction() == MotionEvent.ACTION_UP) { 191 | //状态切换 192 | isChecked = !isChecked; 193 | 194 | mSlidePercent = 1; 195 | startSlide(); 196 | 197 | if (mListener != null) 198 | mListener.onCheckedChange(isChecked, this); 199 | } 200 | 201 | return true; 202 | } 203 | 204 | 205 | public boolean isChecked() { 206 | return isChecked; 207 | } 208 | 209 | public void setChecked(boolean checked) { 210 | if (isChecked != checked) { 211 | isChecked = checked; 212 | //开启滑动,重新绘制 213 | mSlidePercent = 1; 214 | startSlide(); 215 | 216 | if (mListener != null) 217 | mListener.onCheckedChange(isChecked, this); 218 | 219 | } 220 | } 221 | 222 | public void setMaskColor(int maskColor) { 223 | mMaskColor = maskColor; 224 | mMaskPaint.setColor(mMaskColor); 225 | invalidate(); 226 | } 227 | 228 | 229 | private void startSlide() { 230 | if (mSlidePercent > 0) 231 | mSlideHandler.sleep(5); 232 | 233 | 234 | } 235 | 236 | 237 | //处理滑动的Handler 238 | private class SlideHandler extends Handler { 239 | @Override 240 | public void handleMessage(Message msg) { 241 | 242 | SwitchView.this.invalidate(); 243 | 244 | // 循环调用 245 | SwitchView.this.startSlide(); 246 | } 247 | 248 | //延迟向Handler发送消息实现时间间隔 249 | public void sleep(long delayMillis) { 250 | this.removeMessages(0); 251 | sendMessageDelayed(obtainMessage(0), delayMillis); 252 | } 253 | } 254 | 255 | 256 | } 257 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/view/popupWindow/BasePopupWindow.java: -------------------------------------------------------------------------------- 1 | package com.ebook.view.popupWindow; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.drawable.BitmapDrawable; 6 | import android.util.DisplayMetrics; 7 | import android.view.LayoutInflater; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.WindowManager; 11 | import android.widget.PopupWindow; 12 | 13 | import com.ebook.R; 14 | 15 | /** 16 | * Created by xyy on 2017/3/31. 17 | */ 18 | 19 | public abstract class BasePopupWindow extends PopupWindow { 20 | 21 | protected Context mContext; 22 | protected View mConvertView; 23 | 24 | /** 25 | * @function 返回布局 26 | */ 27 | protected abstract View createConvertView(); 28 | 29 | 30 | public BasePopupWindow(Context context) { 31 | super(context); 32 | mContext = context; 33 | mConvertView = createConvertView(); 34 | setContentView(mConvertView); 35 | 36 | //一些常用的基本设置 37 | 38 | //获取屏幕的宽高 39 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 40 | DisplayMetrics metrics = new DisplayMetrics(); 41 | windowManager.getDefaultDisplay().getMetrics(metrics); 42 | 43 | setSize(metrics.widthPixels, metrics.heightPixels); 44 | 45 | setFocusable(true); 46 | setTouchable(true); 47 | setOutsideTouchable(true); 48 | setBackgroundDrawable(new BitmapDrawable()); 49 | 50 | //点击popupWindow外部消失 51 | setTouchInterceptor(new View.OnTouchListener() { 52 | @Override 53 | public boolean onTouch(View v, MotionEvent event) { 54 | if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 55 | dismiss(); 56 | return true; 57 | } 58 | return false; 59 | } 60 | }); 61 | 62 | } 63 | 64 | 65 | /** 66 | * @function 传入屏幕宽高,设置popupWindow默认宽高 67 | */ 68 | protected void setSize(int width, int height) { 69 | setWidth(width); 70 | setHeight((int) (height * 0.85)); 71 | 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/view/popupWindow/ContentPopup.java: -------------------------------------------------------------------------------- 1 | package com.ebook.view.popupWindow; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.BitmapDrawable; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.DisplayMetrics; 8 | import android.view.LayoutInflater; 9 | import android.view.MotionEvent; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.view.WindowManager; 13 | import android.widget.LinearLayout; 14 | import android.widget.PopupWindow; 15 | import android.widget.TextView; 16 | 17 | import com.ebook.R; 18 | import com.ebook.model.Book; 19 | 20 | import java.util.List; 21 | 22 | 23 | /** 24 | * Created by Administrator on 2017/1/7. 25 | */ 26 | 27 | public class ContentPopup extends BasePopupWindow { 28 | private RecyclerView mRecyclerView; 29 | private LinearLayout mLinearLayout; 30 | private Book mBook; 31 | 32 | private OnContentSelectedListener mListener; 33 | 34 | @Override 35 | protected View createConvertView() { 36 | return LayoutInflater.from(mContext) 37 | .inflate(R.layout.popup_content_layout, null); 38 | } 39 | 40 | 41 | public interface OnContentSelectedListener { 42 | void OnContentClicked(int paraIndex); 43 | } 44 | 45 | public void setOnContentClicked(OnContentSelectedListener listener) { 46 | mListener = listener; 47 | } 48 | 49 | public ContentPopup(Context context, Book book) { 50 | super(context); 51 | mBook = book; 52 | mLinearLayout = (LinearLayout) mConvertView.findViewById(R.id.pop_content_linear_layout); 53 | mRecyclerView = (RecyclerView) mConvertView.findViewById(R.id.pop_contents_recycle_view); 54 | mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext)); 55 | mRecyclerView.setAdapter(new ContentsAdapter(mBook.getBookContents())); 56 | 57 | } 58 | 59 | 60 | private class ContentsHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 61 | private TextView mTextView; 62 | private int mPosition; 63 | 64 | public ContentsHolder(View itemView) { 65 | super(itemView); 66 | mTextView = (TextView) itemView; 67 | itemView.setOnClickListener(this); 68 | } 69 | 70 | public void bind(String content, int position) { 71 | mPosition = position; 72 | mTextView.setText(content); 73 | } 74 | 75 | @Override 76 | public void onClick(View v) { 77 | 78 | if (mListener != null) 79 | mListener.OnContentClicked(mBook.getContentParaIndexs().get(mPosition)); 80 | 81 | } 82 | 83 | } 84 | 85 | private class ContentsAdapter extends RecyclerView.Adapter { 86 | private List mBookContents; 87 | 88 | public ContentsAdapter(List bookContents) { 89 | mBookContents = bookContents; 90 | } 91 | 92 | 93 | @Override 94 | public ContentsHolder onCreateViewHolder(ViewGroup parent, int viewType) { 95 | LayoutInflater inflater = LayoutInflater.from(mContext); 96 | View view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false); 97 | return new ContentsHolder(view); 98 | } 99 | 100 | @Override 101 | public void onBindViewHolder(ContentsHolder holder, int position) { 102 | holder.bind(mBookContents.get(position), position); 103 | 104 | } 105 | 106 | @Override 107 | public int getItemCount() { 108 | return mBookContents.size(); 109 | } 110 | } 111 | 112 | 113 | public void setBackgroundColor(int color) { 114 | mLinearLayout.setBackgroundColor(color); 115 | 116 | } 117 | 118 | 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/view/popupWindow/FontPopup.java: -------------------------------------------------------------------------------- 1 | package com.ebook.view.popupWindow; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.graphics.Typeface; 6 | import android.graphics.drawable.GradientDrawable; 7 | import android.support.design.widget.FloatingActionButton; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.TextView; 12 | 13 | 14 | import com.ebook.R; 15 | import com.ebook.util.bookPageUtil.PaintInfo; 16 | import com.ebook.util.SaveHelper; 17 | 18 | import java.io.IOException; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | 23 | /** 24 | * Created by Administrator on 2017/1/7. 25 | */ 26 | 27 | public class FontPopup extends BasePopupWindow implements View.OnClickListener { 28 | public static final String FONTS = "fonts"; 29 | private int mTypeIndex; 30 | private int[] mTextColors; 31 | 32 | private List mTypefaceList = new ArrayList<>(); 33 | private TextView[] mTexts; 34 | private Button[] mButtons; 35 | private FloatingActionButton[] mFabs; 36 | 37 | private OnFontSelectedListener mListener; 38 | 39 | public interface OnFontSelectedListener { 40 | void onTypefaceSelected(int typeIndex); 41 | 42 | void onColorSelected(int color); 43 | } 44 | 45 | public void setOnFontSelectedListener(OnFontSelectedListener listener) { 46 | mListener = listener; 47 | } 48 | 49 | @Override 50 | protected View createConvertView() { 51 | return LayoutInflater.from(mContext) 52 | .inflate(R.layout.popup_font_layout, null); 53 | } 54 | 55 | 56 | public FontPopup(Context context) { 57 | super(context); 58 | 59 | getFontFromAssets(); //从Assets中获取字体 60 | 61 | initViews(); 62 | 63 | initEvents(); 64 | 65 | } 66 | 67 | private void initEvents() { 68 | 69 | //设置初始状态 70 | PaintInfo paintInfo = SaveHelper.getObject(mContext, SaveHelper.PAINT_INFO); 71 | if (paintInfo != null) { 72 | mTypeIndex = paintInfo.typeIndex; 73 | } 74 | setUsedButton(); 75 | 76 | for (int i = 0; i < mTexts.length; i++) { 77 | mTexts[i].setTypeface(mTypefaceList.get(i)); 78 | } 79 | 80 | for (Button button : mButtons) { 81 | button.setOnClickListener(this); 82 | 83 | } 84 | 85 | for (FloatingActionButton fab : mFabs) { 86 | fab.setOnClickListener(this); 87 | 88 | } 89 | 90 | } 91 | 92 | 93 | @Override 94 | public void onClick(View v) { 95 | int typeIndex = mTypeIndex; 96 | int color = 0; 97 | 98 | for (int i = 0; i < mButtons.length; i++) { 99 | if (v.getId() == mButtons[i].getId()) { 100 | typeIndex = i; 101 | break; 102 | } 103 | } 104 | 105 | for (int i = 0; i < mFabs.length; i++) { 106 | if (v.getId() == mFabs[i].getId()) { 107 | color = mTextColors[i]; 108 | break; 109 | } 110 | } 111 | 112 | //改变字体 113 | if (typeIndex != mTypeIndex) { 114 | mTypeIndex = typeIndex; 115 | setUsedButton(); 116 | if (mListener != null) 117 | mListener.onTypefaceSelected(mTypeIndex); 118 | } 119 | 120 | //改变颜色 121 | if (color != 0) { 122 | if (mListener != null) 123 | mListener.onColorSelected(color); 124 | } 125 | 126 | 127 | } 128 | 129 | 130 | private void setUsedButton() { 131 | int unUsedColor = 0xffc1c0c0; 132 | int usedColor = 0xFF5FE677; 133 | Button usedButton = mButtons[mTypeIndex]; 134 | 135 | for (Button button : mButtons) { 136 | 137 | if (button.getId() == usedButton.getId()) { 138 | 139 | button.setText("正在使用"); 140 | button.setTextColor(usedColor); 141 | GradientDrawable drawable = (GradientDrawable) button.getBackground(); 142 | drawable.setStroke(5, usedColor); // 设置边框颜色 143 | 144 | 145 | } else { 146 | 147 | button.setText("点击使用"); 148 | button.setTextColor(unUsedColor); 149 | GradientDrawable drawable = (GradientDrawable) button.getBackground(); 150 | drawable.setStroke(5, unUsedColor); // 设置边框颜色 151 | 152 | } 153 | 154 | } 155 | 156 | } 157 | 158 | private void getFontFromAssets() { 159 | mTypefaceList.add(Typeface.DEFAULT); 160 | 161 | String[] fontNameList = null; 162 | AssetManager assetManager = mContext.getAssets(); 163 | try { 164 | fontNameList = assetManager.list(FONTS); 165 | } catch (IOException e) { 166 | e.printStackTrace(); 167 | } 168 | 169 | for (int i = 0; i < fontNameList.length; i++) { 170 | 171 | String fontPath = FONTS + "/" + fontNameList[i]; 172 | Typeface typeface = Typeface.createFromAsset(assetManager, fontPath);//根据路径得到Typeface 173 | mTypefaceList.add(typeface); 174 | } 175 | 176 | } 177 | 178 | private void initViews() { 179 | mTextColors = new int[]{ 180 | 0xff121111, //黑 181 | 0x8A000000, //常规 182 | 0xffa9a8a8, //夜间 183 | 0xfbe6e3e3, //白 184 | 0xff486c94, //蓝 185 | }; 186 | 187 | 188 | mTexts = new TextView[]{ 189 | (TextView) mConvertView.findViewById(R.id.text_system), 190 | (TextView) mConvertView.findViewById(R.id.text_hksn), 191 | (TextView) mConvertView.findViewById(R.id.text_hwzs), 192 | (TextView) mConvertView.findViewById(R.id.text_kai), 193 | (TextView) mConvertView.findViewById(R.id.text_yy) 194 | }; 195 | 196 | mButtons = new Button[]{ 197 | (Button) mConvertView.findViewById(R.id.btn_system), 198 | (Button) mConvertView.findViewById(R.id.btn_hksn), 199 | (Button) mConvertView.findViewById(R.id.btn_hwzs), 200 | (Button) mConvertView.findViewById(R.id.btn_kai), 201 | (Button) mConvertView.findViewById(R.id.btn_yy) 202 | }; 203 | 204 | mFabs = new FloatingActionButton[]{ 205 | (FloatingActionButton) mConvertView.findViewById(R.id.fab_black), 206 | (FloatingActionButton) mConvertView.findViewById(R.id.fab_normal), 207 | (FloatingActionButton) mConvertView.findViewById(R.id.fab_night), 208 | (FloatingActionButton) mConvertView.findViewById(R.id.fab_white), 209 | (FloatingActionButton) mConvertView.findViewById(R.id.fab_blue) 210 | }; 211 | 212 | 213 | } 214 | 215 | 216 | } 217 | -------------------------------------------------------------------------------- /app/src/main/java/com/ebook/view/popupWindow/LabelPopup.java: -------------------------------------------------------------------------------- 1 | package com.ebook.view.popupWindow; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | 14 | import com.ebook.R; 15 | import com.ebook.util.bookPageUtil.Label; 16 | 17 | import org.litepal.crud.DataSupport; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | 23 | /** 24 | * Created by Administrator on 2017/1/7. 25 | */ 26 | 27 | public class LabelPopup extends BasePopupWindow { 28 | private int mBookId; 29 | private RecyclerView mRecyclerView; 30 | private LinearLayout mLinearLayout; 31 | private FloatingActionButton mClearFab; 32 | private OnLabelSelectedListener mListener; 33 | 34 | 35 | @Override 36 | protected View createConvertView() { 37 | return LayoutInflater.from(mContext) 38 | .inflate(R.layout.popup_label_layout, null); 39 | } 40 | 41 | public interface OnLabelSelectedListener { 42 | void OnLabelClicked(Label label); 43 | } 44 | 45 | public void setOnLabelClicked(OnLabelSelectedListener listener) { 46 | mListener = listener; 47 | } 48 | 49 | 50 | public LabelPopup(Context context, int bookId) { 51 | super(context); 52 | mBookId = bookId; 53 | 54 | mLinearLayout = (LinearLayout) mConvertView.findViewById(R.id.pop_label_linear_layout); 55 | mClearFab = (FloatingActionButton) mConvertView.findViewById(R.id.pop_label_clear); 56 | mRecyclerView = (RecyclerView) mConvertView.findViewById(R.id.pop_label_recycle_view); 57 | mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext)); 58 | 59 | updateUI(); 60 | 61 | mClearFab.setOnClickListener(new View.OnClickListener() { 62 | @Override 63 | public void onClick(View v) { 64 | DataSupport.deleteAll(Label.class, "mBookId=?", mBookId + ""); 65 | updateUI(); 66 | } 67 | }); 68 | 69 | } 70 | 71 | //刷新列表 72 | public void updateUI() { 73 | 74 | List