├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
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 | 
14 |
15 |
16 | 
17 |
18 | 
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