diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml
index 256c54e..42bd19f 100644
--- a/.idea/assetWizardSettings.xml
+++ b/.idea/assetWizardSettings.xml
@@ -19,8 +19,8 @@
diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser
index e4f1bec..ba3975b 100644
Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ
diff --git a/.idea/modules.xml b/.idea/modules.xml
index c9f6d5e..669fcc4 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,8 +2,8 @@
-
-
+
+
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
index 56782ca..8bc84c2 100644
--- a/.idea/sqldialects.xml
+++ b/.idea/sqldialects.xml
@@ -1,6 +1,7 @@
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 261eeb9..394b624 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright 2020 fengyuecanzhu
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index a9a1b54..0d0ce49 100644
--- a/README.md
+++ b/README.md
@@ -200,3 +200,20 @@



+
+## LICENSE
+```
+ Copyright 2020 fengyuecanzhu
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+```
diff --git a/app/build.gradle b/app/build.gradle
index 9ca848e..0134962 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -136,7 +136,6 @@ dependencies {
implementation 'com.google.android.material:material:1.1.0'
- implementation 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:1.0.0'
//Scroller
implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5'
@@ -154,10 +153,25 @@ dependencies {
//ImmersionBar
implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
+ //简繁转换
+ implementation 'com.luhuiguo:chinese-utils:1.0'
+
+ //颜色选择
+ implementation 'com.jaredrummler:colorpicker:1.1.0'
+
+ //二维码
+ implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.7'
+ //Material Dialogs
+ /*def dialog_version = '3.3.0'
+ implementation "com.afollestad.material-dialogs:core:$dialog_version"
+ implementation "com.afollestad.material-dialogs:input:$dialog_version"
+ implementation "com.afollestad.material-dialogs:files:$dialog_version"
+ implementation "com.afollestad.material-dialogs:bottomsheets:$dialog_version"*/
+
}
greendao {
- schemaVersion 16
+ schemaVersion 18
daoPackage 'xyz.fycz.myreader.greendao.gen'
// targetGenDir 'src/main/java'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b635103..3249073 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -89,6 +89,10 @@
+
+
+
+
@@ -98,6 +102,7 @@
+
diff --git a/app/src/main/assets/bg/p01.jpg b/app/src/main/assets/bg/p01.jpg
new file mode 100644
index 0000000..d353f98
Binary files /dev/null and b/app/src/main/assets/bg/p01.jpg differ
diff --git a/app/src/main/assets/bg/p02.jpg b/app/src/main/assets/bg/p02.jpg
new file mode 100644
index 0000000..5c1f1ec
Binary files /dev/null and b/app/src/main/assets/bg/p02.jpg differ
diff --git a/app/src/main/assets/bg/p03.jpg b/app/src/main/assets/bg/p03.jpg
new file mode 100644
index 0000000..39138f6
Binary files /dev/null and b/app/src/main/assets/bg/p03.jpg differ
diff --git a/app/src/main/assets/bg/p04.jpg b/app/src/main/assets/bg/p04.jpg
new file mode 100644
index 0000000..04f0a6b
Binary files /dev/null and b/app/src/main/assets/bg/p04.jpg differ
diff --git a/app/src/main/assets/bg/p05.jpg b/app/src/main/assets/bg/p05.jpg
new file mode 100644
index 0000000..bc7ebc4
Binary files /dev/null and b/app/src/main/assets/bg/p05.jpg differ
diff --git a/app/src/main/assets/bg/p06.jpeg b/app/src/main/assets/bg/p06.jpeg
new file mode 100644
index 0000000..b3c9c25
Binary files /dev/null and b/app/src/main/assets/bg/p06.jpeg differ
diff --git a/app/src/main/assets/bg/p07.jpeg b/app/src/main/assets/bg/p07.jpeg
new file mode 100644
index 0000000..740f20a
Binary files /dev/null and b/app/src/main/assets/bg/p07.jpeg differ
diff --git a/app/src/main/assets/bg/p08.jpeg b/app/src/main/assets/bg/p08.jpeg
new file mode 100644
index 0000000..9e65f8c
Binary files /dev/null and b/app/src/main/assets/bg/p08.jpeg differ
diff --git a/app/src/main/assets/bg/p09.jpeg b/app/src/main/assets/bg/p09.jpeg
new file mode 100644
index 0000000..9abd953
Binary files /dev/null and b/app/src/main/assets/bg/p09.jpeg differ
diff --git a/app/src/main/assets/bg/p10.jpeg b/app/src/main/assets/bg/p10.jpeg
new file mode 100644
index 0000000..4b3e0b9
Binary files /dev/null and b/app/src/main/assets/bg/p10.jpeg differ
diff --git a/app/src/main/assets/bg/p11.png b/app/src/main/assets/bg/p11.png
new file mode 100644
index 0000000..725fdb4
Binary files /dev/null and b/app/src/main/assets/bg/p11.png differ
diff --git a/app/src/main/assets/bg/p12.png b/app/src/main/assets/bg/p12.png
new file mode 100644
index 0000000..91068c7
Binary files /dev/null and b/app/src/main/assets/bg/p12.png differ
diff --git a/app/src/main/assets/bg/p13.jpg b/app/src/main/assets/bg/p13.jpg
new file mode 100644
index 0000000..7ba1636
Binary files /dev/null and b/app/src/main/assets/bg/p13.jpg differ
diff --git a/app/src/main/assets/bg/p21.png b/app/src/main/assets/bg/p21.png
new file mode 100644
index 0000000..8e7093a
Binary files /dev/null and b/app/src/main/assets/bg/p21.png differ
diff --git a/app/src/main/assets/bg/p22.png b/app/src/main/assets/bg/p22.png
new file mode 100644
index 0000000..7b5abc0
Binary files /dev/null and b/app/src/main/assets/bg/p22.png differ
diff --git a/app/src/main/assets/bg/p23.png b/app/src/main/assets/bg/p23.png
new file mode 100644
index 0000000..fceb5ac
Binary files /dev/null and b/app/src/main/assets/bg/p23.png differ
diff --git a/app/src/main/assets/bg/p24.png b/app/src/main/assets/bg/p24.png
new file mode 100644
index 0000000..dd4d7f8
Binary files /dev/null and b/app/src/main/assets/bg/p24.png differ
diff --git a/app/src/main/assets/bg/p25.png b/app/src/main/assets/bg/p25.png
new file mode 100644
index 0000000..80f9258
Binary files /dev/null and b/app/src/main/assets/bg/p25.png differ
diff --git a/app/src/main/assets/bg/p26.png b/app/src/main/assets/bg/p26.png
new file mode 100644
index 0000000..47a2f6c
Binary files /dev/null and b/app/src/main/assets/bg/p26.png differ
diff --git a/app/src/main/assets/bg/p27.png b/app/src/main/assets/bg/p27.png
new file mode 100644
index 0000000..b5ac4b1
Binary files /dev/null and b/app/src/main/assets/bg/p27.png differ
diff --git a/app/src/main/assets/font_img/仓耳与墨W02.png b/app/src/main/assets/font_img/仓耳与墨W02.png
new file mode 100644
index 0000000..6c8922b
Binary files /dev/null and b/app/src/main/assets/font_img/仓耳与墨W02.png differ
diff --git a/app/src/main/assets/font_img/仓耳舒圆体W02.png b/app/src/main/assets/font_img/仓耳舒圆体W02.png
new file mode 100644
index 0000000..db27d4d
Binary files /dev/null and b/app/src/main/assets/font_img/仓耳舒圆体W02.png differ
diff --git a/app/src/main/assets/font_img/仓耳非白W02.png b/app/src/main/assets/font_img/仓耳非白W02.png
new file mode 100644
index 0000000..ea5d5f6
Binary files /dev/null and b/app/src/main/assets/font_img/仓耳非白W02.png differ
diff --git a/app/src/main/assets/font_img/包图小白体.png b/app/src/main/assets/font_img/包图小白体.png
new file mode 100644
index 0000000..a560b59
Binary files /dev/null and b/app/src/main/assets/font_img/包图小白体.png differ
diff --git a/app/src/main/assets/font_img/千图小兔体.png b/app/src/main/assets/font_img/千图小兔体.png
new file mode 100644
index 0000000..b1669bc
Binary files /dev/null and b/app/src/main/assets/font_img/千图小兔体.png differ
diff --git a/app/src/main/assets/font_img/品如手写体.png b/app/src/main/assets/font_img/品如手写体.png
new file mode 100644
index 0000000..0dea631
Binary files /dev/null and b/app/src/main/assets/font_img/品如手写体.png differ
diff --git a/app/src/main/assets/font_img/手书体.png b/app/src/main/assets/font_img/手书体.png
new file mode 100644
index 0000000..6126f4d
Binary files /dev/null and b/app/src/main/assets/font_img/手书体.png differ
diff --git a/app/src/main/assets/font_img/方正书宋简体.png b/app/src/main/assets/font_img/方正书宋简体.png
new file mode 100644
index 0000000..4ac0400
Binary files /dev/null and b/app/src/main/assets/font_img/方正书宋简体.png differ
diff --git a/app/src/main/assets/font_img/方正仿宋简体.png b/app/src/main/assets/font_img/方正仿宋简体.png
new file mode 100644
index 0000000..6c12b3d
Binary files /dev/null and b/app/src/main/assets/font_img/方正仿宋简体.png differ
diff --git a/app/src/main/assets/font_img/方正楷体.png b/app/src/main/assets/font_img/方正楷体.png
new file mode 100644
index 0000000..12ec889
Binary files /dev/null and b/app/src/main/assets/font_img/方正楷体.png differ
diff --git a/app/src/main/assets/font_img/方正硬笔行书.png b/app/src/main/assets/font_img/方正硬笔行书.png
new file mode 100644
index 0000000..55c5e61
Binary files /dev/null and b/app/src/main/assets/font_img/方正硬笔行书.png differ
diff --git a/app/src/main/assets/font_img/方正行楷.png b/app/src/main/assets/font_img/方正行楷.png
new file mode 100644
index 0000000..2da99db
Binary files /dev/null and b/app/src/main/assets/font_img/方正行楷.png differ
diff --git a/app/src/main/assets/font_img/方正黄草.png b/app/src/main/assets/font_img/方正黄草.png
new file mode 100644
index 0000000..fd8b8c1
Binary files /dev/null and b/app/src/main/assets/font_img/方正黄草.png differ
diff --git a/app/src/main/assets/font_img/方正黑体简体.png b/app/src/main/assets/font_img/方正黑体简体.png
new file mode 100644
index 0000000..2f82f55
Binary files /dev/null and b/app/src/main/assets/font_img/方正黑体简体.png differ
diff --git a/app/src/main/assets/font_img/杨任东竹石体.png b/app/src/main/assets/font_img/杨任东竹石体.png
new file mode 100644
index 0000000..b381b66
Binary files /dev/null and b/app/src/main/assets/font_img/杨任东竹石体.png differ
diff --git a/app/src/main/assets/font_img/演示夏行楷.png b/app/src/main/assets/font_img/演示夏行楷.png
new file mode 100644
index 0000000..153b36f
Binary files /dev/null and b/app/src/main/assets/font_img/演示夏行楷.png differ
diff --git a/app/src/main/assets/font_img/演示悠然小楷.png b/app/src/main/assets/font_img/演示悠然小楷.png
new file mode 100644
index 0000000..0ec2a8d
Binary files /dev/null and b/app/src/main/assets/font_img/演示悠然小楷.png differ
diff --git a/app/src/main/assets/font_img/演示春风楷.png b/app/src/main/assets/font_img/演示春风楷.png
new file mode 100644
index 0000000..00c6470
Binary files /dev/null and b/app/src/main/assets/font_img/演示春风楷.png differ
diff --git a/app/src/main/assets/font_img/演示秋鸿楷.png b/app/src/main/assets/font_img/演示秋鸿楷.png
new file mode 100644
index 0000000..305b5a5
Binary files /dev/null and b/app/src/main/assets/font_img/演示秋鸿楷.png differ
diff --git a/app/src/main/assets/font_img/站酷仓耳渔阳体.png b/app/src/main/assets/font_img/站酷仓耳渔阳体.png
new file mode 100644
index 0000000..f1a9282
Binary files /dev/null and b/app/src/main/assets/font_img/站酷仓耳渔阳体.png differ
diff --git a/app/src/main/assets/font_img/经典宋体.png b/app/src/main/assets/font_img/经典宋体.png
new file mode 100644
index 0000000..e6555e4
Binary files /dev/null and b/app/src/main/assets/font_img/经典宋体.png differ
diff --git a/app/src/main/assets/font_img/迷你隶书.png b/app/src/main/assets/font_img/迷你隶书.png
new file mode 100644
index 0000000..a86c029
Binary files /dev/null and b/app/src/main/assets/font_img/迷你隶书.png differ
diff --git a/app/src/main/assets/share.png b/app/src/main/assets/share.png
new file mode 100644
index 0000000..789abb0
Binary files /dev/null and b/app/src/main/assets/share.png differ
diff --git a/app/src/main/assets/updatelog.fy b/app/src/main/assets/updatelog.fy
index 4e70f17..c242fd9 100644
--- a/app/src/main/assets/updatelog.fy
+++ b/app/src/main/assets/updatelog.fy
@@ -1,16 +1,63 @@
+1、新增书架排序设置
+2、新增书籍分享(以图片+二维码的形式分享)及扫一扫
+
+2020.11.28
+风月读书v1.20.112822
+1、新增书源:妙趣阁、丹书铁券、小说旗、读一读网、飘天文学
+2、新增书城:100本·实体(名著)、妙趣阁、小说旗
+3、新增排行榜:小说旗
+4、新增模糊搜索、精确搜索(建议选择模糊搜索)
+5、修复书源:搜小说网
+6、修复应用切换到后台后从桌面启动时重新进入欢迎页的bug
+7、修复横屏模式下进入阅读界面闪退的bug
+8、修复恢复备份后设置未立即生效的bug
+9、阅读布局支持备份/恢复
+
+2020.11.23
+风月读书v1.20.112309
+1、修复切换布局是预加载章节页面混乱的bug
+2、修复自定义布局后自动覆盖已有布局的bug
+
+2020.11.22
+风月读书v1.20.112216
+1、修复音量键翻页失效的bug
+2、修复滚动模式下自动跳行的bug
+3、修复上下翻页模式下使用音量键翻上页动画丢失的bug
+4、修复不同布局下亮度和护眼混乱的bug
+
+2020.11.22
+风月读书v1.20.112212
+1、新增布局设置:支持自定义阅读背景/背景颜色、字体颜色,并内置20张背景图片;支持保存布局(5套日间布局+1套夜间布局);支持导入导出布局
+2、新增排版设置:支持自定义排版(行间距、段间距、字间距及四边距)
+3、新增文字繁简转换
+4、新增横屏阅读
+5、新增上下翻页模式
+6、新增护眼模式
+7、新增朗读时音量键翻页、菜单显示章节标题/链接设置开关
+8、优化自动翻页,修改自动翻页方式为滚屏
+9、优化阅读设置菜单
+10、优化阅读文字显示
+
2020.11.14
风月读书v1.20.1111420
+1、修复语音朗读时部分章节朗读段落高亮错位的bug
+2、新增阅读界面显示状态栏设置
+3、新增阅读界面全屏点击翻下页设置
+4、新增多种字体
+5、优化翻页速度
+
+2020.11.12
+风月读书v1.20.1111217
1、修复语音朗读定时停止无效的bug
2、修复语音朗读时切换日夜间闪退的bug
3、修复语音朗读时切换章节闪退的bug
4、修复语音朗读时部分安卓版本卡死的bug
-5、修复语音朗读时部分章节朗读段落高亮错位的bug
+5、修复语音朗读时章节首页朗读段落高亮错位的bug
6、修复语音朗读时章节标题不高亮的bug
7、修复安卓5进入字体界面闪退的bug
2020.11.11
-风月读书v1.20.1111121
-1、新增语音朗读(听书)
+1、新增语音朗读
2020.11.08
风月读书v1.20.1110811
diff --git a/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java b/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java
index b16056f..37b1def 100644
--- a/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java
+++ b/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java
@@ -44,6 +44,8 @@ import javax.net.ssl.X509TrustManager;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.common.URLCONST;
+import xyz.fycz.myreader.model.backup.UserService;
+import xyz.fycz.myreader.model.storage.Backup;
import xyz.fycz.myreader.ui.dialog.APPDownloadTip;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.entity.Setting;
@@ -51,7 +53,9 @@ import xyz.fycz.myreader.entity.UpdateInfo;
import xyz.fycz.myreader.ui.activity.MainActivity;
import xyz.fycz.myreader.ui.fragment.BookcaseFragment;
import xyz.fycz.myreader.util.*;
+import xyz.fycz.myreader.util.llog.LLog;
import xyz.fycz.myreader.util.utils.NetworkUtils;
+import xyz.fycz.myreader.webapi.callback.ResultCallback;
public class MyApplication extends Application {
@@ -73,6 +77,7 @@ public class MyApplication extends Application {
}
mFixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());//初始化线程池
initNightTheme();
+// LLog.init(APPCONST.LOG_DIR);
}
public void initNightTheme() {
@@ -87,7 +92,7 @@ public class MyApplication extends Application {
}
}
- protected boolean isNightTheme() {
+ public boolean isNightTheme() {
return !SysManager.getSetting().isDayStyle();
}
@@ -254,7 +259,7 @@ public class MyApplication extends Application {
MyApplication.getApplication().newThread(() -> {
Document doc = null;
try {
- String url = "https://shimo.im/docs/JzVpMgbZUJARSaFs/read";
+ String url = "https://shimo.im/docs/cqkgjPRRydYYhQKt/read";
if (isApkInDebug(getmContext())) {
url = "https://shimo.im/docs/zfzpda7MUGskOC9v/read";
}
diff --git a/app/src/main/java/xyz/fycz/myreader/application/SysManager.java b/app/src/main/java/xyz/fycz/myreader/application/SysManager.java
index 8db9dfa..fd80134 100644
--- a/app/src/main/java/xyz/fycz/myreader/application/SysManager.java
+++ b/app/src/main/java/xyz/fycz/myreader/application/SysManager.java
@@ -1,41 +1,59 @@
package xyz.fycz.myreader.application;
import xyz.fycz.myreader.common.APPCONST;
+import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.enums.BookcaseStyle;
import xyz.fycz.myreader.enums.Font;
import xyz.fycz.myreader.enums.Language;
import xyz.fycz.myreader.enums.ReadStyle;
+import xyz.fycz.myreader.model.backup.UserService;
import xyz.fycz.myreader.model.storage.Backup;
import xyz.fycz.myreader.util.CacheHelper;
+import xyz.fycz.myreader.R;
import xyz.fycz.myreader.entity.Setting;
+import xyz.fycz.myreader.webapi.callback.ResultCallback;
+import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import xyz.fycz.myreader.widget.page.PageMode;
import static xyz.fycz.myreader.application.MyApplication.getVersionCode;
+import static xyz.fycz.myreader.common.APPCONST.READ_STYLE_LEATHER;
+import static xyz.fycz.myreader.common.APPCONST.s;
+import static xyz.fycz.myreader.widget.page.PageLoader.DEFAULT_MARGIN_WIDTH;
public class SysManager {
- public static void logout() {
-
- }
+ private static Setting mSetting;
/**
* 获取设置
* @return
*/
public static Setting getSetting() {
+ if (mSetting != null){
+ return mSetting;
+ }
+ mSetting = (Setting) CacheHelper.readObject(APPCONST.FILE_NAME_SETTING);
+ if (mSetting == null){
+ mSetting = getDefaultSetting();
+ saveSetting(mSetting);
+ }
+ return mSetting;
+ }
+ public static Setting getNewSetting() {
Setting setting = (Setting) CacheHelper.readObject(APPCONST.FILE_NAME_SETTING);
- if (setting == null){
+ if (setting == null) {
setting = getDefaultSetting();
saveSetting(setting);
}
return setting;
}
- /**
- * 保存设置
- * @param setting
- */
+
+ /**
+ * 保存设置
+ * @param setting
+ */
public static void saveSetting(Setting setting) {
CacheHelper.saveObject(setting, APPCONST.FILE_NAME_SETTING);
}
@@ -48,19 +66,8 @@ public class SysManager {
private static Setting getDefaultSetting(){
Setting setting = new Setting();
setting.setDayStyle(true);
- setting.setReadStyle(ReadStyle.leather);
- setting.setReadWordSize(25);
- setting.setBrightProgress(50);
- setting.setBrightFollowSystem(true);
- setting.setLanguage(Language.simplified);
- setting.setFont(Font.默认字体);
- setting.setAutoScrollSpeed(300);
- setting.setPageMode(PageMode.COVER);
- setting.setVolumeTurnPage(true);
- setting.setResetScreen(3);
setting.setBookcaseStyle(BookcaseStyle.listMode);
setting.setNewestVersionCode(getVersionCode());
- setting.setLocalFontName("");
setting.setAutoSyn(false);
setting.setMatchChapter(true);
setting.setMatchChapterSuitability(0.7f);
@@ -68,9 +75,17 @@ public class SysManager {
setting.setRefreshWhenStart(true);
setting.setOpenBookStore(true);
setting.setSettingVersion(APPCONST.SETTING_VERSION);
+ setting.setSourceVersion(APPCONST.SOURCE_VERSION);
+ setting.setHorizontalScreen(false);
+ setting.initReadStyle();
+ setting.setCurReadStyleIndex(1);
return setting;
}
+ public static void regetmSetting(){
+ mSetting = (Setting) CacheHelper.readObject(APPCONST.FILE_NAME_SETTING);
+ }
+
/**
* 重置设置
@@ -78,7 +93,18 @@ public class SysManager {
public static void resetSetting(){
Setting setting = getSetting();
- Backup.INSTANCE.backup(MyApplication.getmContext(), APPCONST.BACKUP_FILE_DIR,null, false);
+ setting.initReadStyle();
+ setting.setCurReadStyleIndex(1);
+ setting.setSharedLayout(true);
setting.setSettingVersion(APPCONST.SETTING_VERSION);
+ saveSetting(setting);
+ }
+
+ public static void resetSource(){
+ Setting setting = getSetting();
+ ReadCrawlerUtil.addReadCrawler(BookSource.miaobi, BookSource.dstq, BookSource.xs7, BookSource.du1du,BookSource.paiotian);
+ ReadCrawlerUtil.removeReadCrawler("cangshu99");
+ setting.setSourceVersion(APPCONST.SOURCE_VERSION);
+ saveSetting(setting);
}
}
diff --git a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
index fab16c9..7b95886 100644
--- a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
+++ b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
@@ -18,6 +18,9 @@ public class APPCONST {
public static final String ALARM_SCHEDULE_MSG = "alarm_schedule_msg";
public static final String FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/";
+ public static final String SHARE_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/share/";
+ public static final String LOG_DIR = Environment.getExternalStorageDirectory() + "/FYReader/log/";
+ public static final String BG_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/bg/";
public static final String TEM_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/tem/";
public static final String BACKUP_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/backup/";
public static final String TXT_BOOK_DIR = Environment.getExternalStorageDirectory() + "/FYReader/noveltxt/";
@@ -29,6 +32,7 @@ public class APPCONST {
+ "book_cache"+ File.separator ;
public static String HTML_CACHE_PATH = FileUtils.getCachePath() + File.separator
+ "html_cache"+ File.separator ;
+ public static final String LOADING_ERROR = "\t\t \t\t\t\t\n\n 章节内容转码失败!\n\n\t\t \t\t\t\n\n";
public static long exitTime;
public static final int exitConfirmTime = 2000;
@@ -41,28 +45,35 @@ public class APPCONST {
public static final String SEARCH_BOOK_BEAN = "SearchBookBean";
public static final String SOURCE_INDEX = "sourceIndex";
public static final String FONT = "font";
- public static final String RESULT_RESET_SCREEN = "result_reset_screen";
+ public static final String RESULT_NEED_REFRESH = "result_need_refresh";
public static final String RESULT_IS_COLLECTED = "result_is_collected";
public static final String RESULT_LAST_READ_POSITION = "result_last_read_position";
public static final String RESULT_HISTORY_CHAPTER = "result_history_chapter";
+ public static final String RESULT_UP_MENU = "result_up_meu";
- public static final int[] READ_STYLE_NIGHT = {R.color.sys_night_word, R.color.sys_night_bg};//黑夜
- public static final int[] READ_STYLE_PROTECTED_EYE = {R.color.sys_protect_eye_word, R.color.sys_protect_eye_bg};//护眼
- public static final int[] READ_STYLE_COMMON = {R.color.sys_common_word, R.color.sys_common_bg};//普通
- public static final int[] READ_STYLE_BLUE_DEEP = {R.color.sys_blue_deep_word, R.color.sys_blue_deep_bg};//深蓝
- public static final int[] READ_STYLE_LEATHER = {R.color.sys_leather_word, R.color.sys_leather_bg};//羊皮纸
- public static final int[] READ_STYLE_BREEN_EYE = {R.color.sys_breen_word, R.color.sys_breen_bg};//棕绿色
+ public static final String[] READ_STYLE_NIGHT = {"#94928c", "#393431"};//黑夜
+ public static final String[] READ_STYLE_PROTECTED_EYE = {"#313031", "#E1F1DA"};//护眼
+ public static final String[] READ_STYLE_COMMON = {"#313031", "#f5f4f0"};//普通
+ public static final String[] READ_STYLE_BLUE_DEEP = {"#637079", "#001c29"};//深蓝
+ public static final String[] READ_STYLE_LEATHER = {"#313031", "#CEC29C"};//羊皮纸
+ public static final String[] READ_STYLE_BREEN = {"#313031", "#b5bd9a"};//棕绿色
public static final String FILE_NAME_SETTING = "setting";
public static final String FILE_NAME_UPDATE_INFO = "updateInfo";
public static final String FILE_NAME_SPLASH_IMAGE = "splashImage";
+ public static final int SELECT_TEXT_COLOR = 201;
+ public static final int SELECT_BG_COLOR = 301;
+
public static final int REQUEST_FONT = 1001;
public static final int REQUEST_CHAPTER_PAGE = 1002;
- public static final int REQUEST_RESET_SCREEN_TIME = 1003;
- public static final int REQUEST_LOGIN = 1004;
+ public static final int REQUEST_LOGIN = 1003;
+ public static final int REQUEST_REFRESH_READ_UI = 1004;
+ public static final int REQUEST_SELECT_BG = 1005;
+ public static final int REQUEST_IMPORT_LAYOUT = 1006;
+ public static final int REQUEST_QR_SCAN = 1007;
public static final int REQUEST_READ = 1;
@@ -79,7 +90,9 @@ public class APPCONST {
public static final int PERMISSIONS_REQUEST_STORAGE = 10001;
//设置版本号
- public static final int SETTING_VERSION = 6;
+ public static final int SETTING_VERSION = 11;
+
+ public static final int SOURCE_VERSION = 1;
public static final String FORMAT_FILE_DATE = "yyyy-MM-dd";
@@ -87,7 +100,6 @@ public class APPCONST {
public final static String channelIdRead = "channel_read_aloud";
-
public static final String DEFAULT_WEB_DAV_URL = "https://dav.jianguoyun.com/dav/";
}
diff --git a/app/src/main/java/xyz/fycz/myreader/entity/ReadBookControl.java b/app/src/main/java/xyz/fycz/myreader/entity/ReadBookControl.java
new file mode 100644
index 0000000..32a1d00
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/entity/ReadBookControl.java
@@ -0,0 +1,813 @@
+//Copyright (c) 2017. 章钦豪. All rights reserved.
+package xyz.fycz.myreader.entity;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import xyz.fycz.myreader.application.MyApplication;
+import xyz.fycz.myreader.util.SharedPreUtils;
+import xyz.fycz.myreader.util.utils.BitmapUtil;
+import xyz.fycz.myreader.util.utils.MeUtils;
+
+import static xyz.fycz.myreader.widget.page2.PageLoader.DEFAULT_MARGIN_WIDTH;
+
+
+public class ReadBookControl {
+ private static final int DEFAULT_BG = 1;
+ private int textDrawableIndex = DEFAULT_BG;
+ private List
+ * Created on 15/12/25.
+ */
+public class LExecutor extends Thread {
+
+ private final BlockingQueue mQueue;
+
+ private volatile boolean mQuit = false;
+
+ private static DateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ private static DateFormat mTimeFormat = new SimpleDateFormat("MM-dd HH:mm:ss");
+
+ public LExecutor(BlockingQueue mQueue) {
+ this.mQueue = mQueue;
+ }
+
+ @Override
+ public void run() {
+ setThreadPriority(THREAD_PRIORITY_BACKGROUND);
+
+ while (true) {
+ LMsg logMsg;
+ try {
+
+ logMsg = mQueue.take();
+
+ } catch (InterruptedException e) {
+
+ if (mQuit) {
+ return;
+ }
+ continue;
+ }
+
+ String date = mDateFormat.format(Calendar.getInstance(Locale.CHINA).getTime());
+
+ String fileName = date + "log.html";
+
+ StringBuilder builder = new StringBuilder();
+
+ switch (logMsg.getPriority()) {
+ case LMsg.INFO:
+ builder.append("");
+ break;
+ case LMsg.DEBUG:
+ builder.append("");
+ break;
+ case LMsg.ERROR:
+ builder.append("");
+ break;
+ case LMsg.WARNING:
+ builder.append("");
+ break;
+ }
+
+ Calendar calendar = Calendar.getInstance();
+ builder.append("[ " + mTimeFormat.format(calendar.getTime()) + " ] ");
+ builder.append("[ " + logMsg.getTag() + " ] :");
+ builder.append(logMsg.getMsg());
+ builder.append("
");
+
+ try {
+ File file = getLogFile(fileName);
+ if (file == null) {
+ return;
+ }
+ FileOutputStream outputStream = new FileOutputStream(file, true);
+ outputStream.write(builder.toString().getBytes());
+ outputStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ /**
+ * Forces this executor to quit immediately. If any requests are still in
+ * the queue, they are not guaranteed to be processed.
+ */
+ public void quit() {
+ mQuit = true;
+ interrupt();
+ }
+
+
+ /**
+ * check sd
+ *
+ * @return
+ */
+ private static boolean checkSDCard() {
+ if (Environment.getExternalStorageState().equals(
+ Environment.MEDIA_MOUNTED)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * generate the log file
+ *
+ * @param fileName
+ * @return
+ */
+ private static File getLogFile(String fileName) {
+
+ if (!checkSDCard()) {
+ return null;
+ }
+
+ File storageRoot = Environment.getExternalStorageDirectory();
+
+ String path = LLog.getRootPath() + File.separator + "log"; //日志文件目录
+
+ File tmpFile = new File(storageRoot.getAbsolutePath().concat(File.separator).concat(path));
+
+ if (!tmpFile.exists()) {
+
+ boolean result = tmpFile.mkdirs();
+
+ if (result) {
+ return new File(tmpFile, fileName);
+ }
+
+ return null;
+ }
+
+ return new File(tmpFile, fileName);
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/llog/LLog.java b/app/src/main/java/xyz/fycz/myreader/util/llog/LLog.java
new file mode 100644
index 0000000..435ed3c
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/llog/LLog.java
@@ -0,0 +1,133 @@
+package xyz.fycz.myreader.util.llog;
+
+/**
+ * @author luocan
+ * @version 1.0
+ *
+ * Created on 15/12/25.
+ */
+public class LLog {
+
+ private static boolean isDebuggable = true;
+ private static LQueue mLogQueue;
+ private static String mRootPath;
+
+
+ /**
+ * @param rootPath the log file path
+ */
+ public static void init(String rootPath) {
+ mLogQueue = new LQueue();
+ mLogQueue.start();
+ mRootPath = rootPath;
+ }
+
+ public static String getRootPath() {
+ return mRootPath;
+ }
+
+
+ /**
+ * open or close the function
+ *
+ * @param isDebuggable if set to false, not print the message.
+ */
+ public static void setIsDebuggable(boolean isDebuggable) {
+ LLog.isDebuggable = isDebuggable;
+ }
+
+ /**
+ * error log
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void e(String tag, String msg) {
+ if (!isDebuggable) {
+ return;
+ }
+
+ if (mLogQueue == null) {
+ throw new NullPointerException("Must call init method first!");
+ }
+
+ LMsg logMsg = new LMsg();
+ logMsg.setTag(tag);
+ logMsg.setMsg(msg);
+ logMsg.setPriority(LMsg.ERROR);
+ mLogQueue.add(logMsg);
+ }
+
+
+ /**
+ * warning log
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void w(String tag, String msg) {
+ if (!isDebuggable) {
+ return;
+ }
+
+ if (mLogQueue == null) {
+ throw new NullPointerException("Must call init method first!");
+ }
+
+ LMsg logMsg = new LMsg();
+ logMsg.setTag(tag);
+ logMsg.setMsg(msg);
+ logMsg.setPriority(LMsg.WARNING);
+ mLogQueue.add(logMsg);
+ }
+
+
+ /**
+ * info log
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void i(String tag, String msg) {
+ if (!isDebuggable) {
+ return;
+ }
+
+ if (mLogQueue == null) {
+ throw new NullPointerException("Must call init method first!");
+ }
+
+ LMsg logMsg = new LMsg();
+ logMsg.setTag(tag);
+ logMsg.setMsg(msg);
+ logMsg.setPriority(LMsg.INFO);
+ mLogQueue.add(logMsg);
+ }
+
+
+ /**
+ * debug log
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void d(String tag, String msg) {
+ if (!isDebuggable) {
+ return;
+ }
+
+ if (mLogQueue == null) {
+ throw new NullPointerException("Must call init method first!");
+ }
+
+ LMsg logMsg = new LMsg();
+ logMsg.setTag(tag);
+ logMsg.setMsg(msg);
+ logMsg.setPriority(LMsg.DEBUG);
+ mLogQueue.add(logMsg);
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/llog/LMsg.java b/app/src/main/java/xyz/fycz/myreader/util/llog/LMsg.java
new file mode 100644
index 0000000..583d1c5
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/llog/LMsg.java
@@ -0,0 +1,44 @@
+package xyz.fycz.myreader.util.llog;
+
+/**
+ * @author luocan
+ * @version 1.0
+ *
+ * Created on 15/12/25.
+ */
+public class LMsg {
+ public static final int INFO = 0x01;
+ public static final int DEBUG = 0x02;
+ public static final int ERROR = 0x03;
+ public static final int WARNING = 0x04;
+
+ protected String mTag;
+ protected String mMsg;
+ protected int mPriority;
+
+
+ public String getTag() {
+ return mTag;
+ }
+
+ public void setTag(String tag) {
+ this.mTag = tag;
+ }
+
+ public String getMsg() {
+ return mMsg;
+ }
+
+ public void setMsg(String msg) {
+ this.mMsg = msg;
+ }
+
+ public int getPriority() {
+ return mPriority;
+ }
+
+ public void setPriority(int priority) {
+ this.mPriority = priority;
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/llog/LQueue.java b/app/src/main/java/xyz/fycz/myreader/util/llog/LQueue.java
new file mode 100644
index 0000000..5325b56
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/llog/LQueue.java
@@ -0,0 +1,46 @@
+package xyz.fycz.myreader.util.llog;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+
+/**
+ * @author luocan
+ * @version 1.0
+ *
+ * Created on 15/12/25.
+ */
+public class LQueue {
+
+ private final LinkedBlockingQueue mLogQueue = new LinkedBlockingQueue();
+ private LExecutor logExecutor;
+
+
+ public void add(LMsg logMsg) {
+ try {
+ mLogQueue.add(logMsg);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public void start() {
+ stop();
+ try {
+ logExecutor = new LExecutor(mLogQueue);
+ logExecutor.start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void stop() {
+ try {
+ if (logExecutor != null) {
+ logExecutor.quit();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/BitmapUtil.java b/app/src/main/java/xyz/fycz/myreader/util/utils/BitmapUtil.java
new file mode 100644
index 0000000..af55e8e
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/BitmapUtil.java
@@ -0,0 +1,394 @@
+package xyz.fycz.myreader.util.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicBlur;
+import android.util.Log;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import xyz.fycz.myreader.application.MyApplication;
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class BitmapUtil {
+ private static final String TAG = "BitmapUtil";
+
+ public static Bitmap getFitSampleBitmap(String file_path, int width, int height) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(file_path, options);
+ options.inSampleSize = getFitInSampleSize(width, height, options);
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeFile(file_path, options);
+ }
+
+ public static int getFitInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) {
+ int inSampleSize = 1;
+ if (options.outWidth > reqWidth || options.outHeight > reqHeight) {
+ int widthRatio = Math.round((float) options.outWidth / (float) reqWidth);
+ int heightRatio = Math.round((float) options.outHeight / (float) reqHeight);
+ inSampleSize = Math.min(widthRatio, heightRatio);
+ }
+ return inSampleSize;
+ }
+
+ /**
+ * 通过资源id转化成Bitmap
+ */
+ public static Bitmap ReadBitmapById(Context context, int resId) {
+ BitmapFactory.Options opt = new BitmapFactory.Options();
+ opt.inPreferredConfig = Config.ARGB_8888;
+ opt.inPurgeable = true;
+ opt.inInputShareable = true;
+ InputStream is = context.getResources().openRawResource(resId);
+ return BitmapFactory.decodeStream(is, null, opt);
+ }
+
+ /**
+ * 缩放Bitmap满屏
+ */
+ public static Bitmap getBitmap(Bitmap bitmap, int screenWidth,
+ int screenHeight) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ Matrix matrix = new Matrix();
+ float scale = (float) screenWidth / w;
+ float scale2 = (float) screenHeight / h;
+ // scale = scale < scale2 ? scale : scale2;
+ matrix.postScale(scale, scale);
+ Bitmap bmp = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
+ if (!bitmap.equals(bmp) && !bitmap.isRecycled()) {
+ bitmap.recycle();
+ bitmap = null;
+ }
+ return bmp;// Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
+ }
+
+ /**
+ * 按最大边按一定大小缩放图片
+ */
+ public static Bitmap scaleImage(byte[] buffer, float size) {
+ // 获取原图宽度
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ options.inPurgeable = true;
+ options.inInputShareable = true;
+ Bitmap bm;
+ // 计算缩放比例
+ float reSize = options.outWidth / size;
+ if (options.outWidth < options.outHeight) {
+ reSize = options.outHeight / size;
+ }
+ // 如果是小图则放大
+ if (reSize <= 1) {
+ int newWidth = 0;
+ int newHeight = 0;
+ if (options.outWidth > options.outHeight) {
+ newWidth = (int) size;
+ newHeight = options.outHeight * (int) size / options.outWidth;
+ } else {
+ newHeight = (int) size;
+ newWidth = options.outWidth * (int) size / options.outHeight;
+ }
+ bm = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
+ bm = scaleImage(bm, newWidth, newHeight);
+ if (bm == null) {
+ Log.e(TAG, "convertToThumb, decode fail:" + null);
+ return null;
+ }
+ return bm;
+ }
+ // 缩放
+ options.inJustDecodeBounds = false;
+ options.inSampleSize = (int) reSize;
+ bm = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
+ if (bm == null) {
+ Log.e(TAG, "convertToThumb, decode fail:" + null);
+ return null;
+ }
+ return bm;
+ }
+
+ /**
+ * 检查图片是否超过一定值,是则缩小
+ */
+ public static Bitmap convertToThumb(byte[] buffer, float size) {
+ // 获取原图宽度
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ options.inPurgeable = true;
+ options.inInputShareable = true;
+ Bitmap bm = BitmapFactory.decodeByteArray(buffer, 0, buffer.length,
+ options);
+ // 计算缩放比例
+ float reSize = options.outWidth / size;
+ if (options.outWidth > options.outHeight) {
+ reSize = options.outHeight / size;
+ }
+ if (reSize <= 0) {
+ reSize = 1;
+ }
+ Log.d(TAG, "convertToThumb, reSize:" + reSize);
+ // 缩放
+ options.inJustDecodeBounds = false;
+ options.inSampleSize = (int) reSize;
+ if (bm != null && !bm.isRecycled()) {
+ bm.recycle();
+ bm = null;
+ Log.e(TAG, "convertToThumb, recyle");
+ }
+ bm = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
+ if (bm == null) {
+ Log.e(TAG, "convertToThumb, decode fail:" + null);
+ return null;
+ }
+ return bm;
+ }
+
+ /**
+ * Bitmap --> byte[]
+ */
+ private static byte[] readBitmap(Bitmap bmp) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ bmp.compress(Bitmap.CompressFormat.JPEG, 60, stream);
+ try {
+ stream.flush();
+ stream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return stream.toByteArray();
+ }
+
+ /**
+ * Bitmap --> byte[]
+ */
+ public static byte[] readBitmapFromBuffer(byte[] buffer, float size) {
+ return readBitmap(convertToThumb(buffer, size));
+ }
+
+ /**
+ * 以屏幕宽度为基准,显示图片
+ */
+ public static Bitmap decodeStream(Context context, Intent data, float size) {
+ Bitmap image = null;
+ try {
+ Uri dataUri = data.getData();
+ // 获取原图宽度
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ options.inPurgeable = true;
+ options.inInputShareable = true;
+ BitmapFactory.decodeStream(context.getContentResolver()
+ .openInputStream(dataUri), null, options);
+ // 计算缩放比例
+ float reSize = (int) (options.outWidth / size);
+ if (reSize <= 0) {
+ reSize = 1;
+ }
+ Log.d(TAG, "old-w:" + options.outWidth + ", llyt-w:" + size
+ + ", resize:" + reSize);
+ // 缩放
+ options.inJustDecodeBounds = false;
+ options.inSampleSize = (int) reSize;
+ image = BitmapFactory.decodeStream(context.getContentResolver()
+ .openInputStream(dataUri), null, options);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return image;
+ }
+
+ /**
+ * 按新的宽高缩放图片
+ */
+ public static Bitmap scaleImage(Bitmap bm, int newWidth, int newHeight) {
+ if (bm == null) {
+ return null;
+ }
+ int width = bm.getWidth();
+ int height = bm.getHeight();
+ float scaleWidth = ((float) newWidth) / width;
+ float scaleHeight = ((float) newHeight) / height;
+ Matrix matrix = new Matrix();
+ matrix.postScale(scaleWidth, scaleHeight);
+ Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix,
+ true);
+ if (!bm.isRecycled()) {
+ bm.recycle();
+ }
+ return newbm;
+ }
+
+ /**
+ * 设置固定的宽度,高度随之变化,使图片不会变形
+ */
+ public static Bitmap fitBitmap(Bitmap target, int newWidth) {
+ int width = target.getWidth();
+ int height = target.getHeight();
+ Matrix matrix = new Matrix();
+ float scaleWidth = ((float) newWidth) / width;
+ // float scaleHeight = ((float)newHeight) / height;
+ matrix.postScale(scaleWidth, scaleWidth);
+ // Bitmap result = Bitmap.createBitmap(target,0,0,width,height,
+ // matrix,true);
+ Bitmap bmp = Bitmap.createBitmap(target, 0, 0, width, height, matrix,
+ true);
+ if (!target.equals(bmp) && !target.isRecycled()) {
+ target.recycle();
+ }
+ return bmp;// Bitmap.createBitmap(target, 0, 0, width, height, matrix,
+ // true);
+ }
+
+ /**
+ * 根据指定的宽高平铺图像
+ */
+ public static Bitmap createRepeater(int width, int heigth, Bitmap src) {
+ int countWidth = (width + src.getWidth() - 1) / src.getWidth();
+ int countHeight = (heigth + src.getHeight() - 1) / src.getHeight();
+
+ Bitmap bitmap = Bitmap.createBitmap(width, heigth, Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ for (int i = 0; i < countHeight; ++i) {
+ for (int idx = 0; idx < countWidth; ++idx) {
+ canvas.drawBitmap(src, idx * src.getWidth(), i * src.getHeight(), null);
+ }
+ }
+ return bitmap;
+
+ }
+
+ /**
+ * 图片的质量压缩方法
+ */
+ public static Bitmap compressImage(Bitmap image) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ image.compress(Bitmap.CompressFormat.JPEG, 100, stream);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
+ int options = 100;
+ while (stream.toByteArray().length / 1024 > 100) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
+ stream.reset();// 重置stream即清空stream
+ image.compress(Bitmap.CompressFormat.JPEG, options, stream);// 这里压缩options%,把压缩后的数据存放到baos中
+ options -= 10;// 每次都减少10
+ }
+ ByteArrayInputStream isBm = new ByteArrayInputStream(stream.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中
+ Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
+ try {
+ stream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ isBm.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (!image.isRecycled()) {
+ image.recycle();
+ }
+ return bitmap;
+ }
+
+ /**
+ * 图片按比例大小压缩方法(根据Bitmap图片压缩)
+ */
+ public static Bitmap getImage(Bitmap image) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ image.compress(Bitmap.CompressFormat.JPEG, 100, stream);
+ if (stream.toByteArray().length / 1024 > 1024) {// 判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
+ stream.reset();// 重置stream即清空stream
+ image.compress(Bitmap.CompressFormat.JPEG, 50, stream);// 这里压缩50%,把压缩后的数据存放到baos中
+ }
+ ByteArrayInputStream isBm;
+ BitmapFactory.Options newOpts = new BitmapFactory.Options();
+ // 开始读入图片,此时把options.inJustDecodeBounds 设回true了
+ newOpts.inJustDecodeBounds = true;
+ Bitmap bitmap;
+ newOpts.inJustDecodeBounds = false;
+ int w = newOpts.outWidth;
+ int h = newOpts.outHeight;
+ // 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
+ float hh = 800f;// 这里设置高度为800f
+ float ww = 480f;// 这里设置宽度为480f
+ // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
+ int be = 1;// be=1表示不缩放
+ if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
+ be = (int) (newOpts.outWidth / ww);
+ } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
+ be = (int) (newOpts.outHeight / hh);
+ }
+ if (be <= 0)
+ be = 1;
+ newOpts.inSampleSize = be;// 设置缩放比例
+ // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
+ isBm = new ByteArrayInputStream(stream.toByteArray());
+ bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
+ try {
+ isBm.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (!image.isRecycled()) {
+ image.recycle();
+ }
+ assert bitmap != null;
+ return compressImage(bitmap);// 压缩好比例大小后再进行质量压缩
+ }
+
+ /**
+ * 通过资源id转化成Bitmap 全屏显示
+ */
+ public static Bitmap ReadBitmapById(Context context, int drawableId,
+ int screenWidth, int screenHight) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Config.ARGB_8888;
+ options.inInputShareable = true;
+ options.inPurgeable = true;
+ InputStream stream = context.getResources().openRawResource(drawableId);
+ Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
+ assert bitmap != null;
+ return getBitmap(bitmap, screenWidth, screenHight);
+ }
+
+ /**
+ * 高斯模糊
+ */
+ public static Bitmap stackBlur(Bitmap srcBitmap) {
+ if (srcBitmap == null) return null;
+ RenderScript rs = RenderScript.create(MyApplication.getApplication());
+ Bitmap blurredBitmap = srcBitmap.copy(Config.ARGB_8888, true);
+
+ //分配用于渲染脚本的内存
+ Allocation input = Allocation.createFromBitmap(rs, blurredBitmap, Allocation.MipmapControl.MIPMAP_FULL, Allocation.USAGE_SHARED);
+ Allocation output = Allocation.createTyped(rs, input.getType());
+
+ //加载我们想要使用的特定脚本的实例。
+ ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
+ script.setInput(input);
+
+ //设置模糊半径
+ script.setRadius(8);
+
+ //启动 ScriptIntrinsicBlur
+ script.forEach(output);
+
+ //将输出复制到模糊的位图
+ output.copyTo(blurredBitmap);
+
+ return blurredBitmap;
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java
index 6f7653d..b20995e 100644
--- a/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java
@@ -16,6 +16,8 @@ import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import xyz.fycz.myreader.application.MyApplication;
+import xyz.fycz.myreader.common.APPCONST;
+import xyz.fycz.myreader.util.IOUtils;
import xyz.fycz.myreader.util.StringHelper;
import java.io.*;
@@ -241,7 +243,27 @@ public class FileUtils {
return charsetName;
}
-
+ public static byte[] getBytes(File file) {
+ byte[] buffer = null;
+ FileInputStream fis = null;
+ ByteArrayOutputStream bos = null;
+ try {
+ fis = new FileInputStream(file);
+ bos = new ByteArrayOutputStream(1000);
+ byte[] b = new byte[1000];
+ int n;
+ while ((n = fis.read(b)) != -1) {
+ bos.write(b, 0, n);
+ }
+ bos.flush();
+ buffer = bos.toByteArray();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }finally {
+ IOUtils.close(fis, bos);
+ }
+ return buffer;
+ }
/**
* 写文本文件 在Android系统中,文件保存在 /data/data/PACKAGE_NAME/files/ 目录下
@@ -281,20 +303,41 @@ public class FileUtils {
return "";
}
+ public static String readText(String path){
+ BufferedReader br = null;
+ StringBuilder sb = new StringBuilder();
+ try {
+ br = new BufferedReader(new FileReader(path));
+ String tem;
+ while ((tem = br.readLine()) != null){
+ sb.append(tem);
+ }
+ return sb.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }finally {
+ IOUtils.close(br);
+ }
+ }
+
private static String readInStream(FileInputStream inStream) {
+ ByteArrayOutputStream outStream = null;
try {
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[512];
int length = -1;
while ((length = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, length);
}
-
+ outStream.flush();
outStream.close();
inStream.close();
return outStream.toString();
} catch (IOException e) {
Log.i("FileTest", e.getMessage());
+ }finally {
+ IOUtils.close(inStream, outStream);
}
return null;
}
@@ -322,6 +365,7 @@ public class FileUtils {
try {
out = new FileOutputStream(file);
out.write(buffer);
+ out.flush();
writeSucc = true;
} catch (Exception e) {
e.printStackTrace();
@@ -342,6 +386,7 @@ public class FileUtils {
try {
out = new FileOutputStream(file);
out.write(buffer);
+ out.flush();
writeSucc = true;
} catch (Exception e) {
e.printStackTrace();
@@ -356,6 +401,12 @@ public class FileUtils {
return writeSucc;
}
+ public static boolean copy(String src, String dest){
+ byte[] buffer = FileUtils.getBytes(new File(src));
+ return buffer != null && FileUtils.writeFile(buffer,
+ FileUtils.getFile(dest));
+ }
+
/**
* 根据文件绝对路径获取文件名
*
@@ -649,15 +700,15 @@ public class FileUtils {
* 创建目录
*
*/
- public static xyz.fycz.myreader.util.FileUtils.PathStatus createPath(String newPath) {
+ public static PathStatus createPath(String newPath) {
File path = new File(newPath);
if (path.exists()) {
- return xyz.fycz.myreader.util.FileUtils.PathStatus.EXITS;
+ return PathStatus.EXITS;
}
if (path.mkdir()) {
- return xyz.fycz.myreader.util.FileUtils.PathStatus.SUCCESS;
+ return PathStatus.SUCCESS;
} else {
- return xyz.fycz.myreader.util.FileUtils.PathStatus.ERROR;
+ return PathStatus.ERROR;
}
}
@@ -829,4 +880,18 @@ public class FileUtils {
}
}
}
+
+ public static String getFileSuffix(String filePath) {
+ File file = new File(filePath);
+ return getFileSuffix(file);
+ }
+
+ public static String getFileSuffix(File file) {
+ if (file == null || !file.exists() || file.isDirectory()) {
+ return "";
+ }
+ String fileName = file.getName();
+ int dotIndex = fileName.lastIndexOf(".");
+ return dotIndex > 0 ? fileName.substring(dotIndex) : "";
+ }
}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/GsonUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/GsonUtils.java
new file mode 100644
index 0000000..f002775
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/GsonUtils.java
@@ -0,0 +1,103 @@
+package xyz.fycz.myreader.util.utils;
+
+import android.text.TextUtils;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings({"unused"})
+public class GsonUtils {
+ /**
+ * 将Json数据解析成相应的映射对象
+ */
+ public static T parseJObject(String jsonData, Class type) {
+ T result = null;
+ if (!TextUtils.isEmpty(jsonData)) {
+ Gson gson = new GsonBuilder().create();
+ try {
+ result = gson.fromJson(jsonData, type);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 将Json数组解析成相应的映射对象List
+ */
+ public static List parseJArray(String jsonData, Class type) {
+ List result = null;
+ if (!TextUtils.isEmpty(jsonData)) {
+ Gson gson = new GsonBuilder().create();
+ try {
+ JsonParser parser = new JsonParser();
+ JsonArray JArray = parser.parse(jsonData).getAsJsonArray();
+ if (JArray != null) {
+ result = new ArrayList<>();
+ for (JsonElement obj : JArray) {
+ try {
+ T cse = gson.fromJson(obj, type);
+ result.add(cse);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 将对象转换成Json
+ */
+ public static String toJsonWithSerializeNulls(T entity) {
+ entity.getClass();
+ Gson gson = new GsonBuilder().serializeNulls().create();
+ String result = "";
+ try {
+ result = gson.toJson(entity);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ /**
+ * 将list排除值为null的字段转换成Json数组
+ */
+ public static String toJsonArrayWithSerializeNulls(List list) {
+ Gson gson = new GsonBuilder().serializeNulls().create();
+ String result = "";
+ try {
+ result = gson.toJson(list);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ /**
+ * 将list中将Expose注解的字段转换成Json数组
+ */
+ public static String toJsonArrayWithExpose(List list) {
+ Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
+ String result = "";
+ try {
+ result = gson.toJson(list);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/MeUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/MeUtils.java
new file mode 100644
index 0000000..532d946
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/MeUtils.java
@@ -0,0 +1,93 @@
+package xyz.fycz.myreader.util.utils;
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+
+/**
+ * by yangyxd
+ * data: 2019.08.29
+ */
+public class MeUtils {
+
+ /** 获取 assets 中指定目录中的文件名称列表 */
+ public static CharSequence[] getAssetsFileList(AssetManager am, String path) throws IOException {
+ final String[] fs = am.list(path);
+ if (fs == null || fs.length == 0)
+ return null;
+ final CharSequence[] items = new CharSequence[fs.length];
+ for (int i=0; i reqWidth || options.outHeight > reqHeight) {
+ int widthRatio = Math.round((float) options.outWidth / (float) reqWidth);
+ int heightRatio = Math.round((float) options.outHeight / (float) reqHeight);
+ inSampleSize = Math.min(widthRatio, heightRatio);
+ }
+ return inSampleSize;
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java
index 2b30e21..847fd2b 100644
--- a/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java
@@ -29,11 +29,11 @@ public class OkHttpUtils {
public static String getHtml(String url, RequestBody requestBody, String encodeType) throws IOException {
Request.Builder builder = new Request.Builder()
- .addHeader("accept", "*/*")
- .addHeader("connection", "Keep-Alive")
+ .addHeader("Accept", "*/*")
+ .addHeader("Connection", "Keep-Alive")
//.addHeader("Charsert", "utf-8")
.addHeader("Cache-Control", "no-cache")
- .addHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36");
+ .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36");
if (requestBody != null) {
builder.post(requestBody);
Log.d("HttpPost URl", url);
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java
index 14a9a93..de527dc 100644
--- a/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java
@@ -1,8 +1,9 @@
package xyz.fycz.myreader.util.utils;
-import android.content.Context;
import android.text.Editable;
+import android.text.TextUtils;
+
import androidx.annotation.StringRes;
import xyz.fycz.myreader.application.MyApplication;
@@ -141,50 +142,6 @@ public class StringUtils {
return new String(c);
}
- //繁簡轉換
- /*public static String convertCC(String input, Context context)
- {
- ConversionType currentConversionType = ConversionType.S2TWP;
- int convertType = SharedPreUtils.getInstance().getInt(SHARED_READ_CONVERT_TYPE, 0);
-
- if (input.length() == 0)
- return "";
-
- switch (convertType) {
- case 1:
- currentConversionType = ConversionType.TW2SP;
- break;
- case 2:
- currentConversionType = ConversionType.S2HK;
- break;
- case 3:
- currentConversionType = ConversionType.S2T;
- break;
- case 4:
- currentConversionType = ConversionType.S2TW;
- break;
- case 5:
- currentConversionType = ConversionType.S2TWP;
- break;
- case 6:
- currentConversionType = ConversionType.T2HK;
- break;
- case 7:
- currentConversionType = ConversionType.T2S;
- break;
- case 8:
- currentConversionType = ConversionType.T2TW;
- break;
- case 9:
- currentConversionType = ConversionType.TW2S;
- break;
- case 10:
- currentConversionType = ConversionType.HK2S;
- break;
- }
-
- return (convertType != 0)?ChineseConverter.convert(input, currentConversionType, context):input;
- }*/
public static void isNotChinese(Editable s){
//禁止输入中文
@@ -347,4 +304,39 @@ public class StringUtils {
return -1;
}
+ public static String repeat(String str, int n) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < n; i++) {
+ stringBuilder.append(str);
+ }
+ return stringBuilder.toString();
+ }
+
+ public static boolean isJsonType(String str) {
+ boolean result = false;
+ if (!TextUtils.isEmpty(str)) {
+ str = str.trim();
+ if (str.startsWith("{") && str.endsWith("}")) {
+ result = true;
+ } else if (str.startsWith("[") && str.endsWith("]")) {
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ public static boolean isContainEachOther(String s1, String s2){
+ if (s1 == null || s2 == null) return true;
+ s1 = s1.trim();
+ s2 = s2.trim();
+ return s1.contains(s2) || s2.contains(s1);
+ }
+
+ public static boolean isEqual(String s1, String s2){
+ if (s1 == null || s2 == null) return true;
+ s1 = s1.trim();
+ s2 = s2.trim();
+ return s1.equals(s2);
+ }
+
}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java
index be14ff1..771da51 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java
@@ -224,7 +224,6 @@ public class BaseApi {
if (!jsonModel.isSuccess()) {
if (jsonModel.getError() == ErrorCode.no_security) {
ToastUtils.showWarring("登录过期,请重新登录");
- SysManager.logout();
} else {
if (jsonModel.getError() == 0) {
callback.onFinish(jsonModel.getResult(), -1);
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java
index af5ed3f..cffecef 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java
@@ -1,6 +1,7 @@
package xyz.fycz.myreader.webapi;
+import xyz.fycz.myreader.entity.bookstore.BookType;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.webapi.crawler.base.FindCrawler;
@@ -21,7 +22,7 @@ public class BookStoreApi extends BaseApi{
getCommonReturnHtmlStringApi(findCrawler.getFindUrl(), null, findCrawler.getCharset(), true, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
- callback.onFinish(findCrawler.getBookTypeList((String) o),0);
+ callback.onFinish(findCrawler.getBookTypes((String) o),0);
}
@Override
@@ -38,12 +39,12 @@ public class BookStoreApi extends BaseApi{
* @param findCrawler
* @param callback
*/
- public static void getBookRankList(String url, FindCrawler findCrawler, final ResultCallback callback){
+ public static void getBookRankList(BookType bookType, FindCrawler findCrawler, final ResultCallback callback){
- getCommonReturnHtmlStringApi(url, null, findCrawler.getCharset(),true, new ResultCallback() {
+ getCommonReturnHtmlStringApi(bookType.getUrl(), null, findCrawler.getCharset(),true, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
- callback.onFinish(findCrawler.getRankBookList((String) o),0);
+ callback.onFinish(findCrawler.getFindBooks((String) o, bookType),0);
}
@Override
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java
index 1716f60..d3b1459 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java
@@ -9,7 +9,11 @@ import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import xyz.fycz.myreader.webapi.crawler.read.FYReadCrawler;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.ResourceBundle;
/**
@@ -48,7 +52,7 @@ public class ReadCrawlerUtil {
public static HashMap getDisableSources() {
SharedPreUtils spu = SharedPreUtils.getInstance();
String searchSource = spu.getString(MyApplication.getmContext().getString(R.string.searchSource), null);
- HashMap mSources = new HashMap<>();
+ HashMap mSources = new LinkedHashMap<>();
if (searchSource == null) {
for (BookSource bookSource : BookSource.values()) {
if (bookSource.equals(BookSource.fynovel) || bookSource.equals(BookSource.local)) continue;
@@ -69,10 +73,9 @@ public class ReadCrawlerUtil {
}
}
return mSources;
-
}
- public static void resetReaderCrawlers(){
+ public static void resetReadCrawlers(){
StringBuilder sb = new StringBuilder();
for (BookSource bookSource : BookSource.values()) {
if (bookSource.equals(BookSource.fynovel) || bookSource.equals(BookSource.local))
@@ -84,6 +87,43 @@ public class ReadCrawlerUtil {
SharedPreUtils.getInstance().putString(MyApplication.getmContext().getString(R.string.searchSource), sb.toString());
}
+ public synchronized static void addReadCrawler(BookSource... bookSources){
+ SharedPreUtils spu = SharedPreUtils.getInstance();
+ String searchSource = spu.getString(MyApplication.getmContext().getString(R.string.searchSource), null);
+ StringBuilder sb = new StringBuilder(searchSource);
+ for (BookSource bookSource : bookSources){
+ sb.append(",");
+ sb.append(bookSource.toString());
+ }
+ SharedPreUtils.getInstance().putString(MyApplication.getmContext().getString(R.string.searchSource), sb.toString());
+ }
+
+ public synchronized static void removeReadCrawler(String... bookSources){
+ SharedPreUtils spu = SharedPreUtils.getInstance();
+ String searchSource = spu.getString(MyApplication.getmContext().getString(R.string.searchSource), null);
+ if (searchSource == null) {
+ return;
+ }
+ String[] ableSources = searchSource.split(",");
+ for (int i = 0; i < ableSources.length; i++) {
+ String ableSource = ableSources[i];
+ for (String bookSource : bookSources) {
+ if (ableSource.equals(bookSource)) {
+ ableSources[i] = "";
+ break;
+ }
+ }
+ }
+ StringBuilder sb = new StringBuilder();
+ for (String ableSource : ableSources){
+ if ("".equals(ableSource)) continue;
+ sb.append(ableSource);
+ sb.append(",");
+ }
+ sb.deleteCharAt(sb.lastIndexOf(","));
+ spu.putString(MyApplication.getmContext().getString(R.string.searchSource), sb.toString());
+ }
+
public static ReadCrawler getReadCrawler(String bookSource) {
ResourceBundle rb = ResourceBundle.getBundle("crawler");
try {
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java
index c2881c0..a8e3e1b 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java
@@ -16,8 +16,16 @@ public abstract class FindCrawler implements Serializable {
public abstract String getCharset();
public abstract String getFindName();
public abstract String getFindUrl();
- public abstract boolean getTypePage(BookType curType, int page);
public abstract boolean hasImg();
- public abstract List getBookTypeList(String html);
- public abstract List getRankBookList(String html);
+ public abstract boolean needSearch();
+ //动态获取
+ public List getBookTypes(String html) {
+ return null;
+ }
+ //静态获取
+ public List getBookTypes(){
+ return null;
+ }
+ public abstract List getFindBooks(String html, BookType bookType);
+ public abstract boolean getTypePage(BookType curType, int page);
}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/ReadCrawler.java
index 9f0de47..7df7a12 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/ReadCrawler.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/ReadCrawler.java
@@ -18,7 +18,7 @@ public interface ReadCrawler {
String getSearchCharset(); // 书源搜索关键字的字符编码,和书源的字符编码就行
String getNameSpace(); // 书源主页地址
Boolean isPost(); // 是否以post请求搜索
- String getContentFormHtml(String html); // 获取书籍内容规则
- ArrayList getChaptersFromHtml(String html); // 获取书籍章节列表规则
ConcurrentMultiValueMap getBooksFromSearchHtml(String html); // 搜索书籍规则
+ ArrayList getChaptersFromHtml(String html); // 获取书籍章节列表规则
+ String getContentFormHtml(String html); // 获取书籍内容规则
}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java
index 53263cd..12c64cc 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java
@@ -1,6 +1,8 @@
package xyz.fycz.myreader.webapi.crawler.find;
import android.content.Context;
+import android.util.Log;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -40,6 +42,7 @@ public class QiDianMobileRank extends FindCrawler {
private LinkedHashMap sortName = new LinkedHashMap<>();
private boolean isFemale;
private boolean isSort;
+ private int onePageNum = 20;
public QiDianMobileRank(boolean isFemale) {
this.isFemale = isFemale;
@@ -118,7 +121,7 @@ public class QiDianMobileRank extends FindCrawler {
}
- public List getRankTypes() {
+ public List getBookTypes() {
if (!isSort) {
initMaleRankName();
} else {
@@ -159,7 +162,7 @@ public class QiDianMobileRank extends FindCrawler {
new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
- rc.onFinish(getBooksFromJson((String) o), 1);
+ rc.onFinish(getBooksFromJson((String) o, bookType), 1);
}
@Override
@@ -169,11 +172,13 @@ public class QiDianMobileRank extends FindCrawler {
});
}
- private List getBooksFromJson(String json) {
+ private List getBooksFromJson(String json, BookType bookType) {
List books = new ArrayList<>();
try {
JSONObject all = new JSONObject(json);
JSONObject data = all.getJSONObject("data");
+ int total = data.getInt("total");
+ bookType.setPageSize(total % onePageNum == 0 ? total / onePageNum : total / onePageNum + 1);
JSONArray jsonBooks = data.getJSONArray("records");
for (int i = 0; i < jsonBooks.length(); i++) {
JSONObject jsonBook = jsonBooks.getJSONObject(i);
@@ -218,7 +223,10 @@ public class QiDianMobileRank extends FindCrawler {
@Override
public boolean getTypePage(BookType curType, int page) {
if (!isSort) {
- if (page > 30) {
+ if (curType.getPageSize() <= 0){
+ curType.setPageSize(30);
+ }
+ if (page > curType.getPageSize()) {
return true;
}
}else {
@@ -237,12 +245,12 @@ public class QiDianMobileRank extends FindCrawler {
}
@Override
- public List getBookTypeList(String html) {
- return null;
+ public boolean needSearch() {
+ return true;
}
@Override
- public List getRankBookList(String html) {
+ public List getFindBooks(String html, BookType bookType) {
return null;
}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java
new file mode 100644
index 0000000..42f77e8
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java
@@ -0,0 +1,78 @@
+package xyz.fycz.myreader.webapi.crawler.find;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import xyz.fycz.myreader.entity.bookstore.BookType;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.webapi.crawler.base.FindCrawler;
+import xyz.fycz.myreader.webapi.crawler.read.XS7ReadCrawler;
+
+/**
+ * @author fengyue
+ * @date 2020/11/28 22:43
+ */
+public class XS7Rank extends FindCrawler {
+ private FindCrawler xs7 = new XS7ReadCrawler();
+ public static final String CHARSET = "GBK";
+ public static final String FIND_NAME = "排行榜[小说旗]";
+ public static final String FIND_URL = "https://www.xs7.la/top/lastupdate/1.html";
+ @Override
+ public String getCharset() {
+ return xs7.getCharset();
+ }
+
+ @Override
+ public String getFindName() {
+ return FIND_NAME;
+ }
+
+ @Override
+ public String getFindUrl() {
+ return FIND_URL;
+ }
+
+ @Override
+ public boolean hasImg() {
+ return true;
+ }
+
+ @Override
+ public boolean needSearch() {
+ return false;
+ }
+
+ @Override
+ public List getBookTypes(String html) {
+ List bookTypes = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element div = doc.getElementsByClass("toplist").first();
+ Elements as = div.getElementsByTag("a");
+ for (Element a : as){
+ BookType bookType = new BookType();
+ bookType.setUrl(a.attr("href"));
+ bookType.setTypeName(a.text());
+ bookTypes.add(bookType);
+ }
+ return bookTypes;
+ }
+
+ @Override
+ public List getFindBooks(String html, BookType bookType) {
+ return xs7.getFindBooks(html, bookType);
+ }
+
+ @Override
+ public boolean getTypePage(BookType curType, int page) {
+ if (page != 1 && page > curType.getPageSize()){
+ return true;
+ }
+ curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page + ".html");
+ return false;
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Ben100ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Ben100ReadCrawler.java
index 1d1998d..daf6df6 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Ben100ReadCrawler.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Ben100ReadCrawler.java
@@ -1,26 +1,36 @@
package xyz.fycz.myreader.webapi.crawler.read;
import android.text.Html;
+import android.util.Log;
+
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
+
import xyz.fycz.myreader.entity.SearchBookBean;
+import xyz.fycz.myreader.entity.bookstore.BookType;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
+import xyz.fycz.myreader.webapi.crawler.base.FindCrawler;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
-public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler {
+public class Ben100ReadCrawler extends FindCrawler implements ReadCrawler, BookInfoCrawler {
public static final String NAME_SPACE = "https://www.100ben.net";
public static final String NOVEL_SEARCH = "https://www.100ben.net/plus/search.php?keyword={key}";
public static final String CHARSET = "UTF-8";
public static final String SEARCH_CHARSET = "utf-8";
+ public static final String FIND_NAME = "书城[100本书·实体]";
+ private LinkedHashMap mBookTypes = new LinkedHashMap<>();
+
@Override
public String getSearchLink() {
@@ -32,6 +42,26 @@ public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler {
return CHARSET;
}
+ @Override
+ public String getFindName() {
+ return FIND_NAME;
+ }
+
+ @Override
+ public String getFindUrl() {
+ return NAME_SPACE;
+ }
+
+ @Override
+ public boolean hasImg() {
+ return true;
+ }
+
+ @Override
+ public boolean needSearch() {
+ return false;
+ }
+
@Override
public String getNameSpace() {
return NAME_SPACE;
@@ -108,26 +138,27 @@ public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler {
ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>();
Document doc = Jsoup.parse(html);
// try {
- Elements divs = doc.getElementsByClass("recommand");
- Element div = divs.get(0);
- Elements elementsByTag = div.getElementsByTag("li");
- for (Element element : elementsByTag) {
- Book book = new Book();
- String name = element.getElementsByClass("titles").first().getElementsByTag("a").first().text();
- book.setName(name);
- String author = element.getElementsByClass("author").first().text().replace("作者:", "");
- book.setAuthor(author);
- String imgUrl = element.getElementsByTag("img").first().attr("src");
- book.setImgUrl(imgUrl);
- String chapterUrl = element.getElementsByClass("titles").first().getElementsByTag("a").first().attr("href");
- book.setChapterUrl(NAME_SPACE + chapterUrl);
- String desc = element.getElementsByClass("intro").first().text();
- book.setDesc(desc);
- book.setNewestChapterTitle("");
- book.setSource(BookSource.ben100.toString());
- SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
- books.add(sbb, book);
- }
+ Elements divs = doc.getElementsByClass("recommand");
+ Element div = divs.get(0);
+ Elements elementsByTag = div.getElementsByTag("li");
+ for (Element element : elementsByTag) {
+ Book book = new Book();
+ String name = element.getElementsByClass("titles").first().getElementsByTag("a").first().text();
+ book.setName(name);
+ String author = element.getElementsByClass("author").first().text().replace("作者:", "");
+ book.setAuthor(author);
+ String imgUrl = element.getElementsByTag("img").first().attr("src");
+ book.setImgUrl(imgUrl);
+ String chapterUrl = element.getElementsByClass("titles").first().getElementsByTag("a").first().attr("href");
+ book.setChapterUrl(NAME_SPACE + chapterUrl);
+ String desc = element.getElementsByClass("intro").first().text();
+ book.setDesc(desc);
+ book.setNewestChapterTitle("");
+ book.setIsCloseUpdate(true);
+ book.setSource(BookSource.ben100.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ }
// } catch (Exception e) {
// e.printStackTrace();
// }
@@ -136,9 +167,10 @@ public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler {
/**
* 获取书籍详细信息
+ *
* @param book
*/
- public Book getBookInfo(String html, Book book){
+ public Book getBookInfo(String html, Book book) {
Document doc = Jsoup.parse(html);
Element img = doc.getElementById("fmimg");
book.setImgUrl(img.getElementsByTag("img").get(0).attr("src"));
@@ -148,4 +180,110 @@ public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler {
book.setType(type.getElementsByTag("a").get(2).text());
return book;
}
+
+
+ @Override
+ public List getBookTypes(String html) {
+ List bookTypes = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element menu = doc.getElementsByClass("menu").first();
+ for (Element a : menu.children()) {
+ String name = a.text();
+ String url = a.attr("href");
+ if ("首页".equals(name)) {
+ name = "100本书";
+ } else if (name.contains("全部小说")) {
+ name = "全部小说";
+ }
+ BookType bookType = new BookType();
+ bookType.setTypeName(name);
+ bookType.setUrl(url);
+ bookTypes.add(bookType);
+ System.out.println("bookTypes.put(\""+ bookType.getTypeName() + "\", \"" + bookType.getUrl() + "\");");
+ }
+ return bookTypes;
+ }
+
+ @Override
+ public List getBookTypes() {
+ initBookTypes();
+ List bookTypes = new ArrayList<>();
+ for (String name : mBookTypes.keySet()){
+ BookType bookType = new BookType();
+ bookType.setTypeName(name);
+ bookType.setUrl(mBookTypes.get(name));
+ bookTypes.add(bookType);
+ }
+ return bookTypes;
+ }
+
+ private void initBookTypes(){
+ mBookTypes.put("世界名著", "https://www.100ben.net/shijiemingzhu/");
+ mBookTypes.put("现代文学", "https://www.100ben.net/xiandaiwenxue/");
+ mBookTypes.put("外国小说", "https://www.100ben.net/waiguoxiaoshuo/");
+ mBookTypes.put("励志书籍", "https://www.100ben.net/lizhishuji/");
+ mBookTypes.put("古典文学", "https://www.100ben.net/gudianwenxue/");
+ mBookTypes.put("武侠小说", "https://www.100ben.net/wuxiaxiaoshuo/");
+ mBookTypes.put("言情小说", "https://www.100ben.net/yanqingxiaoshuo/");
+ mBookTypes.put("推理小说", "https://www.100ben.net/tuilixiaoshuo/");
+ mBookTypes.put("科幻小说", "https://www.100ben.net/kehuanxiaoshuo/");
+ mBookTypes.put("人物传记", "https://www.100ben.net/renwuzhuanji/");
+ mBookTypes.put("盗墓悬疑", "https://www.100ben.net/daomuxuanyi/");
+ mBookTypes.put("玄幻穿越", "https://www.100ben.net/xuanhuanchuanyue/");
+ mBookTypes.put("科普书籍", "https://www.100ben.net/kepushuji/");
+ mBookTypes.put("100本书", "https://www.100ben.net/");
+ mBookTypes.put("全部小说", "https://www.100ben.net/all/");
+ }
+
+ @Override
+ public List getFindBooks(String html, BookType bookType) {
+ List books = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+
+ try {
+ Element pageDiv = doc.getElementsByClass("page").first();
+ String page = pageDiv.getElementsByTag("a").last().text();
+ String pageStr = page.replace("末页(", "").replace(")", "");
+ bookType.setPageSize(Integer.parseInt(pageStr));
+ } catch (Exception ignored) {}
+
+ Elements divs = doc.getElementsByClass("recommand");
+ Element div = divs.get(0);
+ Elements elementsByTag = div.getElementsByTag("li");
+ for (Element element : elementsByTag) {
+ Book book = new Book();
+ String name = element.getElementsByClass("titles").first().getElementsByTag("a").first().text();
+ book.setName(name);
+ String author = element.getElementsByClass("author").first().text().replace("作者:", "");
+ book.setAuthor(author);
+ String imgUrl = element.getElementsByTag("img").first().attr("src");
+ book.setImgUrl(imgUrl);
+ String chapterUrl = element.getElementsByClass("titles").first().getElementsByTag("a").first().attr("href");
+ book.setChapterUrl(chapterUrl.contains("http") ? chapterUrl : NAME_SPACE + chapterUrl);
+ String desc = element.getElementsByClass("intro").first().text();
+ book.setDesc(desc);
+ book.setNewestChapterTitle("");
+ book.setType(bookType.getTypeName());
+ book.setSource(BookSource.ben100.toString());
+ books.add(book);
+ }
+ return books;
+ }
+
+ @Override
+ public boolean getTypePage(BookType curType, int page) {
+ if (curType.getPageSize() <= 0) {
+ curType.setPageSize(1);
+ }
+ if (page > curType.getPageSize()) {
+ return true;
+ }
+ if (curType.getTypeName().equals("100本书")) return false;
+ if (curType.getUrl().contains("list")) {
+ curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + ".html");
+ } else {
+ curType.setUrl(curType.getUrl() + "list_" + page + ".html");
+ }
+ return false;
+ }
}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/DSTQReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/DSTQReadCrawler.java
new file mode 100644
index 0000000..92f2863
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/DSTQReadCrawler.java
@@ -0,0 +1,129 @@
+package xyz.fycz.myreader.webapi.crawler.read;
+
+import android.text.Html;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+
+import xyz.fycz.myreader.entity.SearchBookBean;
+import xyz.fycz.myreader.enums.BookSource;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
+import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
+
+
+public class DSTQReadCrawler implements ReadCrawler {
+ public static final String NAME_SPACE = "https://www.dstiejuan.com";
+ public static final String NOVEL_SEARCH = "https://www.dstiejuan.com/search.html,searchkey={key}";
+ public static final String CHARSET = "UTF-8";
+ public static final String SEARCH_CHARSET = "UTF-8";
+
+ @Override
+ public String getSearchLink() {
+ return NOVEL_SEARCH;
+ }
+
+ @Override
+ public String getCharset() {
+ return CHARSET;
+ }
+
+ @Override
+ public String getNameSpace() {
+ return NAME_SPACE;
+ }
+
+ @Override
+ public Boolean isPost() {
+ return true;
+ }
+
+ @Override
+ public String getSearchCharset() {
+ return SEARCH_CHARSET;
+ }
+
+ /**
+ * 从html中获取章节正文
+ *
+ * @param html
+ * @return
+ */
+ public String getContentFormHtml(String html) {
+ Document doc = Jsoup.parse(html);
+ Element divContent = doc.getElementById("content");
+ if (divContent != null) {
+ String content = Html.fromHtml(divContent.html()).toString();
+ char c = 160;
+ String spaec = "" + c;
+ content = content.replace(spaec, " ");
+ return content;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * 从html中获取章节列表
+ *
+ * @param html
+ * @return
+ */
+ public ArrayList getChaptersFromHtml(String html) {
+ ArrayList chapters = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element divList = doc.getElementsByClass("read").first();
+ Elements elementsByTag = divList.getElementsByTag("a");
+ int i = 0;
+ for (int j = 12; j < elementsByTag.size(); j++) {
+ Element a = elementsByTag.get(j);
+ String title = a.text();
+ String url = a.attr("href");
+ Chapter chapter = new Chapter();
+ chapter.setNumber(i++);
+ chapter.setTitle(title);
+ chapter.setUrl(NAME_SPACE + url);
+ chapters.add(chapter);
+ }
+ return chapters;
+ }
+
+ /**
+ * 从搜索html中得到书列表
+ *
+ * @param html
+ * @return
+ */
+ public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) {
+ ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>();
+ Document doc = Jsoup.parse(html);
+ try {
+ Element div = doc.getElementsByClass("library").first();
+ Elements lis = div.getElementsByTag("li");
+ for (Element li : lis){
+ Elements as = li.getElementsByTag("a");
+ Book book = new Book();
+ book.setName(as.get(1).text());
+ book.setAuthor(as.get(2).text());
+ book.setType(as.get(3).text());
+ book.setNewestChapterTitle(as.get(4).text().replace("最新章节:", ""));
+ book.setDesc(li.getElementsByClass("intro").first().text());
+ book.setImgUrl(li.getElementsByTag("img").attr("src"));
+ book.setChapterUrl(NAME_SPACE + as.get(1).attr("href").replace(".html", "/"));
+ book.setSource(BookSource.dstq.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return books;
+ }
+
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Du1DuReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Du1DuReadCrawler.java
new file mode 100644
index 0000000..e7be199
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Du1DuReadCrawler.java
@@ -0,0 +1,146 @@
+package xyz.fycz.myreader.webapi.crawler.read;
+
+import android.text.Html;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.TextNode;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+
+import xyz.fycz.myreader.entity.SearchBookBean;
+import xyz.fycz.myreader.enums.BookSource;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
+import xyz.fycz.myreader.util.StringHelper;
+import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
+import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
+
+/**
+ * 此小说源书籍详情页可添加推荐书籍,暂未开始做
+ */
+public class Du1DuReadCrawler implements ReadCrawler, BookInfoCrawler {
+ public static final String NAME_SPACE = "http://du1du.org";
+ public static final String NOVEL_SEARCH = "http://du1du.org/search.htm?keyword={key}";
+ public static final String CHARSET = "GBK";
+ public static final String SEARCH_CHARSET = "utf-8";
+ @Override
+ public String getSearchLink() {
+ return NOVEL_SEARCH;
+ }
+
+ @Override
+ public String getCharset() {
+ return CHARSET;
+ }
+
+ @Override
+ public String getNameSpace() {
+ return NAME_SPACE;
+ }
+ @Override
+ public Boolean isPost() {
+ return false;
+ }
+ @Override
+ public String getSearchCharset() {
+ return SEARCH_CHARSET;
+ }
+
+ /**
+ * 从html中获取章节正文
+ * @param html
+ * @return
+ */
+ public String getContentFormHtml(String html) {
+ Document doc = Jsoup.parse(html);
+ Element divContent = doc.getElementById("txtContent");
+ if (divContent != null) {
+ StringBuilder sb = new StringBuilder();
+ for (TextNode textNode : divContent.textNodes()){
+ sb.append(textNode.text());
+ sb.append("\n");
+ }
+ String content = sb.toString();
+ char c = 160;
+ String spaec = "" + c;
+ content = content.replace(spaec, " ");
+ return content;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * 从html中获取章节列表
+ *
+ * @param html
+ * @return
+ */
+ public ArrayList getChaptersFromHtml(String html) {
+ ArrayList chapters = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element divList = doc.getElementById("chapters-list");
+ Elements elementsByTag = divList.getElementsByTag("a");
+ int i = 0;
+ for (int j = 0; j < elementsByTag.size(); j++) {
+ Element a = elementsByTag.get(j);
+ String title = a.text();
+ String url = a.attr("href");
+ Chapter chapter = new Chapter();
+ chapter.setNumber(i++);
+ chapter.setTitle(title);
+ chapter.setUrl(NAME_SPACE + url);
+ chapters.add(chapter);
+ }
+ return chapters;
+ }
+
+ /**
+ * 从搜索html中得到书列表
+ * @param html
+ * @return
+ */
+ public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) {
+ ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>();
+ Document doc = Jsoup.parse(html);
+ Elements divs = doc.getElementsByClass("panel-body");
+ Element div = divs.get(0);
+ Elements lis = div.getElementsByClass("clearfix");
+ for (int i = 1; i < lis.size(); i++) {
+ Element li = lis.get(i);
+ Book book = new Book();
+ Elements info = li.getElementsByTag("div");
+ book.setName(info.get(1).text());
+ book.setInfoUrl(NAME_SPACE + info.get(1).getElementsByTag("a").attr("href"));
+ book.setChapterUrl(book.getInfoUrl() + "mulu.htm");
+ book.setAuthor(info.get(3).text());
+ book.setNewestChapterTitle(info.get(2).text());
+ book.setType(info.get(0).text() + "小说");
+ book.setUpdateDate(info.get(4).text());
+ book.setSource(BookSource.du1du.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ }
+ return books;
+ }
+
+ /**
+ * 获取书籍详细信息
+ * @param book
+ */
+ public Book getBookInfo(String html, Book book){
+ Document doc = Jsoup.parse(html);
+ Element img = doc.selectFirst("meta[property=og:image]");
+ book.setImgUrl(img.attr("content"));
+ Element desc = doc.selectFirst("meta[property=og:description]");
+ book.setDesc(desc.attr("content"));
+ Element type = doc.selectFirst("meta[property=og:novel:category]");
+ book.setType(type.attr("content"));
+ return book;
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/LiuLangCatReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/LiuLangCatReadCrawler.java
new file mode 100644
index 0000000..5867b76
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/LiuLangCatReadCrawler.java
@@ -0,0 +1,170 @@
+package xyz.fycz.myreader.webapi.crawler.read;
+
+import android.text.Html;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+
+import xyz.fycz.myreader.entity.SearchBookBean;
+import xyz.fycz.myreader.enums.BookSource;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
+import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
+
+/**
+ * @author fengyue
+ * @date 2020/11/27 14:09
+ */
+public class LiuLangCatReadCrawler implements ReadCrawler {
+ public static final String NAME_SPACE = "http://m.liulangcat.com";
+ public static final String NOVEL_SEARCH = "http://m.liulangcat.com/get/get_search_result.php?page=0&keyword={key}";
+// public static final String NOVEL_SEARCH = "http://www.liulangcat.com/search.php?k={key}&submit=搜索&wgxojg=wc0yz&uwzgzw=yi1p7&amlmvy=mcp50&rybwbm=1s0y7";
+ public static final String CHARSET = "utf-8";
+ public static final String SEARCH_CHARSET = "utf-8";
+
+ @Override
+ public String getSearchLink() {
+ return NOVEL_SEARCH;
+ }
+
+ @Override
+ public String getCharset() {
+ return CHARSET;
+ }
+
+ @Override
+ public String getSearchCharset() {
+ return SEARCH_CHARSET;
+ }
+
+ @Override
+ public String getNameSpace() {
+ return NAME_SPACE;
+ }
+
+ @Override
+ public Boolean isPost() {
+ return false;
+ }
+
+ /*
+ *
+
+
+
+
+
+ 《白夜行》是日本作家东野圭吾的代表作。小说故事围绕着一对有着不同寻常情愫的小学生展开。1973年,大阪的一栋废弃建筑内发现了一具男尸,此后19年,嫌疑人之女雪穗与被害者之子桐原亮司走上截然不同的人生道路,一个跻身上流社会,一个却在底层游走,而他们身边的人,却接二连三地离奇死去,警察经过19年的艰苦追踪,终于使真相大白。
+
+
+
+ * */
+ /*@Override
+ public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) {
+ ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>();
+ Document doc = Jsoup.parse(html);
+ Element div = doc.getElementById("booklist");
+ Elements lis = div.getElementsByTag("li");
+ for (Element li : lis){
+ Book book = new Book();
+ Element bookName = li.getElementsByClass("bookname").first();
+ Element author = li.getElementsByClass("author").first();
+ Element desc = li.getElementsByClass("item_bottom").first();
+ book.setName(bookName.text());
+ book.setChapterUrl(NAME_SPACE + bookName.attr("href"));
+ book.setAuthor(author.text().replace("[", "").replace("]", ""));
+ book.setDesc(desc.text());
+ book.setNewestChapterTitle("");
+ book.setIsCloseUpdate(true);
+ book.setSource(BookSource.liulangcat.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ }
+ return books;
+ }*/
+
+ /*
+ author: "海明威"
+ authorid: "9102"
+ bookname: "老人与海"
+ descrption: "反映了社会经济发展某一阶段的普遍模式,这种模式甚至在现在的发展中国家还可以看到。在二十世纪三四十年代的农业国古巴,传统的渔业文化(和工业化世界隔绝,贴近自然,脱离现代技术,受庞大的家族和紧密联系的村落的约束)开始受到捕鱼产业(依赖工业化的世界,不顾或忽视环境,依靠机械设备获取利润,受庞大的家族和地方村落的约束较少)的冲击。在《老人与海》里,一方面,海明威把圣地亚哥描绘成一个一心一意将捕鱼手艺与自身身份、行为准则和自然法则完美结合在一起的渔民;另一方面,海明威刻画了一些奉行实用主义的年轻渔民,他们把鲨鱼肝卖给美国的肝油产业,用这些利润购买摩托艇及其他机械设备,把捕鱼全然当做改善物质生活的一种手段。"
+ no: "31072"
+ pycode: "wgwx"
+ * */
+
+ @Override
+ public ConcurrentMultiValueMap getBooksFromSearchHtml(String json) {
+ ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>();
+ try {
+ JSONArray array = new JSONArray(json);
+ for (int i = 0; i < array.length(); i++) {
+ Book book = new Book();
+ JSONObject jsonBook = array.getJSONObject(i);
+ book.setName(jsonBook.getString("bookname"));
+ book.setAuthor(jsonBook.getString("author"));
+ book.setDesc(jsonBook.getString("descrption"));
+ //http://m.liulangcat.com/wgwx/31072.html
+ book.setChapterUrl(NAME_SPACE + "/" + jsonBook.getString("pycode") + "/" +
+ jsonBook.getString("no") + ".html");
+ book.setNewestChapterTitle("");
+ book.setIsCloseUpdate(true);
+ //book.setSource(BookSource.liulangcat.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return books;
+ }
+
+ @Override
+ public ArrayList getChaptersFromHtml(String html) {
+ ArrayList chapters = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element ul = doc.getElementsByClass("booklist").first();
+ Elements as = ul.getElementsByTag("a");
+ int i = 0;
+ for (Element a : as) {
+ String title = a.text();
+ String url = a.attr("href");
+ Chapter chapter = new Chapter();
+ chapter.setNumber(i++);
+ chapter.setTitle(title);
+ chapter.setUrl(NAME_SPACE + url);
+ chapters.add(chapter);
+ }
+ return chapters;
+ }
+
+ @Override
+ public String getContentFormHtml(String html) {
+ Document doc = Jsoup.parse(html);
+ Element divContent = doc.getElementsByClass("item_content").first();
+ Element nameSpan = doc.getElementsByClass("top_categoryname").first();
+ if (divContent != null) {
+ String content = Html.fromHtml(divContent.html()).toString();
+ char c = 160;
+ String spaec = "" + c;
+ content = content.replace(spaec, " ").
+ replace(nameSpan.text(), "").
+ replace("主页", "").
+ replace("目录", "");
+ return content;
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/MiaoBiReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/MiaoBiReadCrawler.java
new file mode 100644
index 0000000..9e86eb3
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/MiaoBiReadCrawler.java
@@ -0,0 +1,217 @@
+package xyz.fycz.myreader.webapi.crawler.read;
+
+import android.text.Html;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import xyz.fycz.myreader.entity.SearchBookBean;
+import xyz.fycz.myreader.entity.bookstore.BookType;
+import xyz.fycz.myreader.enums.BookSource;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
+import xyz.fycz.myreader.webapi.crawler.base.FindCrawler;
+import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
+
+
+public class MiaoBiReadCrawler extends FindCrawler implements ReadCrawler {
+ public static final String NAME_SPACE = "https://www.imiaobige.com";
+ public static final String NOVEL_SEARCH = "https://www.imiaobige.com/search.html,searchkey={key}";
+ public static final String CHARSET = "UTF-8";
+ public static final String SEARCH_CHARSET = "UTF-8";
+ public static final String FIND_NAME = "书城[妙笔阁]";
+ private final LinkedHashMap mBookTypes = new LinkedHashMap<>();
+
+ @Override
+ public String getSearchLink() {
+ return NOVEL_SEARCH;
+ }
+
+ @Override
+ public String getCharset() {
+ return CHARSET;
+ }
+
+ @Override
+ public String getFindName() {
+ return FIND_NAME;
+ }
+
+ @Override
+ public String getFindUrl() {
+ return NAME_SPACE;
+ }
+
+ @Override
+ public boolean hasImg() {
+ return true;
+ }
+
+ @Override
+ public boolean needSearch() {
+ return false;
+ }
+
+ @Override
+ public String getNameSpace() {
+ return NAME_SPACE;
+ }
+
+ @Override
+ public Boolean isPost() {
+ return true;
+ }
+
+ @Override
+ public String getSearchCharset() {
+ return SEARCH_CHARSET;
+ }
+
+ /**
+ * 从html中获取章节正文
+ *
+ * @param html
+ * @return
+ */
+ public String getContentFormHtml(String html) {
+ Document doc = Jsoup.parse(html);
+ Element divContent = doc.getElementById("content");
+ if (divContent != null) {
+ String content = Html.fromHtml(divContent.html()).toString();
+ char c = 160;
+ String spaec = "" + c;
+ content = content.replace(spaec, " ").replaceAll("您可以在.*最新章节!|\\\\", "");
+ return content;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * 从html中获取章节列表
+ *
+ * @param html
+ * @return
+ */
+ public ArrayList getChaptersFromHtml(String html) {
+ ArrayList chapters = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element divList = doc.getElementById("readerlists");
+ Elements elementsByTag = divList.getElementsByTag("a");
+ int i = 0;
+ for (int j = 12; j < elementsByTag.size(); j++) {
+ Element a = elementsByTag.get(j);
+ String title = a.text();
+ String url = a.attr("href");
+ Chapter chapter = new Chapter();
+ chapter.setNumber(i++);
+ chapter.setTitle(title);
+ chapter.setUrl(NAME_SPACE + url);
+ chapters.add(chapter);
+ }
+ return chapters;
+ }
+
+ /**
+ * 从搜索html中得到书列表
+ *
+ * @param html
+ * @return
+ */
+ public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) {
+ ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>();
+ Document doc = Jsoup.parse(html);
+// try {
+ Element div = doc.getElementById("sitembox");
+ Elements dls = div.getElementsByTag("dl");
+ for (Element dl : dls){
+ Elements as = dl.getElementsByTag("a");
+ Book book = new Book();
+ book.setName(as.get(1).text());
+ book.setAuthor(as.get(3).text());
+ book.setType(as.get(2).text());
+ book.setNewestChapterTitle(as.get(4).text());
+ book.setDesc(dl.getElementsByClass("book_des").first().text());
+ book.setImgUrl(as.first().getElementsByTag("img").attr("src"));
+ book.setChapterUrl(NAME_SPACE + as.get(1).attr("href").replace("novel", "read").replace(".html", "/"));
+ book.setSource(BookSource.miaobi.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ }
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+ return books;
+ }
+
+ @Override
+ public List getBookTypes() {
+ initBookTypes();
+ List bookTypes = new ArrayList<>();
+ for (String name : mBookTypes.keySet()){
+ BookType bookType = new BookType();
+ bookType.setTypeName(name);
+ bookType.setUrl(mBookTypes.get(name));
+ bookType.setPageSize(100);
+ bookTypes.add(bookType);
+ }
+ return bookTypes;
+ }
+
+ private void initBookTypes(){
+ mBookTypes.put("玄幻奇幻", "https://www.imiaobige.com/xuanhuan/1.html");
+ mBookTypes.put("武侠仙侠", "https://www.imiaobige.com/wuxia/1.html");
+ mBookTypes.put("都市生活", "https://www.imiaobige.com/dushi/1.html");
+ mBookTypes.put("历史军事", "https://www.imiaobige.com/lishi/1.html");
+ mBookTypes.put("游戏竞技", "https://www.imiaobige.com/youxi/1.html");
+ mBookTypes.put("科幻未来", "https://www.imiaobige.com/kehuan/1.html");
+ }
+
+ /*
+
+ 
+ - 20-11-26 16:57
重生之我变成了火星
+ - 作者:仰望黑夜状态:连载中
+ - 重生成了火星?第一阶段科技进化,第二阶段高魔进化,第三阶段超魔进化!宇宙之间除了人类还有无数可怕存在,让我带领人类征服宇宙,我们的目标是星辰大海~
+ - 最新章节:第三百一十六章 时空道兵
+
+ */
+ @Override
+ public List getFindBooks(String html, BookType bookType) {
+ List books = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element div = doc.getElementById("sitebox");
+ Elements dls = div.getElementsByTag("dl");
+ for (Element dl : dls){
+ Book book = new Book();
+ Elements as = dl.getElementsByTag("a");
+ book.setName(as.get(1).text());
+ book.setAuthor(as.get(2).text());
+ book.setType(bookType.getTypeName());
+ book.setNewestChapterTitle(as.get(3).text());
+ book.setDesc(dl.getElementsByClass("book_des").first().text());
+ book.setImgUrl(as.first().getElementsByTag("img").attr("src"));
+ book.setChapterUrl(NAME_SPACE + as.get(1).attr("href").replace("novel", "read").replace(".html", "/"));
+ book.setUpdateDate(dl.getElementsByClass("uptime").first().text());
+ book.setSource(BookSource.miaobi.toString());
+ books.add(book);
+ }
+ return books;
+ }
+
+ @Override
+ public boolean getTypePage(BookType curType, int page) {
+ if (page > curType.getPageSize()){
+ return true;
+ }
+ curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page + ".html");
+ return false;
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/PiaoTianReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/PiaoTianReadCrawler.java
new file mode 100644
index 0000000..ca2f1bb
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/PiaoTianReadCrawler.java
@@ -0,0 +1,154 @@
+package xyz.fycz.myreader.webapi.crawler.read;
+
+import android.text.Html;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.TextNode;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+
+import xyz.fycz.myreader.entity.SearchBookBean;
+import xyz.fycz.myreader.enums.BookSource;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
+import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
+import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
+
+public class PiaoTianReadCrawler implements ReadCrawler, BookInfoCrawler {
+ public static final String NAME_SPACE = "https://www.piaotian.org";
+ public static final String NOVEL_SEARCH = "https://www.piaotian.org/modules/article/search.php?searchkey={key}&submit=%CB%D1%CB%F7";
+ public static final String CHARSET = "GBK";
+ public static final String SEARCH_CHARSET = "GBK";
+ @Override
+ public String getSearchLink() {
+ return NOVEL_SEARCH;
+ }
+
+ @Override
+ public String getCharset() {
+ return CHARSET;
+ }
+
+ @Override
+ public String getNameSpace() {
+ return NAME_SPACE;
+ }
+ @Override
+ public Boolean isPost() {
+ return false;
+ }
+ @Override
+ public String getSearchCharset() {
+ return SEARCH_CHARSET;
+ }
+
+ /**
+ * 从html中获取章节正文
+ * @param html
+ * @return
+ */
+ public String getContentFormHtml(String html) {
+ Document doc = Jsoup.parse(html);
+ Element divContent = doc.getElementById("htmlContent");
+ if (divContent != null) {
+ String content = Html.fromHtml(divContent.html()).toString();
+ char c = 160;
+ String spaec = "" + c;
+ content = content.replace(spaec, " ").replaceAll("飘天文学.*最新章节!", "");
+ return content;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * 从html中获取章节列表
+ *
+ * @param html
+ * @return
+ */
+ public ArrayList getChaptersFromHtml(String html) {
+ ArrayList chapters = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content");
+ Element ul = doc.getElementsByClass("chapterlist").get(1);
+ Elements elementsByTag = ul.getElementsByTag("a");
+ int i = 0;
+ for (int j = 0; j < elementsByTag.size(); j++) {
+ Element a = elementsByTag.get(j);
+ String title = a.text();
+ String url = a.attr("href");
+ Chapter chapter = new Chapter();
+ chapter.setNumber(i++);
+ chapter.setTitle(title);
+ chapter.setUrl(readUrl + url);
+ chapters.add(chapter);
+ }
+ return chapters;
+ }
+
+ /**
+ * 从搜索html中得到书列表
+ * @param html
+ * @return
+ */
+ public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) {
+ ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>();
+ Document doc = Jsoup.parse(html);
+ String urlType = doc.select("meta[property=og:type]").attr("content");
+ if ("novel".equals(urlType)) {
+ String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content");
+ Book book = new Book();
+ book.setChapterUrl(readUrl);
+ getBookInfo(html, book);
+ book.setSource(BookSource.paiotian.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ } else {
+ Elements divs = doc.getElementsByClass("grid");
+ Element div = divs.get(0);
+ Elements trs = div.getElementsByTag("tr");
+ for (int i = 1; i < trs.size(); i++) {
+ Element tr = trs.get(i);
+ Book book = new Book();
+ Elements info = tr.getElementsByTag("td");
+ book.setName(info.get(0).text());
+ book.setChapterUrl(info.get(0).getElementsByTag("a").attr("href"));
+ book.setAuthor(info.get(2).text());
+ book.setNewestChapterTitle(info.get(1).text());
+ book.setUpdateDate(info.get(4).text());
+ book.setDesc("");
+ book.setSource(BookSource.paiotian.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ }
+ }
+ return books;
+ }
+
+ /**
+ * 获取书籍详细信息
+ * @param book
+ */
+ public Book getBookInfo(String html, Book book){
+ Document doc = Jsoup.parse(html);
+ Element name = doc.selectFirst("meta[property=og:novel:book_name]");
+ book.setName(name.attr("content"));
+ Element author = doc.selectFirst("meta[property=og:novel:author]");
+ book.setAuthor(author.attr("content"));
+ Element newestChapter = doc.selectFirst("meta[property=og:novel:latest_chapter_name]");
+ book.setNewestChapterTitle(newestChapter.attr("content"));
+ Element img = doc.selectFirst("meta[property=og:image]");
+ book.setImgUrl(img.attr("content"));
+ Element desc = doc.getElementsByClass("bookinfo_intro").first();
+ book.setDesc(desc.text());
+ Element type = doc.selectFirst("meta[property=og:novel:category]");
+ book.setType(type.attr("content"));
+ return book;
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/QB5ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/QB5ReadCrawler.java
index 144db3c..cec3359 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/QB5ReadCrawler.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/QB5ReadCrawler.java
@@ -65,13 +65,19 @@ public class QB5ReadCrawler extends FindCrawler implements ReadCrawler, BookInfo
public boolean hasImg() {
return false;
}
+
+ @Override
+ public boolean needSearch() {
+ return false;
+ }
+
/**
* 获取书城小说分类列表
*
* @param html
* @return
*/
- public List getBookTypeList(String html) {
+ public List getBookTypes(String html) {
List bookTypes = new ArrayList<>();
Document doc = Jsoup.parse(html);
Elements divs = doc.getElementsByClass("nav_cont");
@@ -126,9 +132,13 @@ public class QB5ReadCrawler extends FindCrawler implements ReadCrawler, BookInfo
return books;
}
- public List getRankBookList(String html) {
+ public List getFindBooks(String html, BookType bookType) {
List books = new ArrayList<>();
Document doc = Jsoup.parse(html);
+ try {
+ int pageSize = Integer.parseInt(doc.getElementsByClass("last").first().text());
+ bookType.setPageSize(pageSize);
+ }catch (Exception ignored){}
String type = doc.select("meta[name=keywords]").attr("content").replace(",全本小说网", "");
Element div = doc.getElementById("tlist");
Elements uls = div.getElementsByTag("ul");
@@ -267,19 +277,18 @@ public class QB5ReadCrawler extends FindCrawler implements ReadCrawler, BookInfo
public boolean getTypePage(BookType curType, int page){
- if (page > 10){
+ if (curType.getPageSize() <= 0){
+ curType.setPageSize(10);
+ }
+ if (page > curType.getPageSize()){
return true;
}
if (!curType.getTypeName().equals("完本小说")) {
curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + "/");
}else {
- if (page > 1){
- return true;
- }
+ curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page);
}
return false;
}
-
-
}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/SoNovelReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/SoNovelReadCrawler.java
index 3f91783..33206c6 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/SoNovelReadCrawler.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/SoNovelReadCrawler.java
@@ -19,8 +19,8 @@ import java.util.List;
public class SoNovelReadCrawler implements ReadCrawler, BookInfoCrawler {
- public static final String NAME_SPACE = "https://www.soxs.cc";
- public static final String NOVEL_SEARCH = "https://www.soxs.cc/search.html,searchtype=all&searchkey={key}";
+ public static final String NAME_SPACE = "https://www.soxscc.com";
+ public static final String NOVEL_SEARCH = "https://www.soxscc.com/search.html,searchtype=all&searchkey={key}&action=search&submit= 搜 索 ";
public static final String CHARSET = "UTF-8";
public static final String SEARCH_CHARSET = "UTF-8";
@@ -119,7 +119,7 @@ public class SoNovelReadCrawler implements ReadCrawler, BookInfoCrawler {
book.setName(info.text());
book.setChapterUrl(NAME_SPACE + info.getElementsByTag("a").attr("href"));
book.setAuthor(element.getElementsByClass("s4").first().text());
- book.setType(element.getElementsByTag("span").first().text());
+ book.setType(element.getElementsByTag("span").first().text().replace("[", "").replace("]", ""));
book.setNewestChapterTitle(element.getElementsByClass("s3").first().text());
book.setSource(BookSource.sonovel.toString());
SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/XS7ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/XS7ReadCrawler.java
new file mode 100644
index 0000000..715c869
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/XS7ReadCrawler.java
@@ -0,0 +1,221 @@
+package xyz.fycz.myreader.webapi.crawler.read;
+
+import android.text.Html;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import xyz.fycz.myreader.entity.SearchBookBean;
+import xyz.fycz.myreader.entity.bookstore.BookType;
+import xyz.fycz.myreader.enums.BookSource;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
+import xyz.fycz.myreader.webapi.crawler.base.FindCrawler;
+import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
+
+
+public class XS7ReadCrawler extends FindCrawler implements ReadCrawler {
+ public static final String NAME_SPACE = "https://www.xs7.la";
+ public static final String NOVEL_SEARCH = "https://www.xs7.la/modules/article/search.php,searchkey={key}";
+ public static final String CHARSET = "GBK";
+ public static final String SEARCH_CHARSET = "GBK";
+ public static final String FIND_NAME = "书城[小说旗]";
+
+ @Override
+ public String getSearchLink() {
+ return NOVEL_SEARCH;
+ }
+
+ @Override
+ public String getCharset() {
+ return CHARSET;
+ }
+
+ @Override
+ public String getFindName() {
+ return FIND_NAME;
+ }
+
+ @Override
+ public String getFindUrl() {
+ return NAME_SPACE;
+ }
+
+ @Override
+ public boolean hasImg() {
+ return true;
+ }
+
+ @Override
+ public boolean needSearch() {
+ return false;
+ }
+
+ @Override
+ public String getNameSpace() {
+ return NAME_SPACE;
+ }
+
+ @Override
+ public Boolean isPost() {
+ return true;
+ }
+
+ @Override
+ public String getSearchCharset() {
+ return SEARCH_CHARSET;
+ }
+
+ /**
+ * 从html中获取章节正文
+ *
+ * @param html
+ * @return
+ */
+ public String getContentFormHtml(String html) {
+ Document doc = Jsoup.parse(html);
+ Element divContent = doc.getElementById("content");
+ if (divContent != null) {
+ String content = Html.fromHtml(divContent.html()).toString();
+ char c = 160;
+ String spaec = "" + c;
+ content = content.replace(spaec, " ").replaceAll("一秒记住.*免费阅读!", "");
+ return content;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * 从html中获取章节列表
+ *
+ * @param html
+ * @return
+ */
+ public ArrayList getChaptersFromHtml(String html) {
+ ArrayList chapters = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content");
+ Element table = doc.getElementsByTag("table").first();
+ Elements elementsByTag = table.getElementsByTag("a");
+ int i = 0;
+ for (int j = 0; j < elementsByTag.size(); j++) {
+ Element a = elementsByTag.get(j);
+ String title = a.text();
+ String url = a.attr("href");
+ Chapter chapter = new Chapter();
+ chapter.setNumber(i++);
+ chapter.setTitle(title);
+ chapter.setUrl(readUrl + url);
+ chapters.add(chapter);
+ }
+ return chapters;
+ }
+
+ /**
+ * 从搜索html中得到书列表
+ *
+ * @param html
+ * @return
+ */
+ public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) {
+ ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>();
+ Document doc = Jsoup.parse(html);
+// try {
+ Element alist = doc.getElementById("alist");
+ Elements divs = alist.select("#alistbox");
+ for (Element div : divs){
+ Elements as = div.getElementsByTag("a");
+ Book book = new Book();
+ book.setName(as.get(1).text());
+ book.setAuthor(div.getElementsByTag("span").first().text().replace("作者:", ""));
+ book.setNewestChapterTitle(as.get(2).text());
+ book.setDesc(div.getElementsByClass("intro").first().text());
+ book.setImgUrl(div.getElementsByTag("img").attr("src"));
+ book.setChapterUrl(as.get(1).attr("href"));
+ book.setSource(BookSource.xs7.toString());
+ SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
+ books.add(sbb, book);
+ }
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+ return books;
+ }
+
+ @Override
+ public List getBookTypes(String html) {
+ List bookTypes = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element div = doc.getElementsByClass("subnav-hot").first();
+ Elements as = div.getElementsByTag("a");
+ for (Element a : as){
+ BookType bookType = new BookType();
+ bookType.setUrl(a.attr("href"));
+ bookType.setTypeName(a.text());
+ bookTypes.add(bookType);
+ }
+ return bookTypes;
+ }
+
+ /*
+
+
+
+
+
+
他横任他横,我自种我田,若要来惹我,过不了明年。
+ 宅男赵海带着QQ农场穿越到了异界,附身到了一个落迫的小贵族身上,他的封地是一片种不出东西的黑土地,而最主要的是,...
+
+
+
+ */
+ @Override
+ public List getFindBooks(String html, BookType bookType) {
+ List books = new ArrayList<>();
+ Document doc = Jsoup.parse(html);
+ Element last = doc.getElementsByClass("last").first();
+ bookType.setPageSize(Integer.parseInt(last.text()));
+ Element div = doc.getElementById("alist");
+ Elements bDivs = div.select("div[id=alistbox]");
+ for (Element bDiv : bDivs){
+ Book book = new Book();
+ Element title = bDiv.getElementsByClass("title").first();
+ Element sys = bDiv.getElementsByClass("sys").first();
+ Element intro = bDiv.getElementsByClass("intro").first();
+ book.setName(title.getElementsByTag("a").first().text());
+ book.setAuthor(title.getElementsByTag("span").first().text().replace("作者:", ""));
+ book.setNewestChapterTitle(sys.getElementsByTag("a").first().text());
+ book.setImgUrl(bDiv.getElementsByTag("img").attr("src"));
+ book.setDesc(intro.text());
+ book.setType(bookType.getTypeName());
+ book.setChapterUrl(title.getElementsByTag("a").first().attr("href"));
+ book.setSource(BookSource.xs7.toString());
+ books.add(book);
+ }
+ return books;
+ }
+
+ @Override
+ public boolean getTypePage(BookType curType, int page) {
+ if (page != 1 && page > curType.getPageSize()){
+ return true;
+ }
+ curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + ".html");
+ return false;
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/CircleCheckBox.java b/app/src/main/java/xyz/fycz/myreader/widget/CircleCheckBox.java
new file mode 100644
index 0000000..ff8dabd
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/CircleCheckBox.java
@@ -0,0 +1,480 @@
+package xyz.fycz.myreader.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.Timer;
+
+import xyz.fycz.myreader.R;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+
+/**
+ * Copyright (c) 2016 Arlind Hajredinaj
+ *
+ * Permission is hereby granted, free of charge,
+ * to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+public class CircleCheckBox extends View {
+ private float innerCircleRadius = 30.f;
+ private float outerCircleRadius = innerCircleRadius / 2f;
+ private float textSize = 35f;
+ private float borderThickness = 5f;
+ private float tickThickness = 2f;
+ private float textLeftPadding = 2f;
+
+ private float increment = 20;
+ private float total_time = 200;
+ private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
+ private final Paint mPaintPageStroke = new Paint(ANTI_ALIAS_FLAG);
+ private final Paint mPaintTick = new Paint(ANTI_ALIAS_FLAG);
+ private final Paint mPaintOuter = new Paint(ANTI_ALIAS_FLAG);
+ private final Paint mPaintText = new Paint(ANTI_ALIAS_FLAG);
+ private boolean firstRun = true;
+ private boolean timer_running = false;
+ private float tick_third_ = innerCircleRadius / 3;
+ private boolean draw_tick_part_one = false;
+ private String text = "";
+
+ private int tick_color = Color.argb(255, 255, 255, 255);
+ private int text_color = Color.argb(255, 0, 0, 0);
+ private int outerCircleColor = Color.argb(100, 0, 207, 173);
+ private int innerCircleColor = Color.argb(255, 0, 207, 173);
+ private int circleBorderColor = Color.argb(255, 0, 207, 173);
+
+ private OnCheckedChangeListener listener;
+
+ private boolean showOuterCircle = true;
+
+ float centerX = 0;
+ float centerY = 0;
+ private boolean isChecked = false;
+
+ private boolean draw_tick = false;
+
+ Timer timer = new Timer();
+
+ public CircleCheckBox(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public CircleCheckBox(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public CircleCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs);
+ }
+
+ public void init(Context context, AttributeSet attrs) {
+ if (attrs != null) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.CircleCheckbox,
+ 0, 0);
+
+ try {
+ setTickColorHex(a.getString(R.styleable.CircleCheckbox_tickColor));
+ setTextColorHex(a.getString(R.styleable.CircleCheckbox_textColor));
+ setShowOuterCircle(a.getBoolean(R.styleable.CircleCheckbox_showOuterCircle, true));
+ setInnerCircleColorHex(a.getString(R.styleable.CircleCheckbox_innerCircleColor));
+ setOuterCircleColorHex(a.getString(R.styleable.CircleCheckbox_outerCircleColor));
+ setCircleBorderColorHex(a.getString(R.styleable.CircleCheckbox_circleBorderColor));
+ setTickThickness(a.getDimension(R.styleable.CircleCheckbox_tickThickness, tickThickness));
+ setBorderThickness(a.getDimension(R.styleable.CircleCheckbox_borderThickness, borderThickness));
+ setTextLeftPadding(a.getDimension(R.styleable.CircleCheckbox_textLeftPadding, textLeftPadding));
+ setTextSize(a.getDimension(R.styleable.CircleCheckbox_textSize, textSize));
+ setInnerCircleRadius(a.getDimension(R.styleable.CircleCheckbox_innerCircleRadius, innerCircleRadius));
+ setOuterCircleRadius(a.getDimension(R.styleable.CircleCheckbox_outerCircleRadius, outerCircleRadius));
+ setText(a.getString(R.styleable.CircleCheckbox_text));
+ } finally {
+ a.recycle();
+ }
+ }
+
+ mPaintOuter.setColor(outerCircleColor);
+ mPaintPageFill.setColor(innerCircleColor);
+ mPaintTick.setColor(tick_color);
+ mPaintTick.setStrokeWidth(tickThickness * 2);
+
+ mPaintPageStroke.setColor(circleBorderColor);
+ mPaintPageStroke.setStrokeWidth(borderThickness);
+ mPaintPageStroke.setStyle(Paint.Style.STROKE);
+
+ mPaintText.setTextSize(textSize);
+ mPaintText.setColor(text_color);
+
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setChecked(!isChecked);
+ }
+ });
+ }
+
+ private float current_radius = 0.0f;
+ float time = 0;
+
+ float tick_x = 0;
+ float tick_y = 0;
+ float tick_x_two = 0;
+ float tick_y_two = 0;
+ // Interpolator interpolator = new BounceInterpolator();
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ centerX = innerCircleRadius + outerCircleRadius + getPaddingLeft();
+
+ centerY = getHeight() / 2;
+
+ //float interpolation = Math.abs(interpolator.getInterpolation(time));
+
+ canvas.drawCircle(centerX, centerY, innerCircleRadius, mPaintPageStroke);
+
+ if (isChecked) {
+ if (draw_tick) {
+ float tick_offset = tickThickness * 2;
+ if (showOuterCircle) {
+ canvas.drawCircle(centerX, centerY, current_radius + outerCircleRadius, mPaintOuter);
+ }
+ canvas.drawCircle(centerX, centerY, innerCircleRadius, mPaintPageFill);
+ if (draw_tick_part_one) {
+
+ canvas.drawCircle(centerX - tick_offset - tick_third_, centerY, tickThickness, mPaintTick);
+ canvas.drawLine(centerX - tick_offset - tick_third_, centerY, tick_x - tick_offset, tick_y, mPaintTick);
+ canvas.drawCircle(tick_x - tick_offset, tick_y, tickThickness, mPaintTick);
+ } else {
+ canvas.drawCircle(centerX - tick_offset - tick_third_, centerY, tickThickness, mPaintTick);
+ canvas.drawLine(centerX - tick_offset - tick_third_, centerY, tick_x - tick_offset, tick_y, mPaintTick);
+ canvas.drawCircle(tick_x - tick_offset, tick_y, tickThickness, mPaintTick);
+
+ canvas.drawLine(centerX - tick_offset, tick_y, tick_x_two - tick_offset, tick_y_two, mPaintTick);
+ canvas.drawCircle(tick_x_two - tick_offset, tick_y_two, tickThickness, mPaintTick);
+ }
+
+ } else {
+ if (showOuterCircle && current_radius >= innerCircleRadius - outerCircleRadius) {
+ canvas.drawCircle(centerX, centerY, (current_radius + outerCircleRadius), mPaintOuter);
+ }
+ canvas.drawCircle(centerX, centerY, current_radius, mPaintPageFill);
+ }
+ } else {
+ if (!firstRun) {
+ canvas.drawCircle(centerX, centerY, current_radius, mPaintPageFill);
+ }
+ }
+
+ if (isChecked) {
+ if (!timer_running) {
+ tick_x = centerX;// tick_third_;
+ tick_y = centerY + tick_third_;
+ float tick_offset = tickThickness * 2;
+ canvas.drawCircle(centerX - tick_offset - tick_third_, centerY, tickThickness, mPaintTick);
+ canvas.drawLine(centerX - tick_offset - tick_third_, centerY, tick_x - tick_offset, tick_y, mPaintTick);
+ canvas.drawCircle(tick_x - tick_offset, tick_y, tickThickness, mPaintTick);
+
+ tick_x_two = tick_x + (tick_third_ * 1.7f);
+ tick_y_two = tick_y - (tick_third_ * 1.7f);
+ canvas.drawLine(centerX - tick_offset, tick_y, tick_x_two - tick_offset, tick_y_two, mPaintTick);
+ canvas.drawCircle(tick_x_two - tick_offset, tick_y_two, tickThickness, mPaintTick);
+ tick_x = 0;
+ tick_y = 0;
+ tick_x_two = 0;
+ tick_x_two = 0;
+ }
+ }
+
+ canvas.drawText(text, centerX + textLeftPadding + innerCircleRadius + outerCircleRadius, centerY + textSize / 2, mPaintText);
+ firstRun = false;
+ }
+
+ Handler handler = new Handler();
+
+ private void startAnimationTimer() {
+ this.post(new Runnable() {
+ @Override
+ public void run() {
+ runAnimation();
+ }
+ });
+ }
+
+ private void runAnimation() {
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ timer_running = true;
+ time += increment;
+ if (time < total_time) {
+
+ float inc = innerCircleRadius / (total_time / increment);
+ if (isChecked) {
+ current_radius = current_radius + inc;
+ } else {
+ current_radius = current_radius - inc;
+ }
+ postInvalidate();
+ runAnimation();
+ } else {
+ if (isChecked) {
+ time = 0;
+ startTickAnimation();
+ } else {
+ timer_running = false;
+ }
+ }
+ }
+ }, (long) increment);
+ }
+
+ private void startTickAnimation() {
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ draw_tick_part_one = true;
+ timer_running = true;
+ draw_tick = true;
+
+ if (time == 0) {
+ tick_x = centerX - tick_third_;
+ tick_y = centerY;
+ }
+ float inc_tick = tick_third_ / (total_time / increment);
+
+ tick_x += inc_tick;
+ tick_y += inc_tick;
+
+ time += increment;
+ if (time <= total_time) {
+ postInvalidate();
+ startTickAnimation();
+ } else {
+ draw_tick_part_one = false;
+ time = 0;
+ startTickPartTwoAnimation();
+ }
+ }
+ }, (int) increment);
+ }
+
+ private void startTickPartTwoAnimation() {
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ timer_running = true;
+ draw_tick = true;
+ if (time == 0) {
+ tick_x_two = tick_x;
+ tick_y_two = tick_y;
+ }
+
+ float inc_tick = tick_third_ * 1.7f / (total_time / increment);
+
+ tick_x_two += inc_tick;
+ tick_y_two -= inc_tick;
+
+ time += increment;
+ if (time <= total_time) {
+ postInvalidate();
+ startTickPartTwoAnimation();
+ } else {
+ timer_running = false;
+ draw_tick = false;
+ time = 0;
+ }
+ }
+ }, (long) increment);
+ }
+
+ public int getTickColor() {
+ return tick_color;
+ }
+
+ public void setTickColor(int tick_color) {
+ this.tick_color = tick_color;
+ }
+
+ public void setTickColorHex(String tick_color) {
+ if (tick_color != null)
+ this.tick_color = Color.parseColor(tick_color);
+ }
+
+ public int getTextColor() {
+ return text_color;
+ }
+
+ public void setTextColor(int text_color) {
+ this.text_color = text_color;
+ }
+
+ public void setTextColorHex(String color) {
+ if (color != null)
+ this.text_color = Color.parseColor(color);
+ }
+
+ public boolean isShowOuterCircle() {
+ return showOuterCircle;
+ }
+
+ public void setShowOuterCircle(boolean showOuterCircle) {
+ this.showOuterCircle = showOuterCircle;
+ }
+
+ public int getInnerCircleColor() {
+ return innerCircleColor;
+ }
+
+ public void setInnerCircleColor(int innerCircleColor) {
+ this.innerCircleColor = innerCircleColor;
+ }
+
+ public void setInnerCircleColorHex(String innerCircleColor) {
+ if (innerCircleColor != null)
+ this.innerCircleColor = Color.parseColor(innerCircleColor);
+ }
+
+ public int getOuterCircleColor() {
+ return outerCircleColor;
+ }
+
+ public void setOuterCircleColor(int outerCircleColor) {
+ this.outerCircleColor = outerCircleColor;
+ }
+
+ public void setOuterCircleColorHex(String outerCircleColor) {
+ if (outerCircleColor != null)
+ this.outerCircleColor = Color.parseColor(outerCircleColor);
+ }
+
+ public int getCircleBorderColor() {
+ return circleBorderColor;
+ }
+
+ public void setCircleBorderColor(int circleBorderColor) {
+ this.circleBorderColor = circleBorderColor;
+ }
+
+ public void setCircleBorderColorHex(String color) {
+ if (color != null)
+ this.circleBorderColor = Color.parseColor(color);
+ }
+
+ public float getTickThickness() {
+ return tickThickness;
+ }
+
+ public void setTickThickness(float tickThickness) {
+ this.tickThickness = tickThickness;
+ }
+
+ public float getBorderThickness() {
+ return borderThickness;
+ }
+
+ public void setBorderThickness(float borderThickness) {
+ this.borderThickness = borderThickness;
+ }
+
+ public float getTextLeftPadding() {
+ return textLeftPadding;
+ }
+
+ public void setTextLeftPadding(float textLeftPadding) {
+ this.textLeftPadding = textLeftPadding;
+ }
+
+ public float getTextSize() {
+ return textSize;
+ }
+
+ public void setTextSize(float textSize) {
+ this.textSize = textSize;
+ }
+
+ public float getInnerCircleRadius() {
+ return innerCircleRadius;
+ }
+
+ public void setInnerCircleRadius(float innerCircleRadius) {
+ this.innerCircleRadius = innerCircleRadius;
+ }
+
+ public float getOuterCircleRadius() {
+ return outerCircleRadius;
+ }
+
+ public void setOuterCircleRadius(float outerCircleRadius) {
+ this.outerCircleRadius = outerCircleRadius;
+ }
+
+ public void setChecked(boolean isChecked) {
+ if (!timer_running) {
+ this.isChecked = isChecked;
+ if (listener != null)
+ listener.onCheckedChanged(this, isChecked);
+ if (isChecked) {
+ tick_x = 0;
+ tick_y = 0;
+ tick_x_two = 0;
+ tick_y_two = 0;
+ current_radius = 0;
+ }
+ time = 0;
+ startAnimationTimer();
+ }
+ }
+
+ public boolean isChecked(){
+ return isChecked;
+ }
+
+ public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
+ this.listener = listener;
+ }
+
+ int position;
+
+ public void setPosition(int position) {
+ this.position = position;
+ }
+
+ public interface OnCheckedChangeListener {
+ void onCheckedChanged(CircleCheckBox view, boolean isChecked);
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ if (text != null) {
+ this.text = text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/HorizontalListView.java b/app/src/main/java/xyz/fycz/myreader/widget/HorizontalListView.java
new file mode 100644
index 0000000..d458989
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/HorizontalListView.java
@@ -0,0 +1,362 @@
+package xyz.fycz.myreader.widget;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.Scroller;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class HorizontalListView extends AdapterView {
+
+ public boolean mAlwaysOverrideTouch = true;
+ protected ListAdapter mAdapter;
+ private int mLeftViewIndex = -1;
+ private int mRightViewIndex = 0;
+ protected int mCurrentX;
+ protected int mNextX;
+ private int mMaxX = Integer.MAX_VALUE;
+ private int mDisplayOffset = 0;
+ protected Scroller mScroller;
+ private GestureDetector mGesture;
+ private Queue mRemovedViewQueue = new LinkedList();
+ private OnItemSelectedListener mOnItemSelected;
+ private OnItemClickListener mOnItemClicked;
+ private OnItemLongClickListener mOnItemLongClicked;
+ private boolean mDataChanged = false;
+
+
+ public HorizontalListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initView();
+ }
+
+ private synchronized void initView() {
+ mLeftViewIndex = -1;
+ mRightViewIndex = 0;
+ mDisplayOffset = 0;
+ mCurrentX = 0;
+ mNextX = 0;
+ mMaxX = Integer.MAX_VALUE;
+ mScroller = new Scroller(getContext());
+ mGesture = new GestureDetector(getContext(), mOnGesture);
+ }
+
+ @Override
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelected = listener;
+ }
+
+ @Override
+ public void setOnItemClickListener(OnItemClickListener listener){
+ mOnItemClicked = listener;
+ }
+
+ @Override
+ public void setOnItemLongClickListener(OnItemLongClickListener listener) {
+ mOnItemLongClicked = listener;
+ }
+
+ private DataSetObserver mDataObserver = new DataSetObserver() {
+
+ @Override
+ public void onChanged() {
+ synchronized(HorizontalListView.this){
+ mDataChanged = true;
+ }
+ invalidate();
+ requestLayout();
+ }
+
+ @Override
+ public void onInvalidated() {
+ reset();
+ invalidate();
+ requestLayout();
+ }
+
+ };
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ return mGesture.onTouchEvent(ev);
+ };
+ @Override
+ public ListAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public View getSelectedView() {
+ return null;
+ }
+
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ if(mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(mDataObserver);
+ }
+ mAdapter = adapter;
+ mAdapter.registerDataSetObserver(mDataObserver);
+ reset();
+ }
+
+ private synchronized void reset(){
+ initView();
+ removeAllViewsInLayout();
+ requestLayout();
+ }
+
+ @Override
+ public void setSelection(int position) {
+ }
+
+ private void addAndMeasureChild(final View child, int viewPos) {
+ LayoutParams params = child.getLayoutParams();
+ if(params == null) {
+ params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ }
+
+ addViewInLayout(child, viewPos, params, true);
+ child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
+ }
+
+
+
+ @Override
+ protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if(mAdapter == null){
+ return;
+ }
+
+ if(mDataChanged){
+ int oldCurrentX = mCurrentX;
+ initView();
+ removeAllViewsInLayout();
+ mNextX = oldCurrentX;
+ mDataChanged = false;
+ }
+
+ if(mScroller.computeScrollOffset()){
+ mNextX = mScroller.getCurrX();
+ }
+
+ if(mNextX <= 0){
+ mNextX = 0;
+ mScroller.forceFinished(true);
+ }
+ if(mNextX >= mMaxX) {
+ mNextX = mMaxX;
+ mScroller.forceFinished(true);
+ }
+
+ int dx = mCurrentX - mNextX;
+
+ removeNonVisibleItems(dx);
+ fillList(dx);
+ positionItems(dx);
+
+ mCurrentX = mNextX;
+
+ if(!mScroller.isFinished()){
+ post(new Runnable(){
+ @Override
+ public void run() {
+ requestLayout();
+ }
+ });
+
+ }
+ }
+
+ private void fillList(final int dx) {
+ int edge = 0;
+ View child = getChildAt(getChildCount()-1);
+ if(child != null) {
+ edge = child.getRight();
+ }
+ fillListRight(edge, dx);
+
+ edge = 0;
+ child = getChildAt(0);
+ if(child != null) {
+ edge = child.getLeft();
+ }
+ fillListLeft(edge, dx);
+
+
+ }
+
+ private void fillListRight(int rightEdge, final int dx) {
+ while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) {
+
+ View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);
+ addAndMeasureChild(child, -1);
+ rightEdge += child.getMeasuredWidth();
+
+ if(mRightViewIndex == mAdapter.getCount()-1) {
+ mMaxX = mCurrentX + rightEdge - getWidth();
+ }
+
+ if (mMaxX < 0) {
+ mMaxX = 0;
+ }
+ mRightViewIndex++;
+ }
+
+ }
+
+ private void fillListLeft(int leftEdge, final int dx) {
+ while(leftEdge + dx > 0 && mLeftViewIndex >= 0) {
+ View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this);
+ addAndMeasureChild(child, 0);
+ leftEdge -= child.getMeasuredWidth();
+ mLeftViewIndex--;
+ mDisplayOffset -= child.getMeasuredWidth();
+ }
+ }
+
+ private void removeNonVisibleItems(final int dx) {
+ View child = getChildAt(0);
+ while(child != null && child.getRight() + dx <= 0) {
+ mDisplayOffset += child.getMeasuredWidth();
+ mRemovedViewQueue.offer(child);
+ removeViewInLayout(child);
+ mLeftViewIndex++;
+ child = getChildAt(0);
+
+ }
+
+ child = getChildAt(getChildCount()-1);
+ while(child != null && child.getLeft() + dx >= getWidth()) {
+ mRemovedViewQueue.offer(child);
+ removeViewInLayout(child);
+ mRightViewIndex--;
+ child = getChildAt(getChildCount()-1);
+ }
+ }
+
+ private void positionItems(final int dx) {
+ if(getChildCount() > 0){
+ mDisplayOffset += dx;
+ int left = mDisplayOffset;
+ for(int i=0;i mViewHeight){
+ dis = mViewHeight;
+ }
+ //计算bitmap截取的区域
+ mSrcRect.top = mViewHeight - dis;
+ //计算bitmap在canvas显示的区域
+ mDestRect.bottom = dis;
+ canvas.drawBitmap(mNextBitmap,0,0,null);
+ canvas.drawBitmap(mCurBitmap,mSrcRect,mDestRect,null);
+ addShadow(dis,canvas);
+ break;
+ default:
+ mSrcRect.top = (int) (mViewHeight - mTouchY);
+ mDestRect.bottom = (int) mTouchY;
+ canvas.drawBitmap(mCurBitmap,0,0,null);
+ canvas.drawBitmap(mNextBitmap,mSrcRect,mDestRect,null);
+ addShadow((int) mTouchY,canvas);
+ break;
+ }
+ }
+
+ //添加阴影
+ public void addShadow(int top, Canvas canvas) {
+ mBackShadowDrawableLR.setBounds(0, top, mScreenWidth , top + 30);
+ mBackShadowDrawableLR.draw(canvas);
+ }
+
+ @Override
+ public void startAnim() {
+ super.startAnim();
+ int dy = 0;
+ switch (mDirection){
+ case NEXT:
+ if (isCancel){
+ int dis = (int) ((mViewHeight - mStartY) + mTouchY);
+ if (dis > mViewHeight){
+ dis = mViewHeight;
+ }
+ dy = mViewHeight - dis;
+ }else{
+ dy = (int) -(mTouchY + (mViewHeight - mStartY));
+ }
+ break;
+ default:
+ if (isCancel){
+ dy = (int) -mTouchY;
+ }else{
+ dy = (int) (mViewHeight - mTouchY);
+ }
+ break;
+ }
+
+ //滑动速度保持一致
+ int duration = (animationSpeed * Math.abs(dy)) / mViewHeight;
+ mScroller.startScroll(0, (int) mTouchY, 0, dy, duration);
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java
index 83b9c8c..31687e3 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java
@@ -6,6 +6,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
+import xyz.fycz.myreader.application.SysManager;
+
/**
* Created by newbiechen on 17-7-24.
* 横向动画的模板
@@ -31,12 +33,12 @@ public abstract class HorizonPageAnim extends PageAnimation{
private boolean noNext = false;
public HorizonPageAnim(int w, int h, View view, OnPageChangeListener listener) {
- this(w, h, 0, 0, view, listener);
+ this(w, h, 0, 0,0, view, listener);
}
- public HorizonPageAnim(int w, int h, int marginWidth, int marginHeight,
+ public HorizonPageAnim(int w, int h, int marginWidth, int marginTop, int marginBottom,
View view, OnPageChangeListener listener) {
- super(w, h, marginWidth, marginHeight, view,listener);
+ super(w, h, marginWidth, marginTop, marginBottom, view,listener);
//创建图片
mCurBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565);
mNextBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565);
@@ -143,11 +145,7 @@ public abstract class HorizonPageAnim extends PageAnimation{
break;
case MotionEvent.ACTION_UP:
if (!isMove){
- if (x < mScreenWidth / 2){
- isNext = false;
- }else{
- isNext = true;
- }
+ isNext = x > mScreenWidth / 2 || SysManager.getSetting().isAlwaysNext();
if (isNext) {
//判断是否下一页存在
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java
index 742a410..760c622 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java
@@ -13,6 +13,8 @@ import android.widget.Scroller;
*/
public abstract class PageAnimation {
+ //动画速度
+ static final int animationSpeed = 300;
//正在使用的View
protected View mView;
//滑动装置
@@ -44,18 +46,18 @@ public abstract class PageAnimation {
protected float mLastY;
public PageAnimation(int w, int h, View view, OnPageChangeListener listener){
- this(w, h, 0, 0, view,listener);
+ this(w, h, 0, 0, 0, view,listener);
}
- public PageAnimation(int w, int h, int marginWidth, int marginHeight, View view, OnPageChangeListener listener){
+ public PageAnimation(int w, int h, int marginWidth, int marginTop, int marginBottom, View view, OnPageChangeListener listener){
mScreenWidth = w;
mScreenHeight = h;
mMarginWidth = marginWidth;
- mMarginHeight = marginHeight;
+ mMarginHeight = marginTop;
- mViewWidth = mScreenWidth - mMarginWidth * 2;
- mViewHeight = mScreenHeight - mMarginHeight * 2;
+ mViewWidth = mScreenWidth - marginWidth * 2;
+ mViewHeight = mScreenHeight - marginTop - marginBottom;
mView = view;
mListener = listener;
@@ -138,6 +140,14 @@ public abstract class PageAnimation {
*/
public abstract Bitmap getNextBitmap();
+ public float getmTouchX() {
+ return mTouchX;
+ }
+
+ public float getmTouchY() {
+ return mTouchY;
+ }
+
public enum Direction {
NONE(true),NEXT(true), PRE(true), UP(false), DOWN(false);
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java
index 3914acb..19439da 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java
@@ -3,14 +3,18 @@ package xyz.fycz.myreader.widget.animation;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
+import android.view.ViewConfiguration;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Iterator;
+import xyz.fycz.myreader.application.SysManager;
+
/**
* Created by newbiechen on 17-7-23.
* 原理:仿照ListView源码实现的上下滑动效果
@@ -28,7 +32,8 @@ public class ScrollPageAnim extends PageAnimation {
// 滑动追踪的时间
private static final int VELOCITY_DURATION = 1000;
private VelocityTracker mVelocity;
-
+ //是否移动了
+ private boolean isMove = false;
// 整个Bitmap的背景显示
private Bitmap mBgBitmap;
@@ -43,9 +48,9 @@ public class ScrollPageAnim extends PageAnimation {
// 是否处于刷新阶段
private boolean isRefresh = true;
- public ScrollPageAnim(int w, int h, int marginWidth, int marginHeight,
+ public ScrollPageAnim(int w, int h, int marginWidth, int marginTop, int marginBottom,
View view, OnPageChangeListener listener) {
- super(w, h, marginWidth, marginHeight, view, listener);
+ super(w, h, marginWidth, marginTop, marginBottom, view, listener);
// 创建两个BitmapView
initWidget();
}
@@ -304,12 +309,17 @@ public class ScrollPageAnim extends PageAnimation {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isRunning = false;
+ isMove = false;
// 设置起始点
setStartPoint(x, y);
// 停止动画
abortAnim();
break;
case MotionEvent.ACTION_MOVE:
+ final int slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+ if (!isMove) {
+ isMove = Math.abs(mStartX - x) > slop || Math.abs(mStartY - y) > slop;
+ }
mVelocity.computeCurrentVelocity(VELOCITY_DURATION);
isRunning = true;
// 进行刷新
@@ -317,11 +327,23 @@ public class ScrollPageAnim extends PageAnimation {
break;
case MotionEvent.ACTION_UP:
isRunning = false;
- // 开启动画
- startAnim();
+ if (!isMove) {
+ //是否翻阅下一页。true表示翻到下一页,false表示上一页。
+ boolean isNext = x > mScreenWidth / 2 || SysManager.getSetting().isAlwaysNext();
+ if (isNext) {
+ startAnim(Direction.NEXT);
+ } else {
+ startAnim(Direction.PRE);
+ }
+ } else {
+ // 开启动画
+ startAnim();
+ }
// 删除检测器
- mVelocity.recycle();
- mVelocity = null;
+ if (mVelocity != null) {
+ mVelocity.recycle();
+ mVelocity = null;
+ }
break;
case MotionEvent.ACTION_CANCEL:
@@ -369,6 +391,23 @@ public class ScrollPageAnim extends PageAnimation {
, 0, 0, Integer.MAX_VALUE * -1, Integer.MAX_VALUE);
}
+ /**
+ * 翻页动画
+ */
+ public void startAnim(Direction direction) {
+ setStartPoint(0, 0);
+ setTouchPoint(0, 0);
+ switch (direction) {
+ case NEXT:
+ isRunning = true;
+ mScroller.startScroll(0, 0, 0, -mViewHeight + mMarginHeight * 2, animationSpeed);
+ break;
+ case PRE:
+ isRunning = true;
+ mScroller.startScroll(0, 0, 0, mViewHeight - mMarginHeight * 2, animationSpeed);
+ break;
+ }
+ }
@Override
public void scrollAnim() {
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java
index 2b85402..86a5f27 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java
@@ -142,7 +142,7 @@ public class SimulationPageAnim extends HorizonPageAnim {
dy = (int) (1 - mTouchY); // 防止mTouchY最终变为0
}
}
- mScroller.startScroll((int) mTouchX, (int) mTouchY, dx, dy, 400);
+ mScroller.startScroll((int) mTouchX, (int) mTouchY, dx, dy, animationSpeed);
}
@Override
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java
index c8ceba9..cfb30d7 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java
@@ -95,7 +95,7 @@ public class SlidePageAnim extends HorizonPageAnim {
break;
}
//滑动速度保持一致
- int duration = (400 * Math.abs(dx)) / mScreenWidth;
+ int duration = (animationSpeed * Math.abs(dx)) / mScreenWidth;
mScroller.startScroll((int) mTouchX, 0, dx, 0, duration);
}
}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/VerticalPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/VerticalPageAnim.java
new file mode 100644
index 0000000..97cb2c7
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/VerticalPageAnim.java
@@ -0,0 +1,228 @@
+package xyz.fycz.myreader.widget.animation;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import xyz.fycz.myreader.application.SysManager;
+
+/**
+ * Created by fengyue on 20-11-22.
+ * 竖向移动的模板
+ */
+
+public abstract class VerticalPageAnim extends PageAnimation{
+ private static final String TAG = "HorizonPageAnim";
+
+ protected Bitmap mCurBitmap;
+ protected Bitmap mNextBitmap;
+ //是否取消翻页
+ protected boolean isCancel = false;
+
+ //可以使用 mLast代替
+ private int mMoveX = 0;
+ private int mMoveY = 0;
+ //是否移动了
+ private boolean isMove = false;
+ //是否翻阅下一页。true表示翻到下一页,false表示上一页。
+ private boolean isNext = false;
+
+ //是否没下一页或者上一页
+ private boolean noNext = false;
+
+ public VerticalPageAnim(int w, int h, View view, OnPageChangeListener listener) {
+ this(w, h, 0, 0,0, view, listener);
+ }
+
+ public VerticalPageAnim(int w, int h, int marginWidth, int marginTop, int marginBottom,
+ View view, OnPageChangeListener listener) {
+ super(w, h, marginWidth, marginTop, marginBottom, view,listener);
+ //创建图片
+ mCurBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565);
+ mNextBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565);
+ }
+
+ /**
+ * 转换页面,在显示下一章的时候,必须首先调用此方法
+ */
+ public void changePage(){
+ Bitmap bitmap = mCurBitmap;
+ mCurBitmap = mNextBitmap;
+ mNextBitmap = bitmap;
+ }
+
+ public abstract void drawStatic(Canvas canvas);
+
+ public abstract void drawMove(Canvas canvas);
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ //获取点击位置
+ int x = (int)event.getX();
+ int y = (int)event.getY();
+ //设置触摸点
+ setTouchPoint(x,y);
+
+ switch (event.getAction()){
+ case MotionEvent.ACTION_DOWN:
+ //移动的点击位置
+ mMoveX = 0;
+ mMoveY = 0;
+ //是否移动
+ isMove = false;
+ //是否存在下一章
+ noNext = false;
+ //是下一章还是前一章
+ isNext = false;
+ //是否正在执行动画
+ isRunning = false;
+ //取消
+ isCancel = false;
+ //设置起始位置的触摸点
+ setStartPoint(x,y);
+ //如果存在动画则取消动画
+ abortAnim();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final int slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+ //判断是否移动了
+ if (!isMove) {
+ isMove = Math.abs(mStartX - x) > slop || Math.abs(mStartY - y) > slop;
+ }
+
+ if (isMove){
+ //判断是否是准备移动的状态(将要移动但是还没有移动)
+ if (mMoveX == 0 && mMoveY ==0) {
+ //判断翻得是上一页还是下一页
+ if (y - mStartY > 0){
+ //上一页的参数配置
+ isNext = false;
+ boolean hasPrev = mListener.hasPrev();
+ setDirection(Direction.PRE);
+ //如果上一页不存在
+ if (!hasPrev) {
+ noNext = true;
+ return true;
+ }
+ }else{
+ //进行下一页的配置
+ isNext = true;
+ //判断是否下一页存在
+ boolean hasNext = mListener.hasNext();
+ //如果存在设置动画方向
+ setDirection(Direction.NEXT);
+
+ //如果不存在表示没有下一页了
+ if (!hasNext) {
+ noNext = true;
+ return true;
+ }
+ }
+ }else{
+ //判断是否取消翻页
+ if (isNext){
+ if (y - mMoveY > 0){
+ isCancel = true;
+ }else {
+ isCancel = false;
+ }
+ }else{
+ if (y - mMoveY < 0){
+ isCancel = true;
+ }else {
+ isCancel = false;
+ }
+ }
+ }
+
+ mMoveX = x;
+ mMoveY = y;
+ isRunning = true;
+ mView.invalidate();
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (!isMove){
+ isNext = y > mScreenHeight / 2 || SysManager.getSetting().isAlwaysNext();
+
+ if (isNext) {
+ //判断是否下一页存在
+ boolean hasNext = mListener.hasNext();
+ //设置动画方向
+ setDirection(Direction.NEXT);
+ if (!hasNext) {
+ return true;
+ }
+ } else {
+ boolean hasPrev = mListener.hasPrev();
+ setDirection(Direction.PRE);
+ if (!hasPrev) {
+ return true;
+ }
+ }
+ }
+
+ // 是否取消翻页
+ if (isCancel){
+ mListener.pageCancel();
+ }
+
+ // 开启翻页效果
+ if (!noNext) {
+ startAnim();
+ mView.invalidate();
+ }
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (isRunning) {
+ drawMove(canvas);
+ } else {
+ if (isCancel){
+ mNextBitmap = mCurBitmap.copy(Bitmap.Config.RGB_565, true);
+ }
+ drawStatic(canvas);
+ }
+ }
+
+ @Override
+ public void scrollAnim() {
+ if (mScroller.computeScrollOffset()) {
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+
+ setTouchPoint(x, y);
+
+ if (mScroller.getFinalX() == x && mScroller.getFinalY() == y){
+ isRunning = false;
+ }
+ mView.postInvalidate();
+ }
+ }
+
+ @Override
+ public void abortAnim() {
+ if (!mScroller.isFinished()){
+ mScroller.abortAnimation();
+ isRunning = false;
+ setTouchPoint(mScroller.getFinalX(),mScroller.getFinalY());
+ mView.postInvalidate();
+ }
+ }
+
+ @Override
+ public Bitmap getBgBitmap() {
+ return mNextBitmap;
+ }
+
+ @Override
+ public Bitmap getNextBitmap() {
+ return mNextBitmap;
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java b/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java
index 239dd5c..16d5281 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java
@@ -4,14 +4,14 @@ package xyz.fycz.myreader.widget.page;
public class BookChapterBean {
//章节所属的小说(网络)
- String bookId;
+ public String bookId;
//章节名
- String title;
+ public String title;
//章节内容在文章中的起始位置(本地)
- long start;
+ public long start;
//章节内容在文章中的终止位置(本地)
- long end;
+ public long end;
public String getBookId() {
return bookId;
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java
index 3efe950..6b700c7 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java
@@ -1,21 +1,30 @@
package xyz.fycz.myreader.widget.page;
+import android.app.Activity;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.*;
+import android.os.Build;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
+
import android.text.TextPaint;
+import android.util.DisplayMetrics;
import android.util.Log;
+import com.gyf.immersionbar.ImmersionBar;
+import com.luhuiguo.chinese.ChineseUtils;
+
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.disposables.Disposable;
import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.enums.Font;
@@ -24,10 +33,16 @@ import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.model.audio.ReadAloudService;
+import xyz.fycz.myreader.util.IOUtils;
+import xyz.fycz.myreader.util.StatusBarUtil;
import xyz.fycz.myreader.util.ToastUtils;
+import xyz.fycz.myreader.util.utils.BitmapUtil;
+import xyz.fycz.myreader.util.utils.MeUtils;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.util.utils.ScreenUtils;
import xyz.fycz.myreader.util.utils.StringUtils;
+import xyz.fycz.myreader.widget.page2.TxtChar;
+import xyz.fycz.myreader.widget.page2.TxtLine;
import java.io.BufferedReader;
import java.io.File;
@@ -39,7 +54,7 @@ import java.util.List;
import static xyz.fycz.myreader.common.APPCONST.*;
/**
- * Created by newbiechen on 17-7-1.
+ * Created by fengyue on 20-11-21
*/
public abstract class PageLoader {
@@ -54,8 +69,8 @@ public abstract class PageLoader {
public static final int STATUS_PARSE_ERROR = 6; // 本地文件解析错误(暂未被使用)
public static final int STATUS_CATEGORY_EMPTY = 7; // 获取到的目录为空
// 默认的显示参数配置
- private static final int DEFAULT_MARGIN_HEIGHT = 28;
- private static final int DEFAULT_MARGIN_WIDTH = 15;
+ public static final int DEFAULT_MARGIN_HEIGHT = 28;
+ public static final int DEFAULT_MARGIN_WIDTH = 15;
private static final int DEFAULT_TIP_SIZE = 12;
private static final int EXTRA_TITLE_SIZE = 4;
private static final int TIP_ALPHA = 180;
@@ -84,7 +99,7 @@ public abstract class PageLoader {
// 绘制提示的画笔
private TextPaint mTipPaint;
// 绘制标题的画笔
- private Paint mTitlePaint;
+ private TextPaint mTitlePaint;
// 绘制背景颜色的画笔(用来擦除需要重绘的部分)
private Paint mBgPaint;
// 绘制小说内容的画笔
@@ -95,7 +110,8 @@ public abstract class PageLoader {
private TxtPage mCancelPage;
// 存储阅读记录类
// private BookRecordBean mBookRecord;
-
+ //缩进
+ String indent;
private Disposable mPreLoadDisp;
/*****************params**************************/
@@ -112,8 +128,6 @@ public abstract class PageLoader {
private PageMode mPageMode;
// 加载器的颜色主题
// private PageStyle mPageStyle;
- //当前是否是日间模式
- private boolean isDayMode;
//书籍绘制区域的宽高
private int mVisibleWidth;
private int mVisibleHeight;
@@ -121,8 +135,10 @@ public abstract class PageLoader {
private int mDisplayWidth;
private int mDisplayHeight;
//间距
- private int mMarginWidth;
- private int mMarginHeight;
+ private int mMarginTop;
+ private int mMarginBottom;
+ private int mMarginLeft;
+ private int mMarginRight;
//字体的颜色
private int mTextColor;
//字体类型
@@ -142,7 +158,6 @@ public abstract class PageLoader {
private int mBatteryLevel;
//当前页面的背景
private int mBgColor;
-
//繁简体
private Language language;
// 当前章
@@ -153,6 +168,12 @@ public abstract class PageLoader {
private boolean resetReadAloud; //是否重新朗读
private int readAloudParagraph = -1; //正在朗读章节
+ private Bitmap bgBitmap;
+
+ public void resetReadAloudParagraph() {
+ readAloudParagraph = -1;
+ }
+
/*****************************init params*******************************/
public PageLoader(PageView pageView, Book collBook, Setting setting) {
mPageView = pageView;
@@ -182,40 +203,72 @@ public abstract class PageLoader {
prepareBook();
}
+ /**
+ * 刷新界面
+ */
+ public void refreshUi() {
+ initData();
+ initPaint();
+ upMargin();
+ }
+
private void initData() {
/*// 获取配置管理器
mSettingManager = SysManager.getSetting();*/
// 获取配置参数
mPageMode = mSettingManager.getPageMode();
- isDayMode = mSettingManager.isDayStyle();
//获取字体
getFont(mSettingManager.getFont());
//获取繁简体
language = mSettingManager.getLanguage();
// mPageStyle = mSettingManager.getPageStyle();
- // 初始化参数
- mMarginWidth = ScreenUtils.dpToPx(DEFAULT_MARGIN_WIDTH);
- mMarginHeight = ScreenUtils.dpToPx(DEFAULT_MARGIN_HEIGHT);
+ indent = StringUtils.repeat(StringUtils.halfToFull(" "), mSettingManager.getIntent());
+
+ initBgBitmap();
// 配置文字有关的参数
- setUpTextParams(mSettingManager.getReadWordSize());
+ setUpTextParams();
}
+ private void initBgBitmap() {
+ if (!mSettingManager.bgIsColor()) {
+ String bgPath = mSettingManager.getBgPath();
+ if (bgPath == null) {
+ return;
+ }
+ Resources resources = MyApplication.getApplication().getResources();
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ int width = dm.widthPixels;
+ int height = dm.heightPixels;
+ try {
+ if (mSettingManager.bgIsAssert()) {
+ bgBitmap = MeUtils.getFitAssetsSampleBitmap(mContext.getAssets(), bgPath, width, height);
+ } else {
+ bgBitmap = BitmapUtil.getFitSampleBitmap(bgPath, width, height);
+ }
+ if (bgBitmap == null) {
+ ToastUtils.showError("背景加载失败,已加载默认背景");
+ bgBitmap = MeUtils.getFitAssetsSampleBitmap(mContext.getAssets(), "bg/p01.jpg", width, height);
+ }
+ } catch (Exception e) {
+ ToastUtils.showError("背景加载失败,已加载默认背景");
+ bgBitmap = MeUtils.getFitAssetsSampleBitmap(mContext.getAssets(), "bg/p01.jpg", width, height);
+ }
+ }
+ }
/**
* 作用:设置与文字相关的参数
- *
- * @param textSize
*/
- private void setUpTextParams(float textSize) {
+ private void setUpTextParams() {
// 文字大小
- mTextSize = textSize * 2;
+ mTextSize = ScreenUtils.spToPx(mSettingManager.getReadWordSize());
mTitleSize = mTextSize + ScreenUtils.spToPx(EXTRA_TITLE_SIZE);
// 行间距(大小为字体的一半)
- mTextInterval = mTextSize / 2;
- mTitleInterval = mTitleSize / 2;
+ mTextInterval = (int) (mTextSize * mSettingManager.getLineMultiplier() / 2);
+ mTitleInterval = (int) (mTitleSize * mSettingManager.getLineMultiplier() / 2);
// 段落间距(大小为字体的高度)
- mTextPara = mTextSize;
- mTitlePara = mTitleSize;
+ mTextPara = (int) (mTextSize * mSettingManager.getParagraphSize());
+ mTitlePara = (int) (mTitleSize * mSettingManager.getParagraphSize());
}
private void initPaint() {
@@ -232,6 +285,9 @@ public abstract class PageLoader {
mTextPaint = new TextPaint();
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mTextPaint.setLetterSpacing(mSettingManager.getTextLetterSpacing());
+ }
mTextPaint.setTypeface(mTypeFace);
mTextPaint.setAntiAlias(true);
@@ -239,14 +295,13 @@ public abstract class PageLoader {
mTitlePaint = new TextPaint();
mTitlePaint.setColor(mTextColor);
mTitlePaint.setTextSize(mTitleSize);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mTitlePaint.setLetterSpacing(mSettingManager.getTextLetterSpacing());
+ }
mTitlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
- mTitlePaint.setTypeface(mTypeFace);
+ mTitlePaint.setTypeface(Typeface.create(mTypeFace, Typeface.BOLD));
mTitlePaint.setAntiAlias(true);
- // 绘制背景的画笔
- mBgPaint = new Paint();
- mBgPaint.setColor(mBgColor);
-
// 绘制电池的画笔
mBatteryPaint = new TextPaint();
mBatteryPaint.setAntiAlias(true);
@@ -255,7 +310,7 @@ public abstract class PageLoader {
mBatteryPaint.setTypeface(Typeface.createFromAsset(mContext.getAssets(), "fonts/number.ttf"));
// 初始化页面样式
- setPageStyle(isDayMode);
+ setPageStyle();
}
private void initPageView() {
@@ -358,6 +413,7 @@ public abstract class PageLoader {
public boolean skipToNextPage() {
return mPageView.autoNextPage();
}
+
/**
* 翻到下一页,无动画
*/
@@ -369,12 +425,24 @@ public abstract class PageLoader {
skipNextChapter();
}
+ /**
+ * 翻到上一页,无动画
+ */
+ void noAnimationToPrePage() {
+ if (getPagePos() > 0) {
+ skipToPage(getPagePos() - 1);
+ return;
+ }
+ skipPreChapter();
+ skipToPage(mCurChapter.getPageSize() - 1);
+ }
+
/**
* 更新时间
*/
public void updateTime() {
- if (!mPageView.isRunning()) {
- mPageView.drawCurPage(true);
+ if (!mPageView.isRunning() && !mSettingManager.isShowStatusBar()) {
+ mPageView.drawCurPage(mPageMode == PageMode.SCROLL);
}
}
@@ -384,9 +452,9 @@ public abstract class PageLoader {
* @param level
*/
public void updateBattery(int level) {
- mBatteryLevel = level;
- if (!mPageView.isRunning()) {
- mPageView.drawCurPage(true);
+ if (!mPageView.isRunning() && !mSettingManager.isShowStatusBar()) {
+ mBatteryLevel = level;
+ mPageView.drawCurPage(mPageMode == PageMode.SCROLL);
}
}
@@ -404,38 +472,12 @@ public abstract class PageLoader {
/**
* 设置文字相关参数
- *
- * @param textSize
*/
- public void setTextSize(int textSize) {
+ public void setTextSize() {
// 设置文字相关参数
- setUpTextParams(textSize);
-
- // 设置画笔的字体大小
- mTextPaint.setTextSize(mTextSize);
- // 设置标题的字体大小
- mTitlePaint.setTextSize(mTitleSize);
- /*// 存储文字大小
- mSettingManager.setReadWordSize(mTextSize);*/
- // 取消缓存
- mPreChapter = null;
- mNextChapter = null;
-
- // 如果当前已经显示数据
- if (isChapterListPrepare && mStatus == STATUS_FINISH) {
- // 重新计算当前页面
- dealLoadPageList(mCurChapterPos);
-
- // 防止在最后一页,通过修改字体,以至于页面数减少导致崩溃的问题
- if (mCurPage.position >= mCurChapter.getPageSize()) {
- mCurPage.position = mCurChapter.getPageSize() - 1;
- }
-
- // 重新获取指定页面
- mCurPage = mCurChapter.getPage(mCurPage.position);
- }
-
- mPageView.drawCurPage(false);
+ setUpTextParams();
+ initPaint();
+ refreshPagePara();
}
/**
@@ -444,7 +486,6 @@ public abstract class PageLoader {
* @param dayMode
*/
public void setNightMode(boolean dayMode) {
- isDayMode = dayMode;
if (!dayMode) {
mBatteryPaint.setColor(Color.WHITE);
} else {
@@ -455,57 +496,21 @@ public abstract class PageLoader {
/**
* 设置页面样式
*/
- public void setPageStyle(boolean dayMode) {
- /*if (pageStyle != PageStyle.NIGHT) {
- mPageStyle = pageStyle;
- mSettingManager.setPageStyle(pageStyle);
- }
-
- if (isNightMode && pageStyle != PageStyle.NIGHT) {
- return;
- }
-*/
+ public void setPageStyle() {
int textColorId;
int bgColorId;
- switch (mSettingManager.getReadStyle()) {
- case common:
- textColorId = READ_STYLE_COMMON[0];
- bgColorId = READ_STYLE_COMMON[1];
- break;
- case leather:
- default:
- textColorId = READ_STYLE_LEATHER[0];
- bgColorId = READ_STYLE_LEATHER[1];
- break;
- case protectedEye:
- textColorId = READ_STYLE_PROTECTED_EYE[0];
- bgColorId = READ_STYLE_PROTECTED_EYE[1];
- break;
- case breen:
- textColorId = READ_STYLE_BREEN_EYE[0];
- bgColorId = READ_STYLE_BREEN_EYE[1];
- break;
- case blueDeep:
- textColorId = READ_STYLE_BLUE_DEEP[0];
- bgColorId = READ_STYLE_BLUE_DEEP[1];
- break;
- }
- if (!dayMode) {
- mTextColor = ContextCompat.getColor(mContext, READ_STYLE_NIGHT[0]);
- mBgColor = ContextCompat.getColor(mContext, READ_STYLE_NIGHT[1]);
- mBatteryPaint.setColor(mTextColor);
- } else {
- mTextColor = ContextCompat.getColor(mContext, textColorId);
- mBgColor = ContextCompat.getColor(mContext, bgColorId);
- mBatteryPaint.setColor(Color.BLACK);
- }
- // 设置当前颜色样式
+ textColorId = mSettingManager.getTextColor();
+ bgColorId = mSettingManager.getBgColor();
+
+ mTextColor = textColorId;
+ mBgColor = bgColorId;
+ mBatteryPaint.setColor(Color.BLACK);
+ // 设置当前颜色样式
mTipPaint.setColor(mTextColor);
mTitlePaint.setColor(mTextColor);
mTextPaint.setColor(mTextColor);
mBatteryPaint.setColor(mTextColor);
- mBgPaint.setColor(mBgColor);
mBatteryPaint.setAlpha(TIP_ALPHA);
@@ -522,7 +527,6 @@ public abstract class PageLoader {
mPageMode = pageMode;
mPageView.setPageMode(mPageMode);
- mSettingManager.setPageMode(mPageMode);
// 重新绘制当前页
mPageView.drawCurPage(false);
@@ -540,6 +544,10 @@ public abstract class PageLoader {
mTipPaint.setTypeface(mTypeFace);
mTextPaint.setTypeface(mTypeFace);
mTitlePaint.setTypeface(mTypeFace);
+ refreshPagePara();
+ }
+
+ private void refreshPagePara() {
// 取消缓存
mPreChapter = null;
mNextChapter = null;
@@ -567,7 +575,7 @@ public abstract class PageLoader {
* @param font
*/
public void getFont(Font font) {
- String fontFileName = mSettingManager.getFont().fileName;
+ String fontFileName = mSettingManager.getFont().toString() + ".ttf";
if (font == Font.本地字体) {
fontFileName = mSettingManager.getLocalFontName();
}
@@ -601,21 +609,10 @@ public abstract class PageLoader {
}
/**
- * 设置内容与屏幕的间距
- *
- * @param marginWidth :单位为 px
- * @param marginHeight :单位为 px
+ * 设置内容与屏幕的间距 单位为 px
*/
- public void setMargin(int marginWidth, int marginHeight) {
- mMarginWidth = marginWidth;
- mMarginHeight = marginHeight;
-
- // 如果是滑动动画,则需要重新创建了
- if (mPageMode == PageMode.SCROLL) {
- mPageView.setPageMode(PageMode.SCROLL);
- }
-
- mPageView.drawCurPage(false);
+ public void upMargin() {
+ prepareDisplay(mDisplayWidth, mDisplayHeight);
}
/**
@@ -693,36 +690,22 @@ public abstract class PageLoader {
}
/**
- * 获取距离屏幕的高度
+ * 获取距离屏幕的上高度
*
* @return
*/
- public int getMarginHeight() {
- return mMarginHeight;
+ public int getMarginTop() {
+ return mMarginTop;
}
/**
- * 保存阅读记录
+ * 获取距离屏幕的上高度
+ *
+ * @return
*/
- /*public void saveRecord() {
-
- if (mChapterList.isEmpty()) {
- return;
- }
-
- mBookRecord.setBookId(mCollBook.get_id());
- mBookRecord.setChapter(mCurChapterPos);
-
- if (mCurPage != null) {
- mBookRecord.setPagePos(mCurPage.position);
- } else {
- mBookRecord.setPagePos(0);
- }
-
- //存储到数据库
- BookRepository.getInstance()
- .saveBookRecord(mBookRecord);
- }*/
+ public int getMarginBottom() {
+ return mMarginBottom;
+ }
/**
* 初始化书籍
@@ -872,7 +855,7 @@ public abstract class PageLoader {
/***********************************default method***********************************************/
void drawPage(Bitmap bitmap, boolean isUpdate) {
- drawBackground(mPageView.getBgBitmap(), isUpdate);
+ drawBackground(mPageView.getBgBitmap());
if (!isUpdate) {
drawContent(bitmap);
}
@@ -880,15 +863,36 @@ public abstract class PageLoader {
mPageView.invalidate();
}
- private void drawBackground(Bitmap bitmap, boolean isUpdate) {
+ /**
+ * 横翻模式绘制背景
+ */
+ private synchronized void drawBackground(Bitmap bitmap) {
+ if (bitmap == null) return;
Canvas canvas = new Canvas(bitmap);
+ if (!mSettingManager.bgIsColor()) {
+ if (bgBitmap == null || bgBitmap.isRecycled()) {
+ initBgBitmap();
+ }
+ Rect mDestRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ canvas.drawBitmap(bgBitmap, null, mDestRect, null);
+ } else {
+ canvas.drawColor(mBgColor);
+ }
+ drawBackground(canvas);
+ }
+
+ public Bitmap getBgBitmap() {
+ initBgBitmap();
+ return bgBitmap;
+ }
+
+ private void drawBackground(Canvas canvas) {
+ if (canvas == null) return;
int tipMarginHeight = ScreenUtils.dpToPx(3);
String progress = (mStatus != STATUS_FINISH) ? ""
: getReadProgress(getChapterPos(), mChapterList.size(), getPagePos(), getAllPagePos());
- if (!isUpdate) {
- /****绘制背景****/
- canvas.drawColor(mBgColor);
-
+ /****绘制背景****/
+ if (!mSettingManager.isShowStatusBar()) {
if (!mChapterList.isEmpty()) {
/*****初始化标题的参数********/
//需要注意的是:绘制text的y的起始点是text的基准线的位置,而不是从text的头部的位置
@@ -897,81 +901,95 @@ public abstract class PageLoader {
if (mStatus != STATUS_FINISH) {
if (isChapterListPrepare) {
canvas.drawText(mChapterList.get(mCurChapterPos).getTitle()
- , mMarginWidth, tipTop, mTipPaint);
+ , mMarginLeft, tipTop, mTipPaint);
}
} else {
- String title = TextUtils.ellipsize(mCurPage.title, mTipPaint, mDisplayWidth - 2 * mMarginWidth - mTipPaint.measureText(progress), TextUtils.TruncateAt.END).toString();
- canvas.drawText(title, mMarginWidth, tipTop, mTipPaint);
+ String title = TextUtils.ellipsize(mCurPage.title, mTipPaint, mDisplayWidth - mMarginLeft - mMarginRight - mTipPaint.measureText(progress), TextUtils.TruncateAt.END).toString();
+ canvas.drawText(title, mMarginLeft, tipTop, mTipPaint);
+ /******绘制页码********/
+ // 底部的字显示的位置Y
+ float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight;
+ String percent = (mCurPage.position + 1) + "/" + mCurChapter.getPageSize();
+ canvas.drawText(percent, mMarginLeft, y, mTipPaint);
}
-
/*******绘制进度*******/
- float progressTipLeft = mDisplayWidth - mMarginWidth - mTipPaint.measureText(progress);
+ float progressTipLeft = mDisplayWidth - mMarginRight - mTipPaint.measureText(progress);
canvas.drawText(progress, progressTipLeft, tipTop, mTipPaint);
-
- /******绘制页码********/
- // 底部的字显示的位置Y
- float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight;
- // 只有finish的时候采用页码
- if (mStatus == STATUS_FINISH) {
- String percent = (mCurPage.position + 1) + "/" + mCurChapter.getPageSize();
- canvas.drawText(percent, mMarginWidth, y, mTipPaint);
+ }
+ } else {
+ float tipBottom = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight;
+ //根据状态不一样,数据不一样
+ if (mStatus != STATUS_FINISH) {
+ if (isChapterListPrepare) {
+ canvas.drawText(mChapterList.get(mCurChapterPos).getTitle()
+ , mMarginLeft, tipBottom, mTipPaint);
}
+ } else {
+ /******绘制页码********/
+ String percent = (mCurPage.position + 1) + "/" + mCurChapter.getPageSize();
+ //页码的x坐标
+ float tipLeft = mDisplayWidth - 2 * mMarginRight - mTipPaint.measureText(percent + progress);
+ canvas.drawText(percent, tipLeft, tipBottom, mTipPaint);
+ String title = TextUtils.ellipsize(mCurPage.title, mTipPaint, tipLeft - 2 * mMarginRight, TextUtils.TruncateAt.END).toString();
+ canvas.drawText(title, mMarginLeft, tipBottom, mTipPaint);
}
- } else {
- //擦除区域
- mBgPaint.setColor(mBgColor);
- canvas.drawRect(mDisplayWidth / 2, mDisplayHeight - mMarginHeight + ScreenUtils.dpToPx(2), mDisplayWidth, mDisplayHeight, mBgPaint);
+ /*******绘制进度*******/
+ float progressTipLeft = mDisplayWidth - mMarginRight - mTipPaint.measureText(progress);
+ canvas.drawText(progress, progressTipLeft, tipBottom, mTipPaint);
}
- /******绘制电池********/
- int visibleRight = mDisplayWidth - mMarginWidth;
- int visibleBottom = mDisplayHeight - tipMarginHeight;
+ if (!mSettingManager.isShowStatusBar()) {
+ /******绘制电池********/
+
+ int visibleRight = mDisplayWidth - mMarginRight;
+ int visibleBottom = mDisplayHeight - tipMarginHeight;
- int outFrameWidth = (int) mTipPaint.measureText("xxx");
- int outFrameHeight = (int) mTipPaint.getTextSize();
+ int outFrameWidth = (int) mTipPaint.measureText("xxx");
+ int outFrameHeight = (int) mTipPaint.getTextSize();
- int polarHeight = ScreenUtils.dpToPx(6);
- int polarWidth = ScreenUtils.dpToPx(2);
- int border = 1;
- int innerMargin = 1;
+ int polarHeight = ScreenUtils.dpToPx(6);
+ int polarWidth = ScreenUtils.dpToPx(2);
+ int border = 1;
+ int innerMargin = 1;
- //电极的制作
- int polarLeft = visibleRight - polarWidth;
- int polarTop = visibleBottom - (outFrameHeight + polarHeight) / 2;
- Rect polar = new Rect(polarLeft, polarTop, visibleRight,
- polarTop + polarHeight - ScreenUtils.dpToPx(2));
+ //电极的制作
+ int polarLeft = visibleRight - polarWidth;
+ int polarTop = visibleBottom - (outFrameHeight + polarHeight) / 2;
+ Rect polar = new Rect(polarLeft, polarTop, visibleRight,
+ polarTop + polarHeight - ScreenUtils.dpToPx(2));
- mBatteryPaint.setStyle(Paint.Style.FILL);
- canvas.drawRect(polar, mBatteryPaint);
+ mBatteryPaint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(polar, mBatteryPaint);
- //外框的制作
- int outFrameLeft = polarLeft - outFrameWidth;
- int outFrameTop = visibleBottom - outFrameHeight;
- int outFrameBottom = visibleBottom - ScreenUtils.dpToPx(2);
- Rect outFrame = new Rect(outFrameLeft, outFrameTop, polarLeft, outFrameBottom);
+ //外框的制作
+ int outFrameLeft = polarLeft - outFrameWidth;
+ int outFrameTop = visibleBottom - outFrameHeight;
+ int outFrameBottom = visibleBottom - ScreenUtils.dpToPx(2);
+ Rect outFrame = new Rect(outFrameLeft, outFrameTop, polarLeft, outFrameBottom);
- mBatteryPaint.setStyle(Paint.Style.STROKE);
- mBatteryPaint.setStrokeWidth(border);
- canvas.drawRect(outFrame, mBatteryPaint);
+ mBatteryPaint.setStyle(Paint.Style.STROKE);
+ mBatteryPaint.setStrokeWidth(border);
+ canvas.drawRect(outFrame, mBatteryPaint);
- //绘制电量
- mBatteryPaint.setStyle(Paint.Style.FILL);
- Paint.FontMetrics fontMetrics = mBatteryPaint.getFontMetrics();
- String batteryLevel = String.valueOf(mBatteryLevel);
- float batTextLeft = outFrameLeft + (outFrameWidth - mBatteryPaint.measureText(batteryLevel)) / 2 - ScreenUtils.dpToPx(1) / 2f;
- float batTextBaseLine = visibleBottom - outFrameHeight / 2f - fontMetrics.top / 2 - fontMetrics.bottom / 2 - ScreenUtils.dpToPx(1);
- canvas.drawText(batteryLevel, batTextLeft, batTextBaseLine, mBatteryPaint);
+ //绘制电量
+ mBatteryPaint.setStyle(Paint.Style.FILL);
+ Paint.FontMetrics fontMetrics = mBatteryPaint.getFontMetrics();
+ String batteryLevel = String.valueOf(mBatteryLevel);
+ float batTextLeft = outFrameLeft + (outFrameWidth - mBatteryPaint.measureText(batteryLevel)) / 2 - ScreenUtils.dpToPx(1) / 2f;
+ float batTextBaseLine = visibleBottom - outFrameHeight / 2f - fontMetrics.top / 2 - fontMetrics.bottom / 2 - ScreenUtils.dpToPx(1);
+ canvas.drawText(batteryLevel, batTextLeft, batTextBaseLine, mBatteryPaint);
- /******绘制当前时间********/
- //底部的字显示的位置Y
- float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight;
- String time = StringUtils.dateConvert(System.currentTimeMillis(), "HH:mm");
- float x = (mDisplayWidth - mTipPaint.measureText(time)) / 2;
- canvas.drawText(time, x, y, mTipPaint);
+ /******绘制当前时间********/
+ //底部的字显示的位置Y
+ float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight;
+ String time = StringUtils.dateConvert(System.currentTimeMillis(), "HH:mm");
+ float x = (mDisplayWidth - mTipPaint.measureText(time)) / 2;
+ canvas.drawText(time, x, y, mTipPaint);
+ }
}
private void drawContent(Bitmap bitmap) {
@@ -1015,11 +1033,13 @@ public abstract class PageLoader {
canvas.drawText(tip, pivotX, pivotY, mTextPaint);
} else {
float top;
-
if (mPageMode == PageMode.SCROLL) {
top = -mTextPaint.getFontMetrics().top;
} else {
- top = mMarginHeight - mTextPaint.getFontMetrics().top;
+ top = mMarginTop - mTextPaint.getFontMetrics().top;
+ if (mSettingManager.isShowStatusBar()) {
+ top += ImmersionBar.getStatusBarHeight((Activity) mContext);
+ }
}
//设置总距离
@@ -1065,7 +1085,17 @@ public abstract class PageLoader {
int paragraphLength = mCurPage.position == 0 ? strLength + titleLen : mCurChapter.getPageLength(mCurPage.position - 1) + strLength;
isLight = ReadAloudService.running && readAloudParagraph == mCurChapter.getParagraphIndex(paragraphLength);
mTextPaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : mTextColor);
- canvas.drawText(str, mMarginWidth, top, mTextPaint);
+ if (!mSettingManager.isTightCom()) {
+ Layout tempLayout = new StaticLayout(str, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ float width = StaticLayout.getDesiredWidth(str, tempLayout.getLineStart(0), tempLayout.getLineEnd(0), mTextPaint);
+ if (needScale(str)) {
+ drawScaledText(canvas, str, width, mTextPaint, top, i, mCurPage.txtLists);
+ } else {
+ canvas.drawText(str, mMarginLeft, top, mTextPaint);
+ }
+ } else {
+ canvas.drawText(str, mMarginLeft, top, mTextPaint);
+ }
if (str.endsWith("\n")) {
top += para;
} else {
@@ -1075,18 +1105,78 @@ public abstract class PageLoader {
}
}
+ private void drawScaledText(Canvas canvas, String line, float lineWidth, TextPaint paint, float top, int y, List txtLists) {
+ float x = mMarginLeft;
+
+ if (isFirstLineOfParagraph(line)) {
+ canvas.drawText(indent, x, top, paint);
+ float bw = StaticLayout.getDesiredWidth(indent, paint);
+ x += bw;
+ line = line.substring(mSettingManager.getIntent());
+ }
+ int gapCount = line.length() - 1;
+ int i = 0;
+
+ TxtLine txtList = new TxtLine();//每一行pzl
+ txtList.setCharsData(new ArrayList<>());//pzl
+
+ float d = ((mDisplayWidth - (mMarginLeft + mMarginRight)) - lineWidth) / gapCount;
+ for (; i < line.length(); i++) {
+ String c = String.valueOf(line.charAt(i));
+ float cw = StaticLayout.getDesiredWidth(c, paint);
+ canvas.drawText(c, x, top, paint);
+ //pzl
+ TxtChar txtChar = new TxtChar();
+ txtChar.setChardata(line.charAt(i));
+ if (i == 0) txtChar.setCharWidth(cw + d / 2);
+ if (i == gapCount) txtChar.setCharWidth(cw + d / 2);
+ txtChar.setCharWidth(cw + d);
+ ;//字宽
+ //txtChar.Index = y;//每页每个字的位置
+ txtList.getCharsData().add(txtChar);
+ //pzl
+ x += cw + d;
+ }
+ if (txtLists != null) {
+ txtLists.set(y, txtList);//pzl
+ }
+ }
+
+ //判断是不是d'hou
+ private boolean isFirstLineOfParagraph(String line) {
+ return line.length() > 3 && line.charAt(0) == (char) 12288 && line.charAt(1) == (char) 12288;
+ }
+
+ private boolean needScale(String line) {//判断不是空行
+ return line != null && line.length() != 0 && line.charAt(line.length() - 1) != '\n';
+ }
+
void prepareDisplay(int w, int h) {
// 获取PageView的宽高
mDisplayWidth = w;
mDisplayHeight = h;
+ // 设置边距
+ mMarginTop = mSettingManager.isShowStatusBar() ?
+ ScreenUtils.dpToPx(mSettingManager.getPaddingTop() + DEFAULT_MARGIN_HEIGHT - 8) :
+ ScreenUtils.dpToPx(mSettingManager.getPaddingTop() + DEFAULT_MARGIN_HEIGHT);
+ mMarginBottom = ScreenUtils.dpToPx(mSettingManager.getPaddingBottom() + DEFAULT_MARGIN_HEIGHT);
+ mMarginLeft = ScreenUtils.dpToPx(mSettingManager.getPaddingLeft());
+ mMarginRight = ScreenUtils.dpToPx(mSettingManager.getPaddingRight());
+
// 获取内容显示位置的大小
- mVisibleWidth = mDisplayWidth - mMarginWidth * 2;
- mVisibleHeight = mDisplayHeight - mMarginHeight * 2;
+ mVisibleWidth = mDisplayWidth - (mMarginLeft + mMarginRight);
+ mVisibleHeight = !mSettingManager.isShowStatusBar() ? mDisplayHeight - (mMarginTop + mMarginBottom)
+ : mDisplayHeight - (mMarginTop + mMarginBottom) - mPageView.getStatusBarHeight();
// 重置 PageMode
mPageView.setPageMode(mPageMode);
+
+ // 取消缓存
+ mPreChapter = null;
+ mNextChapter = null;
+
if (!isChapterOpen) {
// 展示加载界面
mPageView.drawCurPage(false);
@@ -1113,6 +1203,7 @@ public abstract class PageLoader {
* @return
*/
boolean prev() {
+
// 以下情况禁止翻页
if (!canTurnPage()) {
return false;
@@ -1407,6 +1498,7 @@ public abstract class PageLoader {
TxtChapter txtChapter = new TxtChapter(chapter.getNumber());
//使用流的方式加载
List lines = new ArrayList<>();
+ List txtLists = new ArrayList<>();//记录每个字的位置 //pzl
float rHeight = mVisibleHeight;
int titleLinesCount = 0;
boolean showTitle = true; // 是否展示标题
@@ -1414,12 +1506,17 @@ public abstract class PageLoader {
paragraph = paragraph.trim() + "\n";
try {
while (showTitle || (paragraph = br.readLine()) != null) {
+ if (mSettingManager.getLanguage() == Language.traditional) {
+ paragraph = ChineseUtils.toTraditional(paragraph);
+ } else if (mSettingManager.getLanguage() == Language.simplified) {
+ paragraph = ChineseUtils.toSimplified(paragraph);
+ }
// 重置段落
if (!showTitle) {
paragraph = paragraph.replaceAll("\\s", "").trim();
// 如果只有换行符,那么就不执行
if (paragraph.equals("")) continue;
- paragraph = StringUtils.halfToFull(" " + paragraph + "\n");
+ paragraph = indent + paragraph + "\n";
} else {
//设置 title 的顶部间距
rHeight -= mTitlePara;
@@ -1441,33 +1538,65 @@ public abstract class PageLoader {
page.position = txtChapter.getTxtPageList().size();
page.title = chapter.getTitle();
page.lines = new ArrayList<>(lines);
+ page.txtLists = new ArrayList<>(txtLists);
page.titleLines = titleLinesCount;
txtChapter.addPage(page);
addTxtPageLength(txtChapter, page.getContent().length());
// 重置Lines
lines.clear();
+ txtLists.clear();//pzl
rHeight = mVisibleHeight;
titleLinesCount = 0;
continue;
}
//测量一行占用的字节数
- if (showTitle) {
- wordCount = mTitlePaint.breakText(paragraph,
- true, mVisibleWidth, null);
+ if (mSettingManager.isTightCom()) {
+ if (showTitle) {
+ wordCount = mTitlePaint.breakText(paragraph,
+ true, mVisibleWidth, null);
+ } else {
+ wordCount = mTextPaint.breakText(paragraph,
+ true, mVisibleWidth, null);
+ }
+
+ subStr = paragraph.substring(0, wordCount);
+ if (paragraph.substring(wordCount).equals("\n")) {
+ subStr += "\n";
+ }
} else {
- wordCount = mTextPaint.breakText(paragraph,
- true, mVisibleWidth, null);
+ Layout tempLayout;
+ if (showTitle) {
+ tempLayout = new StaticLayout(paragraph, mTitlePaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ } else {
+ tempLayout = new StaticLayout(paragraph, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ }
+ wordCount = tempLayout.getLineEnd(0);
+ subStr = paragraph.substring(0, wordCount);
}
- subStr = paragraph.substring(0, wordCount);
- if (paragraph.substring(wordCount).equals("\n")) {
- subStr += "\n";
- }
if (!subStr.equals("\n")) {
//将一行字节,存储到lines中
lines.add(subStr);
-
+ //begin pzl
+ //记录每个字的位置
+ char[] cs = subStr.toCharArray();
+ TxtLine txtList = new TxtLine();//每一行
+ txtList.setCharsData(new ArrayList());
+ for (char c : cs) {
+ String mesasrustr = String.valueOf(c);
+ float charwidth = mTextPaint.measureText(mesasrustr);
+ if (showTitle) {
+ charwidth = mTitlePaint.measureText(mesasrustr);
+ }
+ TxtChar txtChar = new TxtChar();
+ txtChar.setChardata(c);
+ txtChar.setCharWidth(charwidth);//字宽
+ txtChar.setIndex(66);//每页每个字的位置
+ txtList.getCharsData().add(txtChar);
+ }
+ txtLists.add(txtList);
+ //end pzl
//设置段落间距
if (showTitle) {
titleLinesCount += 1;
@@ -1497,11 +1626,13 @@ public abstract class PageLoader {
page.position = txtChapter.getTxtPageList().size();
page.title = chapter.getTitle();
page.lines = new ArrayList<>(lines);
+ page.txtLists = new ArrayList<>(txtLists);
page.titleLines = titleLinesCount;
txtChapter.addPage(page);
addTxtPageLength(txtChapter, page.getContent().length());
//重置Lines
lines.clear();
+ txtLists.clear();
}
} catch (IOException e) {
e.printStackTrace();
@@ -1517,8 +1648,10 @@ public abstract class PageLoader {
return txtChapter;
}
+
/**
* 添加TxtChapter的长度
+ *
* @param txtChapter
* @param length
*/
@@ -1532,6 +1665,7 @@ public abstract class PageLoader {
/**
* 添加TxtChapter段落长度
+ *
* @param txtChapter
* @param length
*/
@@ -1563,7 +1697,7 @@ public abstract class PageLoader {
return null;
}
if (mPageChangeListener != null) {
- mPageChangeListener.onPageChange(pos,resetReadAloud);
+ mPageChangeListener.onPageChange(pos, resetReadAloud);
resetReadAloud = true;
}
return mCurChapter.getPage(pos);
@@ -1658,6 +1792,7 @@ public abstract class PageLoader {
}
return strLength;
}
+
/**
* @return 本页内容
*/
@@ -1748,6 +1883,7 @@ public abstract class PageLoader {
mPageView.invalidate();
}
}
+
/*****************************************interface*****************************************/
public interface OnPageChangeListener {
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java b/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java
index b5ca9ef..5d55685 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java
@@ -1,10 +1,16 @@
package xyz.fycz.myreader.widget.page;
+import xyz.fycz.myreader.enums.BookSource;
+
/**
* Created by newbiechen on 2018/2/5.
* 作用:翻页动画的模式
*/
public enum PageMode {
- SIMULATION, COVER, SLIDE, NONE, SCROLL
+ SIMULATION, COVER, SLIDE, NONE, SCROLL, VERTICAL_COVER, AUTO;
+
+ public static PageMode fromString(String string) {
+ return valueOf(string);
+ }
}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java b/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java
index f9142c8..d467c73 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java
@@ -1,16 +1,23 @@
package xyz.fycz.myreader.widget.page;
+import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
+
+import com.gyf.immersionbar.ImmersionBar;
+
+import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.service.ChapterService;
+import xyz.fycz.myreader.ui.popmenu.AutoPageMenu;
import xyz.fycz.myreader.util.utils.SnackbarUtils;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import xyz.fycz.myreader.widget.animation.*;
@@ -26,13 +33,14 @@ public class PageView extends View {
private int mViewWidth = 0; // 当前View的宽
private int mViewHeight = 0; // 当前View的高
+ private int statusBarHeight = 0; //状态栏高度
private int mStartX = 0;
private int mStartY = 0;
private boolean isMove = false;
// 初始化参数
private int mBgColor = 0xFFCEC29C;
- private PageMode mPageMode = PageMode.SIMULATION;
+ private PageMode mPageMode = PageMode.COVER;
// 是否允许点击
private boolean canTouch = true;
// 唤醒菜单的区域
@@ -73,6 +81,7 @@ public class PageView extends View {
public PageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ statusBarHeight = ImmersionBar.getStatusBarHeight((Activity) getContext());
}
@Override
@@ -104,12 +113,18 @@ public class PageView extends View {
case SLIDE:
mPageAnim = new SlidePageAnim(mViewWidth, mViewHeight, this, mPageAnimListener);
break;
+ case VERTICAL_COVER:
+ mPageAnim = new CoverVerticalPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener);
+ break;
case NONE:
mPageAnim = new NonePageAnim(mViewWidth, mViewHeight, this, mPageAnimListener);
break;
case SCROLL:
mPageAnim = new ScrollPageAnim(mViewWidth, mViewHeight, 0,
- mPageLoader.getMarginHeight(), this, mPageAnimListener);
+ mPageLoader.getMarginTop(), mPageLoader.getMarginBottom(),this, mPageAnimListener);
+ break;
+ case AUTO:
+ mPageAnim = new AutoPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener);
break;
default:
mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener);
@@ -121,6 +136,10 @@ public class PageView extends View {
return mPageAnim.getNextBitmap();
}
+ public int getStatusBarHeight() {
+ return statusBarHeight;
+ }
+
public Bitmap getBgBitmap() {
if (mPageAnim == null) return null;
return mPageAnim.getBgBitmap();
@@ -145,6 +164,12 @@ public class PageView extends View {
}
}
+ public void autoPageOnSpeedChange() {
+ //是否正在执行动画
+ abortAnimation();
+ mPageLoader.noAnimationToPrePage();
+ }
+
private void startPageAnim(PageAnimation.Direction direction) {
if (mTouchListener == null) return;
//是否正在执行动画
@@ -166,6 +191,10 @@ public class PageView extends View {
} else {
int x = 0;
int y = mViewHeight;
+ if (mPageAnim instanceof VerticalPageAnim) {
+ x = mViewWidth;
+ y = 0;
+ }
//初始化动画
mPageAnim.setStartPoint(x, y);
//设置点击点
@@ -187,10 +216,6 @@ public class PageView extends View {
@Override
protected void onDraw(Canvas canvas) {
-
- //绘制背景
- canvas.drawColor(mBgColor);
-
//绘制动画
mPageAnim.draw(canvas);
}
@@ -253,7 +278,7 @@ public class PageView extends View {
private boolean hasPrevPage() {
mTouchListener.prePage();
boolean hasPrevPage = mPageLoader.prev();
- if (!hasPrevPage){
+ if (!hasPrevPage) {
showSnackBar("已经是第一页了");
}
return hasPrevPage;
@@ -267,7 +292,7 @@ public class PageView extends View {
private boolean hasNextPage() {
boolean hasNextPage = mPageLoader.next();
mTouchListener.nextPage(hasNextPage);
- if (!hasNextPage){
+ if (!hasNextPage) {
showSnackBar("已经是最后一页了");
}
return hasNextPage;
@@ -320,6 +345,10 @@ public class PageView extends View {
if (mPageAnim instanceof HorizonPageAnim) {
((HorizonPageAnim) mPageAnim).changePage();
+ }else if (mPageAnim instanceof VerticalPageAnim){
+ ((VerticalPageAnim) mPageAnim).changePage();
+ } else if (mPageAnim instanceof AutoPageAnim) {
+ ((AutoPageAnim) mPageAnim).changePage();
}
mPageLoader.drawPage(getNextBitmap(), false);
}
@@ -332,7 +361,7 @@ public class PageView extends View {
public void drawCurPage(boolean isUpdate) {
if (!isPrepare) return;
- if (!isUpdate){
+ if (!isUpdate) {
if (mPageAnim instanceof ScrollPageAnim) {
((ScrollPageAnim) mPageAnim).resetBitmap();
}
@@ -363,9 +392,9 @@ public class PageView extends View {
return mPageLoader;
}
// 获取具体的加载器
- if ("本地书籍".equals(collBook.getType())){
+ if ("本地书籍".equals(collBook.getType())) {
mPageLoader = new LocalPageLoader(this, collBook, ChapterService.getInstance(), setting);
- }else {
+ } else {
mPageLoader = new NetPageLoader(this, collBook, ChapterService.getInstance(), mReadCrawler, setting);
}
// 判断是否 PageView 已经初始化完成
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java b/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java
index 85b7e7f..630a418 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java
@@ -2,12 +2,21 @@ package xyz.fycz.myreader.widget.page;
import java.util.List;
+import xyz.fycz.myreader.widget.page2.TxtLine;
+
public class TxtPage {
int position;
String title;
int titleLines; //当前 lines 中为 title 的行数。
List lines;
+ List txtLists;
+ public TxtPage(int position) {
+ this.position = position;
+ }
+
+ public TxtPage() {
+ }
public int getTitleLines() {
return titleLines;
@@ -30,4 +39,5 @@ public class TxtPage {
}
return s.toString();
}
+
}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/ChapterProvider.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/ChapterProvider.java
new file mode 100644
index 0000000..b4b6528
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/ChapterProvider.java
@@ -0,0 +1,213 @@
+package xyz.fycz.myreader.widget.page2;
+
+import android.text.Layout;
+import android.text.StaticLayout;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.util.help.ChapterContentHelp;
+import xyz.fycz.myreader.util.utils.NetworkUtils;
+
+class ChapterProvider {
+ private PageLoader pageLoader;
+ private ChapterContentHelp contentHelper = new ChapterContentHelp();
+
+ ChapterProvider(PageLoader pageLoader) {
+ this.pageLoader = pageLoader;
+ }
+
+ TxtChapter dealLoadPageList(Chapter chapter, boolean isPrepare) {
+ TxtChapter txtChapter = new TxtChapter(chapter.getNumber());
+ // 判断章节是否存在
+ if (!isPrepare || pageLoader.noChapterData(chapter)) {
+ if (pageLoader instanceof PageLoaderNet && !NetworkUtils.isNetWorkAvailable()) {
+ txtChapter.setStatus(TxtChapter.Status.ERROR);
+ txtChapter.setMsg("网络连接不可用");
+ }
+ return txtChapter;
+ }
+ String content;
+ try {
+ content = pageLoader.getChapterContent(chapter);
+ } catch (Exception e) {
+ txtChapter.setStatus(TxtChapter.Status.ERROR);
+ txtChapter.setMsg("读取内容出错\n" + e.getLocalizedMessage());
+ return txtChapter;
+ }
+ if (content == null) {
+ txtChapter.setStatus(TxtChapter.Status.ERROR);
+ txtChapter.setMsg("缓存文件不存在");
+ return txtChapter;
+ }
+ return loadPageList(chapter, content);
+ }
+
+ /**
+ * 将章节数据,解析成页面列表
+ *
+ * @param chapter:章节信息
+ * @param content:章节的文本
+ */
+ private TxtChapter loadPageList(Chapter chapter, @NonNull String content) {
+ //生成的页面
+ TxtChapter txtChapter = new TxtChapter(chapter.getNumber());
+ /*if (pageLoader.book.isAudio()) {
+ txtChapter.setStatus(TxtChapter.Status.FINISH);
+ txtChapter.setMsg(content);
+ TxtPage page = new TxtPage(txtChapter.getTxtPageList().size());
+ page.setTitle(chapter.getDurChapterName());
+ page.addLine(chapter.getDurChapterName());
+ page.addLine(content);
+ page.setTitleLines(1);
+ txtChapter.addPage(page);
+ addTxtPageLength(txtChapter, page.getContent().length());
+ txtChapter.addPage(page);
+ return txtChapter;
+ }*/
+ content = contentHelper.replaceContent(pageLoader.book.getName(), pageLoader.book.getTag(), content, pageLoader.book.getReplaceEnable());
+ String[] allLine = content.split("\n");
+ List lines = new ArrayList<>();
+ List txtLists = new ArrayList<>();//记录每个字的位置 //pzl
+ int rHeight = pageLoader.mVisibleHeight - pageLoader.contentMarginHeight * 2;
+ int titleLinesCount = 0;
+ boolean showTitle = pageLoader.readBookControl.getShowTitle(); // 是否展示标题
+ String paragraph = null;
+ if (showTitle) {
+ paragraph = contentHelper.replaceContent(pageLoader.book.getName(), pageLoader.book.getTag(), chapter.getTitle(), pageLoader.book.getReplaceEnable());
+ paragraph = paragraph.trim() + "\n";
+ }
+ int i = 1;
+ while (showTitle || i < allLine.length) {
+ // 重置段落
+ if (!showTitle) {
+ paragraph = allLine[i].replaceAll("\\s", " ").trim();
+ i++;
+ if (paragraph.equals("")) continue;
+ paragraph = pageLoader.indent + paragraph + "\n";
+ }
+ addParagraphLength(txtChapter, paragraph.length());
+ int wordCount;
+ String subStr;
+ while (paragraph.length() > 0) {
+ //当前空间,是否容得下一行文字
+ if (showTitle) {
+ rHeight -= pageLoader.mTitlePaint.getTextSize();
+ } else {
+ rHeight -= pageLoader.mTextPaint.getTextSize();
+ }
+ // 一页已经填充满了,创建 TextPage
+ if (rHeight <= 0) {
+ // 创建Page
+ TxtPage page = new TxtPage(txtChapter.getTxtPageList().size());
+ page.setTitle(chapter.getTitle());
+ page.addLines(lines);
+ page.setTxtLists(new ArrayList<>(txtLists));
+ page.setTitleLines(titleLinesCount);
+ txtChapter.addPage(page);
+ addTxtPageLength(txtChapter, page.getContent().length());
+ // 重置Lines
+ lines.clear();
+ txtLists.clear();//pzl
+ rHeight = pageLoader.mVisibleHeight - pageLoader.contentMarginHeight * 2;
+ titleLinesCount = 0;
+
+ continue;
+ }
+
+ //测量一行占用的字节数
+ if (showTitle) {
+ Layout tempLayout = new StaticLayout(paragraph, pageLoader.mTitlePaint, pageLoader.mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ wordCount = tempLayout.getLineEnd(0);
+ } else {
+ Layout tempLayout = new StaticLayout(paragraph, pageLoader.mTextPaint, pageLoader.mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ wordCount = tempLayout.getLineEnd(0);
+ }
+
+ subStr = paragraph.substring(0, wordCount);
+ if (!subStr.equals("\n")) {
+ //将一行字节,存储到lines中
+ lines.add(subStr);
+ //begin pzl
+ //记录每个字的位置
+ char[] cs = subStr.toCharArray();
+ TxtLine txtList = new TxtLine();//每一行
+ txtList.setCharsData(new ArrayList());
+ for (char c : cs) {
+ String mesasrustr = String.valueOf(c);
+ float charwidth = pageLoader.mTextPaint.measureText(mesasrustr);
+ if (showTitle) {
+ charwidth = pageLoader.mTitlePaint.measureText(mesasrustr);
+ }
+ TxtChar txtChar = new TxtChar();
+ txtChar.setChardata(c);
+ txtChar.setCharWidth(charwidth);//字宽
+ txtChar.setIndex(66);//每页每个字的位置
+ txtList.getCharsData().add(txtChar);
+ }
+ txtLists.add(txtList);
+ //end pzl
+ //设置段落间距
+ if (showTitle) {
+ titleLinesCount += 1;
+ rHeight -= pageLoader.mTitleInterval;
+ } else {
+ rHeight -= pageLoader.mTextInterval;
+ }
+ }
+ //裁剪
+ paragraph = paragraph.substring(wordCount);
+ }
+
+ //增加段落的间距
+ if (!showTitle && lines.size() != 0) {
+ rHeight = rHeight - pageLoader.mTextPara + pageLoader.mTextInterval;
+ }
+
+ if (showTitle) {
+ rHeight = rHeight - pageLoader.mTitlePara + pageLoader.mTitleInterval;
+ showTitle = false;
+ }
+ }
+
+ if (lines.size() != 0) {
+ //创建Page
+ TxtPage page = new TxtPage(txtChapter.getTxtPageList().size());
+ page.setTitle(chapter.getTitle());
+ page.addLines(lines);
+ page.setTxtLists(new ArrayList<>(txtLists));
+ page.setTitleLines(titleLinesCount);
+ txtChapter.addPage(page);
+ addTxtPageLength(txtChapter, page.getContent().length());
+ //重置Lines
+ lines.clear();
+ txtLists.clear();
+ }
+ if (txtChapter.getPageSize() > 0) {
+ txtChapter.setStatus(TxtChapter.Status.FINISH);
+ } else {
+ txtChapter.setStatus(TxtChapter.Status.ERROR);
+ txtChapter.setMsg("未加载到内容");
+ }
+ return txtChapter;
+ }
+
+ private void addTxtPageLength(TxtChapter txtChapter, int length) {
+ if (txtChapter.getTxtPageLengthList().isEmpty()) {
+ txtChapter.addTxtPageLength(length);
+ } else {
+ txtChapter.addTxtPageLength(txtChapter.getTxtPageLengthList().get(txtChapter.getTxtPageLengthList().size() - 1) + length);
+ }
+ }
+
+ private void addParagraphLength(TxtChapter txtChapter, int length) {
+ if (txtChapter.getParagraphLengthList().isEmpty()) {
+ txtChapter.addParagraphLength(length);
+ } else {
+ txtChapter.addParagraphLength(txtChapter.getParagraphLengthList().get(txtChapter.getParagraphLengthList().size() - 1) + length);
+ }
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/LocalPageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/LocalPageLoader.java
new file mode 100644
index 0000000..c6d98f7
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/LocalPageLoader.java
@@ -0,0 +1,401 @@
+package xyz.fycz.myreader.widget.page2;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import io.reactivex.Single;
+import io.reactivex.SingleObserver;
+import io.reactivex.SingleOnSubscribe;
+import io.reactivex.disposables.Disposable;
+import xyz.fycz.myreader.common.APPCONST;
+import xyz.fycz.myreader.entity.Setting;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.greendao.service.ChapterService;
+import xyz.fycz.myreader.util.IOUtils;
+import xyz.fycz.myreader.util.StringHelper;
+import xyz.fycz.myreader.util.utils.Charset;
+import xyz.fycz.myreader.util.utils.FileUtils;
+import xyz.fycz.myreader.util.utils.RxUtils;
+import xyz.fycz.myreader.webapi.callback.ResultCallback;
+import xyz.fycz.myreader.widget.page.BookChapterBean;
+
+import static xyz.fycz.myreader.widget.page.PageLoader.STATUS_PARING;
+
+/**
+ * Created by newbiechen on 17-7-1.
+ * 问题:
+ * 1. 异常处理没有做好
+ */
+
+public class LocalPageLoader extends PageLoader {
+ private static final String TAG = "LocalPageLoader";
+ //默认从文件中获取数据的长度
+ private final static int BUFFER_SIZE = 512 * 1024;
+ //没有标题的时候,每个章节的最大长度
+ private final static int MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024;
+
+ // "序(章)|前言"
+ private final static Pattern mPreChapterPattern = Pattern.compile("^(\\s{0,10})((\u5e8f[\u7ae0\u8a00]?)|(\u524d\u8a00)|(\u6954\u5b50))(\\s{0,10})$", Pattern.MULTILINE);
+
+ //正则表达式章节匹配模式
+ // "(第)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([章节回集卷])(.*)"
+ private static final String[] CHAPTER_PATTERNS = new String[]{"^(.{0,8})(\u7b2c)([0-9\u96f6\u4e00\u4e8c\u4e24\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u58f9\u8d30\u53c1\u8086\u4f0d\u9646\u67d2\u634c\u7396\u62fe\u4f70\u4edf]{1,10})([\u7ae0\u8282\u56de\u96c6\u5377])(.{0,30})$",
+ "^(\\s{0,4})([\\(\u3010\u300a]?(\u5377)?)([0-9\u96f6\u4e00\u4e8c\u4e24\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u58f9\u8d30\u53c1\u8086\u4f0d\u9646\u67d2\u634c\u7396\u62fe\u4f70\u4edf]{1,10})([\\.:\uff1a\u0020\f\t])(.{0,30})$",
+ "^(\\s{0,4})([\\(\uff08\u3010\u300a])(.{0,30})([\\)\uff09\u3011\u300b])(\\s{0,2})$",
+ "^(\\s{0,4})(\u6b63\u6587)(.{0,20})$",
+ "^(.{0,4})(Chapter|chapter)(\\s{0,4})([0-9]{1,4})(.{0,30})$"};
+
+ //章节解析模式
+ private Pattern mChapterPattern = null;
+ //获取书本的文件
+ private File mBookFile;
+ //编码类型
+ private String mCharset;
+
+ private Disposable mChapterDisp = null;
+
+ private ChapterService mChapterService;
+
+ public LocalPageLoader(PageView pageView, Book book, Callback callback) {
+ super(pageView, book, callback);
+ this.mChapterService = ChapterService.getInstance();
+ }
+
+
+ /**
+ * 未完成的部分:
+ * 1. 序章的添加
+ * 2. 章节存在的书本的虚拟分章效果
+ */
+ public List loadChapters() throws IOException {
+ mBookFile = new File(book.getChapterUrl());
+ //获取文件编码
+ mCharset = FileUtils.getFileEncode(mBookFile.getAbsolutePath());
+ List chapters = new ArrayList<>();
+ RandomAccessFile bookStream = null;
+ boolean hasChapter = false;
+ //获取文件流
+ bookStream = new RandomAccessFile(mBookFile, "r");
+ //寻找匹配文章标题的正则表达式,判断是否存在章节名
+ hasChapter = checkChapterType(bookStream);
+ //加载章节
+ byte[] buffer = new byte[BUFFER_SIZE];
+ //获取到的块起始点,在文件中的位置
+ long curOffset = 0;
+ //block的个数
+ int blockPos = 0;
+ //读取的长度
+ int length;
+
+ //获取文件中的数据到buffer,直到没有数据为止
+ while ((length = bookStream.read(buffer, 0, buffer.length)) > 0) {
+ ++blockPos;
+ //如果存在Chapter
+ if (hasChapter) {
+ //将数据转换成String
+ String blockContent = new String(buffer, 0, length, mCharset);
+ //当前Block下使过的String的指针
+ int seekPos = 0;
+ //进行正则匹配
+ Matcher matcher = mChapterPattern.matcher(blockContent);
+ //如果存在相应章节
+ while (matcher.find()) {
+ //获取匹配到的字符在字符串中的起始位置
+ int chapterStart = matcher.start();
+
+ //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容
+ //第一种情况一定是序章 第二种情况可能是上一个章节的内容
+ if (seekPos == 0 && chapterStart != 0) {
+ //获取当前章节的内容
+ String chapterContent = blockContent.substring(seekPos, chapterStart);
+ //设置指针偏移
+ seekPos += chapterContent.length();
+
+ //如果当前对整个文件的偏移位置为0的话,那么就是序章
+ if (curOffset == 0) {
+ //创建序章
+ BookChapterBean preChapter = new BookChapterBean();
+ preChapter.title = "序章";
+ preChapter.start = 0;
+ preChapter.end = chapterContent.getBytes(mCharset).length; //获取String的byte值,作为最终值
+
+ //如果序章大小大于30才添加进去
+ if (preChapter.end - preChapter.start > 30) {
+ chapters.add(preChapter);
+ }
+
+ //创建当前章节
+ BookChapterBean curChapter = new BookChapterBean();
+ curChapter.title = matcher.group();
+ curChapter.start = preChapter.end;
+ chapters.add(curChapter);
+ }
+ //否则就block分割之后,上一个章节的剩余内容
+ else {
+ //获取上一章节
+ BookChapterBean lastChapter = chapters.get(chapters.size() - 1);
+ //将当前段落添加上一章去
+ lastChapter.end += chapterContent.getBytes(mCharset).length;
+
+ //如果章节内容太小,则移除
+ if (lastChapter.end - lastChapter.start < 30) {
+ chapters.remove(lastChapter);
+ }
+
+ //创建当前章节
+ BookChapterBean curChapter = new BookChapterBean();
+ curChapter.title = matcher.group();
+ curChapter.start = lastChapter.end;
+ chapters.add(curChapter);
+ }
+ } else {
+ //是否存在章节
+ if (chapters.size() != 0) {
+ //获取章节内容
+ String chapterContent = blockContent.substring(seekPos, matcher.start());
+ seekPos += chapterContent.length();
+
+ //获取上一章节
+ BookChapterBean lastChapter = chapters.get(chapters.size() - 1);
+ lastChapter.end = lastChapter.start + chapterContent.getBytes(mCharset).length;
+
+ //如果章节内容太小,则移除
+ if (lastChapter.end - lastChapter.start < 30) {
+ chapters.remove(lastChapter);
+ }
+
+ //创建当前章节
+ BookChapterBean curChapter = new BookChapterBean();
+ curChapter.title = matcher.group();
+ curChapter.start = lastChapter.end;
+ chapters.add(curChapter);
+ }
+ //如果章节不存在则创建章节
+ else {
+ BookChapterBean curChapter = new BookChapterBean();
+ curChapter.title = matcher.group();
+ curChapter.start = 0;
+ chapters.add(curChapter);
+ }
+ }
+ }
+ }
+ //进行本地虚拟分章
+ else {
+ //章节在buffer的偏移量
+ int chapterOffset = 0;
+ //当前剩余可分配的长度
+ int strLength = length;
+ //分章的位置
+ int chapterPos = 0;
+
+ while (strLength > 0) {
+ ++chapterPos;
+ //是否长度超过一章
+ if (strLength > MAX_LENGTH_WITH_NO_CHAPTER) {
+ //在buffer中一章的终止点
+ int end = length;
+ //寻找换行符作为终止点
+ for (int i = chapterOffset + MAX_LENGTH_WITH_NO_CHAPTER; i < length; ++i) {
+ if (buffer[i] == Charset.BLANK) {
+ end = i;
+ break;
+ }
+ }
+ BookChapterBean chapter = new BookChapterBean();
+ chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")";
+ chapter.start = curOffset + chapterOffset + 1;
+ chapter.end = curOffset + end;
+ chapters.add(chapter);
+ //减去已经被分配的长度
+ strLength = strLength - (end - chapterOffset);
+ //设置偏移的位置
+ chapterOffset = end;
+ } else {
+ BookChapterBean chapter = new BookChapterBean();
+ chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")";
+ chapter.start = curOffset + chapterOffset + 1;
+ chapter.end = curOffset + length;
+ chapters.add(chapter);
+ strLength = 0;
+ }
+ }
+ }
+
+ //block的偏移点
+ curOffset += length;
+
+ if (hasChapter) {
+ //设置上一章的结尾
+ BookChapterBean lastChapter = chapters.get(chapters.size() - 1);
+ lastChapter.end = curOffset;
+ }
+
+ //当添加的block太多的时候,执行GC
+ if (blockPos % 15 == 0) {
+ System.gc();
+ System.runFinalization();
+ }
+ }
+
+ IOUtils.close(bookStream);
+ System.gc();
+ System.runFinalization();
+ return chapters;
+ }
+
+ public void loadChapters(final ResultCallback resultCallback) {
+ // 通过RxJava异步处理分章事件
+ Single.create((SingleOnSubscribe>) e -> {
+ e.onSuccess(loadChapters());
+ }).compose(RxUtils::toSimpleSingle).subscribe(new SingleObserver>() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ mChapterDisp = d;
+ }
+
+ @Override
+ public void onSuccess(List chapters) {
+ mChapterDisp = null;
+ isChapterListPrepare = true;
+ List mChapters = new ArrayList<>();
+ int i = 0;
+ for (BookChapterBean bookChapterBean : chapters) {
+ Chapter chapter = new Chapter();
+ chapter.setBookId(book.getId());
+ chapter.setTitle(bookChapterBean.getTitle().replace("\n", "").replaceAll("^\\s+|\\s+$", ""));
+ chapter.setId(StringHelper.getStringRandom(25));
+ String content = getChapterContent(bookChapterBean);
+ if (StringHelper.isEmpty(content)) {
+ continue;
+ }
+ chapter.setNumber(i++);
+ mChapterService.saveOrUpdateChapter(chapter, content);
+ mChapters.add(chapter);
+ }
+ if (resultCallback != null) {
+ resultCallback.onFinish(mChapters, 1);
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ resultCallback.onError((Exception) e);
+ durDhapterError(e.getMessage());
+ Log.d(TAG, "file load error:" + e.toString());
+ }
+ });
+
+ }
+
+ /**
+ * 从文件中提取一章的内容
+ *
+ * @param chapter
+ * @return
+ */
+ private String getChapterContent(BookChapterBean chapter) {
+ RandomAccessFile bookStream = null;
+ try {
+ bookStream = new RandomAccessFile(mBookFile, "r");
+ bookStream.seek(chapter.start);
+ int extent = (int) (chapter.end - chapter.start);
+ byte[] content = new byte[extent];
+ bookStream.read(content, 0, extent);
+ return new String(content, mCharset);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.close(bookStream);
+ }
+ return null;
+ }
+
+ /**
+ * 1. 检查文件中是否存在章节名
+ * 2. 判断文件中使用的章节名类型的正则表达式
+ *
+ * @return 是否存在章节名
+ */
+ private boolean checkChapterType(RandomAccessFile bookStream) throws IOException {
+ //首先获取128k的数据
+ byte[] buffer = new byte[BUFFER_SIZE / 4];
+ int length = bookStream.read(buffer, 0, buffer.length);
+ //进行章节匹配
+ for (String str : CHAPTER_PATTERNS) {
+ Pattern pattern = Pattern.compile(str, Pattern.MULTILINE);
+ Matcher matcher = pattern.matcher(new String(buffer, 0, length, mCharset));
+ //如果匹配存在,那么就表示当前章节使用这种匹配方式
+ if (matcher.find()) {
+ mChapterPattern = pattern;
+ //重置指针位置
+ bookStream.seek(0);
+ return true;
+ }
+ }
+
+ //重置指针位置
+ bookStream.seek(0);
+ return false;
+ }
+
+
+ @Override
+ public void closeBook() {
+ super.closeBook();
+ if (mChapterDisp != null) {
+ mChapterDisp.dispose();
+ mChapterDisp = null;
+ }
+ }
+
+ @Override
+ public void refreshChapterList() {
+ // 对于文件是否存在,或者为空的判断,不作处理。 ==> 在文件打开前处理过了。
+ mBookFile = new File(book.getChapterUrl());
+
+ // 判断文件是否已经加载过,并具有缓存
+ if (book.getChapterTotalNum() != 0) {
+ List chapters = mChapterService.findBookAllChapterByBookId(book.getId());
+
+ isChapterListPrepare = true;
+ // 目录加载完成,执行回调操作。
+ if (!chapters.isEmpty()) {
+ callback.onCategoryFinish(chapters);
+ }
+ // 打开章节
+ skipToChapter(book.getHisttoryChapterNum(), book.getLastReadPosition());
+ }
+ }
+
+
+ @Override
+ public String getChapterContent(Chapter chapter) {
+ File file = new File(APPCONST.BOOK_CACHE_PATH + book.getId()
+ + File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY);
+ if (!file.exists()) return null;
+ byte[] contentByte = FileUtils.getBytes(file);
+ return new String(contentByte, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ protected boolean noChapterData(Chapter chapter) {
+ return !ChapterService.isChapterCached(book.getId(), chapter.getTitle());
+ }
+
+ @Override
+ public void updateChapter() {
+
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoader.java
new file mode 100644
index 0000000..c0953e1
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoader.java
@@ -0,0 +1,1866 @@
+package xyz.fycz.myreader.widget.page2;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import io.reactivex.Single;
+import io.reactivex.SingleObserver;
+import io.reactivex.SingleOnSubscribe;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.disposables.Disposable;
+import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.entity.ReadBookControl;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.model.audio.ReadAloudService;
+import xyz.fycz.myreader.util.help.ChapterContentHelp;
+import xyz.fycz.myreader.util.utils.RxUtils;
+import xyz.fycz.myreader.util.utils.ScreenUtils;
+import xyz.fycz.myreader.util.utils.StringUtils;
+import xyz.fycz.myreader.widget.page2.animation.PageAnimation;
+
+/**
+ * 页面加载器
+ */
+
+public abstract class PageLoader {
+ private static final String TAG = "PageLoader";
+
+ // 默认的显示参数配置
+ private static final int DEFAULT_MARGIN_HEIGHT = 20;
+ public static final int DEFAULT_MARGIN_WIDTH = 15;
+ private static final int DEFAULT_TIP_SIZE = 12;
+ private static final float MAX_SCROLL_OFFSET = 100;
+ private static final int TIP_ALPHA = 180;
+ // 监听器
+ Callback callback;
+ private Context mContext;
+ Book book;
+ // 页面显示类
+ PageView mPageView;
+ private List chapterContainers = new ArrayList<>();
+
+ // 绘制电池的画笔
+ private TextPaint mBatteryPaint;
+ // 绘制提示的画笔(章节名称和时间)
+ private TextPaint mTipPaint;
+ private float pageOffset = 0;
+ // 绘制标题的画笔
+ TextPaint mTitlePaint;
+ // 绘制小说内容的画笔
+ TextPaint mTextPaint;
+ // 绘制结束的画笔
+ private TextPaint mTextEndPaint;
+ // 阅读器的配置选项
+ ReadBookControl readBookControl = ReadBookControl.getInstance();
+ //缩进
+ String indent;
+ /*****************params**************************/
+ // 判断章节列表是否加载完成
+ boolean isChapterListPrepare;
+ private boolean isClose;
+ // 页面的翻页效果模式
+ private PageAnimation.Mode mPageMode;
+ //书籍绘制区域的宽高
+ int mVisibleWidth;
+ int mVisibleHeight;
+ //应用的宽高
+ int mDisplayWidth;
+ private int mDisplayHeight;
+ //间距
+ private int mMarginTop;
+ private int mMarginBottom;
+ private int mMarginLeft;
+ private int mMarginRight;
+ int contentMarginHeight;
+ private int tipMarginTop;
+ private int tipMarginBottom;
+ private int oneSpPx;
+ //标题的大小
+ private int mTitleSize;
+ //字体的大小
+ private int mTextSize;
+ private int mTextEndSize;
+ //行间距
+ int mTextInterval;
+ //标题的行间距
+ int mTitleInterval;
+ //段落距离(基于行间距的额外距离)
+ int mTextPara;
+ int mTitlePara;
+ private int textInterval;
+ private int textPara;
+ private int titleInterval;
+ private int titlePara;
+ private float tipBottomTop;
+ private float tipBottomBot;
+ private float tipDistance;
+ private float tipMarginLeft;
+ private float displayRightEnd;
+ private float tipVisibleWidth;
+
+ private boolean hideStatusBar;
+ private boolean showTimeBattery;
+
+ //电池的百分比
+ private int mBatteryLevel;
+
+ // 当前章
+ int mCurChapterPos;
+ private int mCurPagePos;
+ private int readTextLength; //已读字符数
+ private boolean resetReadAloud; //是否重新朗读
+ private int readAloudParagraph; //正在朗读章节
+
+ Bitmap cover;
+ private int linePos = 0;
+ private boolean isLastPage = false;
+
+ CompositeDisposable compositeDisposable;
+ //翻页时间
+ private long skipPageTime = 0;
+
+ /*****************************init params*******************************/
+ PageLoader(PageView pageView, Book book, Callback callback) {
+ mPageView = pageView;
+ this.book = book;
+ this.callback = callback;
+ for (int i = 0; i < 3; i++) {
+ chapterContainers.add(new ChapterContainer());
+ }
+ mContext = pageView.getContext();
+ mCurChapterPos = book.getHisttoryChapterNum();
+ mCurPagePos = book.getLastReadPosition();
+ compositeDisposable = new CompositeDisposable();
+ oneSpPx = ScreenUtils.spToPx(1);
+ // 初始化数据
+ initData();
+ // 初始化画笔
+ initPaint();
+ }
+
+ private void initData() {
+ // 获取配置参数
+ hideStatusBar = readBookControl.getHideStatusBar();
+ showTimeBattery = hideStatusBar && readBookControl.getShowTimeBattery();
+ mPageMode = PageAnimation.Mode.getPageMode(readBookControl.getPageMode());
+ // 初始化参数
+ indent = StringUtils.repeat(StringUtils.halfToFull(" "), readBookControl.getIndent());
+ // 配置文字有关的参数
+ setUpTextParams();
+ }
+
+ /**
+ * 屏幕大小变化处理
+ */
+ void prepareDisplay(int w, int h) {
+ // 获取PageView的宽高
+ mDisplayWidth = w;
+ mDisplayHeight = h;
+
+ // 设置边距
+ mMarginTop = hideStatusBar ?
+ ScreenUtils.dpToPx(readBookControl.getTipPaddingTop() + readBookControl.getPaddingTop() + DEFAULT_MARGIN_HEIGHT)
+ : ScreenUtils.dpToPx(readBookControl.getPaddingTop());
+ mMarginBottom = ScreenUtils.dpToPx(readBookControl.getTipPaddingBottom() + readBookControl.getPaddingBottom() + DEFAULT_MARGIN_HEIGHT);
+ mMarginLeft = ScreenUtils.dpToPx(readBookControl.getPaddingLeft());
+ mMarginRight = ScreenUtils.dpToPx(readBookControl.getPaddingRight());
+ contentMarginHeight = oneSpPx;
+ tipMarginTop = ScreenUtils.dpToPx(readBookControl.getTipPaddingTop() + DEFAULT_MARGIN_HEIGHT);
+ tipMarginBottom = ScreenUtils.dpToPx(readBookControl.getTipPaddingBottom() + DEFAULT_MARGIN_HEIGHT);
+
+ Paint.FontMetrics fontMetrics = mTipPaint.getFontMetrics();
+ float tipMarginTopHeight = (tipMarginTop + fontMetrics.top - fontMetrics.bottom) / 2;
+ float tipMarginBottomHeight = (tipMarginBottom + fontMetrics.top - fontMetrics.bottom) / 2;
+ tipBottomTop = tipMarginTopHeight - fontMetrics.top;
+ tipBottomBot = mDisplayHeight - fontMetrics.bottom - tipMarginBottomHeight;
+ tipDistance = ScreenUtils.dpToPx(DEFAULT_MARGIN_WIDTH);
+ tipMarginLeft = ScreenUtils.dpToPx(readBookControl.getTipPaddingLeft());
+ float tipMarginRight = ScreenUtils.dpToPx(readBookControl.getTipPaddingRight());
+ displayRightEnd = mDisplayWidth - tipMarginRight;
+ tipVisibleWidth = mDisplayWidth - tipMarginLeft - tipMarginRight;
+
+ // 获取内容显示位置的大小
+ mVisibleWidth = mDisplayWidth - mMarginLeft - mMarginRight;
+ mVisibleHeight = readBookControl.getHideStatusBar()
+ ? mDisplayHeight - mMarginTop - mMarginBottom
+ : mDisplayHeight - mMarginTop - mMarginBottom - mPageView.getStatusBarHeight();
+
+ // 设置翻页模式
+ mPageView.setPageMode(mPageMode, mMarginTop, mMarginBottom);
+ skipToChapter(mCurChapterPos, mCurPagePos);
+ }
+
+ /**
+ * 设置与文字相关的参数
+ */
+ private void setUpTextParams() {
+ // 文字大小
+ mTextSize = ScreenUtils.spToPx(readBookControl.getTextSize());
+ mTitleSize = mTextSize + oneSpPx;
+ mTextEndSize = mTextSize - oneSpPx;
+ // 行间距(大小为字体的一半)
+ mTextInterval = (int) (mTextSize * readBookControl.getLineMultiplier() / 2);
+ mTitleInterval = (int) (mTitleSize * readBookControl.getLineMultiplier() / 2);
+ // 段落间距(大小为字体的高度)
+ mTextPara = (int) (mTextSize * readBookControl.getLineMultiplier() * readBookControl.getParagraphSize() / 2);
+ mTitlePara = (int) (mTitleSize * readBookControl.getLineMultiplier() * readBookControl.getParagraphSize() / 2);
+ }
+
+ /**
+ * 初始化画笔
+ */
+ private void initPaint() {
+ Typeface typeface;
+ try {
+ if (!TextUtils.isEmpty(readBookControl.getFontPath())) {
+ typeface = Typeface.createFromFile(readBookControl.getFontPath());
+ } else {
+ typeface = Typeface.SANS_SERIF;
+ }
+ } catch (Exception e) {
+ Toast.makeText(mContext, "字体文件未找,到恢复默认字体", Toast.LENGTH_SHORT).show();
+ readBookControl.setReadBookFont(null);
+ typeface = Typeface.SANS_SERIF;
+ }
+ // 绘制提示的画笔
+ mTipPaint = new TextPaint();
+ mTipPaint.setColor(readBookControl.getTextColor());
+ mTipPaint.setTextAlign(Paint.Align.LEFT); // 绘制的起始点
+ mTipPaint.setTextSize(ScreenUtils.spToPx(DEFAULT_TIP_SIZE)); // Tip默认的字体大小
+ mTipPaint.setTypeface(Typeface.create(typeface, Typeface.NORMAL));
+ mTipPaint.setAntiAlias(true);
+ mTipPaint.setSubpixelText(true);
+
+ // 绘制标题的画笔
+ mTitlePaint = new TextPaint();
+ mTitlePaint.setColor(readBookControl.getTextColor());
+ mTitlePaint.setTextSize(mTitleSize);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mTitlePaint.setLetterSpacing(readBookControl.getTextLetterSpacing());
+ }
+ mTitlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mTitlePaint.setTypeface(Typeface.create(typeface, Typeface.BOLD));
+ mTitlePaint.setTextAlign(Paint.Align.CENTER);
+ mTitlePaint.setAntiAlias(true);
+
+ // 绘制页面内容的画笔
+ mTextPaint = new TextPaint();
+ mTextPaint.setColor(readBookControl.getTextColor());
+ mTextPaint.setTextSize(mTextSize);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mTextPaint.setLetterSpacing(readBookControl.getTextLetterSpacing());
+ }
+ int bold = readBookControl.getTextBold() ? Typeface.BOLD : Typeface.NORMAL;
+ mTextPaint.setTypeface(Typeface.create(typeface, bold));
+ mTextPaint.setAntiAlias(true);
+
+ // 绘制结束的画笔
+ mTextEndPaint = new TextPaint();
+ mTextEndPaint.setColor(readBookControl.getTextColor());
+ mTextEndPaint.setTextSize(mTextEndSize);
+ mTextEndPaint.setTypeface(Typeface.create(typeface, Typeface.NORMAL));
+ mTextEndPaint.setAntiAlias(true);
+ mTextEndPaint.setSubpixelText(true);
+ mTextEndPaint.setTextAlign(Paint.Align.CENTER);
+
+ // 绘制电池的画笔
+ mBatteryPaint = new TextPaint();
+ mBatteryPaint.setAntiAlias(true);
+ mBatteryPaint.setDither(true);
+ mBatteryPaint.setTextSize(ScreenUtils.spToPx(DEFAULT_TIP_SIZE - 3));
+ mBatteryPaint.setTypeface(Typeface.createFromAsset(mContext.getAssets(), "number.ttf"));
+
+ setupTextInterval();
+ // 初始化页面样式
+ initPageStyle();
+ }
+
+ /**
+ * 设置文字相关参数
+ */
+ public void setTextSize() {
+ // 设置文字相关参数
+ setUpTextParams();
+ initPaint();
+ skipToChapter(mCurChapterPos, mCurPagePos);
+ }
+
+ private void setupTextInterval() {
+ textInterval = mTextInterval + (int) mTextPaint.getTextSize();
+ textPara = mTextPara + (int) mTextPaint.getTextSize();
+ titleInterval = mTitleInterval + (int) mTitlePaint.getTextSize();
+ titlePara = mTitlePara + (int) mTextPaint.getTextSize();
+ }
+
+ /**
+ * 设置页面样式
+ */
+ private void initPageStyle() {
+ mTipPaint.setColor(readBookControl.getTextColor());
+ mTitlePaint.setColor(readBookControl.getTextColor());
+ mTextPaint.setColor(readBookControl.getTextColor());
+ mBatteryPaint.setColor(readBookControl.getTextColor());
+ mTextEndPaint.setColor(readBookControl.getTextColor());
+ mTipPaint.setAlpha(TIP_ALPHA);
+ mBatteryPaint.setAlpha(TIP_ALPHA);
+ mTextEndPaint.setAlpha(TIP_ALPHA);
+ }
+
+ /**
+ * 设置翻页动画
+ */
+ public void setPageMode(PageAnimation.Mode pageMode) {
+ mPageMode = pageMode;
+ mPageView.setPageMode(mPageMode, mMarginTop, mMarginBottom);
+ skipToChapter(mCurChapterPos, mCurPagePos);
+ }
+
+ /**
+ * 设置内容与屏幕的间距 单位为 px
+ */
+ public void upMargin() {
+ prepareDisplay(mDisplayWidth, mDisplayHeight);
+ }
+
+ /**
+ * 刷新界面
+ */
+ public void refreshUi() {
+ initData();
+ initPaint();
+ mPageView.setPageMode(mPageMode, mMarginTop, mMarginBottom);
+ skipToChapter(mCurChapterPos, mCurPagePos);
+ }
+
+ /**
+ * 跳转到上一章
+ */
+ public void skipPreChapter() {
+ if (mCurChapterPos <= 0) {
+ return;
+ }
+
+ // 载入上一章。
+ mCurChapterPos = mCurChapterPos - 1;
+ mCurPagePos = 0;
+ Collections.swap(chapterContainers, 2, 1);
+ Collections.swap(chapterContainers, 1, 0);
+ prevChapter().txtChapter = null;
+ parsePrevChapter();
+
+ chapterChangeCallback();
+ openChapter(mCurPagePos);
+ pagingEnd(PageAnimation.Direction.NONE);
+ }
+
+ /**
+ * 跳转到下一章
+ */
+ public boolean skipNextChapter() {
+ if (mCurChapterPos + 1 >= book.getChapterTotalNum()) {
+ return false;
+ }
+
+ //载入下一章
+ mCurChapterPos = mCurChapterPos + 1;
+ mCurPagePos = 0;
+ Collections.swap(chapterContainers, 0, 1);
+ Collections.swap(chapterContainers, 1, 2);
+ nextChapter().txtChapter = null;
+ parseNextChapter();
+
+ chapterChangeCallback();
+ openChapter(mCurPagePos);
+ pagingEnd(PageAnimation.Direction.NONE);
+ return true;
+ }
+
+ /**
+ * 跳转到指定章节页
+ */
+ public void skipToChapter(int chapterPos, int pagePos) {
+ // 设置参数
+ mCurChapterPos = chapterPos;
+ mCurPagePos = pagePos;
+
+ prevChapter().txtChapter = null;
+ curChapter().txtChapter = null;
+ nextChapter().txtChapter = null;
+
+ openChapter(pagePos);
+ }
+
+ /**
+ * 跳转到指定的页
+ */
+ public void skipToPage(int pos) {
+ if (!isChapterListPrepare) {
+ return;
+ }
+ openChapter(pos);
+ }
+
+ /**
+ * 翻到上一页
+ */
+ public void skipToPrePage() {
+ if ((System.currentTimeMillis() - skipPageTime) > 300) {
+ mPageView.autoPrevPage();
+ skipPageTime = System.currentTimeMillis();
+ }
+ }
+
+ /**
+ * 翻到下一页
+ */
+ public void skipToNextPage() {
+ if ((System.currentTimeMillis() - skipPageTime) > 300) {
+ mPageView.autoNextPage();
+ skipPageTime = System.currentTimeMillis();
+ }
+ }
+
+ /**
+ * 翻到下一页,无动画
+ */
+ private void noAnimationToNextPage() {
+ if (getCurPagePos() < curChapter().txtChapter.getPageSize() - 1) {
+ skipToPage(getCurPagePos() + 1);
+ return;
+ }
+ skipNextChapter();
+ }
+
+ /**
+ * 更新时间
+ */
+ public void updateTime() {
+ if (readBookControl.getHideStatusBar() && readBookControl.getShowTimeBattery()) {
+ if (mPageMode == PageAnimation.Mode.SCROLL) {
+ mPageView.drawBackground(0);
+ } else {
+ upPage();
+ }
+ mPageView.invalidate();
+ }
+ }
+
+ /**
+ * 更新电量
+ */
+ public boolean updateBattery(int level) {
+ if (mBatteryLevel == level) {
+ return false;
+ }
+ mBatteryLevel = level;
+ if (readBookControl.getHideStatusBar() && readBookControl.getShowTimeBattery()) {
+ if (mPageMode == PageAnimation.Mode.SCROLL) {
+ mPageView.drawBackground(0);
+ } else if (curChapter().txtChapter != null) {
+ upPage();
+ }
+ mPageView.invalidate();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 获取当前页的状态
+ */
+ public TxtChapter.Status getPageStatus() {
+ return curChapter().txtChapter != null ? curChapter().txtChapter.getStatus() : TxtChapter.Status.LOADING;
+ }
+
+ /**
+ * 获取当前页的页码
+ */
+ int getCurPagePos() {
+ return mCurPagePos;
+ }
+
+ /**
+ * 更新状态
+ */
+ public void setStatus(TxtChapter.Status status) {
+ curChapter().txtChapter.setStatus(status);
+ reSetPage();
+ mPageView.invalidate();
+ }
+
+ /**
+ * 加载错误
+ */
+ void durDhapterError(String msg) {
+ if (curChapter().txtChapter == null) {
+ curChapter().txtChapter = new TxtChapter(mCurChapterPos);
+ }
+ if (curChapter().txtChapter.getStatus() == TxtChapter.Status.FINISH) return;
+ curChapter().txtChapter.setStatus(TxtChapter.Status.ERROR);
+ curChapter().txtChapter.setMsg(msg);
+ if (mPageMode != PageAnimation.Mode.SCROLL) {
+ upPage();
+ } else {
+ mPageView.drawPage(0);
+ }
+ mPageView.invalidate();
+ }
+
+ /**
+ * @return 当前章节所有内容
+ */
+ public String getAllContent() {
+ return getContentStartPage(0);
+ }
+
+ /**
+ * @return 本页未读内容
+ */
+ public String getContent() {
+ if (curChapter().txtChapter == null) return null;
+ if (curChapter().txtChapter.getPageSize() == 0) return null;
+ TxtPage txtPage = curChapter().txtChapter.getPage(mCurPagePos);
+ StringBuilder s = new StringBuilder();
+ int size = txtPage.size();
+ int start = mPageMode == PageAnimation.Mode.SCROLL ? Math.min(Math.max(0, linePos), size - 1) : 0;
+ for (int i = start; i < size; i++) {
+ s.append(txtPage.getLine(i));
+ }
+ return s.toString();
+ }
+
+ /**
+ * @return 本章未读内容
+ */
+ public String getUnReadContent() {
+ if (curChapter().txtChapter == null) return null;
+ //if (book.isAudio()) return curChapter().txtChapter.getMsg();
+ if (curChapter().txtChapter.getTxtPageList().isEmpty()) return null;
+ StringBuilder s = new StringBuilder();
+ String content = getContent();
+ if (content != null) {
+ s.append(content);
+ }
+ content = getContentStartPage(mCurPagePos + 1);
+ if (content != null) {
+ s.append(content);
+ }
+ readTextLength = mCurPagePos > 0 ? curChapter().txtChapter.getPageLength(mCurPagePos - 1) : 0;
+ if (mPageMode == PageAnimation.Mode.SCROLL) {
+ for (int i = 0; i < Math.min(Math.max(0, linePos), curChapter().txtChapter.getPage(mCurPagePos).size() - 1); i++) {
+ readTextLength += curChapter().txtChapter.getPage(mCurPagePos).getLine(i).length();
+ }
+ }
+ return s.toString();
+ }
+
+ /**
+ * * @return curPageLength 当前页字数
+ */
+ public int curPageLength() {
+ if (curChapter().txtChapter == null) return 0;
+ if (curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) return 0;
+ String str;
+ int strLength = 0;
+ TxtPage txtPage = curChapter().txtChapter.getPage(mCurPagePos);
+ if (txtPage != null) {
+ for (int i = txtPage.getTitleLines(); i < txtPage.size(); ++i) {
+ str = txtPage.getLine(i);
+ strLength = strLength + str.length();
+ }
+ }
+ return strLength;
+ }
+
+ /**
+ * @param page 开始页数
+ * @return 从page页开始的的当前章节所有内容
+ */
+ private String getContentStartPage(int page) {
+ if (curChapter().txtChapter == null) return null;
+ if (curChapter().txtChapter.getTxtPageList().isEmpty()) return null;
+ StringBuilder s = new StringBuilder();
+ if (curChapter().txtChapter.getPageSize() > page) {
+ for (int i = page; i < curChapter().txtChapter.getPageSize(); i++) {
+ s.append(curChapter().txtChapter.getPage(i).getContent());
+ }
+ }
+ return s.toString();
+ }
+
+ /**
+ * @param start 开始朗读字数
+ */
+ public void readAloudStart(int start) {
+ start = readTextLength + start;
+ int x = curChapter().txtChapter.getParagraphIndex(start);
+ if (readAloudParagraph != x) {
+ readAloudParagraph = x;
+ mPageView.drawPage(0);
+ mPageView.invalidate();
+ mPageView.drawPage(-1);
+ mPageView.drawPage(1);
+ mPageView.invalidate();
+ }
+ }
+
+ /**
+ * @param readAloudLength 已朗读字数
+ */
+ public void readAloudLength(int readAloudLength) {
+ if (curChapter().txtChapter == null) return;
+ if (curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) return;
+ if (curChapter().txtChapter.getPageLength(mCurPagePos) < 0) return;
+ if (mPageView.isRunning()) return;
+ readAloudLength = readTextLength + readAloudLength;
+ if (readAloudLength >= curChapter().txtChapter.getPageLength(mCurPagePos)) {
+ resetReadAloud = false;
+ noAnimationToNextPage();
+ mPageView.invalidate();
+ }
+ }
+
+ /**
+ * 刷新章节列表
+ */
+ public abstract void refreshChapterList();
+
+ /**
+ * 获取章节的文本
+ */
+ protected abstract String getChapterContent(Chapter chapter) throws Exception;
+
+ /**
+ * 章节数据是否存在
+ */
+ protected abstract boolean noChapterData(Chapter chapter);
+
+ /**
+ * 打开当前章节指定页
+ */
+ void openChapter(int pagePos) {
+ mCurPagePos = pagePos;
+ if (!mPageView.isPrepare()) {
+ return;
+ }
+
+ if (curChapter().txtChapter == null) {
+ curChapter().txtChapter = new TxtChapter(mCurChapterPos);
+ reSetPage();
+ } else if (curChapter().txtChapter.getStatus() == TxtChapter.Status.FINISH) {
+ reSetPage();
+ mPageView.invalidate();
+ pagingEnd(PageAnimation.Direction.NONE);
+ return;
+ }
+
+ // 如果章节目录没有准备好
+ if (!isChapterListPrepare) {
+ curChapter().txtChapter.setStatus(TxtChapter.Status.LOADING);
+ reSetPage();
+ mPageView.invalidate();
+ return;
+ }
+
+ // 如果获取到的章节目录为空
+ if (callback.getChapterList().isEmpty()) {
+ curChapter().txtChapter.setStatus(TxtChapter.Status.CATEGORY_EMPTY);
+ reSetPage();
+ mPageView.invalidate();
+ return;
+ }
+
+ parseCurChapter();
+ resetPageOffset();
+ }
+
+ /**
+ * 重置页面
+ */
+ private void reSetPage() {
+ if (mPageMode == PageAnimation.Mode.SCROLL) {
+ resetPageOffset();
+ mPageView.invalidate();
+ } else {
+ upPage();
+ }
+ }
+
+ /**
+ * 更新页面
+ */
+ private void upPage() {
+ if (mPageMode != PageAnimation.Mode.SCROLL) {
+ mPageView.drawPage(0);
+ if (mCurPagePos > 0 || curChapter().txtChapter.getPosition() > 0) {
+ mPageView.drawPage(-1);
+ }
+ if (mCurPagePos < curChapter().txtChapter.getPageSize() - 1 || curChapter().txtChapter.getPosition() < callback.getChapterList().size() - 1) {
+ mPageView.drawPage(1);
+ }
+ }
+ }
+
+ /**
+ * 翻页完成
+ */
+ void pagingEnd(PageAnimation.Direction direction) {
+ if (!isChapterListPrepare) {
+ return;
+ }
+ switch (direction) {
+ case NEXT:
+ if (mCurPagePos < curChapter().txtChapter.getPageSize() - 1) {
+ mCurPagePos = mCurPagePos + 1;
+ } else if (mCurChapterPos < book.getChapterTotalNum() - 1) {
+ mCurChapterPos = mCurChapterPos + 1;
+ mCurPagePos = 0;
+ Collections.swap(chapterContainers, 0, 1);
+ Collections.swap(chapterContainers, 1, 2);
+ nextChapter().txtChapter = null;
+ parseNextChapter();
+ chapterChangeCallback();
+ }
+ if (mPageMode != PageAnimation.Mode.SCROLL) {
+ mPageView.drawPage(1);
+ }
+ break;
+ case PREV:
+ if (mCurPagePos > 0) {
+ mCurPagePos = mCurPagePos - 1;
+ } else if (mCurChapterPos > 0) {
+ mCurChapterPos = mCurChapterPos - 1;
+ mCurPagePos = prevChapter().txtChapter.getPageSize() - 1;
+ Collections.swap(chapterContainers, 2, 1);
+ Collections.swap(chapterContainers, 1, 0);
+ prevChapter().txtChapter = null;
+ parsePrevChapter();
+ chapterChangeCallback();
+ }
+ if (mPageMode != PageAnimation.Mode.SCROLL) {
+ mPageView.drawPage(-1);
+ }
+ break;
+ }
+ mPageView.setContentDescription(getContent());
+ book.setHisttoryChapterNum(mCurChapterPos);
+ book.setLastReadPosition(mCurPagePos);
+ callback.onPageChange(mCurChapterPos, getCurPagePos(), resetReadAloud);
+ resetReadAloud = true;
+ }
+
+ /**
+ * 绘制页面
+ * pageOnCur: 位于当前页的位置, 小于0上一页, 0 当前页, 大于0下一页
+ */
+ synchronized void drawPage(Bitmap bitmap, int pageOnCur) {
+ TxtChapter txtChapter;
+ TxtPage txtPage = null;
+ if (curChapter().txtChapter == null) {
+ curChapter().txtChapter = new TxtChapter(mCurChapterPos);
+ }
+ if (pageOnCur == 0) { //当前页
+ txtChapter = curChapter().txtChapter;
+ txtPage = txtChapter.getPage(mCurPagePos);
+ } else if (pageOnCur < 0) { //上一页
+ if (mCurPagePos > 0) {
+ txtChapter = curChapter().txtChapter;
+ txtPage = txtChapter.getPage(mCurPagePos - 1);
+ } else {
+ if (prevChapter().txtChapter == null) {
+ txtChapter = new TxtChapter(mCurChapterPos + 1);
+ txtChapter.setStatus(TxtChapter.Status.ERROR);
+ txtChapter.setMsg("未加载完成");
+ } else {
+ txtChapter = prevChapter().txtChapter;
+ txtPage = txtChapter.getPage(txtChapter.getPageSize() - 1);
+ }
+ }
+ } else { //下一页
+ if (mCurPagePos + 1 < curChapter().txtChapter.getPageSize()) {
+ txtChapter = curChapter().txtChapter;
+ txtPage = txtChapter.getPage(mCurPagePos + 1);
+ } else {
+ if (mCurChapterPos + 1 >= callback.getChapterList().size()) {
+ txtChapter = new TxtChapter(mCurChapterPos + 1);
+ txtChapter.setStatus(TxtChapter.Status.ERROR);
+ txtChapter.setMsg("没有下一页");
+ } else if (nextChapter().txtChapter == null) {
+ txtChapter = new TxtChapter(mCurChapterPos + 1);
+ txtChapter.setStatus(TxtChapter.Status.ERROR);
+ txtChapter.setMsg("未加载完成");
+ } else {
+ txtChapter = nextChapter().txtChapter;
+ txtPage = txtChapter.getPage(0);
+ }
+ }
+ }
+ if (bitmap != null)
+ drawBackground(bitmap, txtChapter, txtPage);
+ drawContent(bitmap, txtChapter, txtPage);
+ }
+
+ /**
+ * 滚动模式绘制背景
+ */
+ void drawBackground(Canvas canvas) {
+ if (curChapter().txtChapter == null) {
+ curChapter().txtChapter = new TxtChapter(mCurChapterPos);
+ }
+ drawBackground(canvas, curChapter().txtChapter, curChapter().txtChapter.getPage(mCurPagePos));
+ }
+
+ /**
+ * 横翻模式绘制背景
+ */
+ private synchronized void drawBackground(Bitmap bitmap, TxtChapter txtChapter, TxtPage txtPage) {
+ if (bitmap == null) return;
+ Canvas canvas = new Canvas(bitmap);
+ if (!readBookControl.bgIsColor() && !readBookControl.bgBitmapIsNull()) {
+ Rect mDestRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ canvas.drawBitmap(readBookControl.getBgBitmap(), null, mDestRect, null);
+ } else {
+ canvas.drawColor(readBookControl.getBgColor());
+ }
+ drawBackground(canvas, txtChapter, txtPage);
+ }
+
+ /**
+ * 绘制背景
+ */
+ @SuppressLint("DefaultLocale")
+ private synchronized void drawBackground(final Canvas canvas, TxtChapter txtChapter, TxtPage txtPage) {
+ if (canvas == null) return;
+ if (!callback.getChapterList().isEmpty()) {
+ String title = callback.getChapterList().size() > txtChapter.getPosition() ? callback.getChapterList().get(txtChapter.getPosition()).getTitle() : "";
+ title = ChapterContentHelp.getInstance().replaceContent(book.getName(), book.getTag(), title, book.getReplaceEnable());
+ String page = (txtChapter.getStatus() != TxtChapter.Status.FINISH || txtPage == null) ? ""
+ : String.format("%d/%d", txtPage.getPosition() + 1, txtChapter.getPageSize());
+ String progress = (txtChapter.getStatus() != TxtChapter.Status.FINISH) ? ""
+ : getReadProgress(mCurChapterPos, book.getChapterTotalNum(), mCurPagePos, curChapter().txtChapter.getPageSize());
+
+ float tipBottom;
+ float tipLeft;
+ //初始化标题的参数
+ //需要注意的是:绘制text的y的起始点是text的基准线的位置,而不是从text的头部的位置
+ if (!hideStatusBar) { //显示状态栏
+ if (txtChapter.getStatus() != TxtChapter.Status.FINISH) {
+ if (isChapterListPrepare) {
+ //绘制标题
+ title = TextUtils.ellipsize(title, mTipPaint, tipVisibleWidth, TextUtils.TruncateAt.END).toString();
+ canvas.drawText(title, tipMarginLeft, tipBottomBot, mTipPaint);
+ }
+ } else {
+ //绘制总进度
+ tipLeft = displayRightEnd - mTipPaint.measureText(progress);
+ canvas.drawText(progress, tipLeft, tipBottomBot, mTipPaint);
+ //绘制页码
+ tipLeft = tipLeft - tipDistance - mTipPaint.measureText(page);
+ canvas.drawText(page, tipLeft, tipBottomBot, mTipPaint);
+ //绘制标题
+ title = TextUtils.ellipsize(title, mTipPaint, tipLeft - tipDistance, TextUtils.TruncateAt.END).toString();
+ canvas.drawText(title, tipMarginLeft, tipBottomBot, mTipPaint);
+ }
+ if (readBookControl.getShowLine()) {
+ //绘制分隔线
+ tipBottom = mDisplayHeight - tipMarginBottom;
+ canvas.drawRect(tipMarginLeft, tipBottom, displayRightEnd, tipBottom + 2, mTipPaint);
+ }
+ } else { //隐藏状态栏
+ if (getPageStatus() != TxtChapter.Status.FINISH) {
+ if (isChapterListPrepare) {
+ //绘制标题
+ title = TextUtils.ellipsize(title, mTipPaint, tipVisibleWidth, TextUtils.TruncateAt.END).toString();
+ canvas.drawText(title, tipMarginLeft, tipBottomTop, mTipPaint);
+ }
+ } else {
+ //绘制标题
+ float titleTipLength = showTimeBattery ? tipVisibleWidth - mTipPaint.measureText(progress) - tipDistance : tipVisibleWidth;
+ title = TextUtils.ellipsize(title, mTipPaint, titleTipLength, TextUtils.TruncateAt.END).toString();
+ canvas.drawText(title, tipMarginLeft, tipBottomTop, mTipPaint);
+ // 绘制页码
+ canvas.drawText(page, tipMarginLeft, tipBottomBot, mTipPaint);
+ //绘制总进度
+ float progressTipLeft = displayRightEnd - mTipPaint.measureText(progress);
+ float progressTipBottom = showTimeBattery ? tipBottomTop : tipBottomBot;
+ canvas.drawText(progress, progressTipLeft, progressTipBottom, mTipPaint);
+ }
+ if (readBookControl.getShowLine()) {
+ //绘制分隔线
+ tipBottom = tipMarginTop - 2;
+ canvas.drawRect(tipMarginLeft, tipBottom, displayRightEnd, tipBottom + 2, mTipPaint);
+ }
+ }
+ }
+
+ int visibleRight = (int) displayRightEnd;
+ if (hideStatusBar && showTimeBattery) {
+ //绘制当前时间
+ String time = StringUtils.dateConvert(System.currentTimeMillis(), "HH:mm");
+ float timeTipLeft = (mDisplayWidth - mTipPaint.measureText(time)) / 2;
+ canvas.drawText(time, timeTipLeft, tipBottomBot, mTipPaint);
+
+ //绘制电池
+ int polarHeight = ScreenUtils.dpToPx(4);
+ int polarWidth = ScreenUtils.dpToPx(2);
+ int border = 2;
+ int outFrameWidth = (int) mBatteryPaint.measureText("0000") + polarWidth;
+ int outFrameHeight = (int) mBatteryPaint.getTextSize() + oneSpPx;
+ int visibleBottom = mDisplayHeight - (tipMarginBottom - outFrameHeight) / 2;
+
+ //电极的制作
+ int polarLeft = visibleRight - polarWidth;
+ int polarTop = visibleBottom - (outFrameHeight + polarHeight) / 2;
+ Rect polar = new Rect(polarLeft, polarTop, visibleRight, polarTop + polarHeight);
+
+ mBatteryPaint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(polar, mBatteryPaint);
+
+ //外框的制作
+ int outFrameLeft = polarLeft - outFrameWidth;
+ int outFrameTop = visibleBottom - outFrameHeight;
+ Rect outFrame = new Rect(outFrameLeft, outFrameTop, polarLeft, visibleBottom);
+
+ mBatteryPaint.setStyle(Paint.Style.STROKE);
+ mBatteryPaint.setStrokeWidth(border);
+ canvas.drawRect(outFrame, mBatteryPaint);
+
+ //绘制电量
+ mBatteryPaint.setStyle(Paint.Style.FILL);
+ Paint.FontMetrics fontMetrics = mBatteryPaint.getFontMetrics();
+ String batteryLevel = String.valueOf(mBatteryLevel);
+ float batTextLeft = outFrameLeft + (outFrameWidth - mBatteryPaint.measureText(batteryLevel)) / 2;
+ float batTextBaseLine = visibleBottom - outFrameHeight / 2f - fontMetrics.top / 2 - fontMetrics.bottom / 2;
+ canvas.drawText(batteryLevel, batTextLeft, batTextBaseLine, mBatteryPaint);
+ }
+ }
+
+
+ /**
+ * 绘制内容
+ */
+ private synchronized void drawContent(Bitmap bitmap, TxtChapter txtChapter, TxtPage txtPage) {
+ if (bitmap == null) return;
+ Canvas canvas = new Canvas(bitmap);
+ if (mPageMode == PageAnimation.Mode.SCROLL) {
+ bitmap.eraseColor(Color.TRANSPARENT);
+ }
+
+ Paint.FontMetrics fontMetricsForTitle = mTitlePaint.getFontMetrics();
+ Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
+
+ if (txtChapter.getStatus() != TxtChapter.Status.FINISH) {
+ //绘制字体
+ String tip = getStatusText(txtChapter);
+ drawErrorMsg(canvas, tip, 0);
+ } else {
+ float top = contentMarginHeight - fontMetrics.ascent;
+ if (mPageMode != PageAnimation.Mode.SCROLL) {
+ top += readBookControl.getHideStatusBar() ? mMarginTop : mPageView.getStatusBarHeight() + mMarginTop;
+ }
+ int ppp = 0;//pzl,文字位置
+ //对标题进行绘制
+ String str;
+ int strLength = 0;
+ boolean isLight;
+ for (int i = 0; i < txtPage.getTitleLines(); ++i) {
+ str = txtPage.getLine(i);
+ strLength = strLength + str.length();
+ isLight = ReadAloudService.running && readAloudParagraph == 0;
+ mTitlePaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : readBookControl.getTextColor());
+
+ //进行绘制
+ canvas.drawText(str, mDisplayWidth / 2f, top, mTitlePaint);
+
+ //pzl
+ float leftposition = mDisplayWidth / 2;
+ float rightposition = 0;
+ float bottomposition = top + mTitlePaint.getFontMetrics().descent;
+ float TextHeight = Math.abs(fontMetricsForTitle.ascent) + Math.abs(fontMetricsForTitle.descent);
+
+ if (txtPage.getTxtLists() != null) {
+ for (TxtChar c : txtPage.getTxtLists().get(i).getCharsData()) {
+ rightposition = leftposition + c.getCharWidth();
+ Point tlp = new Point();
+ c.setTopLeftPosition(tlp);
+ tlp.x = (int) leftposition;
+ tlp.y = (int) (bottomposition - TextHeight);
+
+ Point blp = new Point();
+ c.setBottomLeftPosition(blp);
+ blp.x = (int) leftposition;
+ blp.y = (int) bottomposition;
+
+ Point trp = new Point();
+ c.setTopRightPosition(trp);
+ trp.x = (int) rightposition;
+ trp.y = (int) (bottomposition - TextHeight);
+
+ Point brp = new Point();
+ c.setBottomRightPosition(brp);
+ brp.x = (int) rightposition;
+ brp.y = (int) bottomposition;
+ ppp++;
+ c.setIndex(ppp);
+
+ leftposition = rightposition;
+ }
+ }
+
+ //设置尾部间距
+ if (i == txtPage.getTitleLines() - 1) {
+ top += titlePara;
+ } else {
+ //行间距
+ top += titleInterval;
+ }
+ }
+
+ if (txtPage.getLines().isEmpty()) {
+ return;
+ }
+ //对内容进行绘制
+ for (int i = txtPage.getTitleLines(); i < txtPage.size(); ++i) {
+ str = txtPage.getLine(i);
+ strLength = strLength + str.length();
+ int paragraphLength = txtPage.getPosition() == 0 ? strLength : txtChapter.getPageLength(txtPage.getPosition() - 1) + strLength;
+ isLight = ReadAloudService.running && readAloudParagraph == txtChapter.getParagraphIndex(paragraphLength);
+ mTextPaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : readBookControl.getTextColor());
+ Layout tempLayout = new StaticLayout(str, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ float width = StaticLayout.getDesiredWidth(str, tempLayout.getLineStart(0), tempLayout.getLineEnd(0), mTextPaint);
+ if (needScale(str)) {
+ drawScaledText(canvas, str, width, mTextPaint, top, i, txtPage.getTxtLists());
+ } else {
+ canvas.drawText(str, mMarginLeft, top, mTextPaint);
+ }
+
+ //记录文字位置 --开始 pzl
+ float leftposition = mMarginLeft;
+ if (isFirstLineOfParagraph(str)) {
+ String blanks = StringUtils.halfToFull(" ");
+ //canvas.drawText(blanks, x, top, mTextPaint);
+ float bw = StaticLayout.getDesiredWidth(blanks, mTextPaint);
+ leftposition += bw;
+ }
+ float rightposition = 0;
+ float bottomposition = top + mTextPaint.getFontMetrics().descent;
+ float textHeight = Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent);
+
+ if (txtPage.getTxtLists() != null) {
+ for (TxtChar c : txtPage.getTxtLists().get(i).getCharsData()) {
+ rightposition = leftposition + c.getCharWidth();
+ Point tlp = new Point();
+ c.setTopLeftPosition(tlp);
+ tlp.x = (int) leftposition;
+ tlp.y = (int) (bottomposition - textHeight);
+
+ Point blp = new Point();
+ c.setBottomLeftPosition(blp);
+ blp.x = (int) leftposition;
+ blp.y = (int) bottomposition;
+
+ Point trp = new Point();
+ c.setTopRightPosition(trp);
+ trp.x = (int) rightposition;
+ trp.y = (int) (bottomposition - textHeight);
+
+ Point brp = new Point();
+ c.setBottomRightPosition(brp);
+ brp.x = (int) rightposition;
+ brp.y = (int) bottomposition;
+
+ leftposition = rightposition;
+
+ ppp++;
+ c.setIndex(ppp);
+ }
+ }
+ //记录文字位置 --结束 pzl
+
+ //设置尾部间距
+ if (str.endsWith("\n")) {
+ top += textPara;
+ } else {
+ top += textInterval;
+ }
+ }
+ }
+ }
+
+ public void drawCover(Canvas canvas, float top) {
+ }
+
+ private int getCoverHeight() {
+ return cover == null ? 0 : cover.getHeight() + 20;
+ }
+
+ /**
+ * 绘制内容-滚动
+ */
+ void drawContent(final Canvas canvas, float offset) {
+
+ if (offset > MAX_SCROLL_OFFSET) {
+ offset = MAX_SCROLL_OFFSET;
+ } else if (offset < 0 - MAX_SCROLL_OFFSET) {
+ offset = -MAX_SCROLL_OFFSET;
+ }
+
+ boolean pageChanged = false;
+ Paint.FontMetrics fontMetricsForTitle = mTitlePaint.getFontMetrics();
+ Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
+
+ final float totalHeight = mVisibleHeight + titleInterval;
+ if (curChapter().txtChapter == null) {
+ curChapter().txtChapter = new TxtChapter(mCurChapterPos);
+ }
+
+ if (!isLastPage || offset < 0) {
+ pageOffset += offset;
+ isLastPage = false;
+ }
+ // 首页
+ if (pageOffset < 0 && mCurChapterPos == 0 && mCurPagePos == 0) {
+ pageOffset = 0;
+ }
+
+ float cHeight = getFixedPageHeight(curChapter().txtChapter, mCurPagePos);
+ cHeight = cHeight > 0 ? cHeight : mVisibleHeight;
+ if (offset > 0 && pageOffset > cHeight) {
+ while (pageOffset > cHeight) {
+ switchToPageOffset(1);
+ pageOffset -= cHeight;
+ cHeight = getFixedPageHeight(curChapter().txtChapter, mCurPagePos);
+ cHeight = cHeight > 0 ? cHeight : mVisibleHeight;
+ pageChanged = true;
+ }
+ } else if (offset < 0 && pageOffset < 0) {
+ while (pageOffset < 0) {
+ switchToPageOffset(-1);
+ cHeight = getFixedPageHeight(curChapter().txtChapter, mCurPagePos);
+ cHeight = cHeight > 0 ? cHeight : mVisibleHeight;
+ pageOffset += cHeight;
+ pageChanged = true;
+ }
+ }
+
+ if (pageChanged) {
+ chapterChangeCallback();
+ pagingEnd(PageAnimation.Direction.NONE);
+ }
+
+ float top = contentMarginHeight - mTextPaint.ascent() - pageOffset;
+
+ int chapterPos = mCurChapterPos;
+ int pagePos = mCurPagePos;
+ boolean isLight;
+ int ppp = 0;//pzl,文字位置
+
+ if (curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) {
+ String tip = getStatusText(curChapter().txtChapter);
+ drawErrorMsg(canvas, tip, pageOffset);
+ top += mVisibleHeight;
+ chapterPos += 1;
+ pagePos = 0;
+ }
+ String str;
+ linePos = 0;
+ boolean linePosSet = false;
+ boolean bookEnd = false;
+ float startHeight = -2 * titleInterval;
+ if (pageOffset < mTextPaint.getTextSize()) {
+ linePos = 0;
+ linePosSet = true;
+ }
+ while (top < totalHeight) {
+ TxtChapter chapter = chapterPos == mCurChapterPos ? curChapter().txtChapter : nextChapter().txtChapter;
+ if (chapter == null || chapterPos - mCurChapterPos > 1) break;
+ if (chapter.getStatus() != TxtChapter.Status.FINISH) {
+ String tip = getStatusText(chapter);
+ drawErrorMsg(canvas, tip, 0 - top);
+ top += mVisibleHeight;
+ chapterPos += 1;
+ pagePos = 0;
+ continue;
+ }
+ if (chapter.getPageSize() == 0) break;
+ TxtPage page = chapter.getPage(pagePos);
+ if (page.getLines().isEmpty()) break;
+ if (top > totalHeight) break;
+ float topi = top;
+ int strLength = 0;
+ isLight = ReadAloudService.running && readAloudParagraph == 0;
+ mTitlePaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : readBookControl.getTextColor());
+ for (int i = 0; i < page.getTitleLines(); i++) {
+ if (top > totalHeight) {
+ break;
+ } else if (top > startHeight) {
+ str = page.getLine(i);
+ strLength = strLength + str.length();
+ //进行绘制
+ canvas.drawText(str, mDisplayWidth / 2f, top, mTitlePaint);
+ //pzl
+ float leftposition = mDisplayWidth / 2f;
+ float rightPosition = 0;
+ float bottomPosition = top + mTitlePaint.getFontMetrics().descent;
+ float TextHeight = Math.abs(fontMetricsForTitle.ascent) + Math.abs(fontMetricsForTitle.descent);
+
+ if (page.getTxtLists() != null) {
+ for (TxtChar c : page.getTxtLists().get(i).getCharsData()) {
+ rightPosition = leftposition + c.getCharWidth();
+ Point tlp = new Point();
+ c.setTopLeftPosition(tlp);
+ tlp.x = (int) leftposition;
+ tlp.y = (int) (bottomPosition - TextHeight);
+
+ Point blp = new Point();
+ c.setBottomLeftPosition(blp);
+ blp.x = (int) leftposition;
+ blp.y = (int) bottomPosition;
+
+ Point trp = new Point();
+ c.setTopRightPosition(trp);
+ trp.x = (int) rightPosition;
+ trp.y = (int) (bottomPosition - TextHeight);
+
+ Point brp = new Point();
+ c.setBottomRightPosition(brp);
+ brp.x = (int) rightPosition;
+ brp.y = (int) bottomPosition;
+ ppp++;
+ c.setIndex(ppp);
+
+ leftposition = rightPosition;
+ }
+ }
+ //pzl
+ }
+ top += (i == page.getTitleLines() - 1) ? titlePara : titleInterval;
+ if (!linePosSet && chapterPos == mCurChapterPos && top > titlePara) {
+ linePos = i;
+ linePosSet = true;
+ }
+ }
+ if (top > totalHeight) break;
+ // 首页画封面
+ if (pagePos == 0 && chapterPos == 0) {
+ drawCover(canvas, top);
+ top += getCoverHeight();
+ }
+ if (top > totalHeight) break;
+ for (int i = page.getTitleLines(); i < page.size(); i++) {
+ str = page.getLine(i);
+ strLength = strLength + str.length();
+ int paragraphLength = page.getPosition() == 0 ? strLength : chapter.getPageLength(page.getPosition() - 1) + strLength;
+ isLight = ReadAloudService.running && readAloudParagraph == chapter.getParagraphIndex(paragraphLength);
+ mTextPaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : readBookControl.getTextColor());
+ if (top > totalHeight) {
+ break;
+ } else if (top > startHeight) {
+ Layout tempLayout = new StaticLayout(str, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ float width = StaticLayout.getDesiredWidth(str, tempLayout.getLineStart(0), tempLayout.getLineEnd(0), mTextPaint);
+ if (needScale(str)) {
+ drawScaledText(canvas, str, width, mTextPaint, top, i, page.getTxtLists());
+ } else {
+ canvas.drawText(str, mMarginLeft, top, mTextPaint);
+ }
+ //记录文字位置 --开始 pzl
+
+ float leftposition = mMarginLeft;
+
+ if (isFirstLineOfParagraph(str)) {
+ String blanks = StringUtils.halfToFull(" ");
+ //canvas.drawText(blanks, x, top, mTextPaint);
+ float bw = StaticLayout.getDesiredWidth(blanks, mTextPaint);
+ leftposition += bw;
+ }
+
+ float rightposition = 0;
+ float bottomposition = top + mTextPaint.getFontMetrics().descent;
+ float textHeight = Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent);
+
+ if (page.getTxtLists() != null) {
+ for (TxtChar c : page.getTxtLists().get(i).getCharsData()) {
+
+ rightposition = leftposition + c.getCharWidth();
+ Point tlp = new Point();
+ c.setTopLeftPosition(tlp);
+ tlp.x = (int) leftposition;
+ tlp.y = (int) (bottomposition - textHeight);
+
+ Point blp = new Point();
+ c.setBottomLeftPosition(blp);
+ blp.x = (int) leftposition;
+ blp.y = (int) bottomposition;
+
+ Point trp = new Point();
+ c.setTopRightPosition(trp);
+ trp.x = (int) rightposition;
+ trp.y = (int) (bottomposition - textHeight);
+
+ Point brp = new Point();
+ c.setBottomRightPosition(brp);
+ brp.x = (int) rightposition;
+ brp.y = (int) bottomposition;
+
+ leftposition = rightposition;
+
+ ppp++;
+ c.setIndex(ppp);
+ }
+ }
+ //记录文字位置 --结束 pzl
+ }
+ top += str.endsWith("\n") ? textPara : textInterval;
+ if (!linePosSet && chapterPos == mCurChapterPos && top >= textPara) {
+ linePos = i;
+ linePosSet = true;
+ }
+ }
+ if (top > totalHeight) break;
+ if (pagePos == chapter.getPageSize() - 1) {
+ String sign = "\u23af \u23af";
+ if (chapterPos == book.getChapterTotalNum() - 1) {
+ bookEnd = pagePos == mCurPagePos;
+ str = sign + " 所有章节已读完 " + sign;
+ } else {
+ str = sign + " 本章完 " + sign;
+ }
+ top += textPara;
+ canvas.drawText(str, mDisplayWidth / 2f, top, mTextEndPaint);
+ top += textPara * 2;
+ }
+ if (top > totalHeight) break;
+ if (chapter.getPageSize() == 1) {
+ float pHeight = getFixedPageHeight(chapter, pagePos);
+ if (top - topi < pHeight) {
+ top = topi + pHeight;
+ }
+ if (top > totalHeight) break;
+ }
+ if (pagePos >= chapter.getPageSize() - 1) {
+ chapterPos += 1;
+ pagePos = 0;
+ top += 60;
+ } else {
+ pagePos += 1;
+ }
+ if (bookEnd && top < mVisibleHeight) {
+ isLastPage = true;
+ break;
+ }
+ }
+ }
+
+ void resetPageOffset() {
+ pageOffset = 0;
+ linePos = 0;
+ isLastPage = false;
+ }
+
+ private void switchToPageOffset(int offset) {
+ switch (offset) {
+ case 1:
+ if (mCurPagePos < curChapter().txtChapter.getPageSize() - 1) {
+ mCurPagePos = mCurPagePos + 1;
+ } else if (mCurChapterPos < book.getChapterTotalNum() - 1) {
+ mCurChapterPos = mCurChapterPos + 1;
+ Collections.swap(chapterContainers, 0, 1);
+ Collections.swap(chapterContainers, 1, 2);
+ nextChapter().txtChapter = null;
+ mCurPagePos = 0;
+ if (curChapter().txtChapter == null) {
+ curChapter().txtChapter = new TxtChapter(mCurChapterPos);
+ parseCurChapter();
+ } else {
+ parseNextChapter();
+ }
+ }
+ break;
+ case -1:
+ if (mCurPagePos > 0) {
+ mCurPagePos = mCurPagePos - 1;
+ } else if (mCurChapterPos > 0) {
+ mCurChapterPos = mCurChapterPos - 1;
+ Collections.swap(chapterContainers, 2, 1);
+ Collections.swap(chapterContainers, 1, 0);
+ prevChapter().txtChapter = null;
+ if (curChapter().txtChapter == null) {
+ curChapter().txtChapter = new TxtChapter(mCurChapterPos);
+ mCurPagePos = 0;
+ parseCurChapter();
+ } else {
+ mCurPagePos = curChapter().txtChapter.getPageSize() - 1;
+ parsePrevChapter();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private float getFixedPageHeight(TxtChapter chapter, int pagePos) {
+ float height = getPageHeight(chapter, pagePos);
+ if (height == 0) {
+ return height;
+ }
+ int lastPageIndex = chapter.getPageSize() - 1;
+ if (pagePos == lastPageIndex) {
+ height += 60 + textPara * 3;
+ }
+ if (lastPageIndex <= 0 && height < mVisibleHeight / 2.0f) {
+ height = mVisibleHeight / 2.0f;
+ }
+ return height;
+ }
+
+ private float getPageHeight(TxtChapter chapter, int pagePos) {
+ float height = 0;
+ if (chapter == null || chapter.getStatus() != TxtChapter.Status.FINISH) {
+ return height;
+ }
+ if (pagePos >= 0 && pagePos < chapter.getPageSize()) {
+ height = getPageHeight(chapter.getPage(pagePos));
+ }
+ if (chapter.getPosition() == 0 && pagePos == 0) {
+ height += getCoverHeight();
+ }
+ return height;
+ }
+
+ private float getPageHeight(TxtPage page) {
+ if (page.getLines().isEmpty())
+ return 0;
+ float height = 0;
+ if (page.getTitleLines() > 0)
+ height += titleInterval * (page.getTitleLines() - 1) + titlePara;
+ for (int i = page.getTitleLines(); i < page.size(); i++) {
+ height += page.getLine(i).endsWith("\n") ? textPara : textInterval;
+ }
+ return height;
+ }
+
+ private void drawErrorMsg(Canvas canvas, String msg, float offset) {
+ Layout tempLayout = new StaticLayout(msg, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ List linesData = new ArrayList<>();
+ for (int i = 0; i < tempLayout.getLineCount(); i++) {
+ linesData.add(msg.substring(tempLayout.getLineStart(i), tempLayout.getLineEnd(i)));
+ }
+ float pivotY = (mDisplayHeight - textInterval * linesData.size()) / 3f - offset;
+ for (String str : linesData) {
+ float textWidth = mTextPaint.measureText(str);
+ float pivotX = (mDisplayWidth - textWidth) / 2;
+ canvas.drawText(str, pivotX, pivotY, mTextPaint);
+ pivotY += textInterval;
+ }
+ }
+
+ /**
+ * 获取状态文本
+ */
+ private String getStatusText(TxtChapter chapter) {
+ String tip = "";
+ switch (chapter.getStatus()) {
+ case LOADING:
+ tip = mContext.getString(R.string.loading);
+ break;
+ case ERROR:
+ tip = mContext.getString(R.string.load_error_msg, curChapter().txtChapter.getMsg());
+ break;
+ case EMPTY:
+ tip = mContext.getString(R.string.content_empty);
+ break;
+ case CATEGORY_EMPTY:
+ tip = mContext.getString(R.string.chapter_list_empty);
+ break;
+ case CHANGE_SOURCE:
+ tip = mContext.getString(R.string.on_change_source);
+ }
+ return tip;
+ }
+
+ /**
+ * 判断是否存在上一页
+ */
+ boolean hasPrev() {
+ // 以下情况禁止翻页
+ if (canNotTurnPage()) {
+ return false;
+ }
+ if (getPageStatus() == TxtChapter.Status.FINISH) {
+ // 先查看是否存在上一页
+ if (mCurPagePos > 0) {
+ return true;
+ }
+ }
+ return mCurChapterPos > 0;
+ }
+
+ /**
+ * 判断是否下一页存在
+ */
+ boolean hasNext(int pageOnCur) {
+ // 以下情况禁止翻页
+ if (canNotTurnPage()) {
+ return false;
+ }
+ if (getPageStatus() == TxtChapter.Status.FINISH) {
+ // 先查看是否存在下一页
+ if (mCurPagePos + pageOnCur < curChapter().txtChapter.getPageSize() - 1) {
+ return true;
+ }
+ }
+ return mCurChapterPos + 1 < book.getChapterTotalNum();
+ }
+
+ /**
+ * 解析当前页数据
+ */
+ void parseCurChapter() {
+ if (curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) {
+ Single.create((SingleOnSubscribe) e -> {
+ ChapterProvider chapterProvider = new ChapterProvider(this);
+ TxtChapter txtChapter = chapterProvider.dealLoadPageList(callback.getChapterList().get(mCurChapterPos), mPageView.isPrepare());
+ e.onSuccess(txtChapter);
+ })
+ .compose(RxUtils::toSimpleSingle)
+ .subscribe(new SingleObserver() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ compositeDisposable.add(d);
+ }
+
+ @Override
+ public void onSuccess(TxtChapter txtChapter) {
+ upTextChapter(txtChapter);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ if (curChapter().txtChapter == null || curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) {
+ curChapter().txtChapter = new TxtChapter(mCurChapterPos);
+ curChapter().txtChapter.setStatus(TxtChapter.Status.ERROR);
+ curChapter().txtChapter.setMsg(e.getMessage());
+ }
+ }
+ });
+ }
+ parsePrevChapter();
+ parseNextChapter();
+ }
+
+ /**
+ * 解析上一章数据
+ */
+ void parsePrevChapter() {
+ final int prevChapterPos = mCurChapterPos - 1;
+ if (prevChapterPos < 0) {
+ prevChapter().txtChapter = null;
+ return;
+ }
+ if (prevChapter().txtChapter == null)
+ prevChapter().txtChapter = new TxtChapter(prevChapterPos);
+ if (prevChapter().txtChapter.getStatus() == TxtChapter.Status.FINISH) {
+ return;
+ }
+ Single.create((SingleOnSubscribe) e -> {
+ ChapterProvider chapterProvider = new ChapterProvider(this);
+ TxtChapter txtChapter = chapterProvider.dealLoadPageList(callback.getChapterList().get(prevChapterPos), mPageView.isPrepare());
+ e.onSuccess(txtChapter);
+ })
+ .compose(RxUtils::toSimpleSingle)
+ .subscribe(new SingleObserver() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ compositeDisposable.add(d);
+ }
+
+ @Override
+ public void onSuccess(TxtChapter txtChapter) {
+ upTextChapter(txtChapter);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ if (prevChapter().txtChapter == null || prevChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) {
+ prevChapter().txtChapter = new TxtChapter(prevChapterPos);
+ prevChapter().txtChapter.setStatus(TxtChapter.Status.ERROR);
+ prevChapter().txtChapter.setMsg(e.getMessage());
+ }
+ }
+ });
+ }
+
+ /**
+ * 解析下一章数据
+ */
+ void parseNextChapter() {
+ final int nextChapterPos = mCurChapterPos + 1;
+ if (nextChapterPos >= callback.getChapterList().size()) {
+ nextChapter().txtChapter = null;
+ return;
+ }
+ if (nextChapter().txtChapter == null)
+ nextChapter().txtChapter = new TxtChapter(nextChapterPos);
+ if (nextChapter().txtChapter.getStatus() == TxtChapter.Status.FINISH) {
+ return;
+ }
+ Single.create((SingleOnSubscribe) e -> {
+ ChapterProvider chapterProvider = new ChapterProvider(this);
+ TxtChapter txtChapter = chapterProvider.dealLoadPageList(callback.getChapterList().get(nextChapterPos), mPageView.isPrepare());
+ e.onSuccess(txtChapter);
+ })
+ .compose(RxUtils::toSimpleSingle)
+ .subscribe(new SingleObserver() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ compositeDisposable.add(d);
+ }
+
+ @Override
+ public void onSuccess(TxtChapter txtChapter) {
+ upTextChapter(txtChapter);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ if (nextChapter().txtChapter == null || nextChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) {
+ nextChapter().txtChapter = new TxtChapter(nextChapterPos);
+ nextChapter().txtChapter.setStatus(TxtChapter.Status.ERROR);
+ nextChapter().txtChapter.setMsg(e.getMessage());
+ }
+ }
+ });
+ }
+
+ private void upTextChapter(TxtChapter txtChapter) {
+ if (txtChapter.getPosition() == mCurChapterPos - 1) {
+ prevChapter().txtChapter = txtChapter;
+ if (mPageMode == PageAnimation.Mode.SCROLL) {
+ mPageView.drawContent(-1);
+ } else {
+ mPageView.drawPage(-1);
+ }
+ } else if (txtChapter.getPosition() == mCurChapterPos) {
+ curChapter().txtChapter = txtChapter;
+ reSetPage();
+ chapterChangeCallback();
+ pagingEnd(PageAnimation.Direction.NONE);
+ } else if (txtChapter.getPosition() == mCurChapterPos + 1) {
+ nextChapter().txtChapter = txtChapter;
+ if (mPageMode == PageAnimation.Mode.SCROLL) {
+ mPageView.drawContent(1);
+ } else {
+ mPageView.drawPage(1);
+ }
+ }
+ }
+
+ private void drawScaledText(Canvas canvas, String line, float lineWidth, TextPaint paint, float top, int y, List txtLists) {
+ float x = mMarginLeft;
+
+ if (isFirstLineOfParagraph(line)) {
+ canvas.drawText(indent, x, top, paint);
+ float bw = StaticLayout.getDesiredWidth(indent, paint);
+ x += bw;
+ line = line.substring(readBookControl.getIndent());
+ }
+ int gapCount = line.length() - 1;
+ int i = 0;
+
+ TxtLine txtList = new TxtLine();//每一行pzl
+ txtList.setCharsData(new ArrayList<>());//pzl
+
+ float d = ((mDisplayWidth - (mMarginLeft + mMarginRight)) - lineWidth) / gapCount;
+ for (; i < line.length(); i++) {
+ String c = String.valueOf(line.charAt(i));
+ float cw = StaticLayout.getDesiredWidth(c, paint);
+ canvas.drawText(c, x, top, paint);
+ //pzl
+ TxtChar txtChar = new TxtChar();
+ txtChar.setChardata(line.charAt(i));
+ if (i == 0) txtChar.setCharWidth(cw + d / 2);
+ if (i == gapCount) txtChar.setCharWidth(cw + d / 2);
+ txtChar.setCharWidth(cw + d);
+ ;//字宽
+ //txtChar.Index = y;//每页每个字的位置
+ txtList.getCharsData().add(txtChar);
+ //pzl
+ x += cw + d;
+ }
+ if (txtLists != null) {
+ txtLists.set(y, txtList);//pzl
+ }
+ }
+
+ //判断是不是d'hou
+ private boolean isFirstLineOfParagraph(String line) {
+ return line.length() > 3 && line.charAt(0) == (char) 12288 && line.charAt(1) == (char) 12288;
+ }
+
+ private boolean needScale(String line) {//判断不是空行
+ return line != null && line.length() != 0 && line.charAt(line.length() - 1) != '\n';
+ }
+
+ private void chapterChangeCallback() {
+ if (callback != null) {
+ readAloudParagraph = -1;
+ callback.onChapterChange(mCurChapterPos);
+ callback.onPageCountChange(curChapter().txtChapter != null ? curChapter().txtChapter.getPageSize() : 0);
+ }
+ }
+ /**
+ * 获取书籍进度
+ *
+ * @param durChapterIndex
+ * @param chapterAll
+ * @param durPageIndex
+ * @param durPageAll
+ * @return
+ */
+ private static String getReadProgress(int durChapterIndex, int chapterAll, int durPageIndex, int durPageAll) {
+ DecimalFormat df = new DecimalFormat("0.0%");
+ if (chapterAll == 0 || (durPageAll == 0 && durChapterIndex == 0)) {
+ return "0.0%";
+ } else if (durPageAll == 0) {
+ return df.format((durChapterIndex + 1.0f) / chapterAll);
+ }
+ String percent = df.format(durChapterIndex * 1.0f / chapterAll + 1.0f / chapterAll * (durPageIndex + 1) / durPageAll);
+ if (percent.equals("100.0%") && (durChapterIndex + 1 != chapterAll || durPageIndex + 1 != durPageAll)) {
+ percent = "99.9%";
+ }
+ return percent;
+ }
+
+ public abstract void updateChapter();
+
+ /**
+ * 根据当前状态,决定是否能够翻页
+ */
+ private boolean canNotTurnPage() {
+ return !isChapterListPrepare
+ || getPageStatus() == TxtChapter.Status.CHANGE_SOURCE;
+ }
+
+ /**
+ * 关闭书本
+ */
+ public void closeBook() {
+ compositeDisposable.dispose();
+ compositeDisposable = null;
+
+ isChapterListPrepare = false;
+ isClose = true;
+
+ prevChapter().txtChapter = null;
+ curChapter().txtChapter = null;
+ nextChapter().txtChapter = null;
+ }
+
+ public boolean isClose() {
+ return isClose;
+ }
+
+ private ChapterContainer prevChapter() {
+ return chapterContainers.get(0);
+ }
+
+ ChapterContainer curChapter() {
+ return chapterContainers.get(1);
+ }
+
+ private ChapterContainer nextChapter() {
+ return chapterContainers.get(2);
+ }
+
+ /*****************************************interface*****************************************/
+
+ static class ChapterContainer {
+ TxtChapter txtChapter;
+ }
+
+ /**
+ * --------------------
+ * TODO 检测获取按压坐标所在位置的字符,没有的话返回null
+ * --------------------
+ * author: huangwei
+ * 2017年7月4日上午10:23:19
+ */
+ TxtChar detectPressTxtChar(float down_X2, float down_Y2) {
+ TxtPage txtPage = curChapter().txtChapter.getPage(mCurPagePos);
+ if (txtPage == null) return null;
+ List txtLines = txtPage.getTxtLists();
+ if (txtLines == null) return null;
+ for (TxtLine l : txtLines) {
+ List txtChars = l.getCharsData();
+ if (txtChars != null) {
+ for (TxtChar c : txtChars) {
+ Point leftPoint = c.getBottomLeftPosition();
+ Point rightPoint = c.getBottomRightPosition();
+ if (leftPoint != null && down_Y2 > leftPoint.y) {
+ break;// 说明是在下一行
+ }
+ if (leftPoint != null && rightPoint != null && down_X2 >= leftPoint.x && down_X2 <= rightPoint.x) {
+ return c;
+ }
+
+ }
+ }
+ }
+ return null;
+ }
+
+ public interface Callback {
+ List getChapterList();
+
+ /**
+ * 作用:章节切换的时候进行回调
+ *
+ * @param pos:切换章节的序号
+ */
+ void onChapterChange(int pos);
+
+ /**
+ * 作用:章节目录加载完成时候回调
+ *
+ * @param chapters:返回章节目录
+ */
+ void onCategoryFinish(List chapters);
+
+ /**
+ * 作用:章节页码数量改变之后的回调。==> 字体大小的调整,或者是否关闭虚拟按钮功能都会改变页面的数量。
+ *
+ * @param count:页面的数量
+ */
+ void onPageCountChange(int count);
+
+ /**
+ * 作用:当页面改变的时候回调
+ *
+ * @param chapterIndex 章节序号
+ * @param pageIndex 页数
+ * @param resetReadAloud 是否重置朗读
+ */
+ void onPageChange(int chapterIndex, int pageIndex, boolean resetReadAloud);
+
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoaderNet.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoaderNet.java
new file mode 100644
index 0000000..dd7b691
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoaderNet.java
@@ -0,0 +1,266 @@
+package xyz.fycz.myreader.widget.page2;
+
+import android.annotation.SuppressLint;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import xyz.fycz.myreader.common.APPCONST;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.greendao.service.ChapterService;
+import xyz.fycz.myreader.util.utils.FileUtils;
+
+/**
+ * 网络页面加载器
+ */
+
+public class PageLoaderNet extends PageLoader {
+ private static final String TAG = "PageLoaderNet";
+ private List downloadingChapterList = new ArrayList<>();
+
+ PageLoaderNet(PageView pageView, Book bookShelfBean, Callback callback) {
+ super(pageView, bookShelfBean, callback);
+ }
+
+ @Override
+ public void refreshChapterList() {
+ if (!callback.getChapterList().isEmpty()) {
+ isChapterListPrepare = true;
+ // 打开章节
+ skipToChapter(book.getHisttoryChapterNum(), book.getLastReadPosition());
+ } else {
+ /*WebBookModel.getInstance().getChapterList(book)
+ .compose(RxUtils::toSimpleSingle)
+ .subscribe(new MyObserver>() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ compositeDisposable.add(d);
+ }
+
+ @Override
+ public void onNext(List chapterBeanList) {
+ isChapterListPrepare = true;
+ // 目录加载完成
+ if (!chapterBeanList.isEmpty()) {
+ BookshelfHelp.delChapterList(book.getNoteUrl());
+ callback.onCategoryFinish(chapterBeanList);
+ }
+ // 加载并显示当前章节
+ skipToChapter(book.getDurChapter(), book.getDurChapterPage());
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ if (e instanceof WebBook.NoSourceThrowable) {
+ mPageView.autoChangeSource();
+ } else {
+ durDhapterError(e.getMessage());
+ }
+ }
+ });*/
+ }
+ }
+
+
+ public void changeSourceFinish(Book bookShelfBean) {
+ if (bookShelfBean == null) {
+ openChapter(book.getHisttoryChapterNum());
+ } else {
+ this.book = bookShelfBean;
+ refreshChapterList();
+ }
+ }
+
+ /*@SuppressLint("DefaultLocale")
+ private synchronized void loadContent(final int chapterIndex) {
+ if (downloadingChapterList.size() >= 20) return;
+ if (chapterIndex >= callback.getChapterList().size()
+ || DownloadingList(listHandle.CHECK, callback.getChapterList().get(chapterIndex).getUrl()))
+ return;
+ if (null != book && callback.getChapterList().size() > 0) {
+ Observable.create((ObservableOnSubscribe) e -> {
+ if (shouldRequestChapter(chapterIndex)) {
+ DownloadingList(listHandle.ADD, callback.getChapterList().get(chapterIndex).getUrl());
+ e.onNext(chapterIndex);
+ }
+ e.onComplete();
+ })
+ .flatMap(index -> WebBookModel.getInstance().getBookContent(book, callback.getChapterList().get(chapterIndex), null))
+ .subscribeOn(scheduler)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new MyObserver() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ compositeDisposable.add(d);
+ }
+
+ @SuppressLint("DefaultLocale")
+ @Override
+ public void onNext(BookContentBean bookContentBean) {
+ DownloadingList(listHandle.REMOVE, bookContentBean.getDurChapterUrl());
+ finishContent(bookContentBean.getDurChapterIndex());
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ DownloadingList(listHandle.REMOVE, callback.getChapterList().get(chapterIndex).getDurChapterUrl());
+ if (chapterIndex == book.getDurChapter()) {
+ if (e instanceof WebBook.NoSourceThrowable) {
+ mPageView.autoChangeSource();
+ } else if (e instanceof VipThrowable) {
+ callback.vipPop();
+ } else {
+ durDhapterError(e.getMessage());
+ }
+ }
+ }
+ });
+ }
+ }*/
+
+ /**
+ * 编辑下载列表
+ */
+ private synchronized boolean DownloadingList(listHandle editType, String value) {
+ if (editType == listHandle.ADD) {
+ downloadingChapterList.add(value);
+ return true;
+ } else if (editType == listHandle.REMOVE) {
+ downloadingChapterList.remove(value);
+ return true;
+ } else {
+ return downloadingChapterList.indexOf(value) != -1;
+ }
+ }
+
+ /**
+ * 章节下载完成
+ */
+ private void finishContent(int chapterIndex) {
+ if (chapterIndex == mCurChapterPos) {
+ super.parseCurChapter();
+ }
+ if (chapterIndex == mCurChapterPos - 1) {
+ super.parsePrevChapter();
+ }
+ if (chapterIndex == mCurChapterPos + 1) {
+ super.parseNextChapter();
+ }
+ }
+
+ /**
+ * 刷新当前章节
+ */
+ @SuppressLint("DefaultLocale")
+ /*public void refreshDurChapter() {
+ if (callback.getChapterList().isEmpty()) {
+ updateChapter();
+ return;
+ }
+ if (callback.getChapterList().size() - 1 < mCurChapterPos) {
+ mCurChapterPos = callback.getChapterList().size() - 1;
+ }
+ BookshelfHelp.delChapter(BookshelfHelp.getCachePathName(book.getBookInfoBean().getName(), book.getTag()),
+ mCurChapterPos, callback.getChapterList().get(mCurChapterPos).getDurChapterName());
+ skipToChapter(mCurChapterPos, 0);
+ }*/
+
+ @Override
+ protected String getChapterContent(Chapter chapter) {
+ File file = new File(APPCONST.BOOK_CACHE_PATH + book.getId()
+ + File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY);
+ if (!file.exists()) return null;
+ byte[] contentByte = FileUtils.getBytes(file);
+ return new String(contentByte, StandardCharsets.UTF_8);
+ //return BookshelfHelp.getChapterCache(book, chapter);
+ }
+
+ @SuppressLint("DefaultLocale")
+ @Override
+ protected boolean noChapterData(Chapter chapter) {
+ //return !BookshelfHelp.isChapterCached(book.getBookInfoBean().getName(), book.getTag(), chapter, book.isAudio());
+ return !ChapterService.isChapterCached(book.getId(), chapter.getTitle());
+ }
+
+ /*private boolean shouldRequestChapter(Integer chapterIndex) {
+ return NetworkUtils.isNetWorkAvailable() && noChapterData(callback.getChapterList().get(chapterIndex));
+ }*/
+
+ // 装载上一章节的内容
+ @Override
+ void parsePrevChapter() {
+ /*if (mCurChapterPos >= 1) {
+ loadContent(mCurChapterPos - 1);
+ }*/
+ super.parsePrevChapter();
+ }
+
+ // 装载当前章内容。
+ @Override
+ void parseCurChapter() {
+ /*for (int i = mCurChapterPos; i < Math.min(mCurChapterPos + 5, book.getChapterTotalNum()); i++) {
+ loadContent(i);
+ }*/
+ super.parseCurChapter();
+ }
+
+ // 装载下一章节的内容
+ @Override
+ void parseNextChapter() {
+ /*for (int i = mCurChapterPos; i < Math.min(mCurChapterPos + 5, book.getChapterTotalNum()); i++) {
+ loadContent(i);
+ }*/
+ super.parseNextChapter();
+ }
+
+ @Override
+ public void updateChapter() {
+ /*mPageView.getActivity().toast("目录更新中");
+ WebBookModel.getInstance().getChapterList(book)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ compositeDisposable.add(d);
+ }
+
+ @Override
+ public void onNext(List chapterBeanList) {
+ isChapterListPrepare = true;
+
+ if (chapterBeanList.size() > callback.getChapterList().size()) {
+ mPageView.getActivity().toast("更新完成,有新章节");
+ callback.onCategoryFinish(chapterBeanList);
+ } else {
+ mPageView.getActivity().toast("更新完成,无新章节");
+ }
+
+ // 加载并显示当前章节
+ skipToChapter(book.getDurChapter(), book.getDurChapterPage());
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ durDhapterError(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+
+ }
+ });*/
+ }
+
+ @Override
+ public void closeBook() {
+ super.closeBook();
+ }
+
+ public enum listHandle {
+ ADD, REMOVE, CHECK
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/PageView.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageView.java
new file mode 100644
index 0000000..3099089
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageView.java
@@ -0,0 +1,713 @@
+package xyz.fycz.myreader.widget.page2;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.gyf.immersionbar.ImmersionBar;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import xyz.fycz.myreader.entity.ReadBookControl;
+import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.ui.activity.ReadActivity;
+import xyz.fycz.myreader.util.utils.FileUtils;
+import xyz.fycz.myreader.util.utils.ScreenUtils;
+import xyz.fycz.myreader.util.utils.SnackbarUtils;
+import xyz.fycz.myreader.widget.page2.animation.CoverPageAnim;
+import xyz.fycz.myreader.widget.page2.animation.HorizonPageAnim;
+import xyz.fycz.myreader.widget.page2.animation.NonePageAnim;
+import xyz.fycz.myreader.widget.page2.animation.PageAnimation;
+import xyz.fycz.myreader.widget.page2.animation.ScrollPageAnim;
+import xyz.fycz.myreader.widget.page2.animation.SimulationPageAnim;
+import xyz.fycz.myreader.widget.page2.animation.SlidePageAnim;
+
+import static xyz.fycz.myreader.util.utils.ScreenUtils.getDisplayMetrics;
+
+
+/**
+ * 绘制页面显示内容的类
+ */
+public class PageView extends View implements PageAnimation.OnPageChangeListener {
+
+ private ReadActivity activity;
+
+ private int mViewWidth = 0; // 当前View的宽
+ private int mViewHeight = 0; // 当前View的高
+ private int statusBarHeight = 0; //状态栏高度
+
+ private boolean actionFromEdge = false;
+ // 初始化参数
+ private ReadBookControl readBookControl = ReadBookControl.getInstance();
+ private boolean isPrepare;
+ // 动画类
+ private PageAnimation mPageAnim;
+ //点击监听
+ private TouchListener mTouchListener;
+ //内容加载器
+ private PageLoader mPageLoader;
+
+ //文字选择画笔
+ private Paint mTextSelectPaint = null;
+ //文字选择画笔颜色
+ private int TextSelectColor = Color.parseColor("#77fadb08");
+
+ private Path mSelectTextPath = new Path();
+
+ //触摸到起始位置
+ private int mStartX = 0;
+ private int mStartY = 0;
+ // 是否发触了长按事件
+ private boolean isLongPress = false;
+ //第一个选择的文字
+ private TxtChar firstSelectTxtChar = null;
+ //最后选择的一个文字
+ private TxtChar lastSelectTxtChar = null;
+ //选择模式
+ private SelectMode selectMode = SelectMode.Normal;
+ //文本高度
+ private float textHeight = 0;
+ // 唤醒菜单的区域
+ private RectF mCenterRect = null;
+ //是否在移动
+ private boolean isMove = false;
+ //长按的runnable
+ private Runnable mLongPressRunnable;
+ //长按时间
+ private static final int LONG_PRESS_TIMEOUT = 1000;
+ //选择的列
+ private List mSelectLines = new ArrayList();
+
+
+ public PageView(Context context) {
+ this(context, null);
+ }
+
+ public PageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ //初始化画笔
+ mTextSelectPaint = new Paint();
+ mTextSelectPaint.setAntiAlias(true);
+ mTextSelectPaint.setTextSize(19);
+ mTextSelectPaint.setColor(TextSelectColor);
+
+ mLongPressRunnable = () -> {
+ if (mPageLoader == null) return;
+ performLongClick();
+ if (mStartX > 0 && mStartY > 0) {// 说明还没释放,是长按事件
+ isLongPress = true;//长按
+ TxtChar p = mPageLoader.detectPressTxtChar(mStartX, mStartY);//找到长按的点
+ firstSelectTxtChar = p;//设置开始位置字符
+ lastSelectTxtChar = p;//设置结束位置字符
+ selectMode = SelectMode.PressSelectText;//设置模式为长按选择
+ mTouchListener.onLongPress();//响应长按事件,供上层调用
+ }
+ };
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
+ super.onSizeChanged(width, height, oldWidth, oldHeight);
+ mViewWidth = width;
+ mViewHeight = height;
+
+ isPrepare = true;
+
+ if (mPageLoader != null) {
+ mPageLoader.prepareDisplay(width, height);
+ }
+ //设置中间区域范围
+ mCenterRect = new RectF(mViewWidth / 3f, mViewHeight / 3f,
+ mViewWidth * 2f / 3, mViewHeight * 2f / 3);
+ }
+
+ //设置翻页的模式
+ void setPageMode(PageAnimation.Mode pageMode, int marginTop, int marginBottom) {
+ //视图未初始化的时候,禁止调用
+ if (mViewWidth == 0 || mViewHeight == 0 || mPageLoader == null) return;
+ if (!readBookControl.getHideStatusBar()) {
+ marginTop = marginTop + statusBarHeight;
+ }
+ switch (pageMode) {
+ case COVER:
+ mPageAnim = new CoverPageAnim(mViewWidth, mViewHeight, this, this);
+ break;
+ case SLIDE:
+ mPageAnim = new SlidePageAnim(mViewWidth, mViewHeight, this, this);
+ break;
+ case NONE:
+ mPageAnim = new NonePageAnim(mViewWidth, mViewHeight, this, this);
+ break;
+ case SCROLL:
+ mPageAnim = new ScrollPageAnim(mViewWidth, mViewHeight, 0,
+ marginTop, marginBottom, this, this);
+ break;
+ default:
+ mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, this);
+ }
+ }
+
+ public ReadActivity getActivity() {
+ return activity;
+ }
+
+ public int getStatusBarHeight() {
+ return statusBarHeight;
+ }
+
+ public Bitmap getBgBitmap(int pageOnCur) {
+ if (mPageAnim == null) return null;
+ return mPageAnim.getBgBitmap(pageOnCur);
+ }
+
+ public void autoPrevPage() {
+ if (mPageAnim instanceof ScrollPageAnim) {
+ ((ScrollPageAnim) mPageAnim).startAnim(PageAnimation.Direction.PREV);
+ } else {
+ startHorizonPageAnim(PageAnimation.Direction.PREV);
+ }
+ }
+
+ public void autoNextPage() {
+ if (mPageAnim instanceof ScrollPageAnim) {
+ ((ScrollPageAnim) mPageAnim).startAnim(PageAnimation.Direction.NEXT);
+ } else {
+ startHorizonPageAnim(PageAnimation.Direction.NEXT);
+ }
+ }
+
+ private synchronized void startHorizonPageAnim(PageAnimation.Direction direction) {
+ if (mTouchListener == null) return;
+ //结束动画
+ mPageAnim.abortAnim();
+ if (direction == PageAnimation.Direction.NEXT) {
+ int x = mViewWidth;
+ int y = mViewHeight;
+ //初始化动画
+ mPageAnim.setStartPoint(x, y);
+ //设置点击点
+ mPageAnim.setTouchPoint(x, y);
+ //设置方向
+ boolean hasNext = hasNextPage(0);
+
+ mPageAnim.setDirection(direction);
+ if (!hasNext) {
+ ((HorizonPageAnim) mPageAnim).setNoNext(true);
+ return;
+ }
+ } else if (direction == PageAnimation.Direction.PREV) {
+ int x = 0;
+ int y = mViewHeight;
+ //初始化动画
+ mPageAnim.setStartPoint(x, y);
+ //设置点击点
+ mPageAnim.setTouchPoint(x, y);
+ mPageAnim.setDirection(direction);
+ //设置方向方向
+ boolean hashPrev = hasPrevPage();
+ if (!hashPrev) {
+ ((HorizonPageAnim) mPageAnim).setNoNext(true);
+ return;
+ }
+ } else {
+ return;
+ }
+ ((HorizonPageAnim) mPageAnim).setNoNext(false);
+ ((HorizonPageAnim) mPageAnim).setCancel(false);
+ mPageAnim.startAnim();
+ }
+
+ public void drawPage(int pageOnCur) {
+ if (!isPrepare) return;
+ if (mPageLoader != null) {
+ mPageLoader.drawPage(getBgBitmap(pageOnCur), pageOnCur);
+ }
+ invalidate();
+ }
+
+ /**
+ * 绘制滚动背景
+ */
+ @Override
+ public void drawBackground(Canvas canvas) {
+ if (!isPrepare) return;
+ if (mPageLoader != null) {
+ mPageLoader.drawBackground(canvas);
+ }
+ }
+
+ /**
+ * 绘制滚动内容
+ */
+ @Override
+ public void drawContent(Canvas canvas, float offset) {
+ if (!isPrepare) return;
+ if (mPageLoader != null) {
+ mPageLoader.drawContent(canvas, offset);
+ }
+ }
+
+ /**
+ * 绘制横翻背景
+ */
+ public void drawBackground(int pageOnCur) {
+ if (!isPrepare) return;
+ if (mPageLoader != null) {
+ mPageLoader.drawPage(getBgBitmap(pageOnCur), pageOnCur);
+ }
+ invalidate();
+ }
+
+ /**
+ * 绘制横翻内容
+ *
+ * @param pageOnCur 相对当前页的位置
+ */
+ public void drawContent(int pageOnCur) {
+ if (!isPrepare) return;
+ if (mPageLoader != null) {
+ mPageLoader.drawPage(getBgBitmap(pageOnCur), pageOnCur);
+ }
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mPageAnim instanceof ScrollPageAnim)
+ super.onDraw(canvas);
+ //绘制动画
+ if (mPageAnim != null) {
+ mPageAnim.draw(canvas);
+ }
+ if (selectMode != SelectMode.Normal && !isRunning() && !isMove) {
+ DrawSelectText(canvas);
+ }
+ }
+
+ private void DrawSelectText(Canvas canvas) {
+ if (selectMode == SelectMode.PressSelectText) {
+ drawPressSelectText(canvas);
+ } else if (selectMode == SelectMode.SelectMoveForward) {
+ drawMoveSelectText(canvas);
+ } else if (selectMode == SelectMode.SelectMoveBack) {
+ drawMoveSelectText(canvas);
+ }
+ }
+
+
+ private void drawPressSelectText(Canvas canvas) {
+ if (lastSelectTxtChar != null) {// 找到了选择的字符
+ mSelectTextPath.reset();
+ mSelectTextPath.moveTo(firstSelectTxtChar.getTopLeftPosition().x, firstSelectTxtChar.getTopLeftPosition().y);
+ mSelectTextPath.lineTo(firstSelectTxtChar.getTopRightPosition().x, firstSelectTxtChar.getTopRightPosition().y);
+ mSelectTextPath.lineTo(firstSelectTxtChar.getBottomRightPosition().x, firstSelectTxtChar.getBottomRightPosition().y);
+ mSelectTextPath.lineTo(firstSelectTxtChar.getBottomLeftPosition().x, firstSelectTxtChar.getBottomLeftPosition().y);
+ canvas.drawPath(mSelectTextPath, mTextSelectPaint);
+ }
+ }
+
+
+ public String getSelectStr() {
+
+ if (mSelectLines.size() == 0) {
+ return String.valueOf(firstSelectTxtChar.getChardata());
+ }
+ StringBuilder sb = new StringBuilder();
+ for (TxtLine l : mSelectLines) {
+ //Log.e("selectline", l.getLineData() + "");
+ sb.append(l.getLineData());
+ }
+
+ return sb.toString();
+ }
+
+
+ private void drawMoveSelectText(Canvas canvas) {
+ if (firstSelectTxtChar == null || lastSelectTxtChar == null)
+ return;
+ getSelectData();
+ drawSelectLines(canvas);
+ }
+
+ List mLinseData = null;
+
+ private void getSelectData() {
+ TxtPage txtPage = mPageLoader.curChapter().txtChapter.getPage(mPageLoader.getCurPagePos());
+ if (txtPage != null) {
+ mLinseData = txtPage.getTxtLists();
+
+ Boolean Started = false;
+ Boolean Ended = false;
+
+ mSelectLines.clear();
+
+ // 找到选择的字符数据,转化为选择的行,然后将行选择背景画出来
+ for (TxtLine l : mLinseData) {
+
+ TxtLine selectline = new TxtLine();
+ selectline.setCharsData(new ArrayList<>());
+
+ for (TxtChar c : l.getCharsData()) {
+ if (!Started) {
+ if (c.getIndex() == firstSelectTxtChar.getIndex()) {
+ Started = true;
+ selectline.getCharsData().add(c);
+ if (c.getIndex() == lastSelectTxtChar.getIndex()) {
+ Ended = true;
+ break;
+ }
+ }
+ } else {
+ if (c.getIndex() == lastSelectTxtChar.getIndex()) {
+ Ended = true;
+ if (!selectline.getCharsData().contains(c)) {
+ selectline.getCharsData().add(c);
+ }
+ break;
+ } else {
+ selectline.getCharsData().add(c);
+ }
+ }
+ }
+
+ mSelectLines.add(selectline);
+
+ if (Started && Ended) {
+ break;
+ }
+ }
+ }
+ }
+
+ public SelectMode getSelectMode() {
+ return selectMode;
+ }
+
+ public void setSelectMode(SelectMode mCurrentMode) {
+ this.selectMode = mCurrentMode;
+ }
+
+ private void drawSelectLines(Canvas canvas) {
+ drawOaleSeletLinesBg(canvas);
+ }
+
+ public void clearSelect() {
+ firstSelectTxtChar = null;
+ lastSelectTxtChar = null;
+ selectMode = SelectMode.Normal;
+ mSelectTextPath.reset();
+ invalidate();
+
+ }
+
+
+ //根据当前坐标返回文字
+ public TxtChar getCurrentTxtChar(float x, float y) {
+ return mPageLoader.detectPressTxtChar(x, y);
+ }
+
+ private void drawOaleSeletLinesBg(Canvas canvas) {// 绘制椭圆型的选中背景
+ for (TxtLine l : mSelectLines) {
+ if (l.getCharsData() != null && l.getCharsData().size() > 0) {
+
+ TxtChar fistchar = l.getCharsData().get(0);
+ TxtChar lastchar = l.getCharsData().get(l.getCharsData().size() - 1);
+
+ float fw = fistchar.getCharWidth();
+ float lw = lastchar.getCharWidth();
+
+ RectF rect = new RectF(fistchar.getTopLeftPosition().x, fistchar.getTopLeftPosition().y,
+ lastchar.getTopRightPosition().x, lastchar.getBottomRightPosition().y);
+
+ canvas.drawRoundRect(rect, fw / 2,
+ textHeight / 2, mTextSelectPaint);
+ }
+ }
+ }
+
+ public TxtChar getFirstSelectTxtChar() {
+ return firstSelectTxtChar;
+ }
+
+ public void setFirstSelectTxtChar(TxtChar firstSelectTxtChar) {
+ this.firstSelectTxtChar = firstSelectTxtChar;
+ }
+
+ public TxtChar getLastSelectTxtChar() {
+ return lastSelectTxtChar;
+ }
+
+ public void setLastSelectTxtChar(TxtChar lastSelectTxtChar) {
+ this.lastSelectTxtChar = lastSelectTxtChar;
+ }
+
+ @Override
+ public void computeScroll() {
+ //进行滑动
+ if (mPageAnim != null) {
+ mPageAnim.scrollAnim();
+ }
+ super.computeScroll();
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ if (mPageAnim == null) return true;
+ if (mPageLoader == null) return true;
+
+ Paint.FontMetrics fontMetrics = mPageLoader.mTextPaint.getFontMetrics();
+ textHeight = Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent);
+
+ if (actionFromEdge) {
+ if (event.getAction() == MotionEvent.ACTION_UP)
+ actionFromEdge = false;
+ return true;
+ }
+
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mPageAnim.initTouch(x, y);
+ if (event.getEdgeFlags() != 0 || event.getRawY() < ScreenUtils.dpToPx(5) || event.getRawY() > getDisplayMetrics().heightPixels - ScreenUtils.dpToPx(5)) {
+ actionFromEdge = true;
+ return true;
+ }
+ mStartX = x;
+ mStartY = y;
+ isMove = false;
+
+ //
+ if (readBookControl.isCanSelectText() && mPageLoader.getPageStatus() == TxtChapter.Status.FINISH) {
+ postDelayed(mLongPressRunnable, LONG_PRESS_TIMEOUT);
+ }
+
+ //
+ isLongPress = false;
+ mTouchListener.onTouch();
+ mPageAnim.onTouchEvent(event);
+
+ selectMode = SelectMode.Normal;
+
+ mTouchListener.onTouchClearCursor();
+
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mPageAnim.initTouch(x, y);
+ // 判断是否大于最小滑动值。
+ int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ if (!isMove) {
+ isMove = Math.abs(mStartX - event.getX()) > slop || Math.abs(mStartY - event.getY()) > slop;
+ }
+
+ // 如果滑动了,且不是长按,则进行翻页。
+ if (isMove) {
+ if (readBookControl.isCanSelectText()) {
+ removeCallbacks(mLongPressRunnable);
+ }
+ mPageAnim.onTouchEvent(event);
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mPageAnim.initTouch(x, y);
+ mPageAnim.setTouchInitFalse();
+ if (!isMove) {
+ if (readBookControl.isCanSelectText()) {
+ removeCallbacks(mLongPressRunnable);
+ }
+
+ //是否点击了中间
+ if (mCenterRect.contains(x, y)) {
+ if (firstSelectTxtChar == null) {
+ if (mTouchListener != null) {
+ mTouchListener.center();
+ }
+ } else {
+ if (mSelectTextPath != null) {//长安选择删除选中状态
+ if (!isLongPress) {
+ firstSelectTxtChar = null;
+ mSelectTextPath.reset();
+ invalidate();
+ }
+ }
+ //清除移动选择状态
+ }
+ return true;
+ }
+
+ if (!readBookControl.getCanClickTurn()) {
+ return true;
+ }
+
+ if (mPageAnim instanceof ScrollPageAnim && readBookControl.disableScrollClickTurn()) {
+ return true;
+ }
+ }
+
+ if (firstSelectTxtChar == null || isMove) {//长安选择删除选中状态
+ mPageAnim.onTouchEvent(event);
+ } else {
+ if (!isLongPress) {
+ //释放了
+ if (LONG_PRESS_TIMEOUT != 0) {
+ removeCallbacks(mLongPressRunnable);
+ }
+ firstSelectTxtChar = null;
+ mSelectTextPath.reset();
+ invalidate();
+ }
+ }
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * 判断是否存在上一页
+ */
+ private boolean hasPrevPage() {
+ if (mPageLoader.hasPrev()) {
+ return true;
+ } else {
+ showSnackBar("已经是第一页了");
+ return false;
+ }
+ }
+
+ /**
+ * 判断是否下一页存在
+ */
+ private boolean hasNextPage(int pageOnCur) {
+ if (mPageLoader.hasNext(pageOnCur)) {
+ return true;
+ } else {
+ showSnackBar("已经是最后一页了");
+ return false;
+ }
+ }
+
+ public boolean isRunning() {
+ return mPageAnim != null && mPageAnim.isRunning();
+ }
+
+ public boolean isPrepare() {
+ return isPrepare;
+ }
+
+ public void setTouchListener(TouchListener mTouchListener) {
+ this.mTouchListener = mTouchListener;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mPageAnim != null) {
+ mPageAnim.abortAnim();
+ mPageAnim.clear();
+ }
+
+ mPageLoader = null;
+ mPageAnim = null;
+ }
+
+ @Override
+ public void resetScroll() {
+ mPageLoader.resetPageOffset();
+ }
+
+ @Override
+ public boolean hasPrev() {
+ return PageView.this.hasPrevPage();
+ }
+
+ @Override
+ public boolean hasNext(int pageOnCur) {
+ return PageView.this.hasNextPage(pageOnCur);
+ }
+
+ @Override
+ public void changePage(PageAnimation.Direction direction) {
+ mPageLoader.pagingEnd(direction);
+ }
+
+ /**
+ * 获取 PageLoader
+ */
+ public PageLoader getPageLoader(ReadActivity activity, Book bookShelfBean, PageLoader.Callback callback) {
+ this.activity = activity;
+ this.statusBarHeight = ImmersionBar.getStatusBarHeight(activity);
+ // 判是否已经存在
+ if (mPageLoader != null) {
+ return mPageLoader;
+ }
+ // 根据书籍类型,获取具体的加载器
+ //if (!Objects.equals(bookShelfBean.getTag(), BookShelfBean.LOCAL_TAG)) {
+ if (!"本地书籍".equals(bookShelfBean.getType())) {
+ mPageLoader = new PageLoaderNet(this, bookShelfBean, callback);
+ } else {
+ /*String fileSuffix = FileUtils.getFileSuffix(bookShelfBean.getChapterUrl());
+ if (fileSuffix.equalsIgnoreCase(FileUtils.SUFFIX_EPUB)) {
+ mPageLoader = new PageLoaderEpub(this, bookShelfBean, callback);
+ } else {
+ mPageLoader = new PageLoaderText(this, bookShelfBean, callback);
+ }*/
+ }
+ // 判断是否 PageView 已经初始化完成
+ if (mViewWidth != 0 || mViewHeight != 0) {
+ // 初始化 PageLoader 的屏幕大小
+ mPageLoader.prepareDisplay(mViewWidth, mViewHeight);
+ }
+
+ return mPageLoader;
+ }
+
+ public void autoChangeSource() {
+ mPageLoader.setStatus(TxtChapter.Status.CHANGE_SOURCE);
+ //activity.autoChangeSource();
+ }
+
+ /**
+ * 显示tips
+ *
+ * @param msg
+ */
+ public void showSnackBar(String msg) {
+ SnackbarUtils.show(this, msg);
+ }
+ public enum SelectMode {
+ Normal, PressSelectText, SelectMoveForward, SelectMoveBack
+ }
+
+ public interface TouchListener {
+ void onTouch();
+
+ void onTouchClearCursor();
+
+ void onLongPress();
+
+ void center();
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChapter.kt b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChapter.kt
new file mode 100644
index 0000000..aa12d38
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChapter.kt
@@ -0,0 +1,57 @@
+package xyz.fycz.myreader.widget.page2
+
+import java.util.*
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * 章节
+ */
+
+class TxtChapter internal constructor(val position: Int) {
+ val txtPageList = ArrayList()
+ val txtPageLengthList = ArrayList()
+ val paragraphLengthList = ArrayList()
+ var status = Status.LOADING
+ var msg: String? = null
+
+ val pageSize: Int
+ get() = txtPageList.size
+
+ fun addPage(txtPage: TxtPage) {
+ txtPageList.add(txtPage)
+ }
+
+ fun getPage(page: Int): TxtPage? {
+ return if (txtPageList.isNotEmpty()) {
+ txtPageList[max(0, min(page, txtPageList.size - 1))]
+ } else null
+ }
+
+ fun getPageLength(position: Int): Int {
+ return if (position >= 0 && position < txtPageLengthList.size) {
+ txtPageLengthList[position]
+ } else -1
+ }
+
+ fun addTxtPageLength(length: Int) {
+ txtPageLengthList.add(length)
+ }
+
+ fun addParagraphLength(length: Int) {
+ paragraphLengthList.add(length)
+ }
+
+ fun getParagraphIndex(length: Int): Int {
+ for (i in paragraphLengthList.indices) {
+ if ((i == 0 || paragraphLengthList[i - 1] < length) && length <= paragraphLengthList[i]) {
+ return i
+ }
+ }
+ return -1
+ }
+
+ enum class Status {
+ LOADING, FINISH, ERROR, EMPTY, CATEGORY_EMPTY, CHANGE_SOURCE
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChar.kt b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChar.kt
new file mode 100644
index 0000000..f1cc31b
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChar.kt
@@ -0,0 +1,26 @@
+package xyz.fycz.myreader.widget.page2
+
+import android.graphics.Point
+
+
+class TxtChar {
+ var chardata: Char = ' '//字符数据
+
+ var selected: Boolean? = false//当前字符是否被选中
+
+ //记录文字的左上右上左下右下四个点坐标
+ var topLeftPosition: Point? = null//左上
+ var topRightPosition: Point? = null//右上
+ var bottomLeftPosition: Point? = null//左下
+ var bottomRightPosition: Point? = null//右下
+
+ var charWidth = 0f//字符宽度
+ var Index = 0//当前字符位置
+
+ override fun toString(): String {
+ return ("ShowChar [chardata=" + chardata + ", Selected=" + selected + ", TopLeftPosition=" + topLeftPosition
+ + ", TopRightPosition=" + topRightPosition + ", BottomLeftPosition=" + bottomLeftPosition
+ + ", BottomRightPosition=" + bottomRightPosition + ", charWidth=" + charWidth + ", Index=" + Index
+ + "]");
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtLine.kt b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtLine.kt
new file mode 100644
index 0000000..15c58c8
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtLine.kt
@@ -0,0 +1,23 @@
+package xyz.fycz.myreader.widget.page2
+
+class TxtLine {
+
+ var charsData: List? = null
+
+ fun getLineData(): String {
+ var linedata = ""
+ if (charsData == null) return linedata
+ charsData?.let {
+ if (it.isEmpty()) return linedata
+ for (c in it) {
+ linedata += c.chardata
+ }
+ }
+ return linedata
+ }
+
+ override fun toString(): String {
+ return "ShowLine [Linedata=" + getLineData() + "]"
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtPage.kt b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtPage.kt
new file mode 100644
index 0000000..9723907
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtPage.kt
@@ -0,0 +1,44 @@
+package xyz.fycz.myreader.widget.page2
+
+import java.util.*
+
+/**
+ * 页面
+ */
+
+class TxtPage(val position: Int) {
+ var title: String? = null
+ var titleLines: Int = 0 //当前 lines 中为 title 的行数。
+ private val lines = ArrayList()
+ //存放每个字的位置
+ var txtLists: List? = null
+
+ val content: String
+ get() {
+ val s = StringBuilder()
+ for (i in lines.indices) {
+ s.append(lines[i])
+ }
+ return s.toString()
+ }
+
+ fun addLine(line: String) {
+ lines.add(line)
+ }
+
+ fun addLines(lines: List) {
+ this.lines.addAll(lines)
+ }
+
+ fun getLine(i: Int): String {
+ return lines[i]
+ }
+
+ fun getLines(): List {
+ return lines
+ }
+
+ fun size(): Int {
+ return lines.size
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/CoverPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/CoverPageAnim.java
new file mode 100644
index 0000000..59eedca
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/CoverPageAnim.java
@@ -0,0 +1,87 @@
+package xyz.fycz.myreader.widget.page2.animation;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.view.View;
+
+/**
+ * 覆盖翻页
+ */
+
+public class CoverPageAnim extends HorizonPageAnim {
+
+ private Rect mSrcRect, mDestRect;
+ private GradientDrawable mBackShadowDrawableLR;
+
+ public CoverPageAnim(int w, int h, View view, OnPageChangeListener listener) {
+ super(w, h, view, listener);
+ mSrcRect = new Rect(0, 0, mViewWidth, mViewHeight);
+ mDestRect = new Rect(0, 0, mViewWidth, mViewHeight);
+ int[] mBackShadowColors = new int[]{0x66000000, 0x00000000};
+ mBackShadowDrawableLR = new GradientDrawable(
+ GradientDrawable.Orientation.LEFT_RIGHT, mBackShadowColors);
+ mBackShadowDrawableLR.setGradientType(GradientDrawable.LINEAR_GRADIENT);
+ }
+
+ @Override
+ public void drawMove(Canvas canvas) {
+ int dis;
+ if (mDirection == Direction.NEXT) {
+ dis = (int) (mViewWidth - mStartX + mTouchX);
+ if (dis > mViewWidth) {
+ dis = mViewWidth;
+ }
+ //计算bitmap截取的区域
+ mSrcRect.left = mViewWidth - dis;
+ //计算bitmap在canvas显示的区域
+ mDestRect.right = dis;
+ canvas.drawBitmap(bitmapList.get(2), 0, 0, null);
+ canvas.drawBitmap(bitmapList.get(1), mSrcRect, mDestRect, null);
+ addShadow(dis, canvas);
+ } else {
+ dis = (int) (mTouchX - mStartX);
+ if (dis > mViewWidth) {
+ dis = mViewWidth;
+ }
+ mSrcRect.left = mViewWidth - dis;
+ mDestRect.right = dis;
+ canvas.drawBitmap(bitmapList.get(1), 0, 0, null);
+ canvas.drawBitmap(bitmapList.get(0), mSrcRect, mDestRect, null);
+ addShadow(dis, canvas);
+ }
+ }
+
+ //添加阴影
+ private void addShadow(int left, Canvas canvas) {
+ mBackShadowDrawableLR.setBounds(left, 0, left + 30, mScreenHeight);
+ mBackShadowDrawableLR.draw(canvas);
+ }
+
+ @Override
+ public void startAnim() {
+ int dx;
+ if (mDirection == Direction.NEXT) {
+ if (isCancel) {
+ int dis = (int) ((mViewWidth - mStartX) + mTouchX);
+ if (dis > mViewWidth) {
+ dis = mViewWidth;
+ }
+ dx = mViewWidth - dis;
+ } else {
+ dx = (int) -(mTouchX + (mViewWidth - mStartX));
+ }
+ } else {
+ if (isCancel) {
+ dx = (int) -(mTouchX - mStartX);
+ } else {
+ dx = (int) (mViewWidth - (mTouchX - mStartX));
+ }
+ }
+
+ //滑动速度保持一致
+ int duration = (animationSpeed * Math.abs(dx)) / mViewWidth;
+ mScroller.startScroll((int) mTouchX, 0, dx, 0, duration);
+ super.startAnim();
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/HorizonPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/HorizonPageAnim.java
new file mode 100644
index 0000000..bb0d89f
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/HorizonPageAnim.java
@@ -0,0 +1,190 @@
+package xyz.fycz.myreader.widget.page2.animation;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 横向动画的模板
+ */
+
+public abstract class HorizonPageAnim extends PageAnimation {
+ private static final String TAG = "HorizonPageAnim";
+ List bitmapList = new ArrayList<>();
+
+ HorizonPageAnim(int w, int h, View view, OnPageChangeListener listener) {
+ super(w, h, view, listener);
+ //创建图片
+ for (int i = 0; i < 3; i++) {
+ bitmapList.add(Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.ARGB_8888));
+ }
+ }
+
+ /**
+ * 转换页面,在显示下一章的时候,必须首先调用此方法
+ */
+ @Override
+ public boolean changePage() {
+ if (isCancel) return false;
+ switch (mDirection) {
+ case NEXT:
+ Collections.swap(bitmapList, 0, 1);
+ Collections.swap(bitmapList, 1, 2);
+ break;
+ case PREV:
+ Collections.swap(bitmapList, 1, 2);
+ Collections.swap(bitmapList, 0, 1);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ public abstract void drawMove(Canvas canvas);
+
+ @Override
+ public void onTouchEvent(MotionEvent event) {
+ abortAnim();
+ final int slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+ //获取点击位置
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ //设置触摸点
+ setTouchPoint(x, y);
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ break;
+ case MotionEvent.ACTION_MOVE:
+ //判断是否移动了
+ if (!isMove) {
+ isMove = Math.abs(mStartX - x) > slop || Math.abs(mStartY - y) > slop;
+ }
+
+ if (isMove) {
+ //判断是否是准备移动的状态(将要移动但是还没有移动)
+ if (mMoveX == 0 && mMoveY == 0) {
+ //判断翻得是上一页还是下一页
+ if (x - mStartX > 0) {
+ //上一页的参数配置
+ isNext = false;
+ boolean hasPrev = mListener.hasPrev();
+ setDirection(Direction.PREV);
+ //如果上一页不存在
+ if (!hasPrev) {
+ noNext = true;
+ return;
+ }
+ } else {
+ //进行下一页的配置
+ isNext = true;
+ //判断是否下一页存在
+ boolean hasNext = mListener.hasNext(0);
+ //如果存在设置动画方向
+ setDirection(Direction.NEXT);
+
+ //如果不存在表示没有下一页了
+ if (!hasNext) {
+ noNext = true;
+ return;
+ }
+ }
+ } else {
+ //判断是否取消翻页
+ isCancel = isNext ? x - mMoveX > 0 : x - mMoveX < 0;
+ }
+
+ mMoveX = x;
+ mMoveY = y;
+ isRunning = true;
+ mView.invalidate();
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ isRunning = false;
+ if (!isMove) {
+
+ if (!readBookControl.getCanClickTurn()) {
+ return;
+ }
+
+ isNext = x > mScreenWidth / 2 || readBookControl.getClickAllNext();
+
+ if (isNext) {
+ //判断是否下一页存在
+ boolean hasNext = mListener.hasNext(0);
+ //设置动画方向
+ if (!hasNext) {
+ return;
+ }
+ setDirection(Direction.NEXT);
+ } else {
+ boolean hasPrev = mListener.hasPrev();
+ if (!hasPrev) {
+ return;
+ }
+ setDirection(Direction.PREV);
+ }
+ } else {
+ isCancel = Math.abs(mLastX - mStartX) < slop * 3 || isCancel;
+ }
+
+ // 开启翻页效果
+ if (!noNext) {
+ startAnim();
+ }
+ mView.invalidate();
+ break;
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (isRunning && !noNext) {
+ drawMove(canvas);
+ } else {
+ canvas.drawBitmap(getBgBitmap(0), 0, 0, null);
+ isCancel = true;
+ }
+ }
+
+ @Override
+ public void abortAnim() {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ if (changePage()) {
+ mListener.changePage(mDirection);
+ setDirection(PageAnimation.Direction.NONE);
+ }
+ movingFinish();
+ setTouchPoint(mScroller.getFinalX(), mScroller.getFinalY());
+ mView.invalidate();
+ }
+ }
+
+ @Override
+ public Bitmap getBgBitmap(int pageOnCur) {
+ if (pageOnCur < 0) {
+ return bitmapList.get(0);
+ } else if (pageOnCur > 0) {
+ return bitmapList.get(2);
+ }
+ return bitmapList.get(1);
+ }
+
+ public void setCancel(boolean cancel) {
+ isCancel = cancel;
+ }
+
+ public void setNoNext(boolean noNext) {
+ this.noNext = noNext;
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/NonePageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/NonePageAnim.java
new file mode 100644
index 0000000..097d6a7
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/NonePageAnim.java
@@ -0,0 +1,26 @@
+package xyz.fycz.myreader.widget.page2.animation;
+
+import android.graphics.Canvas;
+import android.view.View;
+
+/**
+ * 无动画翻页
+ */
+
+public class NonePageAnim extends HorizonPageAnim {
+
+ public NonePageAnim(int w, int h, View view, OnPageChangeListener listener) {
+ super(w, h, view, listener);
+ }
+
+ @Override
+ public void drawMove(Canvas canvas) {
+ canvas.drawBitmap(bitmapList.get(1), 0, 0, null);
+ }
+
+ @Override
+ public void startAnim() {
+ super.startAnim();
+ isRunning = false;
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/PageAnimation.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/PageAnimation.java
new file mode 100644
index 0000000..d347f89
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/PageAnimation.java
@@ -0,0 +1,273 @@
+package xyz.fycz.myreader.widget.page2.animation;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.LinearInterpolator;
+import android.widget.Scroller;
+
+import androidx.annotation.NonNull;
+
+import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.application.MyApplication;
+import xyz.fycz.myreader.entity.ReadBookControl;
+
+
+/**
+ * 翻页动画抽象类
+ */
+
+public abstract class PageAnimation {
+ //动画速度
+ static final int animationSpeed = 300;
+ //正在使用的View
+ protected View mView;
+ protected ReadBookControl readBookControl = ReadBookControl.getInstance();
+ //滑动装置
+ Scroller mScroller;
+ //监听器
+ protected OnPageChangeListener mListener;
+ //移动方向
+ Direction mDirection = Direction.NONE;
+
+ //屏幕的尺寸
+ int mScreenWidth;
+ int mScreenHeight;
+ int mMarginTop;
+ //视图的尺寸
+ int mViewWidth;
+ int mViewHeight;
+ //起始点
+ float mStartX;
+ float mStartY;
+ //触碰点
+ float mTouchX;
+ float mTouchY;
+ //上一个触碰点
+ float mLastX;
+ float mLastY;
+ private boolean isMoving = false;
+ boolean isRunning = false;
+ private boolean touchInit = false;
+ //是否取消翻页
+ boolean isCancel = false;
+ //可以使用 mLast代替
+ int mMoveX = 0;
+ int mMoveY = 0;
+ //是否移动了
+ boolean isMove = false;
+ //是否翻阅下一页。true表示翻到下一页,false表示上一页。
+ boolean isNext = false;
+ //是否没下一页或者上一页
+ boolean noNext = false;
+
+ PageAnimation(int w, int h, View view, OnPageChangeListener listener) {
+ this(w, h, 0, 0, 0, view, listener);
+ }
+
+ PageAnimation(int w, int h, int marginWidth, int marginTop, int marginBottom, View view, OnPageChangeListener listener) {
+ mScreenWidth = w;
+ mScreenHeight = h;
+
+ //屏幕的间距
+ mMarginTop = marginTop;
+
+ mViewWidth = mScreenWidth - marginWidth * 2;
+ mViewHeight = mScreenHeight - mMarginTop - marginBottom;
+
+ mView = view;
+ mListener = listener;
+
+ mScroller = new Scroller(mView.getContext(), new LinearInterpolator());
+ }
+
+ public Scroller getScroller() {
+ return mScroller;
+ }
+
+ public void setStartPoint(float x, float y) {
+ mStartX = x;
+ mStartY = y;
+
+ mLastX = mStartX;
+ mLastY = mStartY;
+ }
+
+ public void setTouchPoint(float x, float y) {
+ mLastX = mTouchX;
+ mLastY = mTouchY;
+
+ mTouchX = x;
+ mTouchY = y;
+ }
+
+ public void setTouchInitFalse() {
+ touchInit = false;
+ }
+
+ public void initTouch(int x, int y) {
+ if (!touchInit) {
+ //移动的点击位置
+ mMoveX = 0;
+ mMoveY = 0;
+ //是否移动
+ isMove = false;
+ //是否存在下一章
+ noNext = false;
+ //是下一章还是前一章
+ isNext = false;
+ //是否正在执行动画
+ isRunning = false;
+ //取消
+ isCancel = false;
+ //设置起始位置的触摸点
+ setStartPoint(x, y);
+ touchInit = true;
+ }
+ }
+
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+ void movingFinish() {
+ isMoving = false;
+ isRunning = false;
+ }
+
+ /**
+ * 开启翻页动画
+ */
+ public void startAnim() {
+ isRunning = true;
+ isMoving = true;
+ mView.invalidate();
+ }
+
+ public void setDirection(Direction direction) {
+ mDirection = direction;
+ }
+
+ public void clear() {
+ mView = null;
+ }
+
+ /**
+ * 点击事件的处理
+ */
+ public abstract void onTouchEvent(MotionEvent event);
+
+ /**
+ * 绘制图形
+ */
+ public abstract void draw(Canvas canvas);
+
+ /**
+ * 滚动动画
+ * 必须放在computeScroll()方法中执行
+ */
+ public void scrollAnim() {
+ if (mScroller.computeScrollOffset()) {
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+
+ setTouchPoint(x, y);
+
+ mView.postInvalidate();
+ } else if (isMoving) {
+ if (changePage()) {
+ mListener.changePage(mDirection);
+ setDirection(Direction.NONE);
+ }
+ movingFinish();
+ }
+ }
+
+ /**
+ * 取消动画
+ */
+ public abstract void abortAnim();
+
+ public abstract boolean changePage();
+
+ /**
+ * 获取背景板
+ * pageOnCur: 位于当前页的位置, 小于0上一页, 0 当前页, 大于0下一页
+ */
+ public abstract Bitmap getBgBitmap(int pageOnCur);
+
+ /**
+ * 翻页模式
+ */
+ public enum Mode {
+ COVER(MyApplication.getApplication().getString(R.string.page_mode_COVER)),
+ SIMULATION(MyApplication.getApplication().getString(R.string.page_mode_SIMULATION)),
+ SLIDE(MyApplication.getApplication().getString(R.string.page_mode_SLIDE)),
+ SCROLL(MyApplication.getApplication().getString(R.string.page_mode_SCROLL)),
+ NONE(MyApplication.getApplication().getString(R.string.page_mode_NONE));
+
+ private String name;
+
+ Mode(String name) {
+ this.name = name;
+ }
+
+ public static Mode getPageMode(int pageMode) {
+ switch (pageMode) {
+ case 0:
+ return COVER;
+ case 1:
+ return SIMULATION;
+ case 2:
+ return SLIDE;
+ case 3:
+ return SCROLL;
+ case 4:
+ return NONE;
+ default:
+ return COVER;
+ }
+ }
+
+ public static String[] getAllPageMode() {
+ return new String[]{COVER.name, SIMULATION.name, SLIDE.name, SCROLL.name, NONE.name};
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return this.name;
+ }
+ }
+
+ /**
+ * 翻页方向
+ */
+ public enum Direction {
+ NONE(true), NEXT(true), PREV(true);
+
+ public final boolean isHorizontal;
+
+ Direction(boolean isHorizontal) {
+ this.isHorizontal = isHorizontal;
+ }
+ }
+
+ public interface OnPageChangeListener {
+
+ void resetScroll();
+
+ boolean hasPrev();
+
+ boolean hasNext(int pageOnCur);
+
+ void drawContent(Canvas canvas, float offset);
+
+ void drawBackground(Canvas canvas);
+
+ void changePage(Direction direction);
+
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/ScrollPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/ScrollPageAnim.java
new file mode 100644
index 0000000..bde29de
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/ScrollPageAnim.java
@@ -0,0 +1,148 @@
+package xyz.fycz.myreader.widget.page2.animation;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+
+public class ScrollPageAnim extends PageAnimation {
+ private static final String TAG = "ScrollAnimation";
+ // 滑动追踪的时间
+ private static final int VELOCITY_DURATION = 1000;
+ private VelocityTracker mVelocity;
+ // 整个Bitmap的背景显示
+ private Bitmap mBgBitmap;
+
+ public ScrollPageAnim(int w, int h, int marginWidth, int marginTop, int marginBottom, View view, OnPageChangeListener listener) {
+ super(w, h, marginWidth, marginTop, marginBottom, view, listener);
+ mListener.resetScroll();
+ mBgBitmap = Bitmap.createBitmap(mScreenWidth, mScreenHeight, Bitmap.Config.ARGB_8888);
+ }
+
+ @Override
+ public void onTouchEvent(MotionEvent event) {
+ final int slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ // 初始化速度追踪器
+ if (mVelocity == null) {
+ mVelocity = VelocityTracker.obtain();
+ }
+
+ mVelocity.addMovement(event);
+ // 设置触碰点
+ setTouchPoint(x, y);
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ isMove = false;
+ isRunning = false;
+ // 设置起始点
+ setStartPoint(x, y);
+ // 停止动画
+ abortAnim();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (!isMove) {
+ isMove = Math.abs(mStartX - x) > slop || Math.abs(mStartY - y) > slop;
+ }
+ mVelocity.computeCurrentVelocity(VELOCITY_DURATION);
+ isRunning = true;
+ // 进行刷新
+ mView.postInvalidate();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ isRunning = false;
+ if (!isMove) {
+
+ if (!readBookControl.getCanClickTurn() || readBookControl.disableScrollClickTurn()) {
+ return;
+ }
+
+ //是否翻阅下一页。true表示翻到下一页,false表示上一页。
+ boolean isNext = x > mScreenWidth / 2 || readBookControl.getClickAllNext();
+ if (isNext) {
+ startAnim(Direction.NEXT);
+ } else {
+ startAnim(Direction.PREV);
+ }
+ } else {
+ // 开启动画
+ startAnim();
+ }
+ // 删除检测器
+ if (mVelocity != null) {
+ mVelocity.recycle();
+ mVelocity = null;
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ //进行布局
+ float offset = mLastY - mTouchY;
+
+ //绘制背景
+ mListener.drawBackground(canvas);
+ //绘制内容
+ canvas.save();
+ //移动位置
+ canvas.translate(0, mMarginTop);
+ //裁剪显示区域
+ canvas.clipRect(0, 0, mViewWidth, mViewHeight);
+ mListener.drawContent(canvas, offset);
+ canvas.restore();
+ }
+
+ @Override
+ public synchronized void startAnim() {
+ super.startAnim();
+ //惯性滚动
+ mScroller.fling(0, (int) mTouchY, 0, (int) mVelocity.getYVelocity(),
+ 0, 0, -10 * mViewHeight, 10 * mViewHeight);
+ }
+
+ /**
+ * 翻页动画
+ */
+ public void startAnim(Direction direction) {
+ setStartPoint(0, 0);
+ setTouchPoint(0, 0);
+ switch (direction) {
+ case NEXT:
+ super.startAnim();
+ mScroller.startScroll(0, 0, 0, -mViewHeight + 300, animationSpeed);
+ break;
+ case PREV:
+ super.startAnim();
+ mScroller.startScroll(0, 0, 0, mViewHeight - 300, animationSpeed);
+ break;
+ }
+ }
+
+ @Override
+ public void abortAnim() {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ isRunning = false;
+ }
+ }
+
+ @Override
+ public boolean changePage() {
+ return false;
+ }
+
+ @Override
+ public Bitmap getBgBitmap(int pageOnCur) {
+ return mBgBitmap;
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SimulationPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SimulationPageAnim.java
new file mode 100644
index 0000000..3cb3a03
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SimulationPageAnim.java
@@ -0,0 +1,569 @@
+package xyz.fycz.myreader.widget.page2.animation;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.Region;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Build;
+import android.view.View;
+
+/**
+ * 仿真翻页
+ */
+public class SimulationPageAnim extends HorizonPageAnim {
+ private static final String TAG = "SimulationPageAnim";
+
+ private int mCornerX = 1; // 拖拽点对应的页脚
+ private int mCornerY = 1;
+ private Path mPath0;
+ private Path mPath1;
+
+ private PointF mBezierStart1 = new PointF(); // 贝塞尔曲线起始点
+ private PointF mBezierControl1 = new PointF(); // 贝塞尔曲线控制点
+ private PointF mBezierVertex1 = new PointF(); // 贝塞尔曲线顶点
+ private PointF mBezierEnd1 = new PointF(); // 贝塞尔曲线结束点
+
+ private PointF mBezierStart2 = new PointF(); // 另一条贝塞尔曲线
+ private PointF mBezierControl2 = new PointF();
+ private PointF mBezierVertex2 = new PointF();
+ private PointF mBezierEnd2 = new PointF();
+
+ private float mMiddleX;
+ private float mMiddleY;
+ private float mDegrees;
+ private float mTouchToCornerDis;
+ private ColorMatrixColorFilter mColorMatrixFilter;
+ private Matrix mMatrix;
+ private float[] mMatrixArray = {0, 0, 0, 0, 0, 0, 0, 0, 1.0f};
+
+ private boolean mIsRT_LB; // 是否属于右上左下
+ private float mMaxLength;
+ private int[] mBackShadowColors;// 背面颜色组
+ private int[] mFrontShadowColors;// 前面颜色组
+ private GradientDrawable mBackShadowDrawableLR; // 有阴影的GradientDrawable
+ private GradientDrawable mBackShadowDrawableRL;
+ private GradientDrawable mFolderShadowDrawableLR;
+ private GradientDrawable mFolderShadowDrawableRL;
+
+ private GradientDrawable mFrontShadowDrawableHBT;
+ private GradientDrawable mFrontShadowDrawableHTB;
+ private GradientDrawable mFrontShadowDrawableVLR;
+ private GradientDrawable mFrontShadowDrawableVRL;
+
+ private Paint mPaint;
+
+
+ public SimulationPageAnim(int w, int h, View view, OnPageChangeListener listener) {
+ super(w, h, view, listener);
+ mPath0 = new Path();
+ mPath1 = new Path();
+ mMaxLength = (float) Math.hypot(mScreenWidth, mScreenHeight);
+ mPaint = new Paint();
+
+ mPaint.setStyle(Paint.Style.FILL);
+
+ createDrawable();
+
+ ColorMatrix cm = new ColorMatrix();//设置颜色数组
+ float[] array = {1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0};
+ cm.set(array);
+ mColorMatrixFilter = new ColorMatrixColorFilter(cm);
+ mMatrix = new Matrix();
+
+ mTouchX = 0.01f; // 不让x,y为0,否则在点计算时会有问题
+ mTouchY = 0.01f;
+ }
+
+ @Override
+ public void setStartPoint(float x, float y) {
+ super.setStartPoint(x, y);
+ calcCornerXY(x, y);
+ }
+
+ @Override
+ public void setTouchPoint(float x, float y) {
+ super.setTouchPoint(x, y);
+ //触摸y中间位置吧y变成屏幕高度
+ if ((mStartY > mScreenHeight / 3.0 && mStartY < mScreenHeight * 2 / 3.0) || mDirection.equals(Direction.PREV)) {
+ mTouchY = mScreenHeight;
+ }
+
+ if (mStartY > mScreenHeight / 3.0 && mStartY < mScreenHeight / 2.0 && mDirection.equals(Direction.NEXT)) {
+ mTouchY = 1;
+ }
+ }
+
+ @Override
+ public void setDirection(Direction direction) {
+ super.setDirection(direction);
+
+ switch (direction) {
+ case PREV:
+ //上一页滑动不出现对角
+ if (mStartX > mScreenWidth / 2.0) {
+ calcCornerXY(mStartX, mScreenHeight);
+ } else {
+ calcCornerXY(mScreenWidth - mStartX, mScreenHeight);
+ }
+ break;
+ case NEXT:
+ if (mScreenWidth / 2.0 > mStartX) {
+ calcCornerXY(mScreenWidth - mStartX, mStartY);
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void startAnim() {
+ int dx, dy;
+ // dx 水平方向滑动的距离,负值会使滚动向左滚动
+ // dy 垂直方向滑动的距离,负值会使滚动向上滚动
+ if (isCancel) {
+
+ if (mCornerX > 0 && mDirection.equals(Direction.NEXT)) {
+ dx = (int) (mScreenWidth - mTouchX);
+ } else {
+ dx = -(int) mTouchX;
+ }
+
+ if (!mDirection.equals(Direction.NEXT)) {
+ dx = (int) -(mScreenWidth + mTouchX);
+ }
+
+ if (mCornerY > 0) {
+ dy = (int) (mScreenHeight - mTouchY);
+ } else {
+ dy = -(int) mTouchY; // 防止mTouchY最终变为0
+ }
+ } else {
+ if (mCornerX > 0 && mDirection.equals(Direction.NEXT)) {
+ dx = -(int) (mScreenWidth + mTouchX);
+ } else {
+ dx = (int) (mScreenWidth - mTouchX + mScreenWidth);
+ }
+ if (mCornerY > 0) {
+ dy = (int) (mScreenHeight - mTouchY);
+ } else {
+ dy = (int) (1 - mTouchY); // 防止mTouchY最终变为0
+ }
+ }
+ mScroller.startScroll((int) mTouchX, (int) mTouchY, dx, dy, animationSpeed);
+ super.startAnim();
+ }
+
+ @Override
+ public void drawMove(Canvas canvas) {
+ if (mDirection == Direction.NEXT) {
+ calcPoints();
+ drawCurrentPageArea(canvas, bitmapList.get(1));//绘制翻页时的正面页
+ drawNextPageAreaAndShadow(canvas, bitmapList.get(2));
+ drawCurrentPageShadow(canvas);
+ drawCurrentBackArea(canvas, bitmapList.get(1));
+ } else {
+ calcPoints();
+ drawCurrentPageArea(canvas, bitmapList.get(0));
+ drawNextPageAreaAndShadow(canvas, bitmapList.get(1));
+ drawCurrentPageShadow(canvas);
+ drawCurrentBackArea(canvas, bitmapList.get(0));
+ }
+ }
+
+ /**
+ * 创建阴影的GradientDrawable
+ */
+ private void createDrawable() {
+ int[] color = {0x333333, 0xb0333333};
+ mFolderShadowDrawableRL = new GradientDrawable(
+ GradientDrawable.Orientation.RIGHT_LEFT, color);
+ mFolderShadowDrawableRL
+ .setGradientType(GradientDrawable.LINEAR_GRADIENT);
+
+ mFolderShadowDrawableLR = new GradientDrawable(
+ GradientDrawable.Orientation.LEFT_RIGHT, color);
+ mFolderShadowDrawableLR
+ .setGradientType(GradientDrawable.LINEAR_GRADIENT);
+
+ mBackShadowColors = new int[]{0xff111111, 0x111111};
+ mBackShadowDrawableRL = new GradientDrawable(
+ GradientDrawable.Orientation.RIGHT_LEFT, mBackShadowColors);
+ mBackShadowDrawableRL.setGradientType(GradientDrawable.LINEAR_GRADIENT);
+
+ mBackShadowDrawableLR = new GradientDrawable(
+ GradientDrawable.Orientation.LEFT_RIGHT, mBackShadowColors);
+ mBackShadowDrawableLR.setGradientType(GradientDrawable.LINEAR_GRADIENT);
+
+ mFrontShadowColors = new int[]{0x80111111, 0x111111};
+ mFrontShadowDrawableVLR = new GradientDrawable(
+ GradientDrawable.Orientation.LEFT_RIGHT, mFrontShadowColors);
+ mFrontShadowDrawableVLR
+ .setGradientType(GradientDrawable.LINEAR_GRADIENT);
+ mFrontShadowDrawableVRL = new GradientDrawable(
+ GradientDrawable.Orientation.RIGHT_LEFT, mFrontShadowColors);
+ mFrontShadowDrawableVRL
+ .setGradientType(GradientDrawable.LINEAR_GRADIENT);
+
+ mFrontShadowDrawableHTB = new GradientDrawable(
+ GradientDrawable.Orientation.TOP_BOTTOM, mFrontShadowColors);
+ mFrontShadowDrawableHTB
+ .setGradientType(GradientDrawable.LINEAR_GRADIENT);
+
+ mFrontShadowDrawableHBT = new GradientDrawable(
+ GradientDrawable.Orientation.BOTTOM_TOP, mFrontShadowColors);
+ mFrontShadowDrawableHBT
+ .setGradientType(GradientDrawable.LINEAR_GRADIENT);
+ }
+
+ /**
+ * 绘制翻起页背面
+ */
+ private void drawCurrentBackArea(Canvas canvas, Bitmap bitmap) {
+ int i = (int) (mBezierStart1.x + mBezierControl1.x) / 2;
+ float f1 = Math.abs(i - mBezierControl1.x);
+ int i1 = (int) (mBezierStart2.y + mBezierControl2.y) / 2;
+ float f2 = Math.abs(i1 - mBezierControl2.y);
+ float f3 = Math.min(f1, f2);
+ mPath1.reset();
+ mPath1.moveTo(mBezierVertex2.x, mBezierVertex2.y);
+ mPath1.lineTo(mBezierVertex1.x, mBezierVertex1.y);
+ mPath1.lineTo(mBezierEnd1.x, mBezierEnd1.y);
+ mPath1.lineTo(mTouchX, mTouchY);
+ mPath1.lineTo(mBezierEnd2.x, mBezierEnd2.y);
+ mPath1.close();
+ GradientDrawable mFolderShadowDrawable;
+ int left;
+ int right;
+ if (mIsRT_LB) {
+ left = (int) (mBezierStart1.x - 1);
+ right = (int) (mBezierStart1.x + f3 + 1);
+ mFolderShadowDrawable = mFolderShadowDrawableLR;
+ } else {
+ left = (int) (mBezierStart1.x - f3 - 1);
+ right = (int) (mBezierStart1.x + 1);
+ mFolderShadowDrawable = mFolderShadowDrawableRL;
+ }
+ canvas.save();
+ try {
+ canvas.clipPath(mPath0);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ canvas.clipPath(mPath1);
+ } else {
+ canvas.clipPath(mPath1, Region.Op.INTERSECT);
+ }
+ } catch (Exception ignored) {
+ }
+
+ mPaint.setColorFilter(mColorMatrixFilter);
+
+ float dis = (float) Math.hypot(mCornerX - mBezierControl1.x,
+ mBezierControl2.y - mCornerY);
+ float f8 = (mCornerX - mBezierControl1.x) / dis;
+ float f9 = (mBezierControl2.y - mCornerY) / dis;
+ mMatrixArray[0] = 1 - 2 * f9 * f9;
+ mMatrixArray[1] = 2 * f8 * f9;
+ mMatrixArray[3] = mMatrixArray[1];
+ mMatrixArray[4] = 1 - 2 * f8 * f8;
+ mMatrix.reset();
+ mMatrix.setValues(mMatrixArray);
+ mMatrix.preTranslate(-mBezierControl1.x, -mBezierControl1.y);
+ mMatrix.postTranslate(mBezierControl1.x, mBezierControl1.y);
+ canvas.drawBitmap(bitmap, mMatrix, mPaint);
+
+ mPaint.setColorFilter(null);
+
+ canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y);
+ mFolderShadowDrawable.setBounds(
+ left, (int) mBezierStart1.y,
+ right, (int) (mBezierStart1.y + mMaxLength)
+ );
+ mFolderShadowDrawable.draw(canvas);
+ canvas.restore();
+ }
+
+ /**
+ * 绘制翻起页的阴影
+ */
+ private void drawCurrentPageShadow(Canvas canvas) {
+ double degree;
+ if (mIsRT_LB) {
+ degree = Math.PI / 4 - Math.atan2(mBezierControl1.y - mTouchY, mTouchX - mBezierControl1.x);
+ } else {
+ degree = Math.PI / 4 - Math.atan2(mTouchY - mBezierControl1.y, mTouchX - mBezierControl1.x);
+ }
+ // 翻起页阴影顶点与touch点的距离
+ double d1 = (float) 25 * 1.414 * Math.cos(degree);
+ double d2 = (float) 25 * 1.414 * Math.sin(degree);
+ float x = (float) (mTouchX + d1);
+ float y;
+ if (mIsRT_LB) {
+ y = (float) (mTouchY + d2);
+ } else {
+ y = (float) (mTouchY - d2);
+ }
+ mPath1.reset();
+ mPath1.moveTo(x, y);
+ mPath1.lineTo(mTouchX, mTouchY);
+ mPath1.lineTo(mBezierControl1.x, mBezierControl1.y);
+ mPath1.lineTo(mBezierStart1.x, mBezierStart1.y);
+ mPath1.close();
+ canvas.save();
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ canvas.clipOutPath(mPath0);
+ } else {
+ canvas.clipPath(mPath0, Region.Op.XOR);
+ }
+
+ canvas.clipPath(mPath1, Region.Op.INTERSECT);
+ } catch (Exception ignored) {
+ }
+
+ int leftx;
+ int rightx;
+ GradientDrawable mCurrentPageShadow;
+ if (mIsRT_LB) {
+ leftx = (int) (mBezierControl1.x);
+ rightx = (int) mBezierControl1.x + 25;
+ mCurrentPageShadow = mFrontShadowDrawableVLR;
+ } else {
+ leftx = (int) (mBezierControl1.x - 25);
+ rightx = (int) mBezierControl1.x + 1;
+ mCurrentPageShadow = mFrontShadowDrawableVRL;
+ }
+
+ float rotateDegrees;
+ rotateDegrees = (float) Math.toDegrees(Math.atan2(mTouchX - mBezierControl1.x, mBezierControl1.y - mTouchY));
+ canvas.rotate(rotateDegrees, mBezierControl1.x, mBezierControl1.y);
+ mCurrentPageShadow.setBounds(
+ leftx, (int) (mBezierControl1.y - mMaxLength),
+ rightx, (int) (mBezierControl1.y));
+ mCurrentPageShadow.draw(canvas);
+ canvas.restore();
+
+ mPath1.reset();
+ mPath1.moveTo(x, y);
+ mPath1.lineTo(mTouchX, mTouchY);
+ mPath1.lineTo(mBezierControl2.x, mBezierControl2.y);
+ mPath1.lineTo(mBezierStart2.x, mBezierStart2.y);
+ mPath1.close();
+ canvas.save();
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ canvas.clipOutPath(mPath0);
+ } else {
+ canvas.clipPath(mPath0, Region.Op.XOR);
+ }
+
+ canvas.clipPath(mPath1);
+ } catch (Exception ignored) {
+ }
+
+ if (mIsRT_LB) {
+ leftx = (int) (mBezierControl2.y);
+ rightx = (int) (mBezierControl2.y + 25);
+ mCurrentPageShadow = mFrontShadowDrawableHTB;
+ } else {
+ leftx = (int) (mBezierControl2.y - 25);
+ rightx = (int) (mBezierControl2.y + 1);
+ mCurrentPageShadow = mFrontShadowDrawableHBT;
+ }
+ rotateDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl2.y - mTouchY, mBezierControl2.x - mTouchX));
+ canvas.rotate(rotateDegrees, mBezierControl2.x, mBezierControl2.y);
+ float temp;
+ if (mBezierControl2.y < 0) temp = mBezierControl2.y - mScreenHeight;
+ else temp = mBezierControl2.y;
+
+ int hmg = (int) Math.hypot(mBezierControl2.x, temp);
+ if (hmg > mMaxLength)
+ mCurrentPageShadow.setBounds(
+ (int) (mBezierControl2.x - 25) - hmg, leftx,
+ (int) (mBezierControl2.x + mMaxLength) - hmg, rightx
+ );
+ else
+ mCurrentPageShadow.setBounds(
+ (int) (mBezierControl2.x - mMaxLength), leftx,
+ (int) (mBezierControl2.x), rightx);
+
+ mCurrentPageShadow.draw(canvas);
+ canvas.restore();
+ }
+
+ private void drawNextPageAreaAndShadow(Canvas canvas, Bitmap bitmap) {
+ mPath1.reset();
+ mPath1.moveTo(mBezierStart1.x, mBezierStart1.y);
+ mPath1.lineTo(mBezierVertex1.x, mBezierVertex1.y);
+ mPath1.lineTo(mBezierVertex2.x, mBezierVertex2.y);
+ mPath1.lineTo(mBezierStart2.x, mBezierStart2.y);
+ mPath1.lineTo(mCornerX, mCornerY);
+ mPath1.close();
+
+ mDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl1.x - mCornerX,
+ mBezierControl2.y - mCornerY));
+ int leftx;
+ int rightx;
+ GradientDrawable mBackShadowDrawable;
+ if (mIsRT_LB) { //左下及右上
+ leftx = (int) (mBezierStart1.x);
+ rightx = (int) (mBezierStart1.x + mTouchToCornerDis / 4);
+ mBackShadowDrawable = mBackShadowDrawableLR;
+ } else {
+ leftx = (int) (mBezierStart1.x - mTouchToCornerDis / 4);
+ rightx = (int) mBezierStart1.x;
+ mBackShadowDrawable = mBackShadowDrawableRL;
+ }
+ canvas.save();
+ try {
+ canvas.clipPath(mPath0);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ canvas.clipPath(mPath1);
+ } else {
+ canvas.clipPath(mPath1, Region.Op.INTERSECT);
+ }
+ //canvas.clipPath(mPath1, Region.Op.INTERSECT);
+ } catch (Exception ignored) {
+ }
+
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y);
+ mBackShadowDrawable.setBounds(
+ leftx, (int) mBezierStart1.y,
+ rightx, (int) (mMaxLength + mBezierStart1.y));//左上及右下角的xy坐标值,构成一个矩形
+ mBackShadowDrawable.draw(canvas);
+ canvas.restore();
+ }
+
+ private void drawCurrentPageArea(Canvas canvas, Bitmap bitmap) {
+ mPath0.reset();
+ mPath0.moveTo(mBezierStart1.x, mBezierStart1.y);
+ mPath0.quadTo(mBezierControl1.x, mBezierControl1.y, mBezierEnd1.x, mBezierEnd1.y);
+ mPath0.lineTo(mTouchX, mTouchY);
+ mPath0.lineTo(mBezierEnd2.x, mBezierEnd2.y);
+ mPath0.quadTo(mBezierControl2.x, mBezierControl2.y, mBezierStart2.x, mBezierStart2.y);
+ mPath0.lineTo(mCornerX, mCornerY);
+ mPath0.close();
+
+ canvas.save();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ canvas.clipOutPath(mPath0);
+ } else {
+ canvas.clipPath(mPath0, Region.Op.XOR);
+ }
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ try {
+ canvas.restore();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 计算拖拽点对应的拖拽脚
+ */
+ private void calcCornerXY(float x, float y) {
+ if (x <= mScreenWidth / 2.0) {
+ mCornerX = 0;
+ } else {
+ mCornerX = mScreenWidth;
+ }
+ if (y <= mScreenHeight / 2.0) {
+ mCornerY = 0;
+ } else {
+ mCornerY = mScreenHeight;
+ }
+
+ mIsRT_LB = (mCornerX == 0 && mCornerY == mScreenHeight)
+ || (mCornerX == mScreenWidth && mCornerY == 0);
+
+ }
+
+ private void calcPoints() {
+ mMiddleX = (mTouchX + mCornerX) / 2;
+ mMiddleY = (mTouchY + mCornerY) / 2;
+ mBezierControl1.x =
+ mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX);
+ mBezierControl1.y = mCornerY;
+
+ mBezierControl2.x = mCornerX;
+ if (mCornerY - mMiddleY == 0) {
+ mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f;
+ } else {
+ mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX)
+ * (mCornerX - mMiddleX) / (mCornerY - mMiddleY);
+ }
+ mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2;
+ mBezierStart1.y = mCornerY;
+
+ // 固定左边上下两个点
+ if (mTouchX > 0 && mTouchX < mScreenWidth) {
+ if (mBezierStart1.x < 0 || mBezierStart1.x > mScreenWidth) {
+ if (mBezierStart1.x < 0)
+ mBezierStart1.x = mScreenWidth - mBezierStart1.x;
+
+ float f1 = Math.abs(mCornerX - mTouchX);
+ float f2 = mScreenWidth * f1 / mBezierStart1.x;
+ mTouchX = Math.abs(mCornerX - f2);
+
+ float f3 = Math.abs(mCornerX - mTouchX) * Math.abs(mCornerY - mTouchY) / f1;
+ mTouchY = Math.abs(mCornerY - f3);
+
+ mMiddleX = (mTouchX + mCornerX) / 2;
+ mMiddleY = (mTouchY + mCornerY) / 2;
+
+ mBezierControl1.x =
+ mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX);
+ mBezierControl1.y = mCornerY;
+
+ mBezierControl2.x = mCornerX;
+ float f5 = mCornerY - mMiddleY;
+ if (f5 == 0) {
+ mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX)
+ * (mCornerX - mMiddleX) / 0.1f;
+ } else {
+ mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX)
+ * (mCornerX - mMiddleX) / (mCornerY - mMiddleY);
+ }
+
+ mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2;
+ }
+ }
+ mBezierStart2.x = mCornerX;
+ mBezierStart2.y = mBezierControl2.y - (mCornerY - mBezierControl2.y) / 2;
+
+ mTouchToCornerDis = (float) Math.hypot((mTouchX - mCornerX), (mTouchY - mCornerY));
+
+ mBezierEnd1 =
+ getCross(new PointF(mTouchX, mTouchY), mBezierControl1, mBezierStart1, mBezierStart2);
+ mBezierEnd2 =
+ getCross(new PointF(mTouchX, mTouchY), mBezierControl2, mBezierStart1, mBezierStart2);
+
+ mBezierVertex1.x = (mBezierStart1.x + 2 * mBezierControl1.x + mBezierEnd1.x) / 4;
+ mBezierVertex1.y = (2 * mBezierControl1.y + mBezierStart1.y + mBezierEnd1.y) / 4;
+ mBezierVertex2.x = (mBezierStart2.x + 2 * mBezierControl2.x + mBezierEnd2.x) / 4;
+ mBezierVertex2.y = (2 * mBezierControl2.y + mBezierStart2.y + mBezierEnd2.y) / 4;
+ }
+
+ /**
+ * 求解直线P1P2和直线P3P4的交点坐标
+ */
+ private PointF getCross(PointF P1, PointF P2, PointF P3, PointF P4) {
+ PointF CrossP = new PointF();
+ // 二元函数通式: y=ax+b
+ float a1 = (P2.y - P1.y) / (P2.x - P1.x);
+ float b1 = ((P1.x * P2.y) - (P2.x * P1.y)) / (P1.x - P2.x);
+
+ float a2 = (P4.y - P3.y) / (P4.x - P3.x);
+ float b2 = ((P3.x * P4.y) - (P4.x * P3.y)) / (P3.x - P4.x);
+ CrossP.x = (b2 - b1) / (a1 - a2);
+ CrossP.y = a1 * CrossP.x + b1;
+ return CrossP;
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SlidePageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SlidePageAnim.java
new file mode 100644
index 0000000..fba5a4e
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SlidePageAnim.java
@@ -0,0 +1,85 @@
+package xyz.fycz.myreader.widget.page2.animation;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.view.View;
+
+/**
+ * 滑动翻页
+ */
+
+public class SlidePageAnim extends HorizonPageAnim {
+ private Rect mSrcRect, mDestRect, mNextSrcRect, mNextDestRect;
+
+ public SlidePageAnim(int w, int h, View view, OnPageChangeListener listener) {
+ super(w, h, view, listener);
+ mSrcRect = new Rect(0, 0, mViewWidth, mViewHeight);
+ mDestRect = new Rect(0, 0, mViewWidth, mViewHeight);
+ mNextSrcRect = new Rect(0, 0, mViewWidth, mViewHeight);
+ mNextDestRect = new Rect(0, 0, mViewWidth, mViewHeight);
+ }
+
+ @Override
+ public void drawMove(Canvas canvas) {
+ int dis;
+ if (mDirection == Direction.NEXT) {//左半边的剩余区域
+ dis = (int) (mScreenWidth - mStartX + mTouchX);
+ if (dis > mScreenWidth) {
+ dis = mScreenWidth;
+ }
+ //计算bitmap截取的区域
+ mSrcRect.left = mScreenWidth - dis;
+ //计算bitmap在canvas显示的区域
+ mDestRect.right = dis;
+ //计算下一页截取的区域
+ mNextSrcRect.right = mScreenWidth - dis;
+ //计算下一页在canvas显示的区域
+ mNextDestRect.left = dis;
+
+ canvas.drawBitmap(bitmapList.get(2), mNextSrcRect, mNextDestRect, null);
+ canvas.drawBitmap(bitmapList.get(1), mSrcRect, mDestRect, null);
+ } else {
+ dis = (int) (mTouchX - mStartX);
+ if (dis < 0) {
+ dis = 0;
+ mStartX = mTouchX;
+ }
+ mSrcRect.left = mScreenWidth - dis;
+ mDestRect.right = dis;
+
+ //计算下一页截取的区域
+ mNextSrcRect.right = mScreenWidth - dis;
+ //计算下一页在canvas显示的区域
+ mNextDestRect.left = dis;
+
+ canvas.drawBitmap(bitmapList.get(1), mNextSrcRect, mNextDestRect, null);
+ canvas.drawBitmap(bitmapList.get(0), mSrcRect, mDestRect, null);
+ }
+ }
+
+ @Override
+ public void startAnim() {
+ int dx;
+ if (mDirection == Direction.NEXT) {
+ if (isCancel) {
+ int dis = (int) ((mScreenWidth - mStartX) + mTouchX);
+ if (dis > mScreenWidth) {
+ dis = mScreenWidth;
+ }
+ dx = mScreenWidth - dis;
+ } else {
+ dx = (int) -(mTouchX + (mScreenWidth - mStartX));
+ }
+ } else {
+ if (isCancel) {
+ dx = (int) -Math.abs(mTouchX - mStartX);
+ } else {
+ dx = (int) (mScreenWidth - (mTouchX - mStartX));
+ }
+ }
+ //滑动速度保持一致
+ int duration = (animationSpeed * Math.abs(dx)) / mScreenWidth;
+ mScroller.startScroll((int) mTouchX, 0, dx, 0, duration);
+ super.startAnim();
+ }
+}
diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml
index 95a9075..092a560 100644
--- a/app/src/main/res/anim/fade_in.xml
+++ b/app/src/main/res/anim/fade_in.xml
@@ -1,8 +1,8 @@
+ android:duration="500"
+ android:fromAlpha="0"
+ android:toAlpha="1"
+ android:interpolator="@android:anim/decelerate_interpolator"/>
\ No newline at end of file
diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml
index 3dfb4a7..fcbca92 100644
--- a/app/src/main/res/anim/fade_out.xml
+++ b/app/src/main/res/anim/fade_out.xml
@@ -1,8 +1,8 @@
+ android:duration="500"
+ android:fromAlpha="1"
+ android:toAlpha="0"
+ android:interpolator="@android:anim/decelerate_interpolator"/>
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/image_btn_bg.xml b/app/src/main/res/drawable-xxxhdpi/image_btn_bg.xml
new file mode 100644
index 0000000..75975f7
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/image_btn_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/image_view_bg1.xml b/app/src/main/res/drawable-xxxhdpi/image_view_bg1.xml
new file mode 100644
index 0000000..bc8ec2f
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/image_view_bg1.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/image_view_bg2.xml b/app/src/main/res/drawable-xxxhdpi/image_view_bg2.xml
new file mode 100644
index 0000000..cb09085
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/image_view_bg2.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml
index 4cef809..86c01b3 100644
--- a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml
+++ b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml
@@ -1,6 +1,7 @@
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml
index 7aae36a..34078c8 100644
--- a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml
+++ b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml
@@ -1,6 +1,7 @@
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/setting_circle1.xml b/app/src/main/res/drawable-xxxhdpi/setting_circle1.xml
new file mode 100644
index 0000000..ab08311
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/setting_circle1.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/setting_circle.xml b/app/src/main/res/drawable-xxxhdpi/setting_circle2.xml
similarity index 84%
rename from app/src/main/res/drawable-xxxhdpi/setting_circle.xml
rename to app/src/main/res/drawable-xxxhdpi/setting_circle2.xml
index 128b945..5bea095 100644
--- a/app/src/main/res/drawable-xxxhdpi/setting_circle.xml
+++ b/app/src/main/res/drawable-xxxhdpi/setting_circle2.xml
@@ -4,7 +4,7 @@
+ android:color="@color/textSecondary" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_aa.xml b/app/src/main/res/drawable/ic_aa.xml
index d204794..34c4503 100644
--- a/app/src/main/res/drawable/ic_aa.xml
+++ b/app/src/main/res/drawable/ic_aa.xml
@@ -1,9 +1,9 @@
+ android:pathData="M268.97,205.07h44.37l267.95,659.87h-39.73L458.41,656.04L124.79,656.04L40.69,864.94L0,864.94l268.97,-659.87zM137.69,622.87h307.75L293,243.85L290.13,243.85l-152.51,378.88zM837.29,374.1c64.72,0 113.73,18.5 146.09,55.5 26.76,30.45 40.62,73.05 40.62,126.57v308.7h-34.13v-102.6a286.72,286.72 0,0 1,-79.53 76.73c-41.64,25.94 -86.02,38.84 -133.12,38.84 -45.26,0 -82.19,-12.01 -109.91,-34.2 -30.52,-23.14 -45.33,-54.55 -45.33,-94.28 0,-55.43 23.07,-95.23 69.29,-121.11 38.84,-24.03 95.23,-35.09 169.16,-35.09l128.48,-0.96v-32.29c0,-102.6 -50.86,-153.46 -152.51,-153.46 -42.53,0 -77.62,8.33 -105.4,26.83a126.36,126.36 0,0 0,-57.28 84.99l-34.13,-3.69c9.22,-46.22 31.4,-81.31 68.33,-105.34 34.13,-24.03 77.62,-35.16 129.37,-35.16zM988.84,622.73l-127.52,0.96c-135.85,0.89 -203.3,43.42 -203.3,126.57 0,29.63 11.06,52.7 34.13,70.31 23.21,16.59 53.66,25.8 91.55,25.8 51.75,0 98.92,-18.43 142.34,-55.43 41.64,-35.09 62.81,-71.13 62.81,-108.13v-60.07z"
+ android:fillColor="#ffffff"/>
diff --git a/app/src/main/res/drawable/ic_auto_page.xml b/app/src/main/res/drawable/ic_auto_page.xml
new file mode 100644
index 0000000..6a212c8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_auto_page.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_catalog.xml b/app/src/main/res/drawable/ic_catalog.xml
index e372f4b..8679a85 100644
--- a/app/src/main/res/drawable/ic_catalog.xml
+++ b/app/src/main/res/drawable/ic_catalog.xml
@@ -1,18 +1,9 @@
-
-
-
+ android:pathData="M106.66,192a21.34,21.34 0,1 0,0 42.69L192,234.69A21.34,21.34 0,1 0,192 192L106.66,192zM106.66,490.66a21.34,21.34 0,1 0,0 42.69L192,533.34a21.34,21.34 0,1 0,0 -42.69L106.66,490.66zM106.66,789.34a21.34,21.34 0,1 0,0 42.69L192,832.03a21.34,21.34 0,1 0,0 -42.69L106.66,789.34zM320,192a21.34,21.34 0,1 0,0 42.69h597.34a21.34,21.34 0,1 0,0 -42.69L320,192zM320,490.66a21.34,21.34 0,1 0,0 42.69h597.34a21.34,21.34 0,1 0,0 -42.69L320,490.66zM320,789.34a21.34,21.34 0,1 0,0 42.69h597.34a21.34,21.34 0,1 0,0 -42.69L320,789.34z"/>
diff --git a/app/src/main/res/drawable/ic_hv_screen.xml b/app/src/main/res/drawable/ic_hv_screen.xml
new file mode 100644
index 0000000..d7a4437
--- /dev/null
+++ b/app/src/main/res/drawable/ic_hv_screen.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_line_spacing2.xml b/app/src/main/res/drawable/ic_line_spacing2.xml
new file mode 100644
index 0000000..bd8f720
--- /dev/null
+++ b/app/src/main/res/drawable/ic_line_spacing2.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_line_spacing3.xml b/app/src/main/res/drawable/ic_line_spacing3.xml
new file mode 100644
index 0000000..16dfd97
--- /dev/null
+++ b/app/src/main/res/drawable/ic_line_spacing3.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_line_spacing4.xml b/app/src/main/res/drawable/ic_line_spacing4.xml
new file mode 100644
index 0000000..3b8c896
--- /dev/null
+++ b/app/src/main/res/drawable/ic_line_spacing4.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_listen_book.xml b/app/src/main/res/drawable/ic_listen_book.xml
index 8663f4a..ee7517a 100644
--- a/app/src/main/res/drawable/ic_listen_book.xml
+++ b/app/src/main/res/drawable/ic_listen_book.xml
@@ -4,9 +4,6 @@
android:viewportWidth="1024"
android:viewportHeight="1024">
-
+ android:pathData="M511.3,159.6C289.9,159.6 110.6,339 112,560.3v193.4c0,61.6 46.2,110.7 105.1,110.7 57.4,0 105.1,-50.4 105.1,-110.7L322.2,629c0,-61.6 -46.2,-110.7 -105.1,-110.7 -23.8,0 -46.2,7 -63,21 9.8,-189.1 166.7,-337.6 357.3,-337.6 190.5,0 347.5,148.5 358.7,337.6 -18.2,-14 -39.2,-21 -63,-22.4 -57.4,0 -105.1,50.4 -105.1,110.7v124.7c0,61.6 46.2,110.7 105.1,110.7 57.4,0 105.1,-50.4 105.1,-110.7v-192C912,339 732.7,159.6 511.3,159.6zM215.7,560.3c35,0 63,29.4 63,67.2v124.7c0,36.4 -28,67.2 -63,67.2s-63,-29.4 -63,-67.2L152.7,627.6c-0.1,-36.5 28,-67.3 63,-67.3zM870,752.3c0,36.4 -28,67.2 -63,67.2s-63,-29.4 -63,-67.2L744,627.6c0,-36.4 28,-67.2 63,-67.2s63,29.4 63,67.2v124.7z"
+ android:fillColor="#ffffff"/>
diff --git a/app/src/main/res/drawable/ic_more_setting.xml b/app/src/main/res/drawable/ic_more_setting.xml
new file mode 100644
index 0000000..9d4b944
--- /dev/null
+++ b/app/src/main/res/drawable/ic_more_setting.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_night_mode.xml b/app/src/main/res/drawable/ic_night_mode.xml
index bddd4c1..53a611a 100644
--- a/app/src/main/res/drawable/ic_night_mode.xml
+++ b/app/src/main/res/drawable/ic_night_mode.xml
@@ -1,9 +1,9 @@
+ android:fillColor="#FF000000"
+ android:pathData="M504.03,895.62l0.09,-0.08c103.99,-0.59 201.66,-41.34 275.27,-114.94 74.12,-74.13 114.94,-172.68 114.94,-277.53 0,-86.19 -27.67,-167.56 -80.05,-235.28 -25.09,-32.46 -55.12,-60.72 -89.24,-84.03 -34.48,-23.53 -72.32,-41.47 -112.47,-53.28 -2.22,-0.66 -4.55,-0.99 -6.87,-0.99 -7.25,0 -14.09,3.2 -18.75,8.8 -6.21,7.5 -7.36,17.94 -2.92,26.62 23.95,46.67 35.09,91.13 35.09,139.95 0,173.97 -141.55,315.51 -315.51,315.51 -48.83,0 -93.3,-11.15 -139.95,-35.07 -3.44,-1.76 -7.27,-2.69 -11.11,-2.69 -5.66,0 -11.16,1.99 -15.51,5.6 -7.47,6.2 -10.55,16.25 -7.82,25.61 11.9,40.28 29.83,78.12 53.29,112.49 23.28,34.11 51.56,64.14 84.02,89.24 67.72,52.38 149.08,80.05 235.27,80.05l2.25,0L504.03,895.62zM650.19,196.65c119.17,56.25 195.5,175.18 195.5,306.43 0,189.59 -154.25,343.84 -343.84,343.84 -131.24,0 -250.16,-76.32 -306.43,-195.51 35.32,11.81 70.84,17.58 108.2,17.58 97.27,0 188.7,-37.86 257.47,-106.62 68.79,-68.79 106.68,-160.26 106.68,-257.52C667.78,267.59 662.01,232.07 650.19,196.65L650.19,196.65z"/>
diff --git a/app/src/main/res/drawable/ic_page_mode.xml b/app/src/main/res/drawable/ic_page_mode.xml
new file mode 100644
index 0000000..e4a4e14
--- /dev/null
+++ b/app/src/main/res/drawable/ic_page_mode.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_remove.xml b/app/src/main/res/drawable/ic_remove.xml
new file mode 100644
index 0000000..90eec6f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_remove.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml
new file mode 100644
index 0000000..f1b4cf5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_scan.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml
index 0022d3b..3ce5e52 100644
--- a/app/src/main/res/drawable/ic_share.xml
+++ b/app/src/main/res/drawable/ic_share.xml
@@ -1,14 +1,9 @@
-
-
-
-
-
\ No newline at end of file
+ android:viewportWidth="1024"
+ android:viewportHeight="1024">
+
+
diff --git a/app/src/main/res/drawable/ic_sun.xml b/app/src/main/res/drawable/ic_sun.xml
new file mode 100644
index 0000000..5562789
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sun.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/icon_image.png b/app/src/main/res/drawable/icon_image.png
new file mode 100644
index 0000000..56f64f0
Binary files /dev/null and b/app/src/main/res/drawable/icon_image.png differ
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..dcda132
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_more_setting.xml b/app/src/main/res/layout/activity_more_setting.xml
index 961a2bc..68b9fd6 100644
--- a/app/src/main/res/layout/activity_more_setting.xml
+++ b/app/src/main/res/layout/activity_more_setting.xml
@@ -17,7 +17,7 @@
android:orientation="vertical">
-
+ android:id="@+id/rl_reset_screen"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:gravity="center"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:background="@drawable/selector_common_bg">
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/textSecondary"
+ android:layout_centerVertical="true"
+ android:textSize="@dimen/text_normal_size"
+ android:text="@string/reset_screen_time"/>
+ android:id="@+id/sc_reset_screen"
+ android:layout_width="115dp"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true"
+ android:gravity="center"
+ android:dropDownWidth="75dp"
+ android:clickable="false"
+ android:longClickable="false"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_read.xml b/app/src/main/res/layout/activity_read.xml
index a2faaba..82d972a 100644
--- a/app/src/main/res/layout/activity_read.xml
+++ b/app/src/main/res/layout/activity_read.xml
@@ -77,22 +77,6 @@
android:clickable="true"
tools:visibility="gone" />
-
-
-
-
-
-
+
@@ -122,10 +106,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
+ android:layout_marginTop="4dp"
android:layout_marginBottom="10dp"
android:background="@color/read_menu_bg"
android:gravity="center"
- android:layout_marginTop="4dp"
android:paddingLeft="12dp"
android:paddingTop="8dp"
android:paddingRight="12dp"
@@ -134,19 +118,18 @@
android:textColor="@color/md_white_1000" />
+ app:pressedTranslationZ="2dp" />
@@ -162,9 +145,9 @@
style="@style/MAppTheme.TextAppearance.Read.BottomMenu"
android:layout_width="80dp"
android:layout_height="match_parent"
- android:gravity="center"
android:clickable="true"
android:focusable="true"
+ android:gravity="center"
android:text="上一章" />
@@ -206,34 +189,78 @@
+ android:text="目录"
+ app:drawableTint="@drawable/setting_btn_bg_color"
+ app:drawableTopCompat="@drawable/ic_catalog" />
+ android:text="语音朗读"
+ app:drawableTint="@drawable/setting_btn_bg_color"
+ app:drawableTopCompat="@drawable/ic_listen_book" />
+ android:text="亮度/护眼"
+ app:drawableTint="@drawable/setting_btn_bg_color"
+ app:drawableTopCompat="@drawable/ic_sun" />
+ android:text="设置"
+ app:drawableTint="@drawable/setting_btn_bg_color"
+ app:drawableTopCompat="@drawable/ic_aa" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml
new file mode 100644
index 0000000..d4aeca4
--- /dev/null
+++ b/app/src/main/res/layout/activity_register.xml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_search_book.xml b/app/src/main/res/layout/activity_search_book.xml
index c7d83ab..d04779d 100644
--- a/app/src/main/res/layout/activity_search_book.xml
+++ b/app/src/main/res/layout/activity_search_book.xml
@@ -46,10 +46,41 @@
android:text="搜索"
android:textColor="@color/textPrimaryInverted"
android:textSize="18sp"/>
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_find.xml b/app/src/main/res/layout/fragment_find.xml
index 4a5846b..ad1fb2a 100644
--- a/app/src/main/res/layout/fragment_find.xml
+++ b/app/src/main/res/layout/fragment_find.xml
@@ -52,7 +52,7 @@
android:layout_centerVertical="true"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size"
- android:text="@string/novel_boy"/>
+ android:text="@string/find_qidian"/>
@@ -70,7 +70,25 @@
android:layout_centerVertical="true"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size"
- android:text="@string/novel_girl"/>
+ android:text="@string/find_qidian_ns"/>
+
+
+
+
+
+
@@ -114,7 +132,7 @@
android:layout_centerVertical="true"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size"
- android:text="@string/novel_boy"/>
+ android:text="@string/find_qidian"/>
@@ -132,7 +150,7 @@
android:layout_centerVertical="true"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size"
- android:text="@string/novel_girl"/>
+ android:text="@string/find_qidian_ns"/>
@@ -161,7 +179,24 @@
android:textSize="@dimen/text_small_size"
android:text="书城"/>
+
+
+
+
+ android:text="@string/read_qb5"/>
+ android:id="@+id/find_rl_miaoqu_store"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:background="@drawable/selector_common_bg">
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:textColor="@color/textSecondary"
+ android:textSize="@dimen/text_normal_size"
+ android:text="@string/read_miaobi"/>
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_mine.xml b/app/src/main/res/layout/fragment_mine.xml
index 7be99b3..f49f246 100644
--- a/app/src/main/res/layout/fragment_mine.xml
+++ b/app/src/main/res/layout/fragment_mine.xml
@@ -3,6 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/colorBackground"
android:scrollbars="vertical">
@@ -13,6 +14,50 @@
android:id="@+id/ll_mine"
android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
@@ -37,11 +38,10 @@
android:id="@+id/tv_no_read_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
android:layout_marginTop="15dp"
android:layout_marginEnd="25dp"
- android:includeFontPadding="false"
- android:layout_marginRight="10dp"/>
+ android:includeFontPadding="false" />
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_about_content.xml b/app/src/main/res/layout/layout_about_content.xml
index a194c96..2cf5121 100644
--- a/app/src/main/res/layout/layout_about_content.xml
+++ b/app/src/main/res/layout/layout_about_content.xml
@@ -78,7 +78,6 @@
android:src="@mipmap/ic_avatar_fycz"/>
+
+
@@ -200,6 +207,42 @@
+
+
+
+
+
+
+
+
+
+
+
-
+ android:gravity="center" />
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/menu_brightness_eye.xml b/app/src/main/res/layout/menu_brightness_eye.xml
new file mode 100644
index 0000000..96b2307
--- /dev/null
+++ b/app/src/main/res/layout/menu_brightness_eye.xml
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/menu_cumtomize_layout.xml b/app/src/main/res/layout/menu_cumtomize_layout.xml
new file mode 100644
index 0000000..15b818a
--- /dev/null
+++ b/app/src/main/res/layout/menu_cumtomize_layout.xml
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/menu_customize_com.xml b/app/src/main/res/layout/menu_customize_com.xml
new file mode 100644
index 0000000..1398bb7
--- /dev/null
+++ b/app/src/main/res/layout/menu_customize_com.xml
@@ -0,0 +1,378 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/menu_read_setting.xml b/app/src/main/res/layout/menu_read_setting.xml
new file mode 100644
index 0000000..0fc4135
--- /dev/null
+++ b/app/src/main/res/layout/menu_read_setting.xml
@@ -0,0 +1,343 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_book.xml b/app/src/main/res/menu/menu_book.xml
index 4fa7470..7fee944 100644
--- a/app/src/main/res/menu/menu_book.xml
+++ b/app/src/main/res/menu/menu_book.xml
@@ -47,6 +47,11 @@
android:title="@string/menu_bookcase_download_all"
app:showAsAction="never"
android:visible="true"/>
-
+
diff --git a/app/src/main/res/menu/menu_book_detail.xml b/app/src/main/res/menu/menu_book_detail.xml
index 0be5539..e3ced2e 100644
--- a/app/src/main/res/menu/menu_book_detail.xml
+++ b/app/src/main/res/menu/menu_book_detail.xml
@@ -8,6 +8,13 @@
android:icon="@drawable/ic_change_source"
android:title="@string/menu_change_source"
app:showAsAction="ifRoom"/>
+
+
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_read.xml b/app/src/main/res/menu/menu_read.xml
index b3dbd09..98d7960 100644
--- a/app/src/main/res/menu/menu_read.xml
+++ b/app/src/main/res/menu/menu_read.xml
@@ -15,6 +15,11 @@
app:showAsAction="ifRoom"/>
+
- #8affffff
#43ffffff
#ffe7492e
-
+ #30ffffff
+ #80686868
#b7b7b7
#282828
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 140ae64..e5ddfeb 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -21,7 +21,6 @@
-
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 39ca8b5..3885c3d 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -27,7 +27,7 @@
#ffe7492e
#96ffffff
-
+ #30000000
#383838
#fcfcfc
@@ -41,6 +41,8 @@
@color/colorDivider
+ #80858585
+
#88303F9F
@@ -161,27 +163,27 @@
#aaffffff
-
-
#90cbf6
-
- #ceebce
+ #E1F1DA
#313031
#f5f4f0
#313031
- #313031
#CEC29C
+ #313031
- #001c29
#637079
+ #001c29
#b5bd9a
#313031
+ #2B241E
+ #94928c
+
#393431
#94928c
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 096ca63..ca89096 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -155,6 +155,12 @@
当前线程数:%1$d
最新章节:%1$s
书源:%1$s 共%2$d个源
+ 覆盖
+ 仿真
+ 滑动
+ 滚动
+ 无动画
+ 自动翻页
lanzousKeyStart
@@ -179,6 +185,31 @@
拷贝内容
+
+ 天籁小说
+ 笔趣阁44
+ 品书网
+ 笔趣阁
+ 全本小说
+ 米趣小说
+ 九桃小说
+ 妙笔阁
+ 丹书铁券
+ 云中书库
+ 搜小说网
+ 全小说网
+ 奇奇小说
+ 小说旗
+ 读一读网
+ 飘天文学
+ 超星图书·实体
+ 作品集·实体
+ 99藏书·实体
+ 100本·实体
+
+ 起点中文网
+ 起点女生网
+
朗读
正在朗读
@@ -198,6 +229,36 @@
%d分钟
"%d小时%d分"
"计时已取消"
+ 显示状态栏
+ 全屏点击翻下页
+ 加载中…
+ 加载失败\n%s
+ 文章内容为空
+ 正在换源请等待…
+ 目录列表为空
+ 已设置文本为繁转简,不需要时请及时取消。如果所阅读文本并非繁体字,强制繁转简后可能出现一些异常文字。
+ 无缩进
+ 一字符缩进
+ 二字符缩进
+ 三字符缩进
+ 四字符缩进
+ 已切换为等距排版,该排版下所有段落字符间等距,将会允许标点符号出现在行首,允许拆分标点组合等。
+ 开启共用布局将会使所有布局共用当前排版、字体方案,修改排版(背景颜色、字体颜色除外)时会同时应用于所有布局,确定要开启共用布局吗?
+ 菜单隐藏章节标题/链接
+ 朗读时音量键翻页
+ 缓存书籍
+ 模糊搜索
+ 精确搜索
+ 全部
+ 书架排序
+ 手动排序
+ 按时间排序
+ 按书名排序
+ 分享书籍
+ 相册
+ 扫一扫
+ 轻触点亮
+ 轻触关闭
- 常亮
@@ -232,4 +293,27 @@
- 重命名分组
- 删除分组
+
+
+ - 覆盖
+ - 仿真
+ - 滑动
+ - 上下
+ - 滚动
+ - 无动画
+
+
+
+ - @string/indent_0
+ - @string/indent_1
+ - @string/indent_2
+ - @string/indent_3
+ - @string/indent_4
+
+
+
+ - @string/manual_sort
+ - @string/time_sort
+ - @string/book_name_sort
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 681fee0..ee786c9 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -61,7 +61,24 @@
- @color/gray
- @color/colorAccent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -171,6 +188,8 @@
- 0dp
- match_parent
- 1
+ - @drawable/setting_btn_bg_color
+ - @dimen/text_small_size