| @@ -31,5 +31,10 @@ | |||||
| <option name="name" value="maven2" /> | <option name="name" value="maven2" /> | ||||
| <option name="url" value="https://dl.bintray.com/umsdk/release" /> | <option name="url" value="https://dl.bintray.com/umsdk/release" /> | ||||
| </remote-repository> | </remote-repository> | ||||
| <remote-repository> | |||||
| <option name="id" value="maven3" /> | |||||
| <option name="name" value="maven3" /> | |||||
| <option name="url" value="https://dl.bintray.com/qichuan/maven/" /> | |||||
| </remote-repository> | |||||
| </component> | </component> | ||||
| </project> | </project> | ||||
| @@ -4,16 +4,25 @@ apply plugin: 'kotlin-android' | |||||
| apply plugin: 'kotlin-android-extensions' | apply plugin: 'kotlin-android-extensions' | ||||
| //apply from: 'https://raw.githubusercontent.com/apache/incubator-weex/release/0.28/android/sdk/buildSrc/download_jsc.gradle' | |||||
| apply plugin: 'org.greenrobot.greendao' | |||||
| android { | android { | ||||
| compileSdkVersion 30 | compileSdkVersion 30 | ||||
| buildToolsVersion "30.0.3" | buildToolsVersion "30.0.3" | ||||
| defaultConfig { | defaultConfig { | ||||
| applicationId "com.yzx.webebook" | applicationId "com.yzx.webebook" | ||||
| minSdkVersion 26 | |||||
| targetSdkVersion 30 | |||||
| // minSdkVersion 26 | |||||
| minSdkVersion 21 | |||||
| targetSdkVersion 25 | |||||
| versionCode 3 | versionCode 3 | ||||
| versionName "2.0.1" | versionName "2.0.1" | ||||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
| ndk { | |||||
| abiFilters "armeabi-v7a", "arm64-v8a", "x86" | |||||
| } | |||||
| } | } | ||||
| signingConfigs { | signingConfigs { | ||||
| yzx { | yzx { | ||||
| @@ -59,10 +68,10 @@ android { | |||||
| debug { | debug { | ||||
| minifyEnabled false | minifyEnabled false | ||||
| proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
| // debuggable true | |||||
| debuggable true | |||||
| signingConfig signingConfigs.release | signingConfig signingConfigs.release | ||||
| // 移除无用的resource文件 | // 移除无用的resource文件 | ||||
| // shrinkResources false | |||||
| shrinkResources false | |||||
| buildConfigField "String", "BASE_URL", '"http://192.168.69.99:9009"' | buildConfigField "String", "BASE_URL", '"http://192.168.69.99:9009"' | ||||
| buildConfigField "String", "M_URL", '"http://192.168.69.112:8098"' | buildConfigField "String", "M_URL", '"http://192.168.69.112:8098"' | ||||
| buildConfigField "String", "APP_NAME", '"家校互通(开发)"' | buildConfigField "String", "APP_NAME", '"家校互通(开发)"' | ||||
| @@ -96,6 +105,24 @@ android { | |||||
| // but continue the build even when errors are found: | // but continue the build even when errors are found: | ||||
| abortOnError false | abortOnError false | ||||
| } | } | ||||
| compileOptions { | |||||
| sourceCompatibility JavaVersion.VERSION_1_8 | |||||
| targetCompatibility JavaVersion.VERSION_1_8 | |||||
| } | |||||
| } | |||||
| greendao{ | |||||
| schemaVersion 3 | |||||
| daoPackage 'com.yzx.webebook.model.gen' | |||||
| targetGenDir 'src/main/java' | |||||
| } | |||||
| repositories { | |||||
| maven { | |||||
| url 'https://dl.bintray.com/qichuan/maven/' | |||||
| } | |||||
| } | } | ||||
| dependencies { | dependencies { | ||||
| @@ -127,4 +154,16 @@ dependencies { | |||||
| implementation 'com.umeng.umsdk:asms:1.2.1' // 必选 | implementation 'com.umeng.umsdk:asms:1.2.1' // 必选 | ||||
| implementation 'com.github.thomhurst:RoundImageView:1.0.2' | implementation 'com.github.thomhurst:RoundImageView:1.0.2' | ||||
| implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4' | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4' | ||||
| implementation 'org.apache.weex:sdk:0.28.0' | |||||
| implementation 'com.alibaba:fastjson:1.1.46.android' | |||||
| //ORM Database | |||||
| implementation deps.greendao.runtime | |||||
| testImplementation deps.testing.junit | |||||
| //RxJava | |||||
| implementation deps.reactivex.rxandroid | |||||
| implementation deps.reactivex.rxjava2 | |||||
| } | } | ||||
| @@ -12,15 +12,23 @@ | |||||
| <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | ||||
| <uses-permission android:name="android.permission.READ_MEDIA_STORAGE" /> | <uses-permission android:name="android.permission.READ_MEDIA_STORAGE" /> | ||||
| <uses-permission android:name="android.permission.WAKE_LOCK" /> | <uses-permission android:name="android.permission.WAKE_LOCK" /> | ||||
| <uses-permission android:name="android.permission.WRITE_SETTINGS"/> | |||||
| <uses-permission android:name="android.permission.BATTERY_STATS"/> | |||||
| <uses-permission android:name="android.permission.WAKE_LOCK" /> | |||||
| <application | <application | ||||
| android:name=".App" | android:name=".App" | ||||
| android:allowBackup="true" | android:allowBackup="true" | ||||
| android:icon="@mipmap/ic_launcher" | android:icon="@mipmap/ic_launcher" | ||||
| android:label="@string/app_name" | android:label="@string/app_name" | ||||
| android:networkSecurityConfig="@xml/network_security_config" | |||||
| android:roundIcon="@mipmap/ic_launcher_round" | android:roundIcon="@mipmap/ic_launcher_round" | ||||
| android:supportsRtl="true" | android:supportsRtl="true" | ||||
| android:theme="@style/AppTheme"> | |||||
| android:theme="@style/AppTheme" | |||||
| android:requestLegacyExternalStorage="true" | |||||
| android:usesCleartextTraffic="true"> | |||||
| <activity android:name=".activity.ReadActivity"></activity> | |||||
| <activity android:name=".activity.WeexTestActivity" /> | |||||
| <activity | <activity | ||||
| android:name=".activity.HomeActivity" | android:name=".activity.HomeActivity" | ||||
| android:configChanges="orientation|keyboard" | android:configChanges="orientation|keyboard" | ||||
| @@ -51,10 +59,6 @@ | |||||
| android:name=".activity.BookActivity" | android:name=".activity.BookActivity" | ||||
| android:configChanges="orientation|keyboard" | android:configChanges="orientation|keyboard" | ||||
| android:screenOrientation="portrait" /> | android:screenOrientation="portrait" /> | ||||
| <activity | |||||
| android:name=".activity.Main1Activity" | |||||
| android:configChanges="orientation|keyboard" | |||||
| android:screenOrientation="portrait" /> | |||||
| <provider | <provider | ||||
| android:name=".utils.YzxFileProvider" | android:name=".utils.YzxFileProvider" | ||||
| @@ -0,0 +1,132 @@ | |||||
| // { "framework": "Vue"} | |||||
| /******/ (function(modules) { // webpackBootstrap | |||||
| /******/ // The module cache | |||||
| /******/ var installedModules = {}; | |||||
| /******/ | |||||
| /******/ // The require function | |||||
| /******/ function __webpack_require__(moduleId) { | |||||
| /******/ | |||||
| /******/ // Check if module is in cache | |||||
| /******/ if(installedModules[moduleId]) { | |||||
| /******/ return installedModules[moduleId].exports; | |||||
| /******/ } | |||||
| /******/ // Create a new module (and put it into the cache) | |||||
| /******/ var module = installedModules[moduleId] = { | |||||
| /******/ i: moduleId, | |||||
| /******/ l: false, | |||||
| /******/ exports: {} | |||||
| /******/ }; | |||||
| /******/ | |||||
| /******/ // Execute the module function | |||||
| /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); | |||||
| /******/ | |||||
| /******/ // Flag the module as loaded | |||||
| /******/ module.l = true; | |||||
| /******/ | |||||
| /******/ // Return the exports of the module | |||||
| /******/ return module.exports; | |||||
| /******/ } | |||||
| /******/ | |||||
| /******/ | |||||
| /******/ // expose the modules object (__webpack_modules__) | |||||
| /******/ __webpack_require__.m = modules; | |||||
| /******/ | |||||
| /******/ // expose the module cache | |||||
| /******/ __webpack_require__.c = installedModules; | |||||
| /******/ | |||||
| /******/ // define getter function for harmony exports | |||||
| /******/ __webpack_require__.d = function(exports, name, getter) { | |||||
| /******/ if(!__webpack_require__.o(exports, name)) { | |||||
| /******/ Object.defineProperty(exports, name, { | |||||
| /******/ configurable: false, | |||||
| /******/ enumerable: true, | |||||
| /******/ get: getter | |||||
| /******/ }); | |||||
| /******/ } | |||||
| /******/ }; | |||||
| /******/ | |||||
| /******/ // getDefaultExport function for compatibility with non-harmony modules | |||||
| /******/ __webpack_require__.n = function(module) { | |||||
| /******/ var getter = module && module.__esModule ? | |||||
| /******/ function getDefault() { return module['default']; } : | |||||
| /******/ function getModuleExports() { return module; }; | |||||
| /******/ __webpack_require__.d(getter, 'a', getter); | |||||
| /******/ return getter; | |||||
| /******/ }; | |||||
| /******/ | |||||
| /******/ // Object.prototype.hasOwnProperty.call | |||||
| /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; | |||||
| /******/ | |||||
| /******/ // __webpack_public_path__ | |||||
| /******/ __webpack_require__.p = ""; | |||||
| /******/ | |||||
| /******/ // Load entry module and return exports | |||||
| /******/ return __webpack_require__(__webpack_require__.s = 2); | |||||
| /******/ }) | |||||
| /************************************************************************/ | |||||
| /******/ ([ | |||||
| /* 0 */ | |||||
| /***/ (function(module, exports, __webpack_require__) { | |||||
| var __vue_exports__, __vue_options__ | |||||
| var __vue_styles__ = [] | |||||
| /* template */ | |||||
| var __vue_template__ = __webpack_require__(1) | |||||
| __vue_options__ = __vue_exports__ = __vue_exports__ || {} | |||||
| if ( | |||||
| typeof __vue_exports__.default === "object" || | |||||
| typeof __vue_exports__.default === "function" | |||||
| ) { | |||||
| if (Object.keys(__vue_exports__).some(function (key) { return key !== "default" && key !== "__esModule" })) {console.error("named exports are not supported in *.vue files.")} | |||||
| __vue_options__ = __vue_exports__ = __vue_exports__.default | |||||
| } | |||||
| if (typeof __vue_options__ === "function") { | |||||
| __vue_options__ = __vue_options__.options | |||||
| } | |||||
| __vue_options__.__file = "D:\\demo\\androidweex\\src\\components\\HelloWorld.vue" | |||||
| __vue_options__.render = __vue_template__.render | |||||
| __vue_options__.staticRenderFns = __vue_template__.staticRenderFns | |||||
| __vue_options__.style = __vue_options__.style || {} | |||||
| __vue_styles__.forEach(function (module) { | |||||
| for (var name in module) { | |||||
| __vue_options__.style[name] = module[name] | |||||
| } | |||||
| }) | |||||
| if (typeof __register_static_styles__ === "function") { | |||||
| __register_static_styles__(__vue_options__._scopeId, __vue_styles__) | |||||
| } | |||||
| module.exports = __vue_exports__ | |||||
| /***/ }), | |||||
| /* 1 */ | |||||
| /***/ (function(module, exports) { | |||||
| module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; | |||||
| return _c('text', { | |||||
| staticClass: ["message"] | |||||
| }, [_vm._v("Now, let's use Vue.js to build your Weex app.")]) | |||||
| },staticRenderFns: []} | |||||
| module.exports.render._withStripped = true | |||||
| /***/ }), | |||||
| /* 2 */ | |||||
| /***/ (function(module, exports, __webpack_require__) { | |||||
| "use strict"; | |||||
| var _HelloWorld = __webpack_require__(0); | |||||
| var _HelloWorld2 = _interopRequireDefault(_HelloWorld); | |||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |||||
| _HelloWorld2.default.el = '#root'; | |||||
| new Vue(_HelloWorld2.default); | |||||
| /***/ }) | |||||
| /******/ ]); | |||||
| @@ -0,0 +1,287 @@ | |||||
| // { "framework": "Vue"} | |||||
| /******/ (function(modules) { // webpackBootstrap | |||||
| /******/ // The module cache | |||||
| /******/ var installedModules = {}; | |||||
| /******/ | |||||
| /******/ // The require function | |||||
| /******/ function __webpack_require__(moduleId) { | |||||
| /******/ | |||||
| /******/ // Check if module is in cache | |||||
| /******/ if(installedModules[moduleId]) { | |||||
| /******/ return installedModules[moduleId].exports; | |||||
| /******/ } | |||||
| /******/ // Create a new module (and put it into the cache) | |||||
| /******/ var module = installedModules[moduleId] = { | |||||
| /******/ i: moduleId, | |||||
| /******/ l: false, | |||||
| /******/ exports: {} | |||||
| /******/ }; | |||||
| /******/ | |||||
| /******/ // Execute the module function | |||||
| /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); | |||||
| /******/ | |||||
| /******/ // Flag the module as loaded | |||||
| /******/ module.l = true; | |||||
| /******/ | |||||
| /******/ // Return the exports of the module | |||||
| /******/ return module.exports; | |||||
| /******/ } | |||||
| /******/ | |||||
| /******/ | |||||
| /******/ // expose the modules object (__webpack_modules__) | |||||
| /******/ __webpack_require__.m = modules; | |||||
| /******/ | |||||
| /******/ // expose the module cache | |||||
| /******/ __webpack_require__.c = installedModules; | |||||
| /******/ | |||||
| /******/ // define getter function for harmony exports | |||||
| /******/ __webpack_require__.d = function(exports, name, getter) { | |||||
| /******/ if(!__webpack_require__.o(exports, name)) { | |||||
| /******/ Object.defineProperty(exports, name, { | |||||
| /******/ configurable: false, | |||||
| /******/ enumerable: true, | |||||
| /******/ get: getter | |||||
| /******/ }); | |||||
| /******/ } | |||||
| /******/ }; | |||||
| /******/ | |||||
| /******/ // getDefaultExport function for compatibility with non-harmony modules | |||||
| /******/ __webpack_require__.n = function(module) { | |||||
| /******/ var getter = module && module.__esModule ? | |||||
| /******/ function getDefault() { return module['default']; } : | |||||
| /******/ function getModuleExports() { return module; }; | |||||
| /******/ __webpack_require__.d(getter, 'a', getter); | |||||
| /******/ return getter; | |||||
| /******/ }; | |||||
| /******/ | |||||
| /******/ // Object.prototype.hasOwnProperty.call | |||||
| /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; | |||||
| /******/ | |||||
| /******/ // __webpack_public_path__ | |||||
| /******/ __webpack_require__.p = ""; | |||||
| /******/ | |||||
| /******/ // Load entry module and return exports | |||||
| /******/ return __webpack_require__(__webpack_require__.s = 9); | |||||
| /******/ }) | |||||
| /************************************************************************/ | |||||
| /******/ ([ | |||||
| /* 0 */ | |||||
| /***/ (function(module, exports, __webpack_require__) { | |||||
| var __vue_exports__, __vue_options__ | |||||
| var __vue_styles__ = [] | |||||
| /* template */ | |||||
| var __vue_template__ = __webpack_require__(1) | |||||
| __vue_options__ = __vue_exports__ = __vue_exports__ || {} | |||||
| if ( | |||||
| typeof __vue_exports__.default === "object" || | |||||
| typeof __vue_exports__.default === "function" | |||||
| ) { | |||||
| if (Object.keys(__vue_exports__).some(function (key) { return key !== "default" && key !== "__esModule" })) {console.error("named exports are not supported in *.vue files.")} | |||||
| __vue_options__ = __vue_exports__ = __vue_exports__.default | |||||
| } | |||||
| if (typeof __vue_options__ === "function") { | |||||
| __vue_options__ = __vue_options__.options | |||||
| } | |||||
| __vue_options__.__file = "D:\\demo\\androidweex\\src\\components\\HelloWorld.vue" | |||||
| __vue_options__.render = __vue_template__.render | |||||
| __vue_options__.staticRenderFns = __vue_template__.staticRenderFns | |||||
| __vue_options__.style = __vue_options__.style || {} | |||||
| __vue_styles__.forEach(function (module) { | |||||
| for (var name in module) { | |||||
| __vue_options__.style[name] = module[name] | |||||
| } | |||||
| }) | |||||
| if (typeof __register_static_styles__ === "function") { | |||||
| __register_static_styles__(__vue_options__._scopeId, __vue_styles__) | |||||
| } | |||||
| module.exports = __vue_exports__ | |||||
| /***/ }), | |||||
| /* 1 */ | |||||
| /***/ (function(module, exports) { | |||||
| module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; | |||||
| return _c('text', { | |||||
| staticClass: ["message"] | |||||
| }, [_vm._v("Now, let's use Vue.js to build your Weex app.")]) | |||||
| },staticRenderFns: []} | |||||
| module.exports.render._withStripped = true | |||||
| /***/ }), | |||||
| /* 2 */, | |||||
| /* 3 */, | |||||
| /* 4 */, | |||||
| /* 5 */, | |||||
| /* 6 */, | |||||
| /* 7 */, | |||||
| /* 8 */, | |||||
| /* 9 */ | |||||
| /***/ (function(module, exports, __webpack_require__) { | |||||
| "use strict"; | |||||
| var _reader = __webpack_require__(10); | |||||
| var _reader2 = _interopRequireDefault(_reader); | |||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |||||
| _reader2.default.el = '#root'; | |||||
| new Vue(_reader2.default); | |||||
| /***/ }), | |||||
| /* 10 */ | |||||
| /***/ (function(module, exports, __webpack_require__) { | |||||
| var __vue_exports__, __vue_options__ | |||||
| var __vue_styles__ = [] | |||||
| /* styles */ | |||||
| __vue_styles__.push(__webpack_require__(11) | |||||
| ) | |||||
| /* script */ | |||||
| __vue_exports__ = __webpack_require__(12) | |||||
| /* template */ | |||||
| var __vue_template__ = __webpack_require__(13) | |||||
| __vue_options__ = __vue_exports__ = __vue_exports__ || {} | |||||
| if ( | |||||
| typeof __vue_exports__.default === "object" || | |||||
| typeof __vue_exports__.default === "function" | |||||
| ) { | |||||
| if (Object.keys(__vue_exports__).some(function (key) { return key !== "default" && key !== "__esModule" })) {console.error("named exports are not supported in *.vue files.")} | |||||
| __vue_options__ = __vue_exports__ = __vue_exports__.default | |||||
| } | |||||
| if (typeof __vue_options__ === "function") { | |||||
| __vue_options__ = __vue_options__.options | |||||
| } | |||||
| __vue_options__.__file = "D:\\demo\\androidweex\\src\\reader.vue" | |||||
| __vue_options__.render = __vue_template__.render | |||||
| __vue_options__.staticRenderFns = __vue_template__.staticRenderFns | |||||
| __vue_options__._scopeId = "data-v-06390049" | |||||
| __vue_options__.style = __vue_options__.style || {} | |||||
| __vue_styles__.forEach(function (module) { | |||||
| for (var name in module) { | |||||
| __vue_options__.style[name] = module[name] | |||||
| } | |||||
| }) | |||||
| if (typeof __register_static_styles__ === "function") { | |||||
| __register_static_styles__(__vue_options__._scopeId, __vue_styles__) | |||||
| } | |||||
| module.exports = __vue_exports__ | |||||
| /***/ }), | |||||
| /* 11 */ | |||||
| /***/ (function(module, exports) { | |||||
| module.exports = { | |||||
| "wrapper": { | |||||
| "justifyContent": "center", | |||||
| "alignItems": "center" | |||||
| }, | |||||
| "logo": { | |||||
| "width": "424", | |||||
| "height": "200" | |||||
| }, | |||||
| "greeting": { | |||||
| "textAlign": "center", | |||||
| "marginTop": "70", | |||||
| "fontSize": "50", | |||||
| "color": "#41b883" | |||||
| }, | |||||
| "message": { | |||||
| "marginTop": "30", | |||||
| "marginRight": "30", | |||||
| "marginBottom": "30", | |||||
| "marginLeft": "30", | |||||
| "fontSize": "32", | |||||
| "color": "#727272" | |||||
| } | |||||
| } | |||||
| /***/ }), | |||||
| /* 12 */ | |||||
| /***/ (function(module, exports, __webpack_require__) { | |||||
| "use strict"; | |||||
| Object.defineProperty(exports, "__esModule", { | |||||
| value: true | |||||
| }); | |||||
| var _HelloWorld = __webpack_require__(0); | |||||
| var _HelloWorld2 = _interopRequireDefault(_HelloWorld); | |||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |||||
| exports.default = { | |||||
| name: 'App', | |||||
| components: { | |||||
| HelloWorld: _HelloWorld2.default | |||||
| }, | |||||
| data: function data() { | |||||
| return { | |||||
| logo: 'https://gw.alicdn.com/tfs/TB1yopEdgoQMeJjy1XaXXcSsFXa-640-302.png', | |||||
| params: "1" | |||||
| }; | |||||
| }, | |||||
| mounted: function mounted() { | |||||
| this.params = weex.config.params; | |||||
| }, | |||||
| methods: { | |||||
| onJumpClick: function onJumpClick() { | |||||
| var json = { | |||||
| url: "weex/index.js", | |||||
| params: "你好首页,我是reader" | |||||
| }; | |||||
| weex.requireModule("activity").navigateTo(json); | |||||
| } | |||||
| } | |||||
| }; // | |||||
| // | |||||
| // | |||||
| // | |||||
| // | |||||
| // | |||||
| // | |||||
| // | |||||
| // | |||||
| // | |||||
| /***/ }), | |||||
| /* 13 */ | |||||
| /***/ (function(module, exports) { | |||||
| module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; | |||||
| return _c('div', { | |||||
| staticClass: ["wrapper"] | |||||
| }, [_c('image', { | |||||
| staticClass: ["logo"], | |||||
| attrs: { | |||||
| "src": _vm.logo | |||||
| }, | |||||
| on: { | |||||
| "click": _vm.onJumpClick | |||||
| } | |||||
| }), _c('text', { | |||||
| staticClass: ["greeting"] | |||||
| }, [_vm._v("这是读书页面")]), _c('text', { | |||||
| staticClass: ["greeting"] | |||||
| }, [_vm._v(_vm._s(_vm.params))])]) | |||||
| },staticRenderFns: []} | |||||
| module.exports.render._withStripped = true | |||||
| /***/ }) | |||||
| /******/ ]); | |||||
| @@ -1,19 +1,64 @@ | |||||
| package com.yzx.webebook | package com.yzx.webebook | ||||
| import android.app.Application | import android.app.Application | ||||
| import android.content.Context | |||||
| import android.util.Log | |||||
| import com.blankj.utilcode.util.Utils | import com.blankj.utilcode.util.Utils | ||||
| import com.lzy.okgo.OkGo | import com.lzy.okgo.OkGo | ||||
| import com.umeng.analytics.MobclickAgent | import com.umeng.analytics.MobclickAgent | ||||
| import com.umeng.commonsdk.UMConfigure | import com.umeng.commonsdk.UMConfigure | ||||
| import com.yzx.webebook.adapter.ImageAdapter | |||||
| import com.yzx.webebook.modules.ActivityWXModule | |||||
| import org.apache.weex.InitConfig | |||||
| import org.apache.weex.WXEnvironment | |||||
| import org.apache.weex.WXSDKEngine | |||||
| import org.apache.weex.adapter.DefaultWXHttpAdapter | |||||
| import org.apache.weex.bridge.WXBridgeManager | |||||
| class App: Application() { | |||||
| class App : Application() { | |||||
| companion object { | |||||
| var app: App? = null | |||||
| fun getContext(): App { | |||||
| return app ?: App() | |||||
| } | |||||
| } | |||||
| override fun onCreate() { | override fun onCreate() { | ||||
| super.onCreate() | super.onCreate() | ||||
| app = this | |||||
| val config = InitConfig.Builder() //图片库接口 | |||||
| .setImgAdapter(ImageAdapter()) //网络库接口 | |||||
| .setHttpAdapter(DefaultWXHttpAdapter()) | |||||
| .build() | |||||
| WXSDKEngine.initialize(this, config) | |||||
| WXBridgeManager.updateGlobalConfig("wson_on") | |||||
| WXEnvironment.setOpenDebugLog(true) | |||||
| WXEnvironment.setApkDebugable(true) | |||||
| WXSDKEngine.addCustomOptions("appName", "WXSample") | |||||
| WXSDKEngine.addCustomOptions("appGroup", "WXApp") | |||||
| val registerSuccess = WXSDKEngine.registerModule("navevent", ActivityWXModule::class.java) | |||||
| Log.i("welog", "WXSDKEngine.isInitializedApplication: " + WXSDKEngine.isInitialized()) | |||||
| Log.i("welog", "WXSDKEngine.registerModule: $registerSuccess") | |||||
| OkGo.getInstance().init(this) | OkGo.getInstance().init(this) | ||||
| Utils.init(this) | Utils.init(this) | ||||
| UMConfigure.init(this, "60334d01425ec25f10fbd5b4", "ebook", UMConfigure.DEVICE_TYPE_PHONE, "") | |||||
| UMConfigure.init( | |||||
| this, | |||||
| "60334d01425ec25f10fbd5b4", | |||||
| "ebook", | |||||
| UMConfigure.DEVICE_TYPE_PHONE, | |||||
| "" | |||||
| ) | |||||
| MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO) | MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO) | ||||
| } | } | ||||
| } | } | ||||
| @@ -4,13 +4,17 @@ import android.annotation.SuppressLint | |||||
| import android.app.Activity | import android.app.Activity | ||||
| import android.content.Intent | import android.content.Intent | ||||
| import android.text.TextUtils | import android.text.TextUtils | ||||
| import android.util.Log | |||||
| import com.blankj.utilcode.util.StringUtils | import com.blankj.utilcode.util.StringUtils | ||||
| import com.bumptech.glide.Glide | import com.bumptech.glide.Glide | ||||
| import com.yzx.webebook.activity.* | import com.yzx.webebook.activity.* | ||||
| import com.yzx.webebook.activity.base.BaseActivity | import com.yzx.webebook.activity.base.BaseActivity | ||||
| import com.yzx.webebook.config.Config | import com.yzx.webebook.config.Config | ||||
| import com.yzx.webebook.model.User | import com.yzx.webebook.model.User | ||||
| import com.yzx.webebook.model.bean.CollBookBean | |||||
| import com.yzx.webebook.model.local.BookRepository | |||||
| import com.yzx.webebook.presenter.base.BasePresenter | import com.yzx.webebook.presenter.base.BasePresenter | ||||
| import com.yzx.webebook.utils.MD5Utils | |||||
| import kotlinx.android.synthetic.main.activity_main.* | import kotlinx.android.synthetic.main.activity_main.* | ||||
| import org.jetbrains.anko.startActivity | import org.jetbrains.anko.startActivity | ||||
| import org.jetbrains.anko.toast | import org.jetbrains.anko.toast | ||||
| @@ -84,7 +88,7 @@ class MainActivity : BaseActivity<BasePresenter<*>>() { | |||||
| } | } | ||||
| btn7.setOnClickListener { | btn7.setOnClickListener { | ||||
| startActivity<Main1Activity>() | |||||
| startActivity<ReadActivity>() | |||||
| } | } | ||||
| /*OkGo.post<String>("https://fileupload.oa.qbjjyyun.net/edufile/fileUpload") | /*OkGo.post<String>("https://fileupload.oa.qbjjyyun.net/edufile/fileUpload") | ||||
| @@ -112,9 +116,48 @@ class MainActivity : BaseActivity<BasePresenter<*>>() { | |||||
| } | } | ||||
| })*/ | })*/ | ||||
| // val apps = AppUtils.getAppInfo("com.tencent.weread.eink") | |||||
| // appInfos.text = apps.toString(); | |||||
| appInfos.setOnClickListener { | |||||
| val intent = packageManager.getLaunchIntentForPackage("com.tencent.weread.eink") | |||||
| intent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | |||||
| startActivity(intent) | |||||
| Log.d("AppUtils", "initView: ${intent.toString()}") | |||||
| } | |||||
| btn8.setOnClickListener { | |||||
| startActivity<WeexTestActivity>("url" to "weex/index.js","params" to "我是android传递的消息") | |||||
| } | |||||
| btn9.setOnClickListener { | |||||
| val intent = packageManager.getLaunchIntentForPackage("com.example.weexdemo") | |||||
| intent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | |||||
| startActivity(intent) | |||||
| Log.d("AppUtils", "initView: ${intent.toString()}") | |||||
| } | |||||
| } | } | ||||
| override fun initData() { | override fun initData() { | ||||
| saveDb() | |||||
| } | |||||
| fun saveDb(){ | |||||
| val path = "/storage/emulated/0/sgyy.txt"; | |||||
| val collBook = CollBookBean() | |||||
| collBook._id = MD5Utils.strToMd5By16(path) | |||||
| collBook.title = "三国演义sgyy" | |||||
| collBook.author = "yzx" | |||||
| collBook.shortIntro = "" | |||||
| collBook.cover = path | |||||
| collBook.lastChapter = "开始阅读" | |||||
| collBook.updated = "2021年5月7日18:20:25" | |||||
| collBook.lastRead = "2021年5月7日18:20:34" | |||||
| collBook.setIsLocal(true) | |||||
| BookRepository.getInstance() | |||||
| .saveCollBooks(mutableListOf(collBook)) | |||||
| } | } | ||||
| override fun initPresenter(): BasePresenter<*>? = null | override fun initPresenter(): BasePresenter<*>? = null | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.yzx.webebook.activity | package com.yzx.webebook.activity | ||||
| import android.Manifest | |||||
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
| import android.app.AlertDialog | import android.app.AlertDialog | ||||
| import android.app.Dialog | import android.app.Dialog | ||||
| @@ -8,6 +9,7 @@ import android.content.DialogInterface | |||||
| import android.util.Log | import android.util.Log | ||||
| import android.view.View | import android.view.View | ||||
| import android.widget.TextView | import android.widget.TextView | ||||
| import androidx.core.app.ActivityCompat | |||||
| import androidx.core.widget.ContentLoadingProgressBar | import androidx.core.widget.ContentLoadingProgressBar | ||||
| import androidx.recyclerview.widget.GridLayoutManager | import androidx.recyclerview.widget.GridLayoutManager | ||||
| import com.allenliu.versionchecklib.v2.AllenVersionChecker | import com.allenliu.versionchecklib.v2.AllenVersionChecker | ||||
| @@ -34,7 +36,6 @@ import com.yzx.webebook.model.Version | |||||
| import com.yzx.webebook.presenter.base.BasePresenter | import com.yzx.webebook.presenter.base.BasePresenter | ||||
| import com.yzx.webebook.widget.BaseDialog | import com.yzx.webebook.widget.BaseDialog | ||||
| import kotlinx.android.synthetic.main.activity_home.* | import kotlinx.android.synthetic.main.activity_home.* | ||||
| import kotlinx.android.synthetic.main.activity_home.titleTv | |||||
| import org.jetbrains.anko.find | import org.jetbrains.anko.find | ||||
| import org.jetbrains.anko.startActivity | import org.jetbrains.anko.startActivity | ||||
| import org.jetbrains.anko.toast | import org.jetbrains.anko.toast | ||||
| @@ -47,8 +48,9 @@ class HomeActivity : BaseActivity<BasePresenter<*>>() { | |||||
| list.add(HomeItem("家庭作业", R.mipmap.ic_home_work, "/homework/ebook_list")) | list.add(HomeItem("家庭作业", R.mipmap.ic_home_work, "/homework/ebook_list")) | ||||
| list.add(HomeItem("笔记本", R.mipmap.ic_home_notebook, "/notebook/list")) | list.add(HomeItem("笔记本", R.mipmap.ic_home_notebook, "/notebook/list")) | ||||
| list.add(HomeItem("背诵默写", R.mipmap.ic_home_write, "/write/index")) | list.add(HomeItem("背诵默写", R.mipmap.ic_home_write, "/write/index")) | ||||
| list.add(HomeItem("书架", R.mipmap.ic_home_rack)) | |||||
| list.add(HomeItem("阅读", R.mipmap.ic_home_rack,"/read/index")) | |||||
| list.add(HomeItem("错题本", R.mipmap.ic_home_errorbook, "/errorbook/ebookindex")) | list.add(HomeItem("错题本", R.mipmap.ic_home_errorbook, "/errorbook/ebookindex")) | ||||
| list.add(HomeItem("随堂检测", R.mipmap.ic_home_classwork)) | |||||
| list.add(HomeItem("我的", R.mipmap.ic_home_my, "/mine/ebook")) | list.add(HomeItem("我的", R.mipmap.ic_home_my, "/mine/ebook")) | ||||
| HomeAdapter(list) | HomeAdapter(list) | ||||
| } | } | ||||
| @@ -102,8 +104,23 @@ class HomeActivity : BaseActivity<BasePresenter<*>>() { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| //请求权限 | |||||
| ActivityCompat.requestPermissions( | |||||
| this, | |||||
| PERMISSIONS, | |||||
| PERMISSIONS_REQUEST_STORAGE | |||||
| ) | |||||
| } | } | ||||
| private val PERMISSIONS_REQUEST_STORAGE = 1 | |||||
| val PERMISSIONS = arrayOf( | |||||
| Manifest.permission.READ_EXTERNAL_STORAGE, | |||||
| Manifest.permission.WRITE_EXTERNAL_STORAGE | |||||
| ) | |||||
| override fun initPresenter(): BasePresenter<*>? { | override fun initPresenter(): BasePresenter<*>? { | ||||
| return null | return null | ||||
| } | } | ||||
| @@ -1,725 +0,0 @@ | |||||
| package com.yzx.webebook.activity; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.BufferedWriter; | |||||
| import java.io.File; | |||||
| import java.io.FileNotFoundException; | |||||
| import java.io.FileOutputStream; | |||||
| import java.io.FileReader; | |||||
| import java.io.FileWriter; | |||||
| import java.io.IOException; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import com.yzx.webebook.utils.FileUtils; | |||||
| import com.yzx.webebook.utils.GsonHelper; | |||||
| import com.yzx.webebook.utils.ListUtils; | |||||
| import com.wetao.note.NotePageInfo; | |||||
| import com.wetao.note.OnInitedListener; | |||||
| import com.wetao.note.WeNoteView; | |||||
| import com.wetao.note.WePoint; | |||||
| import com.yzx.webebook.R; | |||||
| import android.Manifest; | |||||
| import android.app.Activity; | |||||
| import android.content.pm.ActivityInfo; | |||||
| import android.content.pm.PackageManager; | |||||
| import android.graphics.Bitmap; | |||||
| import android.graphics.Canvas; | |||||
| import android.graphics.PixelFormat; | |||||
| import android.graphics.Rect; | |||||
| import android.graphics.Typeface; | |||||
| import android.graphics.drawable.ColorDrawable; | |||||
| import android.graphics.drawable.Drawable; | |||||
| import android.os.Bundle; | |||||
| import android.os.Handler; | |||||
| import android.os.Message; | |||||
| import android.util.Log; | |||||
| import android.view.KeyEvent; | |||||
| import android.view.MotionEvent; | |||||
| import android.view.View; | |||||
| import android.view.Window; | |||||
| import android.widget.Button; | |||||
| import android.widget.CheckBox; | |||||
| import android.widget.RelativeLayout; | |||||
| public class Main1Activity extends Activity { | |||||
| private static final String TAG = "MainActivity"; | |||||
| private static final String NOTE1_FOLDER_DIR = "/mnt/sdcard/MyTestNote/Note/"; | |||||
| static final int[] mBGDrawableList = {-1, R.drawable.background1, R.drawable.background2, | |||||
| R.drawable.background3, R.drawable.background4, R.drawable.background5, R.drawable.background6, | |||||
| R.drawable.background7, R.drawable.background8, R.drawable.background9, R.drawable.background10}; | |||||
| private static final int MSG_SAVE_END = 2001; | |||||
| private WeNoteView mView; | |||||
| private Button mDeleteBtn; | |||||
| private Button mPicBtn; | |||||
| private Button mClearBtn; | |||||
| private Button mZoomBtn; | |||||
| private Button mUndoBtn; | |||||
| private Button mRedoBtn; | |||||
| private Button mPenWidthBtn; | |||||
| private Button mEraserWidthBtn; | |||||
| private Button mCancelBtn; | |||||
| private Button mTypeBtn; | |||||
| private CheckBox mVisibleCB; | |||||
| private static int mBGId = 0; | |||||
| private boolean mReDrawEnable = false; | |||||
| private static ArrayList<WePoint> mWePointList = null; | |||||
| private static NotePageInfo mNotePageInfo = null; | |||||
| @Override | |||||
| protected void onCreate(Bundle savedInstanceState) { | |||||
| super.onCreate(savedInstanceState); | |||||
| requestWindowFeature(Window.FEATURE_NO_TITLE); | |||||
| setContentView(R.layout.activity_main1); | |||||
| checkPermission(); | |||||
| Log.d(TAG, "Flash test : +++++++ onCreate()"); | |||||
| setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |||||
| mView = findViewById(R.id.note_view); | |||||
| mView.setBackground(null); | |||||
| mBGId = 0; | |||||
| mDeleteBtn = findViewById(R.id.delete); | |||||
| mDeleteBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| if (deleteNote(NOTE1_FOLDER_DIR)) { | |||||
| cancel(false); | |||||
| Main1Activity.this.finish(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| mPicBtn = findViewById(R.id.init); | |||||
| mPicBtn.setText("背景" + mBGId); | |||||
| mPicBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| mBGId++; | |||||
| if (mBGId >= mBGDrawableList.length) mBGId = 0; | |||||
| if (mBGId == 0) { | |||||
| mView.setBackground(new ColorDrawable()); | |||||
| mPicBtn.setText("背景0"); | |||||
| } else { | |||||
| mView.setBackgroundResource(mBGDrawableList[mBGId]); | |||||
| mPicBtn.setText("背景" + mBGId); | |||||
| } | |||||
| saveBGId(NOTE1_FOLDER_DIR, mBGId); | |||||
| } | |||||
| }); | |||||
| mClearBtn = findViewById(R.id.clear); | |||||
| mClearBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| mView.clear(); | |||||
| } | |||||
| }); | |||||
| mPenWidthBtn = findViewById(R.id.pen_width); | |||||
| mPenWidthBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| int width = mView.getPenWidth(); | |||||
| if (width == 10) { | |||||
| width = 1; | |||||
| } else { | |||||
| width++; | |||||
| } | |||||
| if (width > 0 && width <= 10) { | |||||
| mView.setPenWidth(width); | |||||
| mPenWidthBtn.setText("宽度" + width); | |||||
| } | |||||
| } | |||||
| }); | |||||
| mZoomBtn = findViewById(R.id.zoom); | |||||
| mZoomBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mView.getLayoutParams(); | |||||
| // 设置宽为100dp | |||||
| params.width = 1000; | |||||
| // 设置高为100dp | |||||
| params.height = 200; | |||||
| // 根据布局参数的设置,重新设置view(这里用了text view,当然其他的view也是通用的)的大小 | |||||
| mView.setLayoutParams(params); | |||||
| } | |||||
| }); | |||||
| mUndoBtn = findViewById(R.id.undo); | |||||
| mUndoBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| mView.unDo(); | |||||
| } | |||||
| }); | |||||
| mRedoBtn = findViewById(R.id.redo); | |||||
| mRedoBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| mView.reDo(); | |||||
| } | |||||
| }); | |||||
| mEraserWidthBtn = findViewById(R.id.eraser_width); | |||||
| mEraserWidthBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| int width = mView.getEraserWidth(); | |||||
| if (width == 20) { | |||||
| width = 1; | |||||
| } else { | |||||
| width++; | |||||
| } | |||||
| if (width > 0 && width <= 20) { | |||||
| mView.setEraserWidth(width); | |||||
| mEraserWidthBtn.setText("橡皮宽" + width); | |||||
| } | |||||
| } | |||||
| }); | |||||
| mCancelBtn = findViewById(R.id.cancel); | |||||
| mCancelBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| cancel(true); | |||||
| Main1Activity.this.finish(); | |||||
| } | |||||
| }); | |||||
| mTypeBtn = findViewById(R.id.draw_circle); | |||||
| mTypeBtn.setTypeface(null, Typeface.BOLD); | |||||
| mTypeBtn.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| int type = mView.getPenType(); | |||||
| Log.d(TAG, "Flash test : +++++++ onClick() type btn current type = " + type); | |||||
| if (type == WeNoteView.TYPE_DRAW_CURVE) { | |||||
| setPenType(WeNoteView.TYPE_DRAW_STROKES); | |||||
| mTypeBtn.setText("笔锋"); | |||||
| } else if (type == WeNoteView.TYPE_DRAW_STROKES) { | |||||
| setPenType(WeNoteView.TYPE_DRAW_LINE); | |||||
| mTypeBtn.setText("直线"); | |||||
| } else if (type == WeNoteView.TYPE_DRAW_LINE) { | |||||
| setPenType(WeNoteView.TYPE_DRAW_TRIANGLE); | |||||
| mTypeBtn.setText("三角形"); | |||||
| } else if (type == WeNoteView.TYPE_DRAW_TRIANGLE) { | |||||
| setPenType(WeNoteView.TYPE_DRAW_CIRCLE); | |||||
| mTypeBtn.setText("圆形"); | |||||
| } else if (type == WeNoteView.TYPE_DRAW_CIRCLE) { | |||||
| setPenType(WeNoteView.TYPE_DRAW_RECT); | |||||
| mTypeBtn.setText("矩形"); | |||||
| } else if (type == WeNoteView.TYPE_DRAW_RECT) { | |||||
| setPenType(WeNoteView.TYPE_DRAW_ERASER); | |||||
| mTypeBtn.setText("橡皮擦"); | |||||
| } else if (type == WeNoteView.TYPE_DRAW_ERASER) { | |||||
| setPenType(WeNoteView.TYPE_DRAW_CIRCLE_ERASER); | |||||
| mTypeBtn.setText("圈涂"); | |||||
| } else if (type == WeNoteView.TYPE_DRAW_CIRCLE_ERASER) { | |||||
| setPenType(WeNoteView.TYPE_DRAW_CURVE); | |||||
| mTypeBtn.setText("曲线"); | |||||
| } | |||||
| } | |||||
| }); | |||||
| mVisibleCB = findViewById(R.id.visible); | |||||
| mVisibleCB.setSelected(true); | |||||
| mVisibleCB.setOnClickListener(new View.OnClickListener() { | |||||
| @Override | |||||
| public void onClick(View view) { | |||||
| if (mVisibleCB.isChecked()) { | |||||
| mVisibleCB.setSelected(false); | |||||
| mView.setVisibility(View.INVISIBLE); | |||||
| } else { | |||||
| mVisibleCB.setSelected(true); | |||||
| mView.setVisibility(View.VISIBLE); | |||||
| } | |||||
| } | |||||
| }); | |||||
| mWePointList = new ArrayList<WePoint>(); | |||||
| mWePointList.clear(); | |||||
| mView.setOnFinishListener(new OnInitedListener() { | |||||
| @Override | |||||
| public void onInited() { | |||||
| //mView.setTouchEventHander(mPointHandler); | |||||
| Log.d(TAG, "Flash test : ++++++ mRunnable() start loader notepageinfo"); | |||||
| if (mNotePageInfo == null) { | |||||
| mNotePageInfo = getNotePageInfo(NOTE1_FOLDER_DIR); | |||||
| } | |||||
| if (mNotePageInfo == null) { | |||||
| Log.d(TAG, "Flash test : +++++ no note, so init a page"); | |||||
| initPage(NOTE1_FOLDER_DIR, 0, mBGId); | |||||
| } | |||||
| loadOldPage(); | |||||
| } | |||||
| @Override | |||||
| public void onSizeChanged() { | |||||
| } | |||||
| }); | |||||
| } | |||||
| @Override | |||||
| public void onWindowFocusChanged(boolean hasFocus) { | |||||
| super.onWindowFocusChanged(hasFocus); | |||||
| Log.d(TAG, "Flash test : ++++++++++ onWindowFocusChanged() hasFocus = " + hasFocus); | |||||
| if (hasFocus) { | |||||
| mView.onResume(); | |||||
| mReDrawEnable = true; | |||||
| } else { | |||||
| mView.onPause(); | |||||
| mReDrawEnable = false; | |||||
| } | |||||
| } | |||||
| private void setPenType(int type) { | |||||
| mView.setPenType(type); | |||||
| mView.updateEnableStatus(); | |||||
| } | |||||
| private void loadOldPage() { | |||||
| if (mNotePageInfo == null) { | |||||
| return; | |||||
| } | |||||
| Log.d(TAG, "Flash test : +++++ loadOldPage() mNotePageInfo = " + mNotePageInfo); | |||||
| mBGId = mNotePageInfo.drwId; | |||||
| if (mBGDrawableList[mBGId] == -1) { | |||||
| mView.setBackground(new ColorDrawable()); | |||||
| } else { | |||||
| mView.setBackgroundResource(mBGDrawableList[mBGId]); | |||||
| } | |||||
| mView.showExistPage(mNotePageInfo.notePath); | |||||
| mPicBtn.setText("背景" + mBGId); | |||||
| } | |||||
| public void onResume() { | |||||
| Log.d(TAG, "Flash test : +++++++ onResume()"); | |||||
| //mView.setEnable(true); | |||||
| mView.onResume(); | |||||
| mReDrawEnable = true; | |||||
| super.onResume(); | |||||
| } | |||||
| public void onPause() { | |||||
| Log.d(TAG, "Flash test : +++++++ onPause()"); | |||||
| mView.onPause(); | |||||
| mReDrawEnable = false; | |||||
| super.onPause(); | |||||
| } | |||||
| public void onDestroy() { | |||||
| Log.d(TAG, "Flash test : +++++++ onDestroy()"); | |||||
| mView.exitView(); | |||||
| super.onDestroy(); | |||||
| // System.exit(0); | |||||
| } | |||||
| private Handler mPointHandler = new Handler() { | |||||
| @Override | |||||
| public void handleMessage(Message msg) { | |||||
| int what = msg.what; | |||||
| //同步获取的笔记点坐标信息msg | |||||
| if (what == WeNoteView.TOUCH_EVENT) { | |||||
| WePoint point = (WePoint) msg.obj; | |||||
| Log.d(TAG, "Flash test : ++++++++ mPointHandler WePoint = " + point); | |||||
| mWePointList.add(point); | |||||
| transData(point); | |||||
| } | |||||
| //保存笔记结束msg | |||||
| if (what == MSG_SAVE_END) { | |||||
| int status = msg.arg1; | |||||
| if (status > 0) { //保存成功 | |||||
| Log.d(TAG, "Flash test : ++++++++ mPointHandler page save ok"); | |||||
| //Toast.makeText(mContext, "Note saved OK", Toast.LENGTH_SHORT).show(); | |||||
| } else { //保存失败 | |||||
| Log.d(TAG, "Flash test : ++++++++ mPointHandler page save failed"); | |||||
| //Toast.makeText(mContext, "Note saved Fail", Toast.LENGTH_SHORT).show(); | |||||
| } | |||||
| } | |||||
| } | |||||
| }; | |||||
| private void cancel(boolean save) { | |||||
| if (save && mView.isHandwritingExist()) { | |||||
| saveNote(NOTE1_FOLDER_DIR, mView, mBGId); | |||||
| } | |||||
| Main1Activity.this.finish(); | |||||
| } | |||||
| private boolean initPage(String dir, int viewId, int bgId) { | |||||
| Log.d(TAG, "Flash test : +++++++++ intPage()"); | |||||
| File file = new File(dir); | |||||
| if(!file.exists()) { | |||||
| Log.e(TAG, "Flash test : +++++++++ addPageFolder() no folder path = " + dir); | |||||
| if (!makeDir(file)) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| String notePath = null; | |||||
| file = new File(dir + "/note.png"); | |||||
| if(file.exists()) { | |||||
| notePath = dir + "/note.png"; | |||||
| } | |||||
| file = new File(dir + "/.drawable.txt"); | |||||
| if(!file.exists()) { | |||||
| try { | |||||
| file.createNewFile(); | |||||
| } catch (IOException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| if (bgId == 1) bgId = 2; | |||||
| saveBGId(dir, bgId); | |||||
| mNotePageInfo = new NotePageInfo(notePath, bgId); | |||||
| return true; | |||||
| } | |||||
| private boolean deleteNote(String dir) { | |||||
| Log.d(TAG, "Flash test : +++++++++ deletdNote()"); | |||||
| File file = new File(dir); | |||||
| if(!file.exists()) { | |||||
| Log.e(TAG, "Flash test : +++++++++ deletdPageFolder() no folder path = " + dir); | |||||
| return true; | |||||
| } | |||||
| deleteDirWithFile(file); | |||||
| return true; | |||||
| } | |||||
| private boolean saveNote(String dir, WeNoteView view, int bgId) { | |||||
| Log.d(TAG, "Flash test : ++++++++ saveNote()"); | |||||
| File f = new File(dir); | |||||
| if(!f.exists()) { | |||||
| Log.d(TAG, "Flash test : +++++++ saveNote() create folder " + dir); | |||||
| if (!makeDir(f)) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| String noteName = "note.png"; | |||||
| final Bitmap bmp = Bitmap.createBitmap(view.getCurrentNoteBitmap()); | |||||
| if (bmp == null) { | |||||
| Log.e(TAG, "Flash test : ++++ saveNote() getCurrentNoteBitmap is null"); | |||||
| return false; | |||||
| } | |||||
| String path = dir + "/" + noteName; | |||||
| if (!saveNoteWithoutBG(path, bmp)) { | |||||
| Log.e(TAG, "Flash test : ++++ saveNote() saveNoteWithoutBG fail"); | |||||
| freeBitmap(bmp); | |||||
| return false; | |||||
| } | |||||
| saveBGId(dir, bgId); | |||||
| freeBitmap(bmp); | |||||
| Log.e(TAG, "Flash test : ++++ saveNote() OK !!!!!"); | |||||
| return true; | |||||
| } | |||||
| private static boolean saveBGId(String dir, int bgId) { | |||||
| Log.d(TAG, "Flash test : ++++++++ saveBGId drwId = " + bgId); | |||||
| File f = new File(dir); | |||||
| if(!f.exists()) { | |||||
| Log.d(TAG, "Flash test : +++++++ saveAllNote() create folder " + dir); | |||||
| if (!makeDir(f)) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| String path = dir + "/.drawable.txt"; | |||||
| writeFileData(path, String.valueOf(bgId)); | |||||
| return true; | |||||
| } | |||||
| private static void freeBitmap(Bitmap bit) { | |||||
| if (bit != null && !bit.isRecycled()) { | |||||
| bit.recycle(); | |||||
| } | |||||
| } | |||||
| //保存不带背景的笔记为png | |||||
| private boolean saveNoteWithoutBG(String picPath, Bitmap bmp) { | |||||
| Log.d(TAG, "Flash test : +++++++ saveNoteWithoutBG() picPath = " + picPath); | |||||
| File f = new File(picPath); | |||||
| if(f.exists()) { | |||||
| f.delete(); | |||||
| } | |||||
| FileOutputStream fos = null; | |||||
| try { | |||||
| fos = new FileOutputStream(f); | |||||
| bmp.compress(Bitmap.CompressFormat.PNG, 90, fos); | |||||
| try { | |||||
| fos.flush(); | |||||
| } catch (IOException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| } | |||||
| try { | |||||
| fos.close(); | |||||
| } catch (IOException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } catch (FileNotFoundException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| //保存带背景的笔记为png | |||||
| private boolean saveAll(String picPath, Bitmap bmp, Drawable drw) { | |||||
| Log.d(TAG, "Flash test : ++++ saveAll() picPath = " + picPath); | |||||
| int width = bmp.getWidth(); | |||||
| int height = bmp.getHeight(); | |||||
| Bitmap bitmap = Bitmap.createBitmap(width, height, | |||||
| drw.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); | |||||
| Canvas canvas = new Canvas(bitmap); | |||||
| drw.setBounds(0, 0, width, height); | |||||
| drw.draw(canvas); | |||||
| canvas.drawBitmap(bmp, new Rect(0, 0, width, height), | |||||
| new Rect(0, 0, width, height) , null); | |||||
| canvas.save();//Canvas.ALL_SAVE_FLAG); | |||||
| canvas.restore(); | |||||
| boolean result = saveNoteWithoutBG(picPath, bitmap); | |||||
| if (bitmap != null && !bitmap.isRecycled()) { | |||||
| bitmap.recycle(); | |||||
| bitmap = null; | |||||
| } | |||||
| System.gc(); | |||||
| return result; | |||||
| } | |||||
| //保存笔记背景为png | |||||
| private boolean saveNoteBG(String picPath, Drawable drw, int width, int height) { | |||||
| Log.d(TAG, "Flash test : ++++ saveNoteBG() picPath = " + picPath); | |||||
| Bitmap bitmap = Bitmap.createBitmap(width, height, | |||||
| drw.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); | |||||
| Canvas canvas = new Canvas(bitmap); | |||||
| drw.setBounds(0, 0, width, height); | |||||
| drw.draw(canvas); | |||||
| boolean result = saveNoteWithoutBG(picPath, bitmap); | |||||
| if (bitmap != null && !bitmap.isRecycled()) { | |||||
| bitmap.recycle(); | |||||
| bitmap = null; | |||||
| } | |||||
| System.gc(); | |||||
| return result; | |||||
| } | |||||
| //重绘之前绘制的画笔 | |||||
| private void drawWePointList() { | |||||
| if (mView.isReDrawDoing()) { | |||||
| Log.i(TAG, "Flash test : +++++ drawWePointList() return because isReDrawDoing is true"); | |||||
| return; | |||||
| } | |||||
| if (mWePointList != null && !mWePointList.isEmpty()) { | |||||
| new Thread(new Runnable() { | |||||
| @Override | |||||
| public void run() { | |||||
| mView.setReDrawEnable(true); | |||||
| mReDrawEnable = true; | |||||
| int size = mWePointList.size(); | |||||
| int i = 0; | |||||
| while (i<size) { | |||||
| while(!mReDrawEnable) { | |||||
| try { | |||||
| Thread.sleep(100); | |||||
| } catch (InterruptedException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| WePoint wp = mWePointList.get(i); | |||||
| mView.drawPoint(wp); | |||||
| try { | |||||
| if (i+1 < size) Thread.sleep(mWePointList.get(i+1).eventTime - wp.eventTime); | |||||
| } catch (InterruptedException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| } | |||||
| i++; | |||||
| } | |||||
| mView.setReDrawEnable(false); | |||||
| } | |||||
| }).start(); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public boolean onKeyDown(int keyCode, KeyEvent event) { | |||||
| if (keyCode == KeyEvent.KEYCODE_BACK) { | |||||
| Main1Activity.this.finish(); | |||||
| return true; | |||||
| } | |||||
| return super.onKeyDown(keyCode, event); | |||||
| } | |||||
| //从笔记文件夹中读取当前笔记的页面信息 | |||||
| private NotePageInfo getNotePageInfo(String path) { | |||||
| Log.d(TAG, "Flash test : +++++++++ getNotePageInfo() path = " + path); | |||||
| File file = new File(path); | |||||
| if(!file.exists()) { | |||||
| Log.e(TAG, "Flash test : +++++++++ getNotePageInfo() no folder path = " + path); | |||||
| return null; | |||||
| } | |||||
| File[] files = file.listFiles(); | |||||
| if (files == null) { | |||||
| Log.e(TAG, "Flash test : +++++++++ getNotePageInfo() no files in this Note folder = " + path); | |||||
| return null; | |||||
| } | |||||
| NotePageInfo info = new NotePageInfo(null, 0); | |||||
| String notePath = path + "/note.png"; | |||||
| file = new File(notePath); | |||||
| if(file.exists()) { | |||||
| info.notePath = notePath; | |||||
| } | |||||
| String drwId = readFileData(path + "/.drawable.txt"); | |||||
| info.drwId = Integer.parseInt(drwId); | |||||
| return info; | |||||
| } | |||||
| private void checkPermission() { | |||||
| boolean isGranted = true; | |||||
| if (android.os.Build.VERSION.SDK_INT >= 23) { | |||||
| if (this.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { | |||||
| //如果没有写sd卡权限 | |||||
| isGranted = false; | |||||
| } | |||||
| if (this.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { | |||||
| isGranted = false; | |||||
| } | |||||
| Log.i("cbs","isGranted == "+isGranted); | |||||
| if (!isGranted) { | |||||
| this.requestPermissions( | |||||
| new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission | |||||
| .ACCESS_FINE_LOCATION, | |||||
| Manifest.permission.READ_EXTERNAL_STORAGE, | |||||
| Manifest.permission.WRITE_EXTERNAL_STORAGE}, | |||||
| 102); | |||||
| } | |||||
| } | |||||
| } | |||||
| private static boolean makeDir(File folder) { | |||||
| try { | |||||
| //按照指定的路径创建文件夹 | |||||
| folder.mkdirs(); | |||||
| } catch (Exception e) { | |||||
| // TODO: handle exception | |||||
| Log.e(TAG, "Flash test : +++++ makeDir() create dir " + folder.getPath() + " error = " + e); | |||||
| return false; | |||||
| } | |||||
| Log.d(TAG, "Flash test : +++++ makeDir() create dir " + folder.getPath()); | |||||
| return true; | |||||
| } | |||||
| private void deleteDirWithFile(File dir) { | |||||
| if (dir == null || !dir.exists() || !dir.isDirectory()) | |||||
| return; | |||||
| for (File file : dir.listFiles()) { | |||||
| if (file.isFile()) | |||||
| file.delete(); // 删除所有文件 | |||||
| else if (file.isDirectory()) | |||||
| deleteDirWithFile(file); // 递规的方式删除文件夹 | |||||
| } | |||||
| dir.delete();// 删除目录本身 | |||||
| } | |||||
| public static void writeFileData(String fileName, String content) { | |||||
| File file = new File(fileName); | |||||
| if (!file.exists()) { | |||||
| try { | |||||
| file.createNewFile(); | |||||
| } catch (IOException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| try { | |||||
| BufferedWriter bw = new BufferedWriter(new FileWriter(file)); | |||||
| bw.write(content); | |||||
| bw.close(); | |||||
| } catch (IOException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| //打开指定文件,读取其数据,返回字符串对象 | |||||
| public static String readFileData(String fileName) { | |||||
| File file = new File(fileName); | |||||
| if (!file.exists()) { | |||||
| return "1"; | |||||
| } | |||||
| BufferedReader br; | |||||
| try { | |||||
| br = new BufferedReader(new FileReader(file)); | |||||
| //System.out.println("br.readLine=" + br.readLine()); | |||||
| return br.readLine(); | |||||
| } catch (IOException e) { | |||||
| // TODO Auto-generated catch block | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return "1"; | |||||
| } | |||||
| private List<String> stringList; | |||||
| private void transData(WePoint point) { | |||||
| if (ListUtils.isEmpty(stringList)) { | |||||
| stringList = new ArrayList<>(); | |||||
| } | |||||
| String s = GsonHelper.toJson(point); | |||||
| stringList.add(s); | |||||
| stringList.add("\r\n"); | |||||
| if (point.action == MotionEvent.ACTION_UP) { | |||||
| String join = String.join("", stringList); | |||||
| saveData(join); | |||||
| stringList.clear(); | |||||
| } | |||||
| } | |||||
| private void saveData(String msg) { | |||||
| File file = new File("/mnt/sdcard/point.txt"); | |||||
| if (!file.exists()) { | |||||
| try { | |||||
| file.createNewFile(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| FileUtils.writeFile(file.getPath(), msg, true); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,765 @@ | |||||
| package com.yzx.webebook.activity; | |||||
| import android.app.Activity; | |||||
| import android.app.AlertDialog; | |||||
| import android.content.BroadcastReceiver; | |||||
| import android.content.ContentResolver; | |||||
| import android.content.Context; | |||||
| import android.content.Intent; | |||||
| import android.content.IntentFilter; | |||||
| import android.database.ContentObserver; | |||||
| import android.graphics.drawable.Drawable; | |||||
| import android.net.Uri; | |||||
| import android.os.Build; | |||||
| import android.os.Bundle; | |||||
| import android.os.Handler; | |||||
| import android.os.Message; | |||||
| import android.os.PowerManager; | |||||
| import android.provider.Settings; | |||||
| import android.util.Log; | |||||
| import android.view.Gravity; | |||||
| import android.view.KeyEvent; | |||||
| import android.view.View; | |||||
| import android.view.ViewGroup; | |||||
| import android.view.animation.Animation; | |||||
| import android.view.animation.AnimationUtils; | |||||
| import android.widget.LinearLayout; | |||||
| import android.widget.ListView; | |||||
| import android.widget.SeekBar; | |||||
| import android.widget.TextView; | |||||
| import androidx.core.content.ContextCompat; | |||||
| import androidx.core.view.GravityCompat; | |||||
| import androidx.drawerlayout.widget.DrawerLayout; | |||||
| import com.google.android.material.appbar.AppBarLayout; | |||||
| import com.yzx.webebook.R; | |||||
| import com.yzx.webebook.activity.base.BaseActivity; | |||||
| import com.yzx.webebook.adapter.CategoryAdapter; | |||||
| import com.yzx.webebook.model.bean.BookChapterBean; | |||||
| import com.yzx.webebook.model.bean.CollBookBean; | |||||
| import com.yzx.webebook.model.local.BookRepository; | |||||
| import com.yzx.webebook.model.local.ReadSettingManager; | |||||
| import com.yzx.webebook.presenter.ReadPresenter; | |||||
| import com.yzx.webebook.presenter.ReadView; | |||||
| import com.yzx.webebook.utils.BrightnessUtils; | |||||
| import com.yzx.webebook.utils.Constant; | |||||
| import com.yzx.webebook.utils.LogUtils; | |||||
| import com.yzx.webebook.utils.RxUtils; | |||||
| import com.yzx.webebook.utils.ScreenUtils; | |||||
| import com.yzx.webebook.utils.StringUtils; | |||||
| import com.yzx.webebook.utils.SystemBarUtils; | |||||
| import com.yzx.webebook.widget.ReadSettingDialog; | |||||
| import com.yzx.webebook.widget.page.PageLoader; | |||||
| import com.yzx.webebook.widget.page.PageView; | |||||
| import com.yzx.webebook.widget.page.TxtChapter; | |||||
| import org.jetbrains.annotations.Nullable; | |||||
| import java.util.List; | |||||
| import io.reactivex.disposables.Disposable; | |||||
| import static android.view.View.GONE; | |||||
| import static android.view.View.VISIBLE; | |||||
| public class ReadActivity extends BaseActivity<ReadPresenter> implements ReadView { | |||||
| private static final String TAG = "ReadActivity"; | |||||
| public static final int REQUEST_MORE_SETTING = 1; | |||||
| public static final String EXTRA_COLL_BOOK = "extra_coll_book"; | |||||
| public static final String EXTRA_IS_COLLECTED = "extra_is_collected"; | |||||
| // 注册 Brightness 的 uri | |||||
| private final Uri BRIGHTNESS_MODE_URI = | |||||
| Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE); | |||||
| private final Uri BRIGHTNESS_URI = | |||||
| Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); | |||||
| private final Uri BRIGHTNESS_ADJ_URI = | |||||
| Settings.System.getUriFor("screen_auto_brightness_adj"); | |||||
| private static final int WHAT_CATEGORY = 1; | |||||
| private static final int WHAT_CHAPTER = 2; | |||||
| DrawerLayout mDlSlide; | |||||
| /*************top_menu_view*******************/ | |||||
| AppBarLayout mAblTopMenu; | |||||
| TextView mTvCommunity; | |||||
| TextView mTvBrief; | |||||
| /***************content_view******************/ | |||||
| PageView mPvPage; | |||||
| /***************bottom_menu_view***************************/ | |||||
| TextView mTvPageTip; | |||||
| LinearLayout mLlBottomMenu; | |||||
| TextView mTvPreChapter; | |||||
| SeekBar mSbChapterProgress; | |||||
| TextView mTvNextChapter; | |||||
| TextView mTvCategory; | |||||
| TextView mTvNightMode; | |||||
| TextView mTvSetting; | |||||
| /***************left slide*******************************/ | |||||
| ListView mLvCategory; | |||||
| /*****************view******************/ | |||||
| private ReadSettingDialog mSettingDialog; | |||||
| private PageLoader mPageLoader; | |||||
| private Animation mTopInAnim; | |||||
| private Animation mTopOutAnim; | |||||
| private Animation mBottomInAnim; | |||||
| private Animation mBottomOutAnim; | |||||
| private CategoryAdapter mCategoryAdapter; | |||||
| private CollBookBean mCollBook; | |||||
| //控制屏幕常亮 | |||||
| private PowerManager.WakeLock mWakeLock; | |||||
| /***************params*****************/ | |||||
| private boolean isCollected = false; // isFromSDCard | |||||
| private boolean isNightMode = false; | |||||
| private boolean isFullScreen = false; | |||||
| private boolean isRegistered = false; | |||||
| private String mBookId; | |||||
| private Handler mHandler = new Handler() { | |||||
| @Override | |||||
| public void handleMessage(Message msg) { | |||||
| super.handleMessage(msg); | |||||
| switch (msg.what) { | |||||
| case WHAT_CATEGORY: | |||||
| mLvCategory.setSelection(mPageLoader.getChapterPos()); | |||||
| break; | |||||
| case WHAT_CHAPTER: | |||||
| mPageLoader.openChapter(); | |||||
| break; | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 接收电池信息和时间更新的广播 | |||||
| private BroadcastReceiver mReceiver = new BroadcastReceiver() { | |||||
| @Override | |||||
| public void onReceive(Context context, Intent intent) { | |||||
| if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) { | |||||
| int level = intent.getIntExtra("level", 0); | |||||
| mPageLoader.updateBattery(level); | |||||
| } | |||||
| // 监听分钟的变化 | |||||
| else if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) { | |||||
| mPageLoader.updateTime(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 亮度调节监听 | |||||
| // 由于亮度调节没有 Broadcast 而是直接修改 ContentProvider 的。所以需要创建一个 Observer 来监听 ContentProvider 的变化情况。 | |||||
| private ContentObserver mBrightObserver = new ContentObserver(new Handler()) { | |||||
| @Override | |||||
| public void onChange(boolean selfChange) { | |||||
| onChange(selfChange, null); | |||||
| } | |||||
| @Override | |||||
| public void onChange(boolean selfChange, Uri uri) { | |||||
| super.onChange(selfChange); | |||||
| // 判断当前是否跟随屏幕亮度,如果不是则返回 | |||||
| if (selfChange || !mSettingDialog.isBrightFollowSystem()) return; | |||||
| // 如果系统亮度改变,则修改当前 Activity 亮度 | |||||
| if (BRIGHTNESS_MODE_URI.equals(uri)) { | |||||
| Log.d(TAG, "亮度模式改变"); | |||||
| } else if (BRIGHTNESS_URI.equals(uri) && !BrightnessUtils.isAutoBrightness(ReadActivity.this)) { | |||||
| Log.d(TAG, "亮度模式为手动模式 值改变"); | |||||
| BrightnessUtils.setBrightness(ReadActivity.this, BrightnessUtils.getScreenBrightness(ReadActivity.this)); | |||||
| } else if (BRIGHTNESS_ADJ_URI.equals(uri) && BrightnessUtils.isAutoBrightness(ReadActivity.this)) { | |||||
| Log.d(TAG, "亮度模式为自动模式 值改变"); | |||||
| BrightnessUtils.setDefaultBrightness(ReadActivity.this); | |||||
| } else { | |||||
| Log.d(TAG, "亮度调整 其他"); | |||||
| } | |||||
| } | |||||
| }; | |||||
| @Override | |||||
| protected void onCreate(Bundle savedInstanceState) { | |||||
| super.onCreate(savedInstanceState); | |||||
| } | |||||
| @Override | |||||
| public int getInflateId() { | |||||
| return R.layout.activity_read; | |||||
| } | |||||
| @Override | |||||
| public void initView() { | |||||
| mDlSlide = findViewById(R.id.read_dl_slide); | |||||
| mAblTopMenu = findViewById(R.id.read_abl_top_menu); | |||||
| mTvCommunity = findViewById(R.id.read_tv_community); | |||||
| mTvBrief = findViewById(R.id.read_tv_brief); | |||||
| mPvPage = findViewById(R.id.read_pv_page); | |||||
| mTvPageTip = findViewById(R.id.read_tv_page_tip); | |||||
| mLlBottomMenu = findViewById(R.id.read_ll_bottom_menu); | |||||
| mTvPreChapter = findViewById(R.id.read_tv_pre_chapter); | |||||
| mSbChapterProgress = findViewById(R.id.read_sb_chapter_progress); | |||||
| mTvNextChapter = findViewById(R.id.read_tv_next_chapter); | |||||
| mTvCategory = findViewById(R.id.read_tv_category); | |||||
| mTvNightMode = findViewById(R.id.read_tv_night_mode); | |||||
| mTvSetting = findViewById(R.id.read_tv_setting); | |||||
| mLvCategory = findViewById(R.id.read_iv_category); | |||||
| } | |||||
| @Override | |||||
| public void initData() { | |||||
| // mCollBook = getIntent().getParcelableExtra(EXTRA_COLL_BOOK); | |||||
| mCollBook = BookRepository.getInstance().getCollBook("75979111277b0955"); | |||||
| isCollected = getIntent().getBooleanExtra(EXTRA_IS_COLLECTED, false); | |||||
| isNightMode = ReadSettingManager.getInstance().isNightMode(); | |||||
| isFullScreen = ReadSettingManager.getInstance().isFullScreen(); | |||||
| mBookId = mCollBook.get_id(); | |||||
| Log.d(TAG, "initData: " + mCollBook.toString()); | |||||
| //获取页面加载器 | |||||
| mPageLoader = mPvPage.getPageLoader(mCollBook); | |||||
| //禁止滑动展示DrawerLayout | |||||
| mDlSlide.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); | |||||
| //侧边打开后,返回键能够起作用 | |||||
| mDlSlide.setFocusableInTouchMode(false); | |||||
| mSettingDialog = new ReadSettingDialog(this, mPageLoader); | |||||
| setUpAdapter(); | |||||
| //夜间模式按钮的状态 | |||||
| toggleNightMode(); | |||||
| //注册广播 | |||||
| IntentFilter intentFilter = new IntentFilter(); | |||||
| intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); | |||||
| intentFilter.addAction(Intent.ACTION_TIME_TICK); | |||||
| registerReceiver(mReceiver, intentFilter); | |||||
| //设置当前Activity的Brightness | |||||
| if (ReadSettingManager.getInstance().isBrightnessAuto()) { | |||||
| BrightnessUtils.setDefaultBrightness(this); | |||||
| } else { | |||||
| BrightnessUtils.setBrightness(this, ReadSettingManager.getInstance().getBrightness()); | |||||
| } | |||||
| //初始化屏幕常亮类 | |||||
| PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); | |||||
| mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "ireader:keep bright"); | |||||
| //隐藏StatusBar | |||||
| mPvPage.post( | |||||
| () -> hideSystemBar() | |||||
| ); | |||||
| //初始化TopMenu | |||||
| initTopMenu(); | |||||
| //初始化BottomMenu | |||||
| initBottomMenu(); | |||||
| Disposable disposable = BookRepository.getInstance() | |||||
| .getBookChaptersInRx(mBookId) | |||||
| .compose(RxUtils::toSimpleSingle) | |||||
| .subscribe( | |||||
| (bookChapterBeen, throwable) -> { | |||||
| // 设置 CollBook | |||||
| mPageLoader.getCollBook().setBookChapters(bookChapterBeen); | |||||
| // 刷新章节列表 | |||||
| mPageLoader.refreshChapterList(); | |||||
| // 如果是网络小说并被标记更新的,则从网络下载目录 | |||||
| // if (mCollBook.isUpdate() && !mCollBook.isLocal()) { | |||||
| // getMPresenter().loadCategory(mBookId); | |||||
| // } | |||||
| LogUtils.e(throwable); | |||||
| } | |||||
| ); | |||||
| addDisposable(disposable); | |||||
| initClick(); | |||||
| } | |||||
| private void initClick(){ | |||||
| mPageLoader.setOnPageChangeListener( | |||||
| new PageLoader.OnPageChangeListener() { | |||||
| @Override | |||||
| public void onChapterChange(int pos) { | |||||
| mCategoryAdapter.setChapter(pos); | |||||
| } | |||||
| @Override | |||||
| public void requestChapters(List<TxtChapter> requestChapters) { | |||||
| getMPresenter().loadChapter(mBookId, requestChapters); | |||||
| mHandler.sendEmptyMessage(WHAT_CATEGORY); | |||||
| //隐藏提示 | |||||
| mTvPageTip.setVisibility(GONE); | |||||
| } | |||||
| @Override | |||||
| public void onCategoryFinish(List<TxtChapter> chapters) { | |||||
| for (TxtChapter chapter : chapters) { | |||||
| chapter.setTitle(StringUtils.convertCC(chapter.getTitle(), mPvPage.getContext())); | |||||
| } | |||||
| mCategoryAdapter.refreshItems(chapters); | |||||
| } | |||||
| @Override | |||||
| public void onPageCountChange(int count) { | |||||
| mSbChapterProgress.setMax(Math.max(0, count - 1)); | |||||
| mSbChapterProgress.setProgress(0); | |||||
| // 如果处于错误状态,那么就冻结使用 | |||||
| if (mPageLoader.getPageStatus() == PageLoader.STATUS_LOADING | |||||
| || mPageLoader.getPageStatus() == PageLoader.STATUS_ERROR) { | |||||
| mSbChapterProgress.setEnabled(false); | |||||
| } else { | |||||
| mSbChapterProgress.setEnabled(true); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void onPageChange(int pos) { | |||||
| mSbChapterProgress.post( | |||||
| () -> mSbChapterProgress.setProgress(pos) | |||||
| ); | |||||
| } | |||||
| } | |||||
| ); | |||||
| mSbChapterProgress.setOnSeekBarChangeListener( | |||||
| new SeekBar.OnSeekBarChangeListener() { | |||||
| @Override | |||||
| public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { | |||||
| if (mLlBottomMenu.getVisibility() == VISIBLE) { | |||||
| //显示标题 | |||||
| mTvPageTip.setText((progress + 1) + "/" + (mSbChapterProgress.getMax() + 1)); | |||||
| mTvPageTip.setVisibility(VISIBLE); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void onStartTrackingTouch(SeekBar seekBar) { | |||||
| } | |||||
| @Override | |||||
| public void onStopTrackingTouch(SeekBar seekBar) { | |||||
| //进行切换 | |||||
| int pagePos = mSbChapterProgress.getProgress(); | |||||
| if (pagePos != mPageLoader.getPagePos()) { | |||||
| mPageLoader.skipToPage(pagePos); | |||||
| } | |||||
| //隐藏提示 | |||||
| mTvPageTip.setVisibility(GONE); | |||||
| } | |||||
| } | |||||
| ); | |||||
| mPvPage.setTouchListener(new PageView.TouchListener() { | |||||
| @Override | |||||
| public boolean onTouch() { | |||||
| return !hideReadMenu(); | |||||
| } | |||||
| @Override | |||||
| public void center() { | |||||
| toggleMenu(true); | |||||
| } | |||||
| @Override | |||||
| public void prePage() { | |||||
| } | |||||
| @Override | |||||
| public void nextPage() { | |||||
| } | |||||
| @Override | |||||
| public void cancel() { | |||||
| } | |||||
| }); | |||||
| mLvCategory.setOnItemClickListener( | |||||
| (parent, view, position, id) -> { | |||||
| mDlSlide.closeDrawer(GravityCompat.START); | |||||
| mPageLoader.skipToChapter(position); | |||||
| } | |||||
| ); | |||||
| mTvCategory.setOnClickListener( | |||||
| (v) -> { | |||||
| //移动到指定位置 | |||||
| if (mCategoryAdapter.getCount() > 0) { | |||||
| mLvCategory.setSelection(mPageLoader.getChapterPos()); | |||||
| } | |||||
| //切换菜单 | |||||
| toggleMenu(true); | |||||
| //打开侧滑动栏 | |||||
| mDlSlide.openDrawer(GravityCompat.START); | |||||
| } | |||||
| ); | |||||
| mTvSetting.setOnClickListener( | |||||
| (v) -> { | |||||
| toggleMenu(false); | |||||
| mSettingDialog.show(); | |||||
| } | |||||
| ); | |||||
| mTvPreChapter.setOnClickListener( | |||||
| (v) -> { | |||||
| if (mPageLoader.skipPreChapter()) { | |||||
| mCategoryAdapter.setChapter(mPageLoader.getChapterPos()); | |||||
| } | |||||
| } | |||||
| ); | |||||
| mTvNextChapter.setOnClickListener( | |||||
| (v) -> { | |||||
| if (mPageLoader.skipNextChapter()) { | |||||
| mCategoryAdapter.setChapter(mPageLoader.getChapterPos()); | |||||
| } | |||||
| } | |||||
| ); | |||||
| mTvNightMode.setOnClickListener( | |||||
| (v) -> { | |||||
| if (isNightMode) { | |||||
| isNightMode = false; | |||||
| } else { | |||||
| isNightMode = true; | |||||
| } | |||||
| mPageLoader.setNightMode(isNightMode); | |||||
| toggleNightMode(); | |||||
| } | |||||
| ); | |||||
| // mTvBrief.setOnClickListener( | |||||
| // (v) -> BookDetailActivity.startActivity(this, mBookId) | |||||
| // ); | |||||
| // mTvCommunity.setOnClickListener( | |||||
| // (v) -> { | |||||
| // Intent intent = new Intent(this, CommunityActivity.class); | |||||
| // startActivity(intent); | |||||
| // } | |||||
| // ); | |||||
| mSettingDialog.setOnDismissListener( | |||||
| dialog -> hideSystemBar() | |||||
| ); | |||||
| } | |||||
| @Nullable | |||||
| @Override | |||||
| public ReadPresenter initPresenter() { | |||||
| return new ReadPresenter(this); | |||||
| } | |||||
| @Override | |||||
| public void showCategory(List<BookChapterBean> bookChapterList) { | |||||
| } | |||||
| @Override | |||||
| public void finishChapter() { | |||||
| } | |||||
| @Override | |||||
| public void errorChapter() { | |||||
| } | |||||
| private void initTopMenu() { | |||||
| // if (Build.VERSION.SDK_INT >= 19) { | |||||
| // mAblTopMenu.setPadding(0, ScreenUtils.getStatusBarHeight(), 0, 0); | |||||
| // } | |||||
| } | |||||
| private void initBottomMenu() { | |||||
| //判断是否全屏 | |||||
| if (ReadSettingManager.getInstance().isFullScreen()) { | |||||
| //还需要设置mBottomMenu的底部高度 | |||||
| ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mLlBottomMenu.getLayoutParams(); | |||||
| params.bottomMargin = ScreenUtils.getNavigationBarHeight(); | |||||
| mLlBottomMenu.setLayoutParams(params); | |||||
| } else { | |||||
| //设置mBottomMenu的底部距离 | |||||
| ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mLlBottomMenu.getLayoutParams(); | |||||
| params.bottomMargin = 0; | |||||
| mLlBottomMenu.setLayoutParams(params); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void onWindowFocusChanged(boolean hasFocus) { | |||||
| super.onWindowFocusChanged(hasFocus); | |||||
| Log.d(TAG, "onWindowFocusChanged: " + mAblTopMenu.getMeasuredHeight()); | |||||
| } | |||||
| private void toggleNightMode() { | |||||
| if (isNightMode) { | |||||
| mTvNightMode.setText("日间"); | |||||
| Drawable drawable = ContextCompat.getDrawable(this, R.mipmap.ic_read_menu_morning); | |||||
| mTvNightMode.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null); | |||||
| } else { | |||||
| mTvNightMode.setText("夜间"); | |||||
| Drawable drawable = ContextCompat.getDrawable(this, R.mipmap.ic_read_menu_night); | |||||
| mTvNightMode.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null); | |||||
| } | |||||
| } | |||||
| private void setUpAdapter() { | |||||
| mCategoryAdapter = new CategoryAdapter(); | |||||
| mLvCategory.setAdapter(mCategoryAdapter); | |||||
| mLvCategory.setFastScrollEnabled(true); | |||||
| } | |||||
| // 注册亮度观察者 | |||||
| private void registerBrightObserver() { | |||||
| try { | |||||
| if (mBrightObserver != null) { | |||||
| if (!isRegistered) { | |||||
| final ContentResolver cr = getContentResolver(); | |||||
| cr.unregisterContentObserver(mBrightObserver); | |||||
| cr.registerContentObserver(BRIGHTNESS_MODE_URI, false, mBrightObserver); | |||||
| cr.registerContentObserver(BRIGHTNESS_URI, false, mBrightObserver); | |||||
| cr.registerContentObserver(BRIGHTNESS_ADJ_URI, false, mBrightObserver); | |||||
| isRegistered = true; | |||||
| } | |||||
| } | |||||
| } catch (Throwable throwable) { | |||||
| LogUtils.e(TAG, "register mBrightObserver error! " + throwable); | |||||
| } | |||||
| } | |||||
| //解注册 | |||||
| private void unregisterBrightObserver() { | |||||
| try { | |||||
| if (mBrightObserver != null) { | |||||
| if (isRegistered) { | |||||
| getContentResolver().unregisterContentObserver(mBrightObserver); | |||||
| isRegistered = false; | |||||
| } | |||||
| } | |||||
| } catch (Throwable throwable) { | |||||
| LogUtils.e(TAG, "unregister BrightnessObserver error! " + throwable); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 隐藏阅读界面的菜单显示 | |||||
| * | |||||
| * @return 是否隐藏成功 | |||||
| */ | |||||
| private boolean hideReadMenu() { | |||||
| hideSystemBar(); | |||||
| if (mAblTopMenu.getVisibility() == VISIBLE) { | |||||
| toggleMenu(true); | |||||
| return true; | |||||
| } else if (mSettingDialog.isShowing()) { | |||||
| mSettingDialog.dismiss(); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| private void showSystemBar() { | |||||
| //显示 | |||||
| // SystemBarUtils.showUnStableStatusBar(this); | |||||
| // if (isFullScreen) { | |||||
| // SystemBarUtils.showUnStableNavBar(this); | |||||
| // } | |||||
| } | |||||
| private void hideSystemBar() { | |||||
| //隐藏 | |||||
| // SystemBarUtils.hideStableStatusBar(this); | |||||
| // if (isFullScreen) { | |||||
| // SystemBarUtils.hideStableNavBar(this); | |||||
| // } | |||||
| } | |||||
| /** | |||||
| * 切换菜单栏的可视状态 | |||||
| * 默认是隐藏的 | |||||
| */ | |||||
| private void toggleMenu(boolean hideStatusBar) { | |||||
| initMenuAnim(); | |||||
| if (mAblTopMenu.getVisibility() == View.VISIBLE) { | |||||
| //关闭 | |||||
| mAblTopMenu.startAnimation(mTopOutAnim); | |||||
| mLlBottomMenu.startAnimation(mBottomOutAnim); | |||||
| mAblTopMenu.setVisibility(GONE); | |||||
| mLlBottomMenu.setVisibility(GONE); | |||||
| mTvPageTip.setVisibility(GONE); | |||||
| if (hideStatusBar) { | |||||
| hideSystemBar(); | |||||
| } | |||||
| } else { | |||||
| mAblTopMenu.setVisibility(View.VISIBLE); | |||||
| mLlBottomMenu.setVisibility(View.VISIBLE); | |||||
| mAblTopMenu.startAnimation(mTopInAnim); | |||||
| mLlBottomMenu.startAnimation(mBottomInAnim); | |||||
| showSystemBar(); | |||||
| } | |||||
| } | |||||
| //初始化菜单动画 | |||||
| private void initMenuAnim() { | |||||
| if (mTopInAnim != null) return; | |||||
| mTopInAnim = AnimationUtils.loadAnimation(this, R.anim.slide_top_in); | |||||
| mTopOutAnim = AnimationUtils.loadAnimation(this, R.anim.slide_top_out); | |||||
| mBottomInAnim = AnimationUtils.loadAnimation(this, R.anim.slide_bottom_in); | |||||
| mBottomOutAnim = AnimationUtils.loadAnimation(this, R.anim.slide_bottom_out); | |||||
| //退出的速度要快 | |||||
| mTopOutAnim.setDuration(200); | |||||
| mBottomOutAnim.setDuration(200); | |||||
| } | |||||
| @Override | |||||
| public void onBackPressed() { | |||||
| if (mAblTopMenu.getVisibility() == View.VISIBLE) { | |||||
| // 非全屏下才收缩,全屏下直接退出 | |||||
| if (!ReadSettingManager.getInstance().isFullScreen()) { | |||||
| toggleMenu(true); | |||||
| return; | |||||
| } | |||||
| } else if (mSettingDialog.isShowing()) { | |||||
| mSettingDialog.dismiss(); | |||||
| return; | |||||
| } else if (mDlSlide.isDrawerOpen(GravityCompat.START)) { | |||||
| mDlSlide.closeDrawer(GravityCompat.START); | |||||
| return; | |||||
| } | |||||
| if (!mCollBook.isLocal() && !isCollected | |||||
| && !mCollBook.getBookChapters().isEmpty()) { | |||||
| AlertDialog alertDialog = new AlertDialog.Builder(this) | |||||
| .setTitle("加入书架") | |||||
| .setMessage("喜欢本书就加入书架吧") | |||||
| .setPositiveButton("确定", (dialog, which) -> { | |||||
| //设置为已收藏 | |||||
| isCollected = true; | |||||
| //设置阅读时间 | |||||
| mCollBook.setLastRead(StringUtils. | |||||
| dateConvert(System.currentTimeMillis(), Constant.FORMAT_BOOK_DATE)); | |||||
| BookRepository.getInstance() | |||||
| .saveCollBookWithAsync(mCollBook); | |||||
| exit(); | |||||
| }) | |||||
| .setNegativeButton("取消", (dialog, which) -> { | |||||
| exit(); | |||||
| }).create(); | |||||
| alertDialog.show(); | |||||
| } else { | |||||
| exit(); | |||||
| } | |||||
| } | |||||
| // 退出 | |||||
| private void exit() { | |||||
| // 返回给BookDetail。 | |||||
| // Intent result = new Intent(); | |||||
| // result.putExtra(BookDetailActivity.RESULT_IS_COLLECTED, isCollected); | |||||
| // setResult(Activity.RESULT_OK, result); | |||||
| // 退出 | |||||
| super.onBackPressed(); | |||||
| } | |||||
| @Override | |||||
| protected void onStart() { | |||||
| super.onStart(); | |||||
| registerBrightObserver(); | |||||
| } | |||||
| @Override | |||||
| protected void onResume() { | |||||
| super.onResume(); | |||||
| mWakeLock.acquire(); | |||||
| } | |||||
| @Override | |||||
| protected void onPause() { | |||||
| super.onPause(); | |||||
| mWakeLock.release(); | |||||
| if (isCollected) { | |||||
| mPageLoader.saveRecord(); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| protected void onStop() { | |||||
| super.onStop(); | |||||
| unregisterBrightObserver(); | |||||
| } | |||||
| @Override | |||||
| protected void onDestroy() { | |||||
| super.onDestroy(); | |||||
| unregisterReceiver(mReceiver); | |||||
| mHandler.removeMessages(WHAT_CATEGORY); | |||||
| mHandler.removeMessages(WHAT_CHAPTER); | |||||
| mPageLoader.closeBook(); | |||||
| mPageLoader = null; | |||||
| } | |||||
| @Override | |||||
| public boolean onKeyDown(int keyCode, KeyEvent event) { | |||||
| boolean isVolumeTurnPage = ReadSettingManager | |||||
| .getInstance().isVolumeTurnPage(); | |||||
| switch (keyCode) { | |||||
| case KeyEvent.KEYCODE_VOLUME_UP: | |||||
| if (isVolumeTurnPage) { | |||||
| return mPageLoader.skipToPrePage(); | |||||
| } | |||||
| break; | |||||
| case KeyEvent.KEYCODE_VOLUME_DOWN: | |||||
| if (isVolumeTurnPage) { | |||||
| return mPageLoader.skipToNextPage(); | |||||
| } | |||||
| break; | |||||
| } | |||||
| return super.onKeyDown(keyCode, event); | |||||
| } | |||||
| @Override | |||||
| protected void onActivityResult(int requestCode, int resultCode, Intent data) { | |||||
| super.onActivityResult(requestCode, resultCode, data); | |||||
| // SystemBarUtils.hideStableStatusBar(this); | |||||
| if (requestCode == REQUEST_MORE_SETTING) { | |||||
| boolean fullScreen = ReadSettingManager.getInstance().isFullScreen(); | |||||
| if (isFullScreen != fullScreen) { | |||||
| isFullScreen = fullScreen; | |||||
| // 刷新BottomMenu | |||||
| initBottomMenu(); | |||||
| } | |||||
| // 设置显示状态 | |||||
| // if (isFullScreen) { | |||||
| // SystemBarUtils.hideStableNavBar(this); | |||||
| // } else { | |||||
| // SystemBarUtils.showStableNavBar(this); | |||||
| // } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| package com.yzx.webebook.activity | |||||
| import androidx.appcompat.app.AppCompatActivity | |||||
| import android.os.Bundle | |||||
| import com.yzx.webebook.activity.base.BaseWeexActivity | |||||
| class WeexTestActivity : BaseWeexActivity() { | |||||
| override fun initView() { | |||||
| } | |||||
| override fun initData() { | |||||
| val url = intent.getStringExtra("url") ?: "" | |||||
| val params = intent.getStringExtra("params") ?: "" | |||||
| setUrlInfo(url, "RaderPage", params) | |||||
| } | |||||
| } | |||||
| @@ -5,6 +5,8 @@ import androidx.appcompat.app.AppCompatActivity | |||||
| import com.gyf.immersionbar.ktx.immersionBar | import com.gyf.immersionbar.ktx.immersionBar | ||||
| import com.yzx.webebook.R | import com.yzx.webebook.R | ||||
| import com.yzx.webebook.presenter.base.BasePresenter | import com.yzx.webebook.presenter.base.BasePresenter | ||||
| import io.reactivex.disposables.CompositeDisposable | |||||
| import io.reactivex.disposables.Disposable | |||||
| /** | /** | ||||
| * 类名:BaseActivity | * 类名:BaseActivity | ||||
| @@ -38,7 +40,7 @@ abstract class BaseActivity<out P : BasePresenter<*>> : AppCompatActivity() { | |||||
| abstract fun initPresenter(): P? | abstract fun initPresenter(): P? | ||||
| protected var mDisposable: CompositeDisposable? = null | |||||
| override fun onCreate(savedInstanceState: Bundle?) { | override fun onCreate(savedInstanceState: Bundle?) { | ||||
| super.onCreate(savedInstanceState) | super.onCreate(savedInstanceState) | ||||
| @@ -59,7 +61,12 @@ abstract class BaseActivity<out P : BasePresenter<*>> : AppCompatActivity() { | |||||
| } | } | ||||
| protected open fun addDisposable(d: Disposable?) { | |||||
| if (mDisposable == null) { | |||||
| mDisposable = CompositeDisposable() | |||||
| } | |||||
| mDisposable!!.add(d!!) | |||||
| } | |||||
| override fun onBackPressed() { | override fun onBackPressed() { | ||||
| @@ -68,6 +75,9 @@ abstract class BaseActivity<out P : BasePresenter<*>> : AppCompatActivity() { | |||||
| override fun onDestroy() { | override fun onDestroy() { | ||||
| super.onDestroy() | super.onDestroy() | ||||
| if (mDisposable != null) { | |||||
| mDisposable!!.dispose() | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,201 @@ | |||||
| package com.yzx.webebook.activity.base | |||||
| import android.annotation.SuppressLint | |||||
| import android.os.Bundle | |||||
| import android.os.Handler | |||||
| import android.os.Message | |||||
| import android.util.Log | |||||
| import android.view.View | |||||
| import androidx.appcompat.app.AppCompatActivity | |||||
| import com.gyf.immersionbar.ktx.immersionBar | |||||
| import com.yzx.webebook.R | |||||
| import com.yzx.webebook.modules.ActivityWXModule | |||||
| import com.yzx.webebook.utils.StringUtils | |||||
| import kotlinx.android.synthetic.main.activity_base_weex.* | |||||
| import org.apache.weex.IWXRenderListener | |||||
| import org.apache.weex.WXSDKEngine | |||||
| import org.apache.weex.WXSDKInstance | |||||
| import org.apache.weex.common.WXRenderStrategy | |||||
| import org.apache.weex.utils.WXFileUtils | |||||
| /** | |||||
| * 类名:BaseActivity | |||||
| * 作者:Yun.Lei | |||||
| * 功能: | |||||
| * 创建日期:2020年5月6日14:27:04 | |||||
| * 修改人: | |||||
| * 修改时间: | |||||
| * 修改备注: | |||||
| */ | |||||
| abstract class BaseWeexActivity : AppCompatActivity(), IWXRenderListener { | |||||
| /** | |||||
| * 初始化视图操作在这里执行,执行时机为onCreate之后 | |||||
| */ | |||||
| abstract fun initView(): Unit | |||||
| /** | |||||
| * 数据初始化在这里执行,执行时机为initView之后 | |||||
| */ | |||||
| abstract fun initData(): Unit | |||||
| var mWXSDKInstance: WXSDKInstance? = null | |||||
| var pageName: String = "WeexPage" | |||||
| var bundleUrl: String = "" | |||||
| var params: String = "" | |||||
| private val handler: Handler = @SuppressLint("HandlerLeak") | |||||
| object : Handler() { | |||||
| override fun handleMessage(msg: Message) { | |||||
| super.handleMessage(msg) | |||||
| /** | |||||
| * 轮询访问 WXSDKEngine 初始化状态 防止异步造成的初始化失败问题 | |||||
| */ | |||||
| if (msg.what == 1) { | |||||
| Log.i( | |||||
| "welog", | |||||
| "WXSDKEngine.isInitializedActivity: " + WXSDKEngine.isInitialized() | |||||
| ) | |||||
| //&& !StringUtils.isEmpty(pageName) && !StringUtils.isEmpty( bundleUrl) | |||||
| if (WXSDKEngine.isInitialized()) { | |||||
| startRender() | |||||
| } else { | |||||
| sendEmptyMessageDelayed(1, 300) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| override fun onCreate(savedInstanceState: Bundle?) { | |||||
| super.onCreate(savedInstanceState) | |||||
| setContentView(R.layout.activity_base_weex) | |||||
| initView() | |||||
| initData() | |||||
| setStatusBar() | |||||
| mWXSDKInstance = WXSDKInstance(this) | |||||
| mWXSDKInstance!!.registerRenderListener(this) | |||||
| startRender() | |||||
| if (WXSDKEngine.isInitialized()) { | |||||
| } else { | |||||
| handler.sendEmptyMessageDelayed(1, 300) | |||||
| } | |||||
| } | |||||
| /** | |||||
| * WXSDKEngine 初始化成功后 开始渲染 | |||||
| */ | |||||
| private fun startRender() { | |||||
| /** | |||||
| * 防止空指针 | |||||
| */ | |||||
| if (mWXSDKInstance == null) { | |||||
| mWXSDKInstance = WXSDKInstance(this) | |||||
| mWXSDKInstance!!.registerRenderListener(this) | |||||
| } | |||||
| /** | |||||
| * 渲染远程js | |||||
| */ | |||||
| // bundleUrl = "http://dotwe.org/raw/dist/38e202c16bdfefbdb88a8754f975454c.bundle.wx"; | |||||
| try { | |||||
| Log.d("welog", "开始加载") | |||||
| bundleUrl = "https://oa.live.educlouddata.com/index.js"; | |||||
| mWXSDKInstance?.renderByUrl("https://oa.live.educlouddata.com", bundleUrl, null, null, WXRenderStrategy.APPEND_ASYNC) | |||||
| Log.d("welog", "加载中") | |||||
| }catch (e:Exception){ | |||||
| Log.d("welog", "加载失败,$e") | |||||
| } | |||||
| /** | |||||
| * 渲染本地js | |||||
| */ | |||||
| // val options = mapOf("params" to this.params) | |||||
| // Log.d("welog", "startRender: $options") | |||||
| // mWXSDKInstance!!.render( | |||||
| // pageName, | |||||
| // WXFileUtils.loadAsset(bundleUrl, this), | |||||
| // options, | |||||
| // null, | |||||
| // WXRenderStrategy.APPEND_ASYNC | |||||
| // ) | |||||
| } | |||||
| fun setUrlInfo(url: String, pageName: String, params: String = "") { | |||||
| this.pageName = pageName | |||||
| this.bundleUrl = url | |||||
| this.params = params | |||||
| } | |||||
| open fun setStatusBar() { | |||||
| immersionBar { | |||||
| statusBarColor(R.color.white) | |||||
| fitsSystemWindows(true) | |||||
| statusBarDarkFont(true, 0.2f) | |||||
| keyboardEnable(true) | |||||
| init() | |||||
| } | |||||
| btnBack.setOnClickListener { onBackPressed() } | |||||
| btnClose.setOnClickListener { | |||||
| finish() | |||||
| } | |||||
| } | |||||
| override fun onViewCreated(instance: WXSDKInstance?, view: View) { | |||||
| Log.d("welog", "onViewCreated:") | |||||
| if (view.parent == null) { | |||||
| webLayout.addView(view); | |||||
| } | |||||
| webLayout.requestLayout(); | |||||
| } | |||||
| override fun onRenderSuccess(instance: WXSDKInstance?, width: Int, height: Int) { | |||||
| Log.d("welog", "onRenderSuccess:") | |||||
| } | |||||
| override fun onRefreshSuccess(instance: WXSDKInstance?, width: Int, height: Int) { | |||||
| Log.d("welog", "onRefreshSuccess:") | |||||
| } | |||||
| override fun onException(instance: WXSDKInstance?, errCode: String?, msg: String?) { | |||||
| Log.d("welog", "onException: $errCode//$msg") | |||||
| handler.sendEmptyMessageDelayed(1, 300) | |||||
| } | |||||
| override fun onResume() { | |||||
| super.onResume() | |||||
| if (mWXSDKInstance != null) { | |||||
| mWXSDKInstance!!.onActivityResume() | |||||
| } | |||||
| } | |||||
| override fun onPause() { | |||||
| super.onPause() | |||||
| if (mWXSDKInstance != null) { | |||||
| mWXSDKInstance!!.onActivityPause() | |||||
| } | |||||
| } | |||||
| override fun onStop() { | |||||
| super.onStop() | |||||
| if (mWXSDKInstance != null) { | |||||
| mWXSDKInstance!!.onActivityStop() | |||||
| } | |||||
| } | |||||
| override fun onBackPressed() { | |||||
| super.onBackPressed() | |||||
| } | |||||
| override fun onDestroy() { | |||||
| super.onDestroy() | |||||
| if (mWXSDKInstance != null) { | |||||
| mWXSDKInstance!!.onActivityDestroy() | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package com.yzx.webebook.adapter; | |||||
| import android.view.View; | |||||
| import android.view.ViewGroup; | |||||
| import com.yzx.webebook.adapter.base.EasyAdapter; | |||||
| import com.yzx.webebook.adapter.base.IViewHolder; | |||||
| import com.yzx.webebook.widget.page.TxtChapter; | |||||
| /** | |||||
| * Created by newbiechen on 17-6-5. | |||||
| */ | |||||
| public class CategoryAdapter extends EasyAdapter<TxtChapter> { | |||||
| private int currentSelected = 0; | |||||
| @Override | |||||
| protected IViewHolder<TxtChapter> onCreateViewHolder(int viewType) { | |||||
| return new CategoryHolder(); | |||||
| } | |||||
| @Override | |||||
| public View getView(int position, View convertView, ViewGroup parent) { | |||||
| View view = super.getView(position, convertView, parent); | |||||
| CategoryHolder holder = (CategoryHolder) view.getTag(); | |||||
| if (position == currentSelected){ | |||||
| holder.setSelectedChapter(); | |||||
| } | |||||
| return view; | |||||
| } | |||||
| public void setChapter(int pos){ | |||||
| currentSelected = pos; | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,63 @@ | |||||
| package com.yzx.webebook.adapter; | |||||
| import android.graphics.drawable.Drawable; | |||||
| import android.widget.TextView; | |||||
| import androidx.core.content.ContextCompat; | |||||
| import com.yzx.webebook.R; | |||||
| import com.yzx.webebook.adapter.base.ViewHolderImpl; | |||||
| import com.yzx.webebook.utils.BookManager; | |||||
| import com.yzx.webebook.widget.page.TxtChapter; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-16. | |||||
| */ | |||||
| public class CategoryHolder extends ViewHolderImpl<TxtChapter> { | |||||
| private TextView mTvChapter; | |||||
| @Override | |||||
| public void initView() { | |||||
| mTvChapter = findById(R.id.category_tv_chapter); | |||||
| } | |||||
| @Override | |||||
| public void onBind(TxtChapter value, int pos){ | |||||
| //首先判断是否该章已下载 | |||||
| Drawable drawable = null; | |||||
| //TODO:目录显示设计的有点不好,需要靠成员变量是否为null来判断。 | |||||
| //如果没有链接地址表示是本地文件 | |||||
| if (value.getLink() == null){ | |||||
| drawable = ContextCompat.getDrawable(getContext(),R.drawable.selector_category_load); | |||||
| } | |||||
| else { | |||||
| if (value.getBookId() != null | |||||
| && BookManager | |||||
| .isChapterCached(value.getBookId(),value.getTitle())){ | |||||
| drawable = ContextCompat.getDrawable(getContext(),R.drawable.selector_category_load); | |||||
| } | |||||
| else { | |||||
| drawable = ContextCompat.getDrawable(getContext(), R.drawable.selector_category_unload); | |||||
| } | |||||
| } | |||||
| mTvChapter.setSelected(false); | |||||
| mTvChapter.setTextColor(ContextCompat.getColor(getContext(),R.color.nb_text_default)); | |||||
| mTvChapter.setCompoundDrawablesWithIntrinsicBounds(drawable,null,null,null); | |||||
| mTvChapter.setText(value.getTitle()); | |||||
| } | |||||
| @Override | |||||
| protected int getItemLayoutId() { | |||||
| return R.layout.item_category; | |||||
| } | |||||
| public void setSelectedChapter(){ | |||||
| mTvChapter.setTextColor(ContextCompat.getColor(getContext(),R.color.light_red)); | |||||
| mTvChapter.setSelected(true); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| package com.yzx.webebook.adapter | |||||
| import android.widget.ImageView | |||||
| import com.bumptech.glide.Glide | |||||
| import org.apache.weex.WXEnvironment | |||||
| import org.apache.weex.adapter.IWXImgLoaderAdapter | |||||
| import org.apache.weex.common.WXImageStrategy | |||||
| import org.apache.weex.dom.WXImageQuality | |||||
| class ImageAdapter:IWXImgLoaderAdapter { | |||||
| override fun setImage( | |||||
| url: String?, | |||||
| view: ImageView?, | |||||
| quality: WXImageQuality?, | |||||
| strategy: WXImageStrategy? | |||||
| ) { | |||||
| Glide.with(WXEnvironment.sApplication) | |||||
| .load(url) | |||||
| .into(view!!) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| package com.yzx.webebook.adapter; | |||||
| import android.graphics.drawable.Drawable; | |||||
| import android.view.View; | |||||
| import androidx.recyclerview.widget.RecyclerView; | |||||
| import com.yzx.webebook.adapter.base.BaseListAdapter; | |||||
| import com.yzx.webebook.adapter.base.BaseViewHolder; | |||||
| import com.yzx.webebook.adapter.base.IViewHolder; | |||||
| import com.yzx.webebook.widget.page.PageStyle; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-19. | |||||
| */ | |||||
| public class PageStyleAdapter extends BaseListAdapter<Drawable> { | |||||
| private int currentChecked; | |||||
| @Override | |||||
| protected IViewHolder<Drawable> createViewHolder(int viewType) { | |||||
| return new PageStyleHolder(); | |||||
| } | |||||
| @Override | |||||
| public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { | |||||
| super.onBindViewHolder(holder, position); | |||||
| IViewHolder iHolder = ((BaseViewHolder) holder).holder; | |||||
| PageStyleHolder pageStyleHolder = (PageStyleHolder) iHolder; | |||||
| if (currentChecked == position){ | |||||
| pageStyleHolder.setChecked(); | |||||
| } | |||||
| } | |||||
| public void setPageStyleChecked(PageStyle pageStyle){ | |||||
| currentChecked = pageStyle.ordinal(); | |||||
| } | |||||
| @Override | |||||
| protected void onItemClick(View v, int pos) { | |||||
| super.onItemClick(v, pos); | |||||
| currentChecked = pos; | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,40 @@ | |||||
| package com.yzx.webebook.adapter; | |||||
| import android.graphics.drawable.Drawable; | |||||
| import android.view.View; | |||||
| import android.widget.ImageView; | |||||
| import com.yzx.webebook.R; | |||||
| import com.yzx.webebook.adapter.base.ViewHolderImpl; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-19. | |||||
| */ | |||||
| public class PageStyleHolder extends ViewHolderImpl<Drawable> { | |||||
| private View mReadBg; | |||||
| private ImageView mIvChecked; | |||||
| @Override | |||||
| public void initView() { | |||||
| mReadBg = findById(R.id.read_bg_view); | |||||
| mIvChecked = findById(R.id.read_bg_iv_checked); | |||||
| } | |||||
| @Override | |||||
| public void onBind(Drawable data, int pos) { | |||||
| mReadBg.setBackground(data); | |||||
| mIvChecked.setVisibility(View.GONE); | |||||
| } | |||||
| @Override | |||||
| protected int getItemLayoutId() { | |||||
| return R.layout.item_read_bg; | |||||
| } | |||||
| public void setChecked(){ | |||||
| mIvChecked.setVisibility(View.VISIBLE); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,154 @@ | |||||
| package com.yzx.webebook.adapter.base; | |||||
| import android.os.Handler; | |||||
| import android.view.View; | |||||
| import android.view.ViewGroup; | |||||
| import androidx.recyclerview.widget.RecyclerView; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Collections; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 17-3-21. | |||||
| */ | |||||
| public abstract class BaseListAdapter <T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>{ | |||||
| private static final String TAG = "BaseListAdapter"; | |||||
| /*common statement*/ | |||||
| protected final List<T> mList = new ArrayList<>(); | |||||
| protected OnItemClickListener mClickListener; | |||||
| protected OnItemLongClickListener mLongClickListener; | |||||
| /************************abstract area************************/ | |||||
| protected abstract IViewHolder<T> createViewHolder(int viewType); | |||||
| /*************************rewrite logic area***************************************/ | |||||
| @Override | |||||
| public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | |||||
| IViewHolder<T> viewHolder = createViewHolder(viewType); | |||||
| View view = viewHolder.createItemView(parent); | |||||
| //初始化 | |||||
| RecyclerView.ViewHolder holder = new BaseViewHolder(view, viewHolder); | |||||
| return holder; | |||||
| } | |||||
| @Override | |||||
| public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { | |||||
| //防止别人直接使用RecyclerView.ViewHolder调用该方法 | |||||
| if (!(holder instanceof BaseViewHolder)) | |||||
| throw new IllegalArgumentException("The ViewHolder item must extend BaseViewHolder"); | |||||
| IViewHolder<T> iHolder = ((BaseViewHolder) holder).holder; | |||||
| iHolder.onBind(getItem(position),position); | |||||
| //设置点击事件 | |||||
| holder.itemView.setOnClickListener((v)->{ | |||||
| if (mClickListener != null){ | |||||
| mClickListener.onItemClick(v,position); | |||||
| } | |||||
| //adapter监听点击事件 | |||||
| iHolder.onClick(); | |||||
| onItemClick(v,position); | |||||
| }); | |||||
| //设置长点击事件 | |||||
| holder.itemView.setOnLongClickListener( | |||||
| (v) -> { | |||||
| boolean isClicked = false; | |||||
| if (mLongClickListener != null){ | |||||
| isClicked = mLongClickListener.onItemLongClick(v,position); | |||||
| } | |||||
| //Adapter监听长点击事件 | |||||
| onItemLongClick(v,position); | |||||
| return isClicked; | |||||
| } | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public int getItemCount() { | |||||
| return mList.size(); | |||||
| } | |||||
| protected void onItemClick(View v,int pos){ | |||||
| } | |||||
| protected void onItemLongClick(View v,int pos){ | |||||
| } | |||||
| /******************************public area***********************************/ | |||||
| public void setOnItemClickListener(OnItemClickListener mListener) { | |||||
| this.mClickListener = mListener; | |||||
| } | |||||
| public void setOnItemLongClickListener(OnItemLongClickListener mListener){ | |||||
| this.mLongClickListener = mListener; | |||||
| } | |||||
| public void addItem(T value){ | |||||
| mList.add(value); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public void addItem(int index,T value){ | |||||
| mList.add(index, value); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public void addItems(List<T> values){ | |||||
| mList.addAll(values); | |||||
| Handler handler = new Handler(); | |||||
| handler.post(new Runnable() { | |||||
| @Override | |||||
| public void run() { | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| }); | |||||
| } | |||||
| public void removeItem(T value){ | |||||
| mList.remove(value); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public void removeItems(List<T> value){ | |||||
| mList.removeAll(value); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public T getItem(int position){ | |||||
| return mList.get(position); | |||||
| } | |||||
| public List<T> getItems(){ | |||||
| return Collections.unmodifiableList(mList); | |||||
| } | |||||
| public int getItemSize(){ | |||||
| return mList.size(); | |||||
| } | |||||
| public void refreshItems(List<T> list){ | |||||
| mList.clear(); | |||||
| mList.addAll(list); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public void clear(){ | |||||
| mList.clear(); | |||||
| } | |||||
| /***************************inner class area***********************************/ | |||||
| public interface OnItemClickListener{ | |||||
| void onItemClick(View view, int pos); | |||||
| } | |||||
| public interface OnItemLongClickListener{ | |||||
| boolean onItemLongClick(View view, int pos); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| package com.yzx.webebook.adapter.base; | |||||
| import android.view.View; | |||||
| import androidx.recyclerview.widget.RecyclerView; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-17. | |||||
| */ | |||||
| public class BaseViewHolder<T> extends RecyclerView.ViewHolder{ | |||||
| public IViewHolder<T> holder; | |||||
| public BaseViewHolder(View itemView, IViewHolder<T> holder) { | |||||
| super(itemView); | |||||
| this.holder = holder; | |||||
| holder.initView(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,92 @@ | |||||
| package com.yzx.webebook.adapter.base; | |||||
| import android.view.View; | |||||
| import android.view.ViewGroup; | |||||
| import android.widget.BaseAdapter; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Collections; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 17-6-5. | |||||
| * ListView 使用的Adapter | |||||
| */ | |||||
| public abstract class EasyAdapter<T> extends BaseAdapter { | |||||
| private List<T> mList = new ArrayList<T>(); | |||||
| @Override | |||||
| public int getCount() { | |||||
| return mList.size(); | |||||
| } | |||||
| @Override | |||||
| public T getItem(int position) { | |||||
| return mList.get(position); | |||||
| } | |||||
| @Override | |||||
| public long getItemId(int position) { | |||||
| return position; | |||||
| } | |||||
| public void addItem(T value){ | |||||
| mList.add(value); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public void addItem(int index,T value){ | |||||
| mList.add(index, value); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public void addItems(List<T> values){ | |||||
| mList.addAll(values); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public void removeItem(T value){ | |||||
| mList.remove(value); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public List<T> getItems(){ | |||||
| return Collections.unmodifiableList(mList); | |||||
| } | |||||
| public int getItemSize(){ | |||||
| return mList.size(); | |||||
| } | |||||
| public void refreshItems(List<T> list){ | |||||
| mList.clear(); | |||||
| mList.addAll(list); | |||||
| notifyDataSetChanged(); | |||||
| } | |||||
| public void clear(){ | |||||
| mList.clear(); | |||||
| } | |||||
| @Override | |||||
| public View getView(int position, View convertView, ViewGroup parent) { | |||||
| IViewHolder holder = null; | |||||
| if (convertView == null){ | |||||
| holder = onCreateViewHolder(getItemViewType(position)); | |||||
| convertView = holder.createItemView(parent); | |||||
| convertView.setTag(holder); | |||||
| //初始化 | |||||
| holder.initView(); | |||||
| } | |||||
| else { | |||||
| holder = (IViewHolder)convertView.getTag(); | |||||
| } | |||||
| //执行绑定 | |||||
| holder.onBind(getItem(position),position); | |||||
| return convertView; | |||||
| } | |||||
| protected abstract IViewHolder<T> onCreateViewHolder(int viewType); | |||||
| } | |||||
| @@ -0,0 +1,204 @@ | |||||
| package com.yzx.webebook.adapter.base; | |||||
| import android.view.View; | |||||
| import android.view.ViewGroup; | |||||
| import androidx.recyclerview.widget.GridLayoutManager; | |||||
| import androidx.recyclerview.widget.RecyclerView; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-5. | |||||
| * 用于头标签 + 表格的布局View | |||||
| */ | |||||
| public abstract class GroupAdapter<T,R> extends RecyclerView.Adapter{ | |||||
| private static final String TAG = "GroupAdapter"; | |||||
| private static final int TYPE_GROUP = 1; | |||||
| private static final int TYPE_CHILD = 2; | |||||
| private OnGroupClickListener mGroupListener; | |||||
| private OnChildClickListener mChildClickListener; | |||||
| public abstract int getGroupCount(); | |||||
| public abstract int getChildCount(int groupPos); | |||||
| public abstract T getGroupItem(int groupPos); | |||||
| public abstract R getChildItem(int groupPos,int childPos); | |||||
| protected abstract IViewHolder<T> createGroupViewHolder(); | |||||
| protected abstract IViewHolder<R> createChildViewHolder(); | |||||
| public GroupAdapter(RecyclerView recyclerView,int spanSize){ | |||||
| GridLayoutManager manager = new GridLayoutManager(recyclerView.getContext(),spanSize); | |||||
| manager.setSpanSizeLookup(new GroupSpanSizeLookup(spanSize)); | |||||
| recyclerView.setLayoutManager(manager); | |||||
| } | |||||
| public void setOnGroupItemListener(OnGroupClickListener listener){ | |||||
| mGroupListener = listener; | |||||
| } | |||||
| public void setOnChildItemListener(OnChildClickListener listener) { | |||||
| mChildClickListener = listener; | |||||
| } | |||||
| @Override | |||||
| public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){ | |||||
| IViewHolder iViewHolder = null; | |||||
| View view = null; | |||||
| if (viewType == TYPE_GROUP){ | |||||
| iViewHolder = createGroupViewHolder(); | |||||
| view = iViewHolder.createItemView(parent); | |||||
| } | |||||
| else if (viewType == TYPE_CHILD){ | |||||
| iViewHolder = createChildViewHolder(); | |||||
| view = iViewHolder.createItemView(parent); | |||||
| } | |||||
| RecyclerView.ViewHolder viewHolder = new BaseViewHolder(view, iViewHolder); | |||||
| return viewHolder; | |||||
| } | |||||
| @Override | |||||
| public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { | |||||
| if (! (holder instanceof BaseViewHolder)) | |||||
| throw new IllegalArgumentException("The ViewHolder item must extend BaseViewHolder"); | |||||
| IViewHolder iHolder = ((BaseViewHolder) holder).holder; | |||||
| int type = getItemViewType(position); | |||||
| if (type == TYPE_GROUP){ | |||||
| //计算当前的group | |||||
| int groupPos = calculateGroup(position); | |||||
| holder.itemView.setOnClickListener( | |||||
| (v) -> { | |||||
| iHolder.onClick(); | |||||
| if (mGroupListener != null){ | |||||
| mGroupListener.onGroupClick(v,groupPos); | |||||
| } | |||||
| } | |||||
| ); | |||||
| iHolder.onBind(getGroupItem(groupPos),groupPos); | |||||
| } | |||||
| else if (type == TYPE_CHILD){ | |||||
| int groupPos = calculateGroup(position); | |||||
| int childPos = calculateChild(position); | |||||
| holder.itemView.setOnClickListener( | |||||
| v -> { | |||||
| iHolder.onClick(); | |||||
| if (mChildClickListener != null) { | |||||
| mChildClickListener.onChildClick(v,groupPos,childPos); | |||||
| } | |||||
| } | |||||
| ); | |||||
| //这里有点小问题,返回的是childPos | |||||
| iHolder.onBind(getChildItem(groupPos,childPos),childPos); | |||||
| } | |||||
| } | |||||
| //计算position是哪个group中的头 | |||||
| private int calculateGroup(int position){ | |||||
| int total = 0; | |||||
| for (int i=0; i<getGroupCount(); ++i){ | |||||
| total += getChildCount(i)+1; //当前group的大小范围 | |||||
| if (total > position){ //判断是否pos在total内 | |||||
| return i; | |||||
| } | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| //计算position是那个group中的child | |||||
| protected int calculateChild(int position){ | |||||
| for (int i=0; i<getGroupCount(); ++i){ | |||||
| int total = getChildCount(i)+1; //每个队列的总和 | |||||
| int loc = position - total; //Loc表示在第二队列的位置 | |||||
| if (loc < 0){ //如果 < 0 表示在上一队列中,则返回 | |||||
| return position-1; | |||||
| } | |||||
| else { //否则设置当前队列为pos | |||||
| position = loc; | |||||
| } | |||||
| } | |||||
| //返回child在指定group的位置 | |||||
| return -1; | |||||
| } | |||||
| @Override | |||||
| public int getItemCount() { | |||||
| int groupCount = getGroupCount(); | |||||
| //因为Group需要有头部 | |||||
| int totalCount = groupCount; | |||||
| for (int i=0; i<groupCount; ++i){ | |||||
| totalCount += getChildCount(i); | |||||
| } | |||||
| return totalCount; | |||||
| } | |||||
| //判断获取的item是group还是child | |||||
| @Override | |||||
| public int getItemViewType(int position) { | |||||
| if (position == 0){ | |||||
| return TYPE_GROUP; | |||||
| } | |||||
| for (int i=0; i<getGroupCount(); ++i){ | |||||
| int total = getChildCount(i)+1; //每个队列的总和 | |||||
| if (position == 0){ | |||||
| return TYPE_GROUP; | |||||
| } | |||||
| else if (position < 0){ | |||||
| return TYPE_CHILD; | |||||
| } | |||||
| position -= total; | |||||
| } | |||||
| //剩下的肯定是最后一行 | |||||
| return TYPE_CHILD; | |||||
| } | |||||
| /** | |||||
| * 设置Group与child在GridLayoutManager情况下占用的格子 | |||||
| */ | |||||
| class GroupSpanSizeLookup extends GridLayoutManager.SpanSizeLookup{ | |||||
| private int maxSize; | |||||
| public GroupSpanSizeLookup(int maxSize) { | |||||
| this.maxSize = maxSize; | |||||
| } | |||||
| @Override | |||||
| public int getSpanSize(int position) { | |||||
| if (getItemViewType(position) == TYPE_GROUP){ | |||||
| return maxSize; | |||||
| } | |||||
| else { | |||||
| return 1; | |||||
| } | |||||
| } | |||||
| } | |||||
| public int getGroupToPosition(int groupPos){ | |||||
| int position = 0; | |||||
| for (int i=0; i<groupPos; ++i){ | |||||
| position += getChildCount(groupPos)+1; | |||||
| } | |||||
| return position; | |||||
| } | |||||
| //child转换成position | |||||
| public int getChildToPosition(int groupPos, int childPos){ | |||||
| int position = 0; | |||||
| for (int i=0; i<groupPos; ++i){ | |||||
| position += getChildCount(i)+1; | |||||
| } | |||||
| position += childPos + 1; | |||||
| return position; | |||||
| } | |||||
| public interface OnGroupClickListener { | |||||
| void onGroupClick(View view,int pos); | |||||
| } | |||||
| public interface OnChildClickListener { | |||||
| void onChildClick(View view,int groupPos,int childPos); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| package com.yzx.webebook.adapter.base; | |||||
| import android.view.View; | |||||
| import android.view.ViewGroup; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-17. | |||||
| */ | |||||
| public interface IViewHolder<T> { | |||||
| View createItemView(ViewGroup parent); | |||||
| void initView(); | |||||
| void onBind(T data,int pos); | |||||
| void onClick(); | |||||
| } | |||||
| @@ -0,0 +1,42 @@ | |||||
| package com.yzx.webebook.adapter.base; | |||||
| import android.content.Context; | |||||
| import android.view.LayoutInflater; | |||||
| import android.view.View; | |||||
| import android.view.ViewGroup; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-17. | |||||
| */ | |||||
| public abstract class ViewHolderImpl<T> implements IViewHolder<T> { | |||||
| private View view; | |||||
| private Context context; | |||||
| /****************************************************/ | |||||
| protected abstract int getItemLayoutId(); | |||||
| @Override | |||||
| public View createItemView(ViewGroup parent) { | |||||
| view = LayoutInflater.from(parent.getContext()) | |||||
| .inflate(getItemLayoutId(), parent, false); | |||||
| context = parent.getContext(); | |||||
| return view; | |||||
| } | |||||
| protected <V extends View> V findById(int id){ | |||||
| return (V) view.findViewById(id); | |||||
| } | |||||
| protected Context getContext(){ | |||||
| return context; | |||||
| } | |||||
| protected View getItemView(){ | |||||
| return view; | |||||
| } | |||||
| @Override | |||||
| public void onClick() { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,116 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| import org.greenrobot.greendao.annotation.Entity; | |||||
| import org.greenrobot.greendao.annotation.Generated; | |||||
| import org.greenrobot.greendao.annotation.Id; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-20. | |||||
| * 作者 | |||||
| */ | |||||
| @Entity | |||||
| public class AuthorBean { | |||||
| /** | |||||
| * _id : 553136ba70feaa764a096f6f | |||||
| * avatar : /avatar/26/eb/26ebf8ede76d7f52cd377960bd66383b | |||||
| * nickname : 九歌 | |||||
| * activityAvatar : | |||||
| * type : normal | |||||
| * lv : 8 | |||||
| * gender : female | |||||
| */ | |||||
| @Id | |||||
| private String _id; | |||||
| private String avatar; | |||||
| private String nickname; | |||||
| private String activityAvatar; | |||||
| private String type; | |||||
| private int lv; | |||||
| private String gender; | |||||
| @Generated(hash = 1152582024) | |||||
| public AuthorBean(String _id, String avatar, String nickname, | |||||
| String activityAvatar, String type, int lv, String gender) { | |||||
| this._id = _id; | |||||
| this.avatar = avatar; | |||||
| this.nickname = nickname; | |||||
| this.activityAvatar = activityAvatar; | |||||
| this.type = type; | |||||
| this.lv = lv; | |||||
| this.gender = gender; | |||||
| } | |||||
| @Generated(hash = 1694633584) | |||||
| public AuthorBean() { | |||||
| } | |||||
| public String get_id() { | |||||
| return _id; | |||||
| } | |||||
| public void set_id(String _id) { | |||||
| this._id = _id; | |||||
| } | |||||
| public String getAvatar() { | |||||
| return avatar; | |||||
| } | |||||
| public void setAvatar(String avatar) { | |||||
| this.avatar = avatar; | |||||
| } | |||||
| public String getNickname() { | |||||
| return nickname; | |||||
| } | |||||
| public void setNickname(String nickname) { | |||||
| this.nickname = nickname; | |||||
| } | |||||
| public String getActivityAvatar() { | |||||
| return activityAvatar; | |||||
| } | |||||
| public void setActivityAvatar(String activityAvatar) { | |||||
| this.activityAvatar = activityAvatar; | |||||
| } | |||||
| public String getType() { | |||||
| return type; | |||||
| } | |||||
| public void setType(String type) { | |||||
| this.type = type; | |||||
| } | |||||
| public int getLv() { | |||||
| return lv; | |||||
| } | |||||
| public void setLv(int lv) { | |||||
| this.lv = lv; | |||||
| } | |||||
| public String getGender() { | |||||
| return gender; | |||||
| } | |||||
| public void setGender(String gender) { | |||||
| this.gender = gender; | |||||
| } | |||||
| @Override | |||||
| public String toString() { | |||||
| return "AuthorBean{" + | |||||
| "_id='" + _id + '\'' + | |||||
| ", avatar='" + avatar + '\'' + | |||||
| ", nickname='" + nickname + '\'' + | |||||
| ", activityAvatar='" + activityAvatar + '\'' + | |||||
| ", type='" + type + '\'' + | |||||
| ", lv=" + lv + | |||||
| ", gender='" + gender + '\'' + | |||||
| '}'; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,146 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| import org.greenrobot.greendao.annotation.Entity; | |||||
| import org.greenrobot.greendao.annotation.Generated; | |||||
| import org.greenrobot.greendao.annotation.Id; | |||||
| import org.greenrobot.greendao.annotation.Index; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-10. | |||||
| * 书的章节链接(作为下载的进度数据) | |||||
| * 同时作为网络章节和本地章节 (没有找到更好分离两者的办法) | |||||
| */ | |||||
| @Entity | |||||
| public class BookChapterBean implements Serializable{ | |||||
| private static final long serialVersionUID = 56423411313L; | |||||
| /** | |||||
| * title : 第一章 他叫白小纯 | |||||
| * link : http://read.qidian.com/chapter/rJgN8tJ_cVdRGoWu-UQg7Q2/6jr-buLIUJSaGfXRMrUjdw2 | |||||
| * unreadble : false | |||||
| */ | |||||
| @Id | |||||
| private String id; | |||||
| private String link; | |||||
| private String title; | |||||
| //所属的下载任务 | |||||
| private String taskName; | |||||
| private boolean unreadble; | |||||
| //所属的书籍 | |||||
| @Index | |||||
| private String bookId; | |||||
| //本地书籍参数 | |||||
| //在书籍文件中的起始位置 | |||||
| private long start; | |||||
| //在书籍文件中的终止位置 | |||||
| private long end; | |||||
| @Generated(hash = 1508543635) | |||||
| public BookChapterBean(String id, String link, String title, String taskName, | |||||
| boolean unreadble, String bookId, long start, long end) { | |||||
| this.id = id; | |||||
| this.link = link; | |||||
| this.title = title; | |||||
| this.taskName = taskName; | |||||
| this.unreadble = unreadble; | |||||
| this.bookId = bookId; | |||||
| this.start = start; | |||||
| this.end = end; | |||||
| } | |||||
| @Generated(hash = 853839616) | |||||
| public BookChapterBean() { | |||||
| } | |||||
| public String getTitle() { | |||||
| return title; | |||||
| } | |||||
| public void setTitle(String title) { | |||||
| this.title = title; | |||||
| } | |||||
| public String getLink() { | |||||
| return link; | |||||
| } | |||||
| public void setLink(String link) { | |||||
| this.link = link; | |||||
| } | |||||
| public boolean isUnreadble() { | |||||
| return unreadble; | |||||
| } | |||||
| public void setUnreadble(boolean unreadble) { | |||||
| this.unreadble = unreadble; | |||||
| } | |||||
| public String getTaskName() { | |||||
| return taskName; | |||||
| } | |||||
| public void setTaskName(String taskName) { | |||||
| this.taskName = taskName; | |||||
| } | |||||
| public boolean getUnreadble() { | |||||
| return this.unreadble; | |||||
| } | |||||
| public String getBookId() { | |||||
| return bookId; | |||||
| } | |||||
| public void setBookId(String bookId) { | |||||
| this.bookId = bookId; | |||||
| } | |||||
| public String getId() { | |||||
| return id; | |||||
| } | |||||
| public void setId(String id) { | |||||
| this.id = id; | |||||
| } | |||||
| public long getStart() { | |||||
| return start; | |||||
| } | |||||
| public void setStart(long start) { | |||||
| this.start = start; | |||||
| } | |||||
| public long getEnd() { | |||||
| return end; | |||||
| } | |||||
| public void setEnd(long end) { | |||||
| this.end = end; | |||||
| } | |||||
| @Override | |||||
| public String toString() { | |||||
| return "BookChapterBean{" + | |||||
| "id='" + id + '\'' + | |||||
| ", link='" + link + '\'' + | |||||
| ", title='" + title + '\'' + | |||||
| ", taskName='" + taskName + '\'' + | |||||
| ", unreadble=" + unreadble + | |||||
| ", bookId='" + bookId + '\'' + | |||||
| ", start=" + start + | |||||
| ", end=" + end + | |||||
| '}'; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,54 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| import org.greenrobot.greendao.annotation.Entity; | |||||
| import org.greenrobot.greendao.annotation.Generated; | |||||
| import org.greenrobot.greendao.annotation.Id; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-20. | |||||
| */ | |||||
| @Entity | |||||
| public class BookRecordBean { | |||||
| //所属的书的id | |||||
| @Id | |||||
| private String bookId; | |||||
| //阅读到了第几章 | |||||
| private int chapter; | |||||
| //当前的页码 | |||||
| private int pagePos; | |||||
| @Generated(hash = 340380968) | |||||
| public BookRecordBean(String bookId, int chapter, int pagePos) { | |||||
| this.bookId = bookId; | |||||
| this.chapter = chapter; | |||||
| this.pagePos = pagePos; | |||||
| } | |||||
| @Generated(hash = 398068002) | |||||
| public BookRecordBean() { | |||||
| } | |||||
| public String getBookId() { | |||||
| return bookId; | |||||
| } | |||||
| public void setBookId(String bookId) { | |||||
| this.bookId = bookId; | |||||
| } | |||||
| public int getChapter() { | |||||
| return chapter; | |||||
| } | |||||
| public void setChapter(int chapter) { | |||||
| this.chapter = chapter; | |||||
| } | |||||
| public int getPagePos() { | |||||
| return pagePos; | |||||
| } | |||||
| public void setPagePos(int pagePos) { | |||||
| this.pagePos = pagePos; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,89 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-10. | |||||
| */ | |||||
| public class ChapterInfoBean { | |||||
| /** | |||||
| * title : 第一章 他叫白小纯 | |||||
| * body : 帽儿山,位于东林山脉中,山下有一个村子,民风淳朴,以耕田为生,与世隔绝。 | |||||
| 清晨,村庄的大门前,整个村子里的乡亲,正为一个十五六岁少年送别,这少年瘦弱,但却白白净净,看起来很是乖巧,衣着尽管是寻常的青衫,可却洗的泛白,穿在这少年的身上,与他目中的纯净搭配在一起,透出一股子灵动。 | |||||
| 他叫白小纯。 | |||||
| “父老乡亲们,我要去修仙了,可我舍不得你们啊。”少年满脸不舍,原本就乖巧的样子,此刻看起来更为纯朴。 | |||||
| 四周的乡亲,面面相觑,顿时摆出难舍之色。 | |||||
| “小纯,你爹娘走的早,你是个……好孩子!!难道你不想长生了么,成为仙人就可以长生,能活的很久很久,走吧,雏鹰长大,总有飞出去的那一天。”人群内走出一个头发花白的老者,说道好孩子三个字时,他顿了一下。 | |||||
| “在外面遇到任何事情,都要坚持下去,走出村子,就不要回来,因为你的路在前方!”老人神色慈祥,拍了拍少年的肩膀。 | |||||
| “长生……”白小纯身体一震,目中慢慢坚定起来,在老者以及四周乡亲鼓励的目光下,他重重的点了点头,深深的看了一眼四周的乡亲,转身迈着大步,渐渐走出了村子。 | |||||
| 眼看少年的身影远去,村中的众人,一个个都激动起来,目中的难舍刹那就被喜悦代替,那之前满脸慈祥的老者,此刻也在颤抖,眼中流下泪水。 | |||||
| “苍天有眼,这白鼠狼,他终于……终于走了,是谁告诉他在附近看到仙人的,你为村子立下了大功!” | |||||
| “这白鼠狼终于肯离开了,可怜我家的几只鸡,就因为这白鼠狼怕鸡打鸣,不知用了什么方法,唆使一群孩子吃鸡肉,把全村的鸡都给吃的干干净净……” | |||||
| “今天过年了!”欢呼之声,立刻在这不大的村子里,沸腾而起,甚至有人拿出了锣鼓,高兴的敲打起来。 | |||||
| 村子外,白小纯还没等走远,他就听到了身后村子内,传出了敲锣打鼓的声音,还夹着欢呼。 | |||||
| 白小纯脚步一顿,神色有些古怪,干咳一声,伴随着耳边传来的锣鼓,白小纯顺着山路,走上了帽儿山。 | |||||
| 这帽儿山虽不高,却灌木杂多,虽是清晨,可看起来也是黑压压一片,很是安静。 | |||||
| “听二狗说,他前几天在这里被一头野猪追赶时,看到天上有仙人飞过……”白小纯走在山路上,心脏怦怦跳动时,忽然一旁的灌林中传来阵阵哗哗声,似野猪一样,这声音来的突然,让本就紧张的白小纯,顿时背后发凉。 | |||||
| “谁,谁在那里!”白小纯右手快速从行囊中拿出四把斧头,六把柴刀,还觉得不放心,又从怀里取出了一小根黑色的香,死死的抓住。 | |||||
| “别出来,千万别出来,我有斧头,有柴刀,手里的香还可以召唤天雷,能引仙人降临,你敢出来,就劈死你!”白小纯哆嗦的大喊,连滚带爬的夹着那些武器,赶紧顺着山路跑去,沿途叮当乱响,斧头柴刀掉了一地。 | |||||
| 或许是真的被他给吓住了,很快的哗哗声就消失,没有什么野兽跑出来,白小纯面色苍白,擦了擦冷汗,有心放弃继续上山,可一想到手中这根香是他爹娘去世前留给他的,据说是祖上曾偶然的救下一个落魄的仙人,那仙人离去时留下这根香作为报答,曾言会收下白家血脉一人为弟子,只要点燃,仙人就会到来。 | |||||
| 可至今为止,这根香他点过十多次,始终不见仙人到来,让白小纯开始怀疑仙人是不是真的会来,这一次之所以下定决心,一方面是香所剩不多,另一方面是他听村子里人说,头几天在这看到有仙人从天上飞过。 | |||||
| 所以他这才到来,想着距离仙人近一些,或许仙人就察觉到了也说不定。 | |||||
| 踌躇一番,白小纯咬牙继续,好在此山不高,不久他气喘吁吁的到了山顶,站在那里,他遥望山下的村庄,神色颇为感慨,又低头看着手中的只有指甲盖大小的黑香,此香似乎被燃烧了好多次,所剩不多。 | |||||
| “三年了,爹娘保佑我,这次一定要成功!”白小纯深吸口气,小心的将香点燃,立刻四周狂风顿起,天空更是眨眼间乌云密布,一道道闪电划过,还有震耳欲聋的雷鸣在白小纯耳边直接炸开。 | |||||
| 声音之大,气势之强,让白小纯身体哆嗦,有种随时会被雷劈死的感觉,下意识的就想要吐口唾沫将那根香灭掉,但却挣扎忍住。 | |||||
| “三年了,我点这根香点了十二次,这是第十三次,这次一定要忍住,小纯不怕,应该不会被劈死……”白小纯想起了这三年的经历,不算这次,点了十二次,每次都是这样的雷鸣闪电,仙人也没有到来,吓的本就怕死的他每次都吐口唾沫将其熄灭,说来也怪,这根香看似不凡,可实际上一样是浇水就灭。 | |||||
| 在白小纯这里心惊肉跳,艰难的于那雷声中等待时,距离这里不远处的天空上,有一道长虹正急速的呼啸而来。 | |||||
| 长虹内是一个中年男子,这男子衣着华丽,仙风道骨,可偏偏风尘仆仆,甚至仔细去看,可以看到他神色内深深的疲惫。 | |||||
| “我倒要看看,到底是个什么样的人,竟然点根香点了三年!” | |||||
| 一想到自己这三年的经历,中年男子就气恼,三年前他察觉有人点燃自己还是凝气时送出的香药,想起了当年在凡俗中的一段人情。 | |||||
| 这才飞出寻来,原本按照他的打算,很快就会回来,可没成想,刚寻着香气过去,还没等多远,那气息就瞬间消失,断了联系。若是一次也就罢了,这三年,气息出现了十多次。 | |||||
| 使得他这里,多次在寻找时中断,就这样来来回回,折腾了三年…… | |||||
| 此刻他遥遥的看到了帽儿山,看到了山顶上白小纯,气不打一处来,一瞬飞出,直接就站在了山顶,大手一挥,那根所剩不多的香,直接熄灭。 | |||||
| 雷声刹那消失,白小纯愣了一下,抬头一看,看到了自己的身边多了一个中年男子。 | |||||
| “仙人?”白小纯小心翼翼的开口,有些拿不准,背后偷偷捡起一把斧头。 | |||||
| “本座李青候,你是白家后人?”中年修士目光如电,无视白小纯身后的斧子,打量了白小纯一番,觉得眼前此子眉清目秀,依稀与当年的故人相似,资质也不错,心底的恼意,也不由缓了一些。 | |||||
| “晚辈正是白家后人,白小纯。”白小纯眨了眨眼,小声说道,虽然心中有些畏惧,但还是挺了挺腰板。 | |||||
| “我问你,点一根香,为什么点了三年!”中年修士淡淡开口,问出了他这三年里,最想要知道的问题。 | |||||
| 白小纯听到这个问题,脑筋飞速转动,然后脸上摆出惆怅,遥望山下的村庄。 | |||||
| “晚辈是一个重情重义的人,舍不得那些乡亲们,每一次我点燃香,他们也都不舍得我离去,如今山下的他们,还在因为我的离去而悲伤呢。” | |||||
| 中年修士一愣,这个缘由,是他之前没想到的,目中的恼色又少了一些,单单从话语上看,此子的本性还是不错的。 | |||||
| 可当他的目光落在山下的村子时,他的神识随之扫过,听到了村子里的敲锣打鼓以及那一句句欢呼白鼠狼离去的话语,面色立刻难看起来,有些头疼,看着眼前这个外表乖巧纯朴,人畜无害的白小纯,已心底明朗对方实际上一肚子坏水。 | |||||
| “说实话!”中年修士一瞪眼,声音如同雷声一样,白小纯吓得一个哆嗦。 | |||||
| “这不怨我啊,你那什么破香啊,每次点燃都会打雷,好几次都差点劈死我,我躲过了十三次,已经很不容易了。”白小纯可怜兮兮的说道。 | |||||
| 中年修士看着白小纯,半晌无语。 | |||||
| “既然你这么害怕,为什么还要强行去点香十多次?”中年修士缓缓开口。 | |||||
| “我怕死啊,修仙不是能长生么,我想长生啊。”白小纯委屈的说道。 | |||||
| 中年修士再次无语,不过觉得此子总算执念可嘉,扔到门派里磨炼一番,或可在性子上改变一二。 | |||||
| 于是略一思索,大袖一甩卷着白小纯化作一道长虹,直奔天边而去。 | |||||
| “跟我走吧。” | |||||
| “去哪?这也太高了吧……”白小纯看到自己在天上飞,下面是万丈深渊,立刻脸色苍白,斧头一扔,死死的抱住仙人的大腿。 | |||||
| 中年修士看了眼自己的腿,无奈开口。 | |||||
| “灵溪宗。” | |||||
| 兄弟姐妹们,阔别2个月,你们想不想我啊,我非常想你们! | |||||
| 这本书,我做了详细的大纲,每次回顾大纲里的情节,都很兴奋,有种燃烧的感觉,我非常满意,明天,正式更新,依旧是中午一章,晚上一章! | |||||
| 很兴奋,我们已沉寂了数月,如今归来,要……再战起点! | |||||
| 新书期,兄弟姐妹,别忘了收藏与推荐啊,收藏与推荐至关重要! | |||||
| 求收藏!!求推荐!! | |||||
| 让众人知晓,我们……归来了! | |||||
| 我们的目标,依旧是……点击榜,推荐榜,第一! | |||||
| */ | |||||
| private String title; | |||||
| private String body; | |||||
| public String getTitle() { | |||||
| return title; | |||||
| } | |||||
| public void setTitle(String title) { | |||||
| this.title = title; | |||||
| } | |||||
| public String getBody() { | |||||
| return body; | |||||
| } | |||||
| public void setBody(String body) { | |||||
| this.body = body; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,372 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| import android.os.Parcel; | |||||
| import android.os.Parcelable; | |||||
| import com.yzx.webebook.App; | |||||
| import com.yzx.webebook.utils.StringUtils; | |||||
| import org.greenrobot.greendao.DaoException; | |||||
| import org.greenrobot.greendao.annotation.Entity; | |||||
| import org.greenrobot.greendao.annotation.Generated; | |||||
| import org.greenrobot.greendao.annotation.Id; | |||||
| import org.greenrobot.greendao.annotation.ToMany; | |||||
| import java.util.List; | |||||
| import com.yzx.webebook.model.gen.DaoSession; | |||||
| import com.yzx.webebook.model.gen.BookChapterBeanDao; | |||||
| import com.yzx.webebook.model.gen.CollBookBeanDao; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-8. | |||||
| * 收藏的书籍 | |||||
| */ | |||||
| @Entity | |||||
| public class CollBookBean implements Parcelable{ | |||||
| public static final int STATUS_UNCACHE = 0; //未缓存 | |||||
| public static final int STATUS_CACHING = 1; //正在缓存 | |||||
| public static final int STATUS_CACHED = 2; //已经缓存 | |||||
| /** | |||||
| * _id : 53663ae356bdc93e49004474 | |||||
| * title : 逍遥派 | |||||
| * author : 白马出淤泥 | |||||
| * shortIntro : 金庸武侠中有不少的神秘高手,书中或提起名字,或不曾提起,总之他们要么留下了绝世秘笈,要么就名震武林。 独孤九剑的创始者,独孤求败,他真的只创出九剑吗? 残本葵花... | |||||
| * cover : /cover/149273897447137 | |||||
| * hasCp : true | |||||
| * latelyFollower : 60213 | |||||
| * retentionRatio : 22.87 | |||||
| * updated : 2017-05-07T18:24:34.720Z | |||||
| * | |||||
| * chaptersCount : 1660 | |||||
| * lastChapter : 第1659章 朱长老 | |||||
| */ | |||||
| @Id | |||||
| private String _id; // 本地书籍中,path 的 md5 值作为本地书籍的 id | |||||
| private String title; | |||||
| private String author; | |||||
| private String shortIntro; | |||||
| private String cover; // 在本地书籍中,该字段作为本地文件的路径 | |||||
| private boolean hasCp; | |||||
| private int latelyFollower; | |||||
| private double retentionRatio; | |||||
| //最新更新日期 | |||||
| private String updated; | |||||
| //最新阅读日期 | |||||
| private String lastRead; | |||||
| private int chaptersCount; | |||||
| private String lastChapter; | |||||
| //是否更新或未阅读 | |||||
| private boolean isUpdate = true; | |||||
| //是否是本地文件 | |||||
| private boolean isLocal = false; | |||||
| @ToMany(referencedJoinProperty = "bookId") | |||||
| private List<BookChapterBean> bookChapterList; | |||||
| /** Used to resolve relations */ | |||||
| @Generated(hash = 2040040024) | |||||
| private transient DaoSession daoSession; | |||||
| /** Used for active entity operations. */ | |||||
| @Generated(hash = 1552163441) | |||||
| private transient CollBookBeanDao myDao; | |||||
| @Generated(hash = 757968961) | |||||
| public CollBookBean(String _id, String title, String author, String shortIntro, String cover, | |||||
| boolean hasCp, int latelyFollower, double retentionRatio, String updated, String lastRead, | |||||
| int chaptersCount, String lastChapter, boolean isUpdate, boolean isLocal) { | |||||
| this._id = _id; | |||||
| this.title = title; | |||||
| this.author = author; | |||||
| this.shortIntro = shortIntro; | |||||
| this.cover = cover; | |||||
| this.hasCp = hasCp; | |||||
| this.latelyFollower = latelyFollower; | |||||
| this.retentionRatio = retentionRatio; | |||||
| this.updated = updated; | |||||
| this.lastRead = lastRead; | |||||
| this.chaptersCount = chaptersCount; | |||||
| this.lastChapter = lastChapter; | |||||
| this.isUpdate = isUpdate; | |||||
| this.isLocal = isLocal; | |||||
| } | |||||
| public CollBookBean() { | |||||
| } | |||||
| public String get_id() { | |||||
| return _id; | |||||
| } | |||||
| public void set_id(String _id) { | |||||
| this._id = _id; | |||||
| } | |||||
| public String getTitle() { | |||||
| return StringUtils.convertCC(title, App.Companion.getContext()); | |||||
| } | |||||
| public void setTitle(String title) { | |||||
| this.title = title; | |||||
| } | |||||
| public String getAuthor() { | |||||
| return StringUtils.convertCC(author, App.Companion.getContext()); | |||||
| } | |||||
| public void setAuthor(String author) { | |||||
| this.author = author; | |||||
| } | |||||
| public String getShortIntro() { | |||||
| return StringUtils.convertCC(shortIntro, App.Companion.getContext()); | |||||
| } | |||||
| public void setShortIntro(String shortIntro) { | |||||
| this.shortIntro = shortIntro; | |||||
| } | |||||
| public String getCover() { | |||||
| return StringUtils.convertCC(cover, App.Companion.getContext()); | |||||
| } | |||||
| public void setCover(String cover) { | |||||
| this.cover = cover; | |||||
| } | |||||
| public boolean isHasCp() { | |||||
| return hasCp; | |||||
| } | |||||
| public void setHasCp(boolean hasCp) { | |||||
| this.hasCp = hasCp; | |||||
| } | |||||
| public int getLatelyFollower() { | |||||
| return latelyFollower; | |||||
| } | |||||
| public void setLatelyFollower(int latelyFollower) { | |||||
| this.latelyFollower = latelyFollower; | |||||
| } | |||||
| public double getRetentionRatio() { | |||||
| return retentionRatio; | |||||
| } | |||||
| public void setRetentionRatio(double retentionRatio) { | |||||
| this.retentionRatio = retentionRatio; | |||||
| } | |||||
| public String getUpdated() { | |||||
| return StringUtils.convertCC(updated, App.Companion.getContext()); | |||||
| } | |||||
| public void setUpdated(String updated) { | |||||
| this.updated = updated; | |||||
| } | |||||
| public int getChaptersCount() { | |||||
| return chaptersCount; | |||||
| } | |||||
| public void setChaptersCount(int chaptersCount) { | |||||
| this.chaptersCount = chaptersCount; | |||||
| } | |||||
| public String getLastChapter() { | |||||
| return StringUtils.convertCC(lastChapter, App.Companion.getContext()); | |||||
| } | |||||
| public void setLastChapter(String lastChapter) { | |||||
| this.lastChapter = lastChapter; | |||||
| } | |||||
| public boolean isUpdate() { | |||||
| return isUpdate; | |||||
| } | |||||
| public void setUpdate(boolean update) { | |||||
| isUpdate = update; | |||||
| } | |||||
| public boolean getHasCp() { | |||||
| return this.hasCp; | |||||
| } | |||||
| public boolean getIsUpdate() { | |||||
| return this.isUpdate; | |||||
| } | |||||
| public void setIsUpdate(boolean isUpdate) { | |||||
| this.isUpdate = isUpdate; | |||||
| } | |||||
| public boolean isLocal() { | |||||
| return isLocal; | |||||
| } | |||||
| public void setLocal(boolean local) { | |||||
| isLocal = local; | |||||
| } | |||||
| public String getLastRead() { | |||||
| return StringUtils.convertCC(lastRead, App.Companion.getContext()); | |||||
| } | |||||
| public void setLastRead(String lastRead) { | |||||
| this.lastRead = lastRead; | |||||
| } | |||||
| public void setBookChapters(List<BookChapterBean> beans){ | |||||
| bookChapterList = beans; | |||||
| for (BookChapterBean bean : bookChapterList){ | |||||
| bean.setBookId(get_id()); | |||||
| } | |||||
| } | |||||
| public List<BookChapterBean> getBookChapters(){ | |||||
| if (daoSession == null){ | |||||
| return bookChapterList; | |||||
| } | |||||
| else { | |||||
| return getBookChapterList(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * To-many relationship, resolved on first access (and after reset). | |||||
| * Changes to to-many relations are not persisted, make changes to the target entity. | |||||
| */ | |||||
| @Generated(hash = 711740787) | |||||
| public List<BookChapterBean> getBookChapterList() { | |||||
| if (bookChapterList == null) { | |||||
| final DaoSession daoSession = this.daoSession; | |||||
| if (daoSession == null) { | |||||
| throw new DaoException("Entity is detached from DAO context"); | |||||
| } | |||||
| BookChapterBeanDao targetDao = daoSession.getBookChapterBeanDao(); | |||||
| List<BookChapterBean> bookChapterListNew = targetDao | |||||
| ._queryCollBookBean_BookChapterList(_id); | |||||
| synchronized (this) { | |||||
| if (bookChapterList == null) { | |||||
| bookChapterList = bookChapterListNew; | |||||
| } | |||||
| } | |||||
| } | |||||
| return bookChapterList; | |||||
| } | |||||
| /** Resets a to-many relationship, making the next get call to query for a fresh result. */ | |||||
| @Generated(hash = 1077762221) | |||||
| public synchronized void resetBookChapterList() { | |||||
| bookChapterList = null; | |||||
| } | |||||
| /** | |||||
| * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. | |||||
| * Entity must attached to an entity context. | |||||
| */ | |||||
| @Generated(hash = 128553479) | |||||
| public void delete() { | |||||
| if (myDao == null) { | |||||
| throw new DaoException("Entity is detached from DAO context"); | |||||
| } | |||||
| myDao.delete(this); | |||||
| } | |||||
| /** | |||||
| * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. | |||||
| * Entity must attached to an entity context. | |||||
| */ | |||||
| @Generated(hash = 1942392019) | |||||
| public void refresh() { | |||||
| if (myDao == null) { | |||||
| throw new DaoException("Entity is detached from DAO context"); | |||||
| } | |||||
| myDao.refresh(this); | |||||
| } | |||||
| /** | |||||
| * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. | |||||
| * Entity must attached to an entity context. | |||||
| */ | |||||
| @Generated(hash = 713229351) | |||||
| public void update() { | |||||
| if (myDao == null) { | |||||
| throw new DaoException("Entity is detached from DAO context"); | |||||
| } | |||||
| myDao.update(this); | |||||
| } | |||||
| public boolean getIsLocal() { | |||||
| return this.isLocal; | |||||
| } | |||||
| public void setIsLocal(boolean isLocal) { | |||||
| this.isLocal = isLocal; | |||||
| } | |||||
| @Override | |||||
| public int describeContents() { | |||||
| return 0; | |||||
| } | |||||
| @Override | |||||
| public void writeToParcel(Parcel dest, int flags) { | |||||
| dest.writeString(this._id); | |||||
| dest.writeString(this.title); | |||||
| dest.writeString(this.author); | |||||
| dest.writeString(this.shortIntro); | |||||
| dest.writeString(this.cover); | |||||
| dest.writeByte(this.hasCp ? (byte) 1 : (byte) 0); | |||||
| dest.writeInt(this.latelyFollower); | |||||
| dest.writeDouble(this.retentionRatio); | |||||
| dest.writeString(this.updated); | |||||
| dest.writeString(this.lastRead); | |||||
| dest.writeInt(this.chaptersCount); | |||||
| dest.writeString(this.lastChapter); | |||||
| dest.writeByte(this.isUpdate ? (byte) 1 : (byte) 0); | |||||
| dest.writeByte(this.isLocal ? (byte) 1 : (byte) 0); | |||||
| } | |||||
| /** called by internal mechanisms, do not call yourself. */ | |||||
| @Generated(hash = 159260324) | |||||
| public void __setDaoSession(DaoSession daoSession) { | |||||
| this.daoSession = daoSession; | |||||
| myDao = daoSession != null ? daoSession.getCollBookBeanDao() : null; | |||||
| } | |||||
| protected CollBookBean(Parcel in) { | |||||
| this._id = in.readString(); | |||||
| this.title = in.readString(); | |||||
| this.author = in.readString(); | |||||
| this.shortIntro = in.readString(); | |||||
| this.cover = in.readString(); | |||||
| this.hasCp = in.readByte() != 0; | |||||
| this.latelyFollower = in.readInt(); | |||||
| this.retentionRatio = in.readDouble(); | |||||
| this.updated = in.readString(); | |||||
| this.lastRead = in.readString(); | |||||
| this.chaptersCount = in.readInt(); | |||||
| this.lastChapter = in.readString(); | |||||
| this.isUpdate = in.readByte() != 0; | |||||
| this.isLocal = in.readByte() != 0; | |||||
| } | |||||
| public static final Creator<CollBookBean> CREATOR = new Creator<CollBookBean>() { | |||||
| @Override | |||||
| public CollBookBean createFromParcel(Parcel source) { | |||||
| return new CollBookBean(source); | |||||
| } | |||||
| @Override | |||||
| public CollBookBean[] newArray(int size) { | |||||
| return new CollBookBean[size]; | |||||
| } | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,81 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-29. | |||||
| */ | |||||
| public class CommentBean { | |||||
| /** | |||||
| * _id : 57fd69356b613e9d1e69febb | |||||
| * content : 2000年 | |||||
| * author : {"_id":"57b6794f138527405e83382c","avatar":"/avatar/bc/3f/bc3f0b58815e497b00dabb7a14476891","nickname":"孤独患者","activityAvatar":"","type":"normal","lv":6,"gender":"female"} | |||||
| * floor : 7150 | |||||
| * likeCount : 0 | |||||
| * created : 2016-10-11T22:35:33.303Z | |||||
| * replyTo : {"_id":"57caec937a142c2277757f2d","floor":7038,"author":{"_id":"576a96dd4cb19fa249303369","nickname":"刘"}} | |||||
| */ | |||||
| private String _id; | |||||
| private String content; | |||||
| private AuthorBean author; | |||||
| private int floor; | |||||
| private int likeCount; | |||||
| private String created; | |||||
| private ReplyToBean replyTo; | |||||
| public String get_id() { | |||||
| return _id; | |||||
| } | |||||
| public void set_id(String _id) { | |||||
| this._id = _id; | |||||
| } | |||||
| public String getContent() { | |||||
| return content; | |||||
| } | |||||
| public void setContent(String content) { | |||||
| this.content = content; | |||||
| } | |||||
| public AuthorBean getAuthor() { | |||||
| return author; | |||||
| } | |||||
| public void setAuthor(AuthorBean author) { | |||||
| this.author = author; | |||||
| } | |||||
| public int getFloor() { | |||||
| return floor; | |||||
| } | |||||
| public void setFloor(int floor) { | |||||
| this.floor = floor; | |||||
| } | |||||
| public int getLikeCount() { | |||||
| return likeCount; | |||||
| } | |||||
| public void setLikeCount(int likeCount) { | |||||
| this.likeCount = likeCount; | |||||
| } | |||||
| public String getCreated() { | |||||
| return created; | |||||
| } | |||||
| public void setCreated(String created) { | |||||
| this.created = created; | |||||
| } | |||||
| public ReplyToBean getReplyTo() { | |||||
| return replyTo; | |||||
| } | |||||
| public void setReplyTo(ReplyToBean replyTo) { | |||||
| this.replyTo = replyTo; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-29. | |||||
| */ | |||||
| public class DetailBean<T> { | |||||
| private T detail; | |||||
| private List<CommentBean> bestComments; | |||||
| private List<CommentBean> comments; | |||||
| public DetailBean(T details, List<CommentBean> bestComments, List<CommentBean> comments) { | |||||
| this.detail = details; | |||||
| this.bestComments = bestComments; | |||||
| this.comments = comments; | |||||
| } | |||||
| public T getDetail() { | |||||
| return detail; | |||||
| } | |||||
| public List<CommentBean> getBestComments() { | |||||
| return bestComments; | |||||
| } | |||||
| public List<CommentBean> getComments() { | |||||
| return comments; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,212 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| import org.greenrobot.greendao.DaoException; | |||||
| import org.greenrobot.greendao.annotation.Entity; | |||||
| import org.greenrobot.greendao.annotation.Generated; | |||||
| import org.greenrobot.greendao.annotation.Id; | |||||
| import org.greenrobot.greendao.annotation.ToMany; | |||||
| import java.util.List; | |||||
| import com.yzx.webebook.model.gen.DaoSession; | |||||
| import com.yzx.webebook.model.gen.BookChapterBeanDao; | |||||
| import com.yzx.webebook.model.gen.DownloadTaskBeanDao; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-11. | |||||
| */ | |||||
| @Entity | |||||
| public class DownloadTaskBean { | |||||
| public static final int STATUS_LOADING = 1; | |||||
| public static final int STATUS_WAIT = 2; | |||||
| public static final int STATUS_PAUSE = 3; | |||||
| public static final int STATUS_ERROR = 4; | |||||
| public static final int STATUS_FINISH = 5; | |||||
| //任务名称 -> 名称唯一不重复 | |||||
| @Id | |||||
| private String taskName; | |||||
| //所属的bookId(外健) | |||||
| private String bookId; | |||||
| @ToMany(referencedJoinProperty = "taskName") | |||||
| private List<BookChapterBean> bookChapterList; | |||||
| //章节的下载进度,默认为初始状态 | |||||
| private int currentChapter = 0; | |||||
| //最后的章节 | |||||
| private int lastChapter = 0; | |||||
| //状态:正在下载、下载完成、暂停、等待、下载错误。 | |||||
| private volatile int status = STATUS_WAIT; | |||||
| //总大小 -> (完成之后才会赋值) | |||||
| private long size = 0; | |||||
| /** Used to resolve relations */ | |||||
| @Generated(hash = 2040040024) | |||||
| private transient DaoSession daoSession; | |||||
| /** Used for active entity operations. */ | |||||
| @Generated(hash = 1584592296) | |||||
| private transient DownloadTaskBeanDao myDao; | |||||
| @Generated(hash = 597395122) | |||||
| public DownloadTaskBean(String taskName, String bookId, int currentChapter, int lastChapter, | |||||
| int status, long size) { | |||||
| this.taskName = taskName; | |||||
| this.bookId = bookId; | |||||
| this.currentChapter = currentChapter; | |||||
| this.lastChapter = lastChapter; | |||||
| this.status = status; | |||||
| this.size = size; | |||||
| } | |||||
| @Generated(hash = 2123101309) | |||||
| public DownloadTaskBean() { | |||||
| } | |||||
| public String getBookId() { | |||||
| return bookId; | |||||
| } | |||||
| public void setBookId(String bookId) { | |||||
| this.bookId = bookId; | |||||
| } | |||||
| public String getTaskName() { | |||||
| return taskName; | |||||
| } | |||||
| public void setTaskName(String taskName) { | |||||
| this.taskName = taskName; | |||||
| if (bookChapterList!=null){ | |||||
| for (BookChapterBean bean : bookChapterList){ | |||||
| bean.setTaskName(getTaskName()); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * To-many relationship, resolved on first access (and after reset). | |||||
| * Changes to to-many relations are not persisted, make changes to the target entity. | |||||
| */ | |||||
| @Generated(hash = 389263273) | |||||
| public List<BookChapterBean> getBookChapterList() { | |||||
| if (bookChapterList == null) { | |||||
| final DaoSession daoSession = this.daoSession; | |||||
| if (daoSession == null) { | |||||
| throw new DaoException("Entity is detached from DAO context"); | |||||
| } | |||||
| BookChapterBeanDao targetDao = daoSession.getBookChapterBeanDao(); | |||||
| List<BookChapterBean> bookChapterListNew = targetDao | |||||
| ._queryDownloadTaskBean_BookChapterList(taskName); | |||||
| synchronized (this) { | |||||
| if (bookChapterList == null) { | |||||
| bookChapterList = bookChapterListNew; | |||||
| } | |||||
| } | |||||
| } | |||||
| return bookChapterList; | |||||
| } | |||||
| /** | |||||
| * 这才是真正的列表使用类。 | |||||
| * | |||||
| */ | |||||
| public void setBookChapters(List<BookChapterBean> beans){ | |||||
| bookChapterList = beans; | |||||
| for (BookChapterBean bean : bookChapterList){ | |||||
| bean.setTaskName(getTaskName()); | |||||
| } | |||||
| } | |||||
| public List<BookChapterBean> getBookChapters(){ | |||||
| if (daoSession == null){ | |||||
| return bookChapterList; | |||||
| } | |||||
| else { | |||||
| return getBookChapterList(); | |||||
| } | |||||
| } | |||||
| public int getCurrentChapter() { | |||||
| return currentChapter; | |||||
| } | |||||
| public void setCurrentChapter(int current) { | |||||
| this.currentChapter = current; | |||||
| } | |||||
| public int getLastChapter() { | |||||
| return lastChapter; | |||||
| } | |||||
| public void setLastChapter(int lastChapter) { | |||||
| this.lastChapter = lastChapter; | |||||
| } | |||||
| //多线程访问的问题,所以需要同步机制 | |||||
| public int getStatus() { | |||||
| return status; | |||||
| } | |||||
| public void setStatus(int status){ | |||||
| this.status = status; | |||||
| } | |||||
| public long getSize() { | |||||
| return size; | |||||
| } | |||||
| public void setSize(long size) { | |||||
| this.size = size; | |||||
| } | |||||
| /** Resets a to-many relationship, making the next get call to query for a fresh result. */ | |||||
| @Generated(hash = 1077762221) | |||||
| public synchronized void resetBookChapterList() { | |||||
| bookChapterList = null; | |||||
| } | |||||
| /** | |||||
| * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. | |||||
| * Entity must attached to an entity context. | |||||
| */ | |||||
| @Generated(hash = 128553479) | |||||
| public void delete() { | |||||
| if (myDao == null) { | |||||
| throw new DaoException("Entity is detached from DAO context"); | |||||
| } | |||||
| myDao.delete(this); | |||||
| } | |||||
| /** | |||||
| * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. | |||||
| * Entity must attached to an entity context. | |||||
| */ | |||||
| @Generated(hash = 1942392019) | |||||
| public void refresh() { | |||||
| if (myDao == null) { | |||||
| throw new DaoException("Entity is detached from DAO context"); | |||||
| } | |||||
| myDao.refresh(this); | |||||
| } | |||||
| /** | |||||
| * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. | |||||
| * Entity must attached to an entity context. | |||||
| */ | |||||
| @Generated(hash = 713229351) | |||||
| public void update() { | |||||
| if (myDao == null) { | |||||
| throw new DaoException("Entity is detached from DAO context"); | |||||
| } | |||||
| myDao.update(this); | |||||
| } | |||||
| /** called by internal mechanisms, do not call yourself. */ | |||||
| @Generated(hash = 1923117869) | |||||
| public void __setDaoSession(DaoSession daoSession) { | |||||
| this.daoSession = daoSession; | |||||
| myDao = daoSession != null ? daoSession.getDownloadTaskBeanDao() : null; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| package com.yzx.webebook.model.bean; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-29. | |||||
| */ | |||||
| public class ReplyToBean { | |||||
| /** | |||||
| * _id : 57caec937a142c2277757f2d | |||||
| * floor : 7038 | |||||
| * author : {"_id":"576a96dd4cb19fa249303369","nickname":"刘"} | |||||
| */ | |||||
| private String _id; | |||||
| private int floor; | |||||
| private ReplyAuthorBean author; | |||||
| public String get_id() { | |||||
| return _id; | |||||
| } | |||||
| public void set_id(String _id) { | |||||
| this._id = _id; | |||||
| } | |||||
| public int getFloor() { | |||||
| return floor; | |||||
| } | |||||
| public void setFloor(int floor) { | |||||
| this.floor = floor; | |||||
| } | |||||
| public ReplyAuthorBean getAuthor() { | |||||
| return author; | |||||
| } | |||||
| public void setAuthor(ReplyAuthorBean author) { | |||||
| this.author = author; | |||||
| } | |||||
| public static class ReplyAuthorBean { | |||||
| /** | |||||
| * _id : 576a96dd4cb19fa249303369 | |||||
| * nickname : 刘 | |||||
| */ | |||||
| private String _id; | |||||
| private String nickname; | |||||
| public String get_id() { | |||||
| return _id; | |||||
| } | |||||
| public void set_id(String _id) { | |||||
| this._id = _id; | |||||
| } | |||||
| public String getNickname() { | |||||
| return nickname; | |||||
| } | |||||
| public void setNickname(String nickname) { | |||||
| this.nickname = nickname; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,276 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| import android.util.Log; | |||||
| import com.yzx.webebook.model.bean.BookChapterBean; | |||||
| import com.yzx.webebook.model.bean.BookRecordBean; | |||||
| import com.yzx.webebook.model.bean.ChapterInfoBean; | |||||
| import com.yzx.webebook.model.bean.CollBookBean; | |||||
| import com.yzx.webebook.model.gen.BookChapterBeanDao; | |||||
| import com.yzx.webebook.model.gen.BookRecordBeanDao; | |||||
| import com.yzx.webebook.model.gen.CollBookBeanDao; | |||||
| import com.yzx.webebook.model.gen.DaoSession; | |||||
| import com.yzx.webebook.model.gen.DownloadTaskBeanDao; | |||||
| import com.yzx.webebook.utils.BookManager; | |||||
| import com.yzx.webebook.utils.Constant; | |||||
| import com.yzx.webebook.utils.FileUtils; | |||||
| import com.yzx.webebook.utils.IOUtils; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.BufferedWriter; | |||||
| import java.io.File; | |||||
| import java.io.FileNotFoundException; | |||||
| import java.io.FileReader; | |||||
| import java.io.FileWriter; | |||||
| import java.io.IOException; | |||||
| import java.io.Reader; | |||||
| import java.io.Writer; | |||||
| import java.util.List; | |||||
| import io.reactivex.Single; | |||||
| import io.reactivex.SingleEmitter; | |||||
| import io.reactivex.SingleOnSubscribe; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-8. | |||||
| * 存储关于书籍内容的信息(CollBook(收藏书籍),BookChapter(书籍列表),ChapterInfo(书籍章节),BookRecord(记录)) | |||||
| */ | |||||
| public class BookRepository { | |||||
| private static final String TAG = "CollBookManager"; | |||||
| private static volatile BookRepository sInstance; | |||||
| private DaoSession mSession; | |||||
| private CollBookBeanDao mCollBookDao; | |||||
| private BookRepository(){ | |||||
| mSession = DaoDbHelper.getInstance() | |||||
| .getSession(); | |||||
| mCollBookDao = mSession.getCollBookBeanDao(); | |||||
| } | |||||
| public static BookRepository getInstance(){ | |||||
| if (sInstance == null){ | |||||
| synchronized (BookRepository.class){ | |||||
| if (sInstance == null){ | |||||
| sInstance = new BookRepository(); | |||||
| } | |||||
| } | |||||
| } | |||||
| return sInstance; | |||||
| } | |||||
| //存储已收藏书籍 | |||||
| public void saveCollBookWithAsync(CollBookBean bean){ | |||||
| //启动异步存储 | |||||
| mSession.startAsyncSession() | |||||
| .runInTx( | |||||
| () -> { | |||||
| if (bean.getBookChapters() != null){ | |||||
| // 存储BookChapterBean | |||||
| mSession.getBookChapterBeanDao() | |||||
| .insertOrReplaceInTx(bean.getBookChapters()); | |||||
| } | |||||
| //存储CollBook (确保先后顺序,否则出错) | |||||
| mCollBookDao.insertOrReplace(bean); | |||||
| } | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * 异步存储。 | |||||
| * 同时保存BookChapter | |||||
| * @param beans | |||||
| */ | |||||
| public void saveCollBooksWithAsync(List<CollBookBean> beans){ | |||||
| mSession.startAsyncSession() | |||||
| .runInTx( | |||||
| () -> { | |||||
| for (CollBookBean bean : beans){ | |||||
| if (bean.getBookChapters() != null){ | |||||
| //存储BookChapterBean(需要修改,如果存在id相同的则无视) | |||||
| mSession.getBookChapterBeanDao() | |||||
| .insertOrReplaceInTx(bean.getBookChapters()); | |||||
| } | |||||
| } | |||||
| //存储CollBook (确保先后顺序,否则出错) | |||||
| mCollBookDao.insertOrReplaceInTx(beans); | |||||
| } | |||||
| ); | |||||
| } | |||||
| public void saveCollBook(CollBookBean bean){ | |||||
| mCollBookDao.insertOrReplace(bean); | |||||
| } | |||||
| public void saveCollBooks(List<CollBookBean> beans){ | |||||
| mCollBookDao.insertOrReplaceInTx(beans); | |||||
| } | |||||
| /** | |||||
| * 异步存储BookChapter | |||||
| * @param beans | |||||
| */ | |||||
| public void saveBookChaptersWithAsync(List<BookChapterBean> beans){ | |||||
| mSession.startAsyncSession() | |||||
| .runInTx( | |||||
| () -> { | |||||
| //存储BookChapterBean | |||||
| mSession.getBookChapterBeanDao() | |||||
| .insertOrReplaceInTx(beans); | |||||
| Log.d(TAG, "saveBookChaptersWithAsync: "+"进行存储"); | |||||
| } | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * 存储章节 | |||||
| * @param folderName | |||||
| * @param fileName | |||||
| * @param content | |||||
| */ | |||||
| public void saveChapterInfo(String folderName,String fileName,String content){ | |||||
| File file = BookManager.getBookFile(folderName, fileName); | |||||
| //获取流并存储 | |||||
| Writer writer = null; | |||||
| try { | |||||
| writer = new BufferedWriter(new FileWriter(file)); | |||||
| writer.write(content); | |||||
| writer.flush(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| IOUtils.close(writer); | |||||
| } | |||||
| } | |||||
| public void saveBookRecord(BookRecordBean bean){ | |||||
| mSession.getBookRecordBeanDao() | |||||
| .insertOrReplace(bean); | |||||
| } | |||||
| /*****************************get************************************************/ | |||||
| public CollBookBean getCollBook(String bookId){ | |||||
| CollBookBean bean = mCollBookDao.queryBuilder() | |||||
| .where(CollBookBeanDao.Properties._id.eq(bookId)) | |||||
| .unique(); | |||||
| return bean; | |||||
| } | |||||
| public List<CollBookBean> getCollBooks(){ | |||||
| return mCollBookDao | |||||
| .queryBuilder() | |||||
| .orderDesc(CollBookBeanDao.Properties.LastRead) | |||||
| .list(); | |||||
| } | |||||
| //获取书籍列表 | |||||
| public Single<List<BookChapterBean>> getBookChaptersInRx(String bookId){ | |||||
| return Single.create(new SingleOnSubscribe<List<BookChapterBean>>() { | |||||
| @Override | |||||
| public void subscribe(SingleEmitter<List<BookChapterBean>> e) throws Exception { | |||||
| List<BookChapterBean> beans = mSession | |||||
| .getBookChapterBeanDao() | |||||
| .queryBuilder() | |||||
| .where(BookChapterBeanDao.Properties.BookId.eq(bookId)) | |||||
| .list(); | |||||
| e.onSuccess(beans); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //获取阅读记录 | |||||
| public BookRecordBean getBookRecord(String bookId){ | |||||
| return mSession.getBookRecordBeanDao() | |||||
| .queryBuilder() | |||||
| .where(BookRecordBeanDao.Properties.BookId.eq(bookId)) | |||||
| .unique(); | |||||
| } | |||||
| //TODO:需要进行获取编码并转换的问题 | |||||
| public ChapterInfoBean getChapterInfoBean(String folderName, String fileName){ | |||||
| File file = new File(Constant.BOOK_CACHE_PATH + folderName | |||||
| + File.separator + fileName + FileUtils.SUFFIX_NB); | |||||
| if (!file.exists()) return null; | |||||
| Reader reader = null; | |||||
| String str = null; | |||||
| StringBuilder sb = new StringBuilder(); | |||||
| try { | |||||
| reader = new FileReader(file); | |||||
| BufferedReader br = new BufferedReader(reader); | |||||
| while ((str = br.readLine()) != null){ | |||||
| sb.append(str); | |||||
| } | |||||
| } catch (FileNotFoundException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| }finally { | |||||
| IOUtils.close(reader); | |||||
| } | |||||
| ChapterInfoBean bean = new ChapterInfoBean(); | |||||
| bean.setTitle(fileName); | |||||
| bean.setBody(sb.toString()); | |||||
| return bean; | |||||
| } | |||||
| /************************************************************/ | |||||
| /************************************************************/ | |||||
| public Single<Void> deleteCollBookInRx(CollBookBean bean) { | |||||
| return Single.create(new SingleOnSubscribe<Void>() { | |||||
| @Override | |||||
| public void subscribe(SingleEmitter<Void> e) throws Exception { | |||||
| //查看文本中是否存在删除的数据 | |||||
| deleteBook(bean.get_id()); | |||||
| //删除任务 | |||||
| deleteDownloadTask(bean.get_id()); | |||||
| //删除目录 | |||||
| deleteBookChapter(bean.get_id()); | |||||
| //删除CollBook | |||||
| mCollBookDao.delete(bean); | |||||
| e.onSuccess(new Void()); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //这个需要用rx,进行删除 | |||||
| public void deleteBookChapter(String bookId){ | |||||
| mSession.getBookChapterBeanDao() | |||||
| .queryBuilder() | |||||
| .where(BookChapterBeanDao.Properties.BookId.eq(bookId)) | |||||
| .buildDelete() | |||||
| .executeDeleteWithoutDetachingEntities(); | |||||
| } | |||||
| public void deleteCollBook(CollBookBean collBook){ | |||||
| mCollBookDao.delete(collBook); | |||||
| } | |||||
| //删除书籍 | |||||
| public void deleteBook(String bookId){ | |||||
| FileUtils.deleteFile(Constant.BOOK_CACHE_PATH+bookId); | |||||
| } | |||||
| public void deleteBookRecord(String id){ | |||||
| mSession.getBookRecordBeanDao() | |||||
| .queryBuilder() | |||||
| .where(BookRecordBeanDao.Properties.BookId.eq(id)) | |||||
| .buildDelete() | |||||
| .executeDeleteWithoutDetachingEntities(); | |||||
| } | |||||
| //删除任务 | |||||
| public void deleteDownloadTask(String bookId){ | |||||
| mSession.getDownloadTaskBeanDao() | |||||
| .queryBuilder() | |||||
| .where(DownloadTaskBeanDao.Properties.BookId.eq(bookId)) | |||||
| .buildDelete() | |||||
| .executeDeleteWithoutDetachingEntities(); | |||||
| } | |||||
| public DaoSession getSession(){ | |||||
| return mSession; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,56 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| import android.database.sqlite.SQLiteDatabase; | |||||
| import com.yzx.webebook.App; | |||||
| import com.yzx.webebook.model.gen.DaoMaster; | |||||
| import com.yzx.webebook.model.gen.DaoSession; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-26. | |||||
| */ | |||||
| public class DaoDbHelper { | |||||
| private static final String DB_NAME = "IReader_DB"; | |||||
| private static volatile DaoDbHelper sInstance; | |||||
| private SQLiteDatabase mDb; | |||||
| private DaoMaster mDaoMaster; | |||||
| private DaoSession mSession; | |||||
| private DaoDbHelper(){ | |||||
| //封装数据库的创建、更新、删除 | |||||
| DaoMaster.DevOpenHelper openHelper = new MyOpenHelper(App.Companion.getContext(),DB_NAME,null); | |||||
| //获取数据库 | |||||
| mDb = openHelper.getWritableDatabase(); | |||||
| //封装数据库中表的创建、更新、删除 | |||||
| mDaoMaster = new DaoMaster(mDb); //合起来就是对数据库的操作 | |||||
| //对表操作的对象。 | |||||
| mSession = mDaoMaster.newSession(); //可以认为是对数据的操作 | |||||
| } | |||||
| public static DaoDbHelper getInstance(){ | |||||
| if (sInstance == null){ | |||||
| synchronized (DaoDbHelper.class){ | |||||
| if (sInstance == null){ | |||||
| sInstance = new DaoDbHelper(); | |||||
| } | |||||
| } | |||||
| } | |||||
| return sInstance; | |||||
| } | |||||
| public DaoSession getSession(){ | |||||
| return mSession; | |||||
| } | |||||
| public SQLiteDatabase getDatabase(){ | |||||
| return mDb; | |||||
| } | |||||
| public DaoSession getNewSession(){ | |||||
| return mDaoMaster.newSession(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| import com.yzx.webebook.model.bean.AuthorBean; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-28. | |||||
| */ | |||||
| public interface DeleteDbHelper { | |||||
| void deleteAuthors(List<AuthorBean> beans); | |||||
| void deleteAll(); | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| import com.yzx.webebook.model.bean.AuthorBean; | |||||
| import com.yzx.webebook.model.bean.DownloadTaskBean; | |||||
| import java.util.List; | |||||
| import io.reactivex.Single; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-28. | |||||
| */ | |||||
| public interface GetDbHelper { | |||||
| AuthorBean getAuthor(String id); | |||||
| /******************************/ | |||||
| List<DownloadTaskBean> getDownloadTaskList(); | |||||
| } | |||||
| @@ -0,0 +1,77 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| import com.google.gson.Gson; | |||||
| import com.yzx.webebook.model.bean.AuthorBean; | |||||
| import com.yzx.webebook.model.bean.DownloadTaskBean; | |||||
| import com.yzx.webebook.model.gen.DaoSession; | |||||
| import org.greenrobot.greendao.Property; | |||||
| import org.greenrobot.greendao.query.Join; | |||||
| import org.greenrobot.greendao.query.QueryBuilder; | |||||
| import java.lang.reflect.Field; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import io.reactivex.Single; | |||||
| import io.reactivex.SingleEmitter; | |||||
| import io.reactivex.SingleOnSubscribe; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-26. | |||||
| */ | |||||
| public class LocalRepository implements SaveDbHelper,GetDbHelper,DeleteDbHelper{ | |||||
| private static final String TAG = "LocalRepository"; | |||||
| private static final String DISTILLATE_ALL = "normal"; | |||||
| private static final String DISTILLATE_BOUTIQUES = "distillate"; | |||||
| private static volatile LocalRepository sInstance; | |||||
| private DaoSession mSession; | |||||
| private LocalRepository(){ | |||||
| mSession = DaoDbHelper.getInstance().getSession(); | |||||
| } | |||||
| public static LocalRepository getInstance(){ | |||||
| if (sInstance == null){ | |||||
| synchronized (LocalRepository.class){ | |||||
| if (sInstance == null){ | |||||
| sInstance = new LocalRepository(); | |||||
| } | |||||
| } | |||||
| } | |||||
| return sInstance; | |||||
| } | |||||
| @Override | |||||
| public void deleteAuthors(List<AuthorBean> beans) { | |||||
| } | |||||
| @Override | |||||
| public void deleteAll() { | |||||
| //清空全部数据。 | |||||
| } | |||||
| @Override | |||||
| public AuthorBean getAuthor(String id) { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public List<DownloadTaskBean> getDownloadTaskList() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public void saveAuthors(List<AuthorBean> beans) { | |||||
| } | |||||
| @Override | |||||
| public void saveDownloadTask(DownloadTaskBean bean) { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| import android.content.Context; | |||||
| import android.database.sqlite.SQLiteDatabase; | |||||
| import com.yzx.webebook.model.gen.DaoMaster; | |||||
| import com.yzx.webebook.model.local.update.Update2Helper; | |||||
| import org.greenrobot.greendao.database.Database; | |||||
| /** | |||||
| * Created by newbiechen on 2017/10/9. | |||||
| */ | |||||
| public class MyOpenHelper extends DaoMaster.DevOpenHelper{ | |||||
| public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) { | |||||
| super(context, name, factory); | |||||
| } | |||||
| @Override | |||||
| public void onUpgrade(Database db, int oldVersion, int newVersion) { | |||||
| // 跨版本更新策略 | |||||
| switch (oldVersion){ | |||||
| case 1: | |||||
| // 暂无 1.0 | |||||
| case 2: | |||||
| // 更新数据到 3.0 | |||||
| Update2Helper.getInstance().update(db); | |||||
| default: | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,134 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| import com.yzx.webebook.utils.ScreenUtils; | |||||
| import com.yzx.webebook.utils.SharedPreUtils; | |||||
| import com.yzx.webebook.widget.page.PageMode; | |||||
| import com.yzx.webebook.widget.page.PageStyle; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-17. | |||||
| * 阅读器的配置管理 | |||||
| */ | |||||
| public class ReadSettingManager { | |||||
| /*************实在想不出什么好记的命名方式。。******************/ | |||||
| public static final int READ_BG_DEFAULT = 0; | |||||
| public static final int READ_BG_1 = 1; | |||||
| public static final int READ_BG_2 = 2; | |||||
| public static final int READ_BG_3 = 3; | |||||
| public static final int READ_BG_4 = 4; | |||||
| public static final int NIGHT_MODE = 5; | |||||
| public static final String SHARED_READ_BG = "shared_read_bg"; | |||||
| public static final String SHARED_READ_BRIGHTNESS = "shared_read_brightness"; | |||||
| public static final String SHARED_READ_IS_BRIGHTNESS_AUTO = "shared_read_is_brightness_auto"; | |||||
| public static final String SHARED_READ_TEXT_SIZE = "shared_read_text_size"; | |||||
| public static final String SHARED_READ_IS_TEXT_DEFAULT = "shared_read_text_default"; | |||||
| public static final String SHARED_READ_PAGE_MODE = "shared_read_mode"; | |||||
| public static final String SHARED_READ_NIGHT_MODE = "shared_night_mode"; | |||||
| public static final String SHARED_READ_VOLUME_TURN_PAGE = "shared_read_volume_turn_page"; | |||||
| public static final String SHARED_READ_FULL_SCREEN = "shared_read_full_screen"; | |||||
| public static final String SHARED_READ_CONVERT_TYPE = "shared_read_convert_type"; | |||||
| private static volatile ReadSettingManager sInstance; | |||||
| private SharedPreUtils sharedPreUtils; | |||||
| public static ReadSettingManager getInstance() { | |||||
| if (sInstance == null) { | |||||
| synchronized (ReadSettingManager.class) { | |||||
| if (sInstance == null) { | |||||
| sInstance = new ReadSettingManager(); | |||||
| } | |||||
| } | |||||
| } | |||||
| return sInstance; | |||||
| } | |||||
| private ReadSettingManager() { | |||||
| sharedPreUtils = SharedPreUtils.getInstance(); | |||||
| } | |||||
| public void setPageStyle(PageStyle pageStyle) { | |||||
| sharedPreUtils.putInt(SHARED_READ_BG, pageStyle.ordinal()); | |||||
| } | |||||
| public void setBrightness(int progress) { | |||||
| sharedPreUtils.putInt(SHARED_READ_BRIGHTNESS, progress); | |||||
| } | |||||
| public void setAutoBrightness(boolean isAuto) { | |||||
| sharedPreUtils.putBoolean(SHARED_READ_IS_BRIGHTNESS_AUTO, isAuto); | |||||
| } | |||||
| public void setDefaultTextSize(boolean isDefault) { | |||||
| sharedPreUtils.putBoolean(SHARED_READ_IS_TEXT_DEFAULT, isDefault); | |||||
| } | |||||
| public void setTextSize(int textSize) { | |||||
| sharedPreUtils.putInt(SHARED_READ_TEXT_SIZE, textSize); | |||||
| } | |||||
| public void setPageMode(PageMode mode) { | |||||
| sharedPreUtils.putInt(SHARED_READ_PAGE_MODE, mode.ordinal()); | |||||
| } | |||||
| public void setNightMode(boolean isNight) { | |||||
| sharedPreUtils.putBoolean(SHARED_READ_NIGHT_MODE, isNight); | |||||
| } | |||||
| public int getBrightness() { | |||||
| return sharedPreUtils.getInt(SHARED_READ_BRIGHTNESS, 40); | |||||
| } | |||||
| public boolean isBrightnessAuto() { | |||||
| return sharedPreUtils.getBoolean(SHARED_READ_IS_BRIGHTNESS_AUTO, false); | |||||
| } | |||||
| public int getTextSize() { | |||||
| return sharedPreUtils.getInt(SHARED_READ_TEXT_SIZE, ScreenUtils.spToPx(28)); | |||||
| } | |||||
| public boolean isDefaultTextSize() { | |||||
| return sharedPreUtils.getBoolean(SHARED_READ_IS_TEXT_DEFAULT, false); | |||||
| } | |||||
| public PageMode getPageMode() { | |||||
| int mode = sharedPreUtils.getInt(SHARED_READ_PAGE_MODE, PageMode.SIMULATION.ordinal()); | |||||
| return PageMode.values()[mode]; | |||||
| } | |||||
| public PageStyle getPageStyle() { | |||||
| int style = sharedPreUtils.getInt(SHARED_READ_BG, PageStyle.BG_0.ordinal()); | |||||
| return PageStyle.values()[style]; | |||||
| } | |||||
| public boolean isNightMode() { | |||||
| return sharedPreUtils.getBoolean(SHARED_READ_NIGHT_MODE, false); | |||||
| } | |||||
| public void setVolumeTurnPage(boolean isTurn) { | |||||
| sharedPreUtils.putBoolean(SHARED_READ_VOLUME_TURN_PAGE, isTurn); | |||||
| } | |||||
| public boolean isVolumeTurnPage() { | |||||
| return sharedPreUtils.getBoolean(SHARED_READ_VOLUME_TURN_PAGE, false); | |||||
| } | |||||
| public void setFullScreen(boolean isFullScreen) { | |||||
| sharedPreUtils.putBoolean(SHARED_READ_FULL_SCREEN, isFullScreen); | |||||
| } | |||||
| public boolean isFullScreen() { | |||||
| return sharedPreUtils.getBoolean(SHARED_READ_FULL_SCREEN, false); | |||||
| } | |||||
| public void setConvertType(int convertType) { | |||||
| sharedPreUtils.putInt(SHARED_READ_CONVERT_TYPE, convertType); | |||||
| } | |||||
| public int getConvertType() { | |||||
| return sharedPreUtils.getInt(SHARED_READ_CONVERT_TYPE, 0); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| import com.yzx.webebook.model.bean.AuthorBean; | |||||
| import com.yzx.webebook.model.bean.DownloadTaskBean; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-28. | |||||
| */ | |||||
| public interface SaveDbHelper { | |||||
| void saveAuthors(List<AuthorBean> beans); | |||||
| /*************DownloadTask*********************/ | |||||
| void saveDownloadTask(DownloadTaskBean bean); | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| package com.yzx.webebook.model.local; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-27. | |||||
| */ | |||||
| public final class Void { | |||||
| } | |||||
| @@ -0,0 +1,207 @@ | |||||
| package com.yzx.webebook.model.local.update; | |||||
| import android.database.Cursor; | |||||
| import android.text.TextUtils; | |||||
| import android.util.Log; | |||||
| import org.greenrobot.greendao.AbstractDao; | |||||
| import org.greenrobot.greendao.database.Database; | |||||
| import org.greenrobot.greendao.internal.DaoConfig; | |||||
| import java.lang.reflect.InvocationTargetException; | |||||
| import java.lang.reflect.Method; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Arrays; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 2017/10/9. | |||||
| * 数据库更新策略 | |||||
| */ | |||||
| public class MigrationHelper { | |||||
| private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS"; | |||||
| private static MigrationHelper instance; | |||||
| public static MigrationHelper getInstance() { | |||||
| if (instance == null) { | |||||
| instance = new MigrationHelper(); | |||||
| } | |||||
| return instance; | |||||
| } | |||||
| public void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) { | |||||
| generateTempTables(db, daoClasses); | |||||
| deleteOriginalTables(db, daoClasses); | |||||
| createOrignalTables(db, daoClasses); | |||||
| restoreData(db, daoClasses); | |||||
| } | |||||
| /** | |||||
| * 生成临时列表 | |||||
| * | |||||
| * @param db | |||||
| * @param daoClasses | |||||
| */ | |||||
| private void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) { | |||||
| for (int i = 0; i < daoClasses.length; i++) { | |||||
| DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]); | |||||
| String divider = ""; | |||||
| String tableName = daoConfig.tablename; | |||||
| String tempTableName = daoConfig.tablename.concat("_TEMP"); | |||||
| ArrayList<String> properties = new ArrayList<>(); | |||||
| StringBuilder createTableStringBuilder = new StringBuilder(); | |||||
| createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" ("); | |||||
| for (int j = 0; j < daoConfig.properties.length; j++) { | |||||
| String columnName = daoConfig.properties[j].columnName; | |||||
| if (getColumns(db, tableName).contains(columnName)) { | |||||
| properties.add(columnName); | |||||
| String type = null; | |||||
| try { | |||||
| type = getTypeByClass(daoConfig.properties[j].type); | |||||
| } catch (Exception exception) { | |||||
| exception.printStackTrace(); | |||||
| } | |||||
| createTableStringBuilder.append(divider).append(columnName).append(" ").append(type); | |||||
| if (daoConfig.properties[j].primaryKey) { | |||||
| createTableStringBuilder.append(" PRIMARY KEY"); | |||||
| } | |||||
| divider = ","; | |||||
| } | |||||
| } | |||||
| createTableStringBuilder.append(");"); | |||||
| db.execSQL(createTableStringBuilder.toString()); | |||||
| StringBuilder insertTableStringBuilder = new StringBuilder(); | |||||
| insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" ("); | |||||
| insertTableStringBuilder.append(TextUtils.join(",", properties)); | |||||
| insertTableStringBuilder.append(") SELECT "); | |||||
| insertTableStringBuilder.append(TextUtils.join(",", properties)); | |||||
| insertTableStringBuilder.append(" FROM ").append(tableName).append(";"); | |||||
| db.execSQL(insertTableStringBuilder.toString()); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 通过反射,删除要更新的表 | |||||
| */ | |||||
| private void deleteOriginalTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) { | |||||
| for (Class<? extends AbstractDao<?, ?>> daoClass : daoClasses) { | |||||
| try { | |||||
| Method method = daoClass.getMethod("dropTable", Database.class, boolean.class); | |||||
| method.invoke(null, db, true); | |||||
| } catch (IllegalAccessException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (InvocationTargetException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (NoSuchMethodException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 通过反射,重新创建要更新的表 | |||||
| */ | |||||
| private void createOrignalTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) { | |||||
| for (Class<? extends AbstractDao<?, ?>> daoClass : daoClasses) { | |||||
| try { | |||||
| Method method = daoClass.getMethod("createTable", Database.class, boolean.class); | |||||
| method.invoke(null, db, false); | |||||
| } catch (IllegalAccessException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (InvocationTargetException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (NoSuchMethodException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 存储新的数据库表 以及数据 | |||||
| * | |||||
| * @param db | |||||
| * @param daoClasses | |||||
| */ | |||||
| private void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) { | |||||
| for (int i = 0; i < daoClasses.length; i++) { | |||||
| DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]); | |||||
| String tableName = daoConfig.tablename; | |||||
| String tempTableName = daoConfig.tablename.concat("_TEMP"); | |||||
| ArrayList<String> properties = new ArrayList(); | |||||
| for (int j = 0; j < daoConfig.properties.length; j++) { | |||||
| String columnName = daoConfig.properties[j].columnName; | |||||
| if (getColumns(db, tempTableName).contains(columnName)) { | |||||
| properties.add(columnName); | |||||
| } | |||||
| } | |||||
| StringBuilder insertTableStringBuilder = new StringBuilder(); | |||||
| insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" ("); | |||||
| insertTableStringBuilder.append(TextUtils.join(",", properties)); | |||||
| insertTableStringBuilder.append(") SELECT "); | |||||
| insertTableStringBuilder.append(TextUtils.join(",", properties)); | |||||
| insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";"); | |||||
| StringBuilder dropTableStringBuilder = new StringBuilder(); | |||||
| dropTableStringBuilder.append("DROP TABLE ").append(tempTableName); | |||||
| db.execSQL(insertTableStringBuilder.toString()); | |||||
| db.execSQL(dropTableStringBuilder.toString()); | |||||
| } | |||||
| } | |||||
| private String getTypeByClass(Class<?> type) throws Exception { | |||||
| if (type.equals(String.class)) { | |||||
| return "TEXT"; | |||||
| } | |||||
| if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) { | |||||
| return "INTEGER"; | |||||
| } | |||||
| if (type.equals(Boolean.class)) { | |||||
| return "BOOLEAN"; | |||||
| } | |||||
| Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString())); | |||||
| exception.printStackTrace(); | |||||
| throw exception; | |||||
| } | |||||
| private List<String> getColumns(Database db, String tableName) { | |||||
| List<String> columns = new ArrayList<>(); | |||||
| Cursor cursor = null; | |||||
| try { | |||||
| cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null); | |||||
| if (cursor != null) { | |||||
| columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames())); | |||||
| } | |||||
| } catch (Exception e) { | |||||
| Log.v(tableName, e.getMessage(), e); | |||||
| e.printStackTrace(); | |||||
| } finally { | |||||
| if (cursor != null) | |||||
| cursor.close(); | |||||
| } | |||||
| return columns; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,284 @@ | |||||
| package com.yzx.webebook.model.local.update; | |||||
| import android.database.Cursor; | |||||
| import android.text.TextUtils; | |||||
| import android.util.Log; | |||||
| import com.yzx.webebook.model.gen.BookChapterBeanDao; | |||||
| import com.yzx.webebook.model.gen.CollBookBeanDao; | |||||
| import com.yzx.webebook.utils.MD5Utils; | |||||
| import org.greenrobot.greendao.AbstractDao; | |||||
| import org.greenrobot.greendao.database.Database; | |||||
| import org.greenrobot.greendao.internal.DaoConfig; | |||||
| import java.lang.reflect.InvocationTargetException; | |||||
| import java.lang.reflect.Method; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Arrays; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 2018/1/5. | |||||
| * 由于 BookChapterBean 做了一次表的大更改,所以需要自定义更新。 | |||||
| * 作用:将数据库2.0 升级到 3.0 | |||||
| */ | |||||
| public class Update2Helper { | |||||
| private static final String TAG = "BookChapterHelper"; | |||||
| private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS"; | |||||
| private static final String DIVIDER = ","; | |||||
| private static final String QUOTE = "'%s'"; | |||||
| private static Update2Helper instance; | |||||
| public static Update2Helper getInstance() { | |||||
| if (instance == null) { | |||||
| instance = new Update2Helper(); | |||||
| } | |||||
| return instance; | |||||
| } | |||||
| public void update(Database db) { | |||||
| updateCollBook(db); | |||||
| updateBookChapter(db); | |||||
| } | |||||
| private void updateBookChapter(Database db) { | |||||
| Class<? extends AbstractDao<?, ?>> bookChapterClass = BookChapterBeanDao.class; | |||||
| generateTempTables(db, bookChapterClass); | |||||
| deleteOriginalTables(db, bookChapterClass); | |||||
| createOrignalTables(db, bookChapterClass); | |||||
| restoreData(db, bookChapterClass); | |||||
| } | |||||
| private void updateCollBook(Database db) { | |||||
| Class<? extends AbstractDao<?, ?>> collBookClass = CollBookBeanDao.class; | |||||
| // 遍历查找本地文件,然后修改本地文件的数据 | |||||
| DaoConfig daoConfig = new DaoConfig(db, collBookClass); | |||||
| String tableName = daoConfig.tablename; | |||||
| Cursor cursor = db.rawQuery("select _ID,IS_LOCAL from " + tableName, null); | |||||
| String id = null; | |||||
| String cover = null; | |||||
| String isLocal = null; | |||||
| StringBuilder updateSb = new StringBuilder(); | |||||
| while (cursor.moveToNext()) { | |||||
| cover = cursor.getString(0); | |||||
| id = MD5Utils.strToMd5By16(cover); | |||||
| isLocal = cursor.getString(1); | |||||
| //如果是本地文件 | |||||
| if (isLocal.equals("1")) { | |||||
| // 数据更新 | |||||
| updateSb.append("UPDATE " + tableName + " SET "); | |||||
| updateSb.append("_ID=").append(String.format(QUOTE, id)).append(DIVIDER); | |||||
| updateSb.append("COVER=").append(String.format(QUOTE, cover)).append(" "); | |||||
| updateSb.append("WHERE _ID=").append(String.format(QUOTE,cover)).append(";"); | |||||
| db.execSQL(updateSb.toString()); | |||||
| updateSb.delete(0, updateSb.length()); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 生成临时列表 | |||||
| * | |||||
| * @param db | |||||
| */ | |||||
| private void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>> bookChapterClass) { | |||||
| // 解析 GreenDao,获取 table 名 | |||||
| DaoConfig daoConfig = new DaoConfig(db, bookChapterClass); | |||||
| String tableName = daoConfig.tablename; | |||||
| // 创建临时 table 名。 | |||||
| String tempTableName = daoConfig.tablename.concat("_TEMP"); | |||||
| ArrayList<String> properties = new ArrayList<>(); | |||||
| StringBuilder createTableStringBuilder = new StringBuilder(); | |||||
| createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" ("); | |||||
| // 新增的三个字段 | |||||
| String ID = "ID"; | |||||
| String START = "START"; | |||||
| String END = "end"; | |||||
| // 新建的 id 主键字段 | |||||
| createTableStringBuilder.append(ID + " ").append("TEXT ").append("PRIMARY KEY"); | |||||
| properties.add(ID); | |||||
| // 获取符合新表的旧字段。 | |||||
| for (int j = 0; j < daoConfig.properties.length; j++) { | |||||
| String columnName = daoConfig.properties[j].columnName; | |||||
| if (getColumns(db, tableName).contains(columnName)) { | |||||
| properties.add(columnName); | |||||
| String type = null; | |||||
| try { | |||||
| type = getTypeByClass(daoConfig.properties[j].type); | |||||
| } catch (Exception exception) { | |||||
| exception.printStackTrace(); | |||||
| } | |||||
| createTableStringBuilder.append(DIVIDER).append(columnName).append(" ").append(type); | |||||
| } | |||||
| } | |||||
| // 新建的 START,和 END 字段。 | |||||
| createTableStringBuilder.append(DIVIDER).append(START).append(" ").append("INTEGER"); | |||||
| createTableStringBuilder.append(DIVIDER).append(END).append(" ").append("INTEGER"); | |||||
| properties.add(START); | |||||
| properties.add(END); | |||||
| createTableStringBuilder.append(");"); | |||||
| // 创建临时数据表 | |||||
| db.execSQL(createTableStringBuilder.toString()); | |||||
| StringBuilder insertTableStringBuilder = new StringBuilder(); | |||||
| // 将 link 字段的文件的内容转换成 Id | |||||
| Cursor cursor = db.rawQuery("select * from " + daoConfig.tablename, null); | |||||
| String id = null; | |||||
| String link = null; | |||||
| String title = null; | |||||
| String taskName = null; | |||||
| String unreadble = null; | |||||
| String bookId = null; | |||||
| while (cursor.moveToNext()) { | |||||
| link = cursor.getString(0); | |||||
| id = MD5Utils.strToMd5By16(link); | |||||
| title = cursor.getString(1); | |||||
| taskName = cursor.getString(2); | |||||
| unreadble = cursor.getString(4); | |||||
| bookId = cursor.getString(3); | |||||
| insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" ("); | |||||
| insertTableStringBuilder.append(TextUtils.join(",", properties)); | |||||
| insertTableStringBuilder.append(") VALUES ("); | |||||
| insertTableStringBuilder.append(String.format(QUOTE, id)).append(DIVIDER); | |||||
| insertTableStringBuilder.append(String.format(QUOTE, link)).append(DIVIDER); | |||||
| insertTableStringBuilder.append(String.format(QUOTE, title)).append(DIVIDER); | |||||
| insertTableStringBuilder.append(String.format(QUOTE, taskName)).append(DIVIDER); | |||||
| insertTableStringBuilder.append(unreadble).append(DIVIDER); | |||||
| insertTableStringBuilder.append(String.format(QUOTE, bookId)).append(DIVIDER); | |||||
| insertTableStringBuilder.append("0").append(DIVIDER); | |||||
| insertTableStringBuilder.append("0").append(");"); | |||||
| db.execSQL(insertTableStringBuilder.toString()); | |||||
| insertTableStringBuilder.delete(0, insertTableStringBuilder.length()); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 通过反射,删除要更新的表 | |||||
| */ | |||||
| private void deleteOriginalTables(Database db,Class<? extends AbstractDao<?, ?>> bookChapterClass) { | |||||
| try { | |||||
| Method method = bookChapterClass.getMethod("dropTable", Database.class, boolean.class); | |||||
| method.invoke(null, db, true); | |||||
| } catch (IllegalAccessException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (InvocationTargetException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (NoSuchMethodException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 通过反射,重新创建要更新的表 | |||||
| */ | |||||
| private void createOrignalTables(Database db,Class<? extends AbstractDao<?, ?>> bookChapterClass) { | |||||
| try { | |||||
| Method method = bookChapterClass.getMethod("createTable", Database.class, boolean.class); | |||||
| method.invoke(null, db, false); | |||||
| } catch (IllegalAccessException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (InvocationTargetException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (NoSuchMethodException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 存储新的数据库表 以及数据 | |||||
| * | |||||
| * @param db | |||||
| */ | |||||
| private void restoreData(Database db,Class<? extends AbstractDao<?, ?>> bookChapterClass) { | |||||
| DaoConfig daoConfig = new DaoConfig(db, bookChapterClass); | |||||
| String tableName = daoConfig.tablename; | |||||
| String tempTableName = daoConfig.tablename.concat("_TEMP"); | |||||
| ArrayList<String> properties = new ArrayList(); | |||||
| for (int j = 0; j < daoConfig.properties.length; j++) { | |||||
| String columnName = daoConfig.properties[j].columnName; | |||||
| if (getColumns(db, tableName).contains(columnName)) { | |||||
| properties.add(columnName); | |||||
| } | |||||
| } | |||||
| StringBuilder insertTableStringBuilder = new StringBuilder(); | |||||
| insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" ("); | |||||
| insertTableStringBuilder.append(TextUtils.join(",", properties)); | |||||
| insertTableStringBuilder.append(") SELECT "); | |||||
| insertTableStringBuilder.append(TextUtils.join(",", properties)); | |||||
| insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";"); | |||||
| Log.d(TAG, "restoreData: " + insertTableStringBuilder.toString()); | |||||
| StringBuilder dropTableStringBuilder = new StringBuilder(); | |||||
| dropTableStringBuilder.append("DROP TABLE ").append(tempTableName); | |||||
| db.execSQL(insertTableStringBuilder.toString()); | |||||
| db.execSQL(dropTableStringBuilder.toString()); | |||||
| } | |||||
| private String getTypeByClass(Class<?> type) throws Exception { | |||||
| if (type.equals(String.class)) { | |||||
| return "TEXT"; | |||||
| } | |||||
| if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) { | |||||
| return "INTEGER"; | |||||
| } | |||||
| if (type.equals(boolean.class)) { | |||||
| return "BOOLEAN"; | |||||
| } | |||||
| Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString())); | |||||
| exception.printStackTrace(); | |||||
| throw exception; | |||||
| } | |||||
| private List<String> getColumns(Database db, String tableName) { | |||||
| List<String> columns = new ArrayList<>(); | |||||
| Cursor cursor = null; | |||||
| try { | |||||
| cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null); | |||||
| if (cursor != null) { | |||||
| columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames())); | |||||
| } | |||||
| } catch (Exception e) { | |||||
| Log.v(tableName, e.getMessage(), e); | |||||
| e.printStackTrace(); | |||||
| } finally { | |||||
| if (cursor != null) | |||||
| cursor.close(); | |||||
| } | |||||
| return columns; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| package com.yzx.webebook.modules | |||||
| import android.widget.Toast | |||||
| import com.yzx.webebook.activity.WeexTestActivity | |||||
| import org.apache.weex.annotation.JSMethod | |||||
| import org.apache.weex.common.WXModule | |||||
| import org.jetbrains.anko.startActivity | |||||
| class ActivityWXModule : WXModule() { | |||||
| @JSMethod(uiThread = true) | |||||
| public fun navigateTo(url:String,params: String) { | |||||
| // val intent = Intent(mWXSDKInstance.context, WeexTestActivity::class.java) | |||||
| // intent.putExtra("url", url) | |||||
| // intent.putExtra("params", params) | |||||
| mWXSDKInstance.context.startActivity<WeexTestActivity>("url" to url,"params" to params) | |||||
| } | |||||
| @JSMethod(uiThread = true) | |||||
| public fun toast(msg: String?) { | |||||
| Toast.makeText(mWXSDKInstance.context, msg, Toast.LENGTH_SHORT).show() | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| package com.yzx.webebook.presenter; | |||||
| import com.yzx.webebook.presenter.base.BasePresenter; | |||||
| import com.yzx.webebook.widget.page.TxtChapter; | |||||
| import org.jetbrains.annotations.NotNull; | |||||
| import java.util.List; | |||||
| public class ReadPresenter extends BasePresenter<ReadView> { | |||||
| public ReadPresenter(@NotNull ReadView view) { | |||||
| super(view); | |||||
| } | |||||
| public void loadCategory(String bookId){ | |||||
| } | |||||
| public void loadChapter(String bookId, List<TxtChapter> bookChapterList){ | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| package com.yzx.webebook.presenter; | |||||
| import com.yzx.webebook.model.bean.BookChapterBean; | |||||
| import com.yzx.webebook.presenter.base.IView; | |||||
| import java.util.List; | |||||
| public interface ReadView extends IView { | |||||
| void showCategory(List<BookChapterBean> bookChapterList); | |||||
| void finishChapter(); | |||||
| void errorChapter(); | |||||
| } | |||||
| @@ -0,0 +1,221 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import java.io.File; | |||||
| import java.lang.ref.WeakReference; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-20. | |||||
| * 处理书籍的工具类,配合PageFactory使用 | |||||
| * 已弃用, | |||||
| */ | |||||
| public class BookManager{ | |||||
| private static final String TAG = "BookManager"; | |||||
| private String chapterName; | |||||
| private String bookId; | |||||
| private long chapterLen; | |||||
| private long position; | |||||
| private Map<String, Cache> cacheMap = new HashMap<>(); | |||||
| private static volatile BookManager sInstance; | |||||
| public static BookManager getInstance(){ | |||||
| if (sInstance == null){ | |||||
| synchronized (BookManager.class){ | |||||
| if (sInstance == null){ | |||||
| sInstance = new BookManager(); | |||||
| } | |||||
| } | |||||
| } | |||||
| return sInstance; | |||||
| } | |||||
| public boolean openChapter(String bookId,String chapterName){ | |||||
| return openChapter(bookId,chapterName,0); | |||||
| } | |||||
| public boolean openChapter(String bookId,String chapterName,long position){ | |||||
| //如果文件不存在,则打开失败 | |||||
| File file = new File(Constant.BOOK_CACHE_PATH + bookId | |||||
| + File.separator + chapterName + FileUtils.SUFFIX_NB); | |||||
| if (!file.exists()){ | |||||
| return false; | |||||
| } | |||||
| this.bookId = bookId; | |||||
| this.chapterName = chapterName; | |||||
| this.position = position; | |||||
| createCache(); | |||||
| return true; | |||||
| } | |||||
| private void createCache(){ | |||||
| //创建Cache | |||||
| if (!cacheMap.containsKey(chapterName)){ | |||||
| Cache cache = new Cache(); | |||||
| File file = getBookFile(bookId, chapterName); | |||||
| //TODO:数据加载默认utf-8(以后会增加判断),FileUtils采用Reader获取数据的,可能用byte会更好一点 | |||||
| char[] array = FileUtils.getFileContent(file).toCharArray(); | |||||
| WeakReference<char[]> charReference = new WeakReference<char[]>(array); | |||||
| cache.size = array.length; | |||||
| cache.data = charReference; | |||||
| cacheMap.put(chapterName, cache); | |||||
| chapterLen = cache.size; | |||||
| } | |||||
| else { | |||||
| chapterLen = cacheMap.get(chapterName).getSize(); | |||||
| } | |||||
| } | |||||
| public void setPosition(long position){ | |||||
| this.position = position; | |||||
| } | |||||
| public long getPosition(){ | |||||
| return position; | |||||
| } | |||||
| //获取上一段 | |||||
| public String getPrevPara(){ | |||||
| //首先判断是否Position已经达到起始位置,已经越界 | |||||
| if (position < 0){ | |||||
| return null; | |||||
| } | |||||
| //初始化从后向前获取的起始点,终止点,文本 | |||||
| int end = (int)position; | |||||
| int begin = end; | |||||
| char[] array = getContent(); | |||||
| while (begin >= 0) { //判断指针是否达到章节的起始位置 | |||||
| char character = array[begin]; //获取当前指针下的字符 | |||||
| //判断当前字符是否为换行,如果为换行,就代表获取到了一个段落,并退出。 | |||||
| //有可能发生初始指针指的就是换行符的情况。 | |||||
| if ((character+"").equals("\n") && begin != end) { | |||||
| position = begin; | |||||
| //当当前指针指向换行符的时候向后退一步 | |||||
| begin++; | |||||
| break; | |||||
| } | |||||
| //向前进一步 | |||||
| begin--; | |||||
| } | |||||
| //最后end获取到段落的起始点,begin是段落的终止点。 | |||||
| //当越界的时候,保证begin在章节内 | |||||
| if (begin < 0){ | |||||
| begin = 0;//在章节内 | |||||
| position = -1; //越界 | |||||
| } | |||||
| int size = end+1 - begin; | |||||
| return new String(array,begin,size); | |||||
| } | |||||
| //获取下一段 | |||||
| public String getNextPara(){ | |||||
| //首先判断是否Position已经达到终点位置 | |||||
| if (position >= chapterLen){ | |||||
| return null; | |||||
| } | |||||
| //初始化起始点,终止点。 | |||||
| int begin = (int)position; | |||||
| int end = begin; | |||||
| char[] array = getContent(); | |||||
| while (end < chapterLen) { //判断指针是否在章节的末尾位置 | |||||
| char character = array[end]; //获取当前指针下的字符 | |||||
| //判断当前字符是否为换行,如果为换行,就代表获取到了一个段落,并退出。 | |||||
| //有可能发生初始指针指的就是换行符的情况。 | |||||
| //这里当遇到\n的时候,不需要回退 | |||||
| if ((character+"").equals("\n") && begin != end){ | |||||
| ++end;//指向下一字段 | |||||
| position = end; | |||||
| break; | |||||
| } | |||||
| //指向下一字段 | |||||
| end++; | |||||
| } | |||||
| //所要获取的字段的长度 | |||||
| int size = end - begin; | |||||
| return new String(array,begin,size); | |||||
| } | |||||
| //获取章节的内容 | |||||
| public char[] getContent() { | |||||
| if (cacheMap.size() == 0){ | |||||
| return new char[1]; | |||||
| } | |||||
| char[] block = cacheMap.get(chapterName).getData().get(); | |||||
| if (block == null) { | |||||
| File file = getBookFile(bookId, chapterName); | |||||
| block = FileUtils.getFileContent(file).toCharArray(); | |||||
| Cache cache = cacheMap.get(chapterName); | |||||
| cache.data = new WeakReference<char[]>(block); | |||||
| } | |||||
| return block; | |||||
| } | |||||
| public long getChapterLen(){ | |||||
| return chapterLen; | |||||
| } | |||||
| public void clear(){ | |||||
| cacheMap.clear(); | |||||
| position = 0; | |||||
| chapterLen = 0; | |||||
| } | |||||
| /** | |||||
| * 创建或获取存储文件 | |||||
| * @param folderName | |||||
| * @param fileName | |||||
| * @return | |||||
| */ | |||||
| public static File getBookFile(String folderName, String fileName){ | |||||
| return FileUtils.getFile(Constant.BOOK_CACHE_PATH + folderName | |||||
| + File.separator + fileName + FileUtils.SUFFIX_NB); | |||||
| } | |||||
| public static long getBookSize(String folderName){ | |||||
| return FileUtils.getDirSize(FileUtils | |||||
| .getFolder(Constant.BOOK_CACHE_PATH + folderName)); | |||||
| } | |||||
| /** | |||||
| * 根据文件名判断是否被缓存过 (因为可能数据库显示被缓存过,但是文件中却没有的情况,所以需要根据文件判断是否被缓存 | |||||
| * 过) | |||||
| * @param folderName : bookId | |||||
| * @param fileName: chapterName | |||||
| * @return | |||||
| */ | |||||
| public static boolean isChapterCached(String folderName, String fileName){ | |||||
| File file = new File(Constant.BOOK_CACHE_PATH + folderName | |||||
| + File.separator + fileName + FileUtils.SUFFIX_NB); | |||||
| return file.exists(); | |||||
| } | |||||
| public class Cache { | |||||
| private long size; | |||||
| private WeakReference<char[]> data; | |||||
| public WeakReference<char[]> getData() { | |||||
| return data; | |||||
| } | |||||
| public void setData(WeakReference<char[]> data) { | |||||
| this.data = data; | |||||
| } | |||||
| public long getSize() { | |||||
| return size; | |||||
| } | |||||
| public void setSize(long size) { | |||||
| this.size = size; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,108 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import android.app.Activity; | |||||
| import android.content.ContentResolver; | |||||
| import android.provider.Settings; | |||||
| import android.util.Log; | |||||
| import android.view.WindowManager; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-19. | |||||
| * 调节亮度的工具类 | |||||
| */ | |||||
| public class BrightnessUtils { | |||||
| private static final String TAG = "BrightnessUtils"; | |||||
| /** | |||||
| * 判断是否开启了自动亮度调节 | |||||
| */ | |||||
| public static boolean isAutoBrightness(Activity activity) { | |||||
| boolean isAuto = false; | |||||
| try { | |||||
| isAuto = Settings.System.getInt(activity.getContentResolver(), | |||||
| Settings.System.SCREEN_BRIGHTNESS_MODE) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; | |||||
| } catch (Settings.SettingNotFoundException e){ | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return isAuto; | |||||
| } | |||||
| /** | |||||
| * 获取屏幕的亮度 | |||||
| * 系统亮度模式中,自动模式与手动模式获取到的系统亮度的值不同 | |||||
| */ | |||||
| public static int getScreenBrightness(Activity activity) { | |||||
| if(isAutoBrightness(activity)){ | |||||
| return getAutoScreenBrightness(activity); | |||||
| }else{ | |||||
| return getManualScreenBrightness(activity); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 获取手动模式下的屏幕亮度 | |||||
| * @return value:0~255 | |||||
| */ | |||||
| public static int getManualScreenBrightness(Activity activity) { | |||||
| int nowBrightnessValue = 0; | |||||
| ContentResolver resolver = activity.getContentResolver(); | |||||
| try { | |||||
| nowBrightnessValue = Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS); | |||||
| } catch (Exception e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return nowBrightnessValue; | |||||
| } | |||||
| /** | |||||
| * 获取自动模式下的屏幕亮度 | |||||
| * @return value:0~255 | |||||
| */ | |||||
| public static int getAutoScreenBrightness(Activity activity) { | |||||
| float nowBrightnessValue = 0; | |||||
| //获取自动调节下的亮度范围在 0~1 之间 | |||||
| ContentResolver resolver = activity.getContentResolver(); | |||||
| try { | |||||
| nowBrightnessValue = Settings.System.getFloat(resolver, Settings.System.SCREEN_BRIGHTNESS); | |||||
| Log.d(TAG, "getAutoScreenBrightness: " + nowBrightnessValue); | |||||
| } catch (Exception e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| //转换范围为 (0~255) | |||||
| float fValue = nowBrightnessValue * 225.0f; | |||||
| Log.d(TAG,"brightness: " + fValue); | |||||
| return (int)fValue; | |||||
| } | |||||
| /** | |||||
| * 设置亮度:通过设置 Windows 的 screenBrightness 来修改当前 Windows 的亮度 | |||||
| * lp.screenBrightness:参数范围为 0~1 | |||||
| */ | |||||
| public static void setBrightness(Activity activity, int brightness) { | |||||
| try{ | |||||
| WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); | |||||
| //将 0~255 范围内的数据,转换为 0~1 | |||||
| lp.screenBrightness = Float.valueOf(brightness) * (1f / 255f); | |||||
| Log.d(TAG, "lp.screenBrightness == " + lp.screenBrightness); | |||||
| activity.getWindow().setAttributes(lp); | |||||
| }catch(Exception ex){ | |||||
| ex.printStackTrace(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 获取当前系统的亮度 | |||||
| * @param activity | |||||
| */ | |||||
| public static void setDefaultBrightness(Activity activity) { | |||||
| try { | |||||
| WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); | |||||
| lp.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE; | |||||
| activity.getWindow().setAttributes(lp); | |||||
| } catch (Exception ex) { | |||||
| ex.printStackTrace(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| /** | |||||
| * 编码类型 | |||||
| */ | |||||
| public enum Charset { | |||||
| UTF8("UTF-8"), | |||||
| UTF16LE("UTF-16LE"), | |||||
| UTF16BE("UTF-16BE"), | |||||
| GBK("GBK"); | |||||
| private String mName; | |||||
| public static final byte BLANK = 0x0a; | |||||
| private Charset(String name) { | |||||
| mName = name; | |||||
| } | |||||
| public String getName() { | |||||
| return mName; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,101 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import androidx.annotation.StringDef; | |||||
| import java.io.File; | |||||
| import java.lang.annotation.Retention; | |||||
| import java.lang.annotation.RetentionPolicy; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-16. | |||||
| */ | |||||
| public class Constant { | |||||
| /*SharedPreference*/ | |||||
| public static final String SHARED_SEX = "sex"; | |||||
| public static final String SHARED_SAVE_BOOK_SORT = "book_sort"; | |||||
| public static final String SHARED_SAVE_BILLBOARD = "billboard"; | |||||
| public static final String SHARED_CONVERT_TYPE = "convert_type"; | |||||
| public static final String SEX_BOY = "boy"; | |||||
| public static final String SEX_GIRL = "girl"; | |||||
| /*URL_BASE*/ | |||||
| public static final String API_BASE_URL = "http://api.zhuishushenqi.com"; | |||||
| public static final String IMG_BASE_URL = "http://statics.zhuishushenqi.com"; | |||||
| //book type | |||||
| public static final String BOOK_TYPE_COMMENT = "normal"; | |||||
| public static final String BOOK_TYPE_VOTE = "vote"; | |||||
| //book state | |||||
| public static final String BOOK_STATE_NORMAL = "normal"; | |||||
| public static final String BOOK_STATE_DISTILLATE = "distillate"; | |||||
| //Book Date Convert Format | |||||
| public static final String FORMAT_BOOK_DATE = "yyyy-MM-dd'T'HH:mm:ss"; | |||||
| public static final String FORMAT_TIME = "HH:mm"; | |||||
| public static final String FORMAT_FILE_DATE = "yyyy-MM-dd"; | |||||
| //RxBus | |||||
| public static final int MSG_SELECTOR = 1; | |||||
| //BookCachePath (因为getCachePath引用了Context,所以必须是静态变量,不能够是静态常量) | |||||
| public static String BOOK_CACHE_PATH = FileUtils.getCachePath()+File.separator | |||||
| + "book_cache"+ File.separator ; | |||||
| //文件阅读记录保存的路径 | |||||
| public static String BOOK_RECORD_PATH = FileUtils.getCachePath() + File.separator | |||||
| + "book_record" + File.separator; | |||||
| //BookType | |||||
| @StringDef({ | |||||
| BookType.ALL, | |||||
| BookType.XHQH, | |||||
| BookType.WXXX, | |||||
| BookType.DSYN, | |||||
| BookType.LSJS, | |||||
| BookType.YXJJ, | |||||
| BookType.KHLY, | |||||
| BookType.CYJK, | |||||
| BookType.HMZC, | |||||
| BookType.XDYQ, | |||||
| BookType.GDYQ, | |||||
| BookType.HXYQ, | |||||
| BookType.DMTR | |||||
| }) | |||||
| @Retention(RetentionPolicy.SOURCE) | |||||
| public @interface BookType { | |||||
| String ALL = "all"; | |||||
| String XHQH = "xhqh"; | |||||
| String WXXX = "wxxx"; | |||||
| String DSYN = "dsyn"; | |||||
| String LSJS = "lsjs"; | |||||
| String YXJJ = "yxjj"; | |||||
| String KHLY = "khly"; | |||||
| String CYJK = "cyjk"; | |||||
| String HMZC = "hmzc"; | |||||
| String XDYQ = "xdyq"; | |||||
| String GDYQ = "gdyq"; | |||||
| String HXYQ = "hxyq"; | |||||
| String DMTR = "dmtr"; | |||||
| } | |||||
| public static Map<String, String> bookType = new HashMap<String, String>() {{ | |||||
| put("qt", "其他"); | |||||
| put(BookType.XHQH, "玄幻奇幻"); | |||||
| put(BookType.WXXX, "武侠仙侠"); | |||||
| put(BookType.DSYN, "都市异能"); | |||||
| put(BookType.LSJS, "历史军事"); | |||||
| put(BookType.YXJJ, "游戏竞技"); | |||||
| put(BookType.KHLY, "科幻灵异"); | |||||
| put(BookType.CYJK, "穿越架空"); | |||||
| put(BookType.HMZC, "豪门总裁"); | |||||
| put(BookType.XDYQ, "现代言情"); | |||||
| put(BookType.GDYQ, "古代言情"); | |||||
| put(BookType.HXYQ, "幻想言情"); | |||||
| put(BookType.DMTR, "耽美同人"); | |||||
| }}; | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import java.io.File; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-28. | |||||
| */ | |||||
| public class FileStack { | |||||
| private Node node = null; | |||||
| private int count = 0; | |||||
| public void push(FileSnapshot fileSnapshot){ | |||||
| if (fileSnapshot == null) return; | |||||
| Node fileNode = new Node(); | |||||
| fileNode.fileSnapshot = fileSnapshot; | |||||
| fileNode.next = node; | |||||
| node = fileNode; | |||||
| ++count; | |||||
| } | |||||
| public FileSnapshot pop(){ | |||||
| Node fileNode = node; | |||||
| if (fileNode == null) return null; | |||||
| FileSnapshot fileSnapshot = fileNode.fileSnapshot; | |||||
| node = fileNode.next; | |||||
| --count; | |||||
| return fileSnapshot; | |||||
| } | |||||
| public int getSize(){ | |||||
| return count; | |||||
| } | |||||
| //节点 | |||||
| public class Node { | |||||
| FileSnapshot fileSnapshot; | |||||
| Node next; | |||||
| } | |||||
| //文件快照 | |||||
| public static class FileSnapshot{ | |||||
| public String filePath; | |||||
| public List<File> files; | |||||
| public int scrollOffset; | |||||
| } | |||||
| } | |||||
| @@ -1,192 +1,270 @@ | |||||
| package com.yzx.webebook.utils; | package com.yzx.webebook.utils; | ||||
| import android.content.Context; | |||||
| import android.os.Environment; | |||||
| import com.wetao.note.WePoint; | |||||
| import com.yzx.webebook.App; | |||||
| import java.io.BufferedInputStream; | |||||
| import java.io.BufferedReader; | import java.io.BufferedReader; | ||||
| import java.io.File; | import java.io.File; | ||||
| import java.io.FileInputStream; | import java.io.FileInputStream; | ||||
| import java.io.FileNotFoundException; | import java.io.FileNotFoundException; | ||||
| import java.io.FileOutputStream; | |||||
| import java.io.FileWriter; | |||||
| import java.io.FileReader; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.io.InputStream; | |||||
| import java.io.InputStreamReader; | |||||
| import java.io.OutputStream; | |||||
| import java.io.Reader; | |||||
| import java.text.DecimalFormat; | |||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||
| import java.util.List; | |||||
| import io.reactivex.Single; | |||||
| import io.reactivex.SingleEmitter; | |||||
| import io.reactivex.SingleOnSubscribe; | |||||
| /** | /** | ||||
| * Used 获取SD卡根目录、读写文件、移动、复制、删除文件、获取文件名、后缀名操作类(需要MyApplication中的getAppContext()方法) | |||||
| * | |||||
| * @author andrew 2015-9-2 | |||||
| * Created by newbiechen on 17-5-11. | |||||
| */ | */ | ||||
| @SuppressWarnings("deprecation") | |||||
| public class FileUtils { | public class FileUtils { | ||||
| private static final String TAG = "FileUtils"; | |||||
| /** | |||||
| * read file to string list, a element of list is a line | |||||
| * | |||||
| * @param filePath | |||||
| * @param charsetName The name of a supported {@link java.nio.charset.Charset </code>charset<code>} | |||||
| * @return if file not exist, return null, else return content of file | |||||
| * @throws RuntimeException if an error occurs while operator BufferedReader | |||||
| */ | |||||
| public static ArrayList<WePoint> readFileToList(String filePath, String charsetName) { | |||||
| File file = new File(filePath); | |||||
| ArrayList<WePoint> fileContent = new ArrayList<WePoint>(); | |||||
| if (file == null || !file.isFile()) { | |||||
| return null; | |||||
| } | |||||
| BufferedReader reader = null; | |||||
| try { | |||||
| InputStreamReader is = new InputStreamReader(new FileInputStream(file), charsetName); | |||||
| reader = new BufferedReader(is); | |||||
| String line = null; | |||||
| while ((line = reader.readLine()) != null) { | |||||
| WePoint wePoint = GsonHelper.fromJson(line, WePoint.class); | |||||
| if (wePoint == null) continue; | |||||
| fileContent.add(wePoint); | |||||
| } | |||||
| reader.close(); | |||||
| return fileContent; | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("Flash test : +++++++++ IOException occurred. ", e); | |||||
| } finally { | |||||
| if (reader != null) { | |||||
| try { | |||||
| reader.close(); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("Flash test : +++++++++ IOException occurred. ", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| public static ArrayList<WePoint> readAssetsFileToList(Context cxt, String filePath) { | |||||
| ArrayList<WePoint> fileContent = new ArrayList<WePoint>(); | |||||
| BufferedReader reader = null; | |||||
| try { | |||||
| InputStreamReader is = new InputStreamReader(cxt.getAssets().open(filePath), "utf-8"); | |||||
| reader = new BufferedReader(is); | |||||
| String line = null; | |||||
| while ((line = reader.readLine()) != null) { | |||||
| WePoint wePoint = GsonHelper.fromJson(line, WePoint.class); | |||||
| if (wePoint == null) continue; | |||||
| fileContent.add(wePoint); | |||||
| } | |||||
| reader.close(); | |||||
| return fileContent; | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("Flash test : +++++++++ IOException occurred. ", e); | |||||
| } finally { | |||||
| if (reader != null) { | |||||
| try { | |||||
| reader.close(); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("Flash test : +++++++++ IOException occurred. ", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * write file【写文件:字符串】 | |||||
| * | |||||
| * @param filePath | |||||
| * @param content | |||||
| * @param append is append, if true, write to the end of file, else clear content of file and write into it | |||||
| * @return return false if content is empty, true otherwise | |||||
| * @throws RuntimeException if an error occurs while operator FileWriter | |||||
| */ | |||||
| public static boolean writeFile(String filePath, String content, boolean append) { | |||||
| //字符串判空 | |||||
| if (StringUtils.isEmpty(content)) { | |||||
| return false; | |||||
| } | |||||
| FileWriter fileWriter = null; | |||||
| try { | |||||
| makeDirs(filePath); | |||||
| fileWriter = new FileWriter(filePath, append); | |||||
| fileWriter.write(content); | |||||
| fileWriter.close(); | |||||
| //updateGallery(filePath);//媒体库数据更新 | |||||
| return true; | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("IOException occurred. ", e); | |||||
| } finally { | |||||
| if (fileWriter != null) { | |||||
| try { | |||||
| fileWriter.close(); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("IOException occurred. ", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * write file | |||||
| * | |||||
| * @param file the file to be opened for writing. | |||||
| * @param stream the input stream | |||||
| * @param append if <code>true</code>, then bytes will be written to the end of the file rather than the beginning | |||||
| * @return return true | |||||
| * @throws RuntimeException if an error occurs while operator FileOutputStream | |||||
| */ | |||||
| public static boolean writeFile(File file, InputStream stream, boolean append) { | |||||
| OutputStream o = null; | |||||
| try { | |||||
| makeDirs(file.getAbsolutePath()); | |||||
| if (!file.exists()) file.createNewFile(); | |||||
| o = new FileOutputStream(file, append); | |||||
| byte data[] = new byte[1024]; | |||||
| int length = -1; | |||||
| while ((length = stream.read(data)) != -1) { | |||||
| o.write(data, 0, length); | |||||
| } | |||||
| o.flush(); | |||||
| //updateGallery(file.getAbsolutePath());//媒体库数据更新 | |||||
| return true; | |||||
| } catch (FileNotFoundException e) { | |||||
| throw new RuntimeException("FileNotFoundException occurred. ", e); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("IOException occurred. ", e); | |||||
| } finally { | |||||
| if (o != null) { | |||||
| try { | |||||
| o.close(); | |||||
| stream.close(); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("IOException occurred. ", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| public static boolean makeDirs(String filePath) { | |||||
| String folderName = getFolderName(filePath); | |||||
| if (StringUtils.isEmpty(folderName)) { | |||||
| return false; | |||||
| } | |||||
| File folder = new File(folderName); | |||||
| return (folder.exists() && folder.isDirectory()) ? true : folder.mkdirs(); | |||||
| } | |||||
| public static String getFolderName(String filePath) { | |||||
| if (StringUtils.isEmpty(filePath)) { | |||||
| return filePath; | |||||
| } | |||||
| int filePosi = filePath.lastIndexOf(File.separator); | |||||
| return (filePosi == -1) ? "" : filePath.substring(0, filePosi); | |||||
| } | |||||
| //采用自己的格式去设置文件,防止文件被系统文件查询到 | |||||
| public static final String SUFFIX_NB = ".nb"; | |||||
| public static final String SUFFIX_TXT = ".txt"; | |||||
| public static final String SUFFIX_EPUB = ".epub"; | |||||
| public static final String SUFFIX_PDF = ".pdf"; | |||||
| //获取文件夹 | |||||
| public static File getFolder(String filePath){ | |||||
| File file = new File(filePath); | |||||
| //如果文件夹不存在,就创建它 | |||||
| if (!file.exists()){ | |||||
| file.mkdirs(); | |||||
| } | |||||
| return file; | |||||
| } | |||||
| //获取文件 | |||||
| public static synchronized File getFile(String filePath){ | |||||
| File file = new File(filePath); | |||||
| try { | |||||
| if (!file.exists()){ | |||||
| //创建父类文件夹 | |||||
| getFolder(file.getParent()); | |||||
| //创建文件 | |||||
| file.createNewFile(); | |||||
| } | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return file; | |||||
| } | |||||
| //获取Cache文件夹 | |||||
| public static String getCachePath(){ | |||||
| if (isSdCardExist()){ | |||||
| return App.Companion.getContext() | |||||
| .getExternalCacheDir() | |||||
| .getAbsolutePath(); | |||||
| } | |||||
| else{ | |||||
| return App.Companion.getContext() | |||||
| .getCacheDir() | |||||
| .getAbsolutePath(); | |||||
| } | |||||
| } | |||||
| public static long getDirSize(File file){ | |||||
| //判断文件是否存在 | |||||
| if (file.exists()) { | |||||
| //如果是目录则递归计算其内容的总大小 | |||||
| if (file.isDirectory()) { | |||||
| File[] children = file.listFiles(); | |||||
| long size = 0; | |||||
| for (File f : children) | |||||
| size += getDirSize(f); | |||||
| return size; | |||||
| } else { | |||||
| return file.length(); | |||||
| } | |||||
| } else { | |||||
| return 0; | |||||
| } | |||||
| } | |||||
| public static String getFileSize(long size) { | |||||
| if (size <= 0) return "0"; | |||||
| final String[] units = new String[]{"b", "kb", "M", "G", "T"}; | |||||
| //计算单位的,原理是利用lg,公式是 lg(1024^n) = nlg(1024),最后 nlg(1024)/lg(1024) = n。 | |||||
| int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); | |||||
| //计算原理是,size/单位值。单位值指的是:比如说b = 1024,KB = 1024^2 | |||||
| return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; | |||||
| } | |||||
| /** | |||||
| * 本来是获取File的内容的。但是为了解决文本缩进、换行的问题 | |||||
| * 这个方法就是专门用来获取书籍的... | |||||
| * | |||||
| * 应该放在BookRepository中。。。 | |||||
| * @param file | |||||
| * @return | |||||
| */ | |||||
| public static String getFileContent(File file){ | |||||
| Reader reader = null; | |||||
| String str = null; | |||||
| StringBuilder sb = new StringBuilder(); | |||||
| try { | |||||
| reader = new FileReader(file); | |||||
| BufferedReader br = new BufferedReader(reader); | |||||
| while ((str = br.readLine()) != null){ | |||||
| //过滤空语句 | |||||
| if (!str.equals("")){ | |||||
| //由于sb会自动过滤\n,所以需要加上去 | |||||
| sb.append(" "+str+"\n"); | |||||
| } | |||||
| } | |||||
| } catch (FileNotFoundException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| }finally { | |||||
| IOUtils.close(reader); | |||||
| } | |||||
| return sb.toString(); | |||||
| } | |||||
| //判断是否挂载了SD卡 | |||||
| public static boolean isSdCardExist(){ | |||||
| if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){ | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| //递归删除文件夹下的数据 | |||||
| public static synchronized void deleteFile(String filePath){ | |||||
| File file = new File(filePath); | |||||
| if (!file.exists()) return; | |||||
| if (file.isDirectory()){ | |||||
| File[] files = file.listFiles(); | |||||
| for (File subFile : files){ | |||||
| String path = subFile.getPath(); | |||||
| deleteFile(path); | |||||
| } | |||||
| } | |||||
| //删除文件 | |||||
| file.delete(); | |||||
| } | |||||
| //由于递归的耗时问题,取巧只遍历内部三层 | |||||
| //获取txt文件 | |||||
| public static List<File> getTxtFiles(String filePath,int layer){ | |||||
| List txtFiles = new ArrayList(); | |||||
| File file = new File(filePath); | |||||
| //如果层级为 3,则直接返回 | |||||
| if (layer == 3){ | |||||
| return txtFiles; | |||||
| } | |||||
| //获取文件夹 | |||||
| File[] dirs = file.listFiles( | |||||
| pathname -> { | |||||
| if (pathname.isDirectory() && !pathname.getName().startsWith(".")) { | |||||
| return true; | |||||
| } | |||||
| //获取txt文件 | |||||
| else if(pathname.getName().endsWith(".txt")){ | |||||
| txtFiles.add(pathname); | |||||
| return false; | |||||
| } | |||||
| else{ | |||||
| return false; | |||||
| } | |||||
| } | |||||
| ); | |||||
| //遍历文件夹 | |||||
| for (File dir : dirs){ | |||||
| //递归遍历txt文件 | |||||
| txtFiles.addAll(getTxtFiles(dir.getPath(),layer + 1)); | |||||
| } | |||||
| return txtFiles; | |||||
| } | |||||
| //由于遍历比较耗时 | |||||
| public static Single<List<File>> getSDTxtFile(){ | |||||
| //外部存储卡路径 | |||||
| String rootPath = Environment.getExternalStorageDirectory().getPath(); | |||||
| return Single.create(new SingleOnSubscribe<List<File>>() { | |||||
| @Override | |||||
| public void subscribe(SingleEmitter<List<File>> e) throws Exception { | |||||
| List<File> files = getTxtFiles(rootPath,0); | |||||
| e.onSuccess(files); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //获取文件的编码格式 | |||||
| public static Charset getCharset(String fileName) { | |||||
| BufferedInputStream bis = null; | |||||
| Charset charset = Charset.GBK; | |||||
| byte[] first3Bytes = new byte[3]; | |||||
| try { | |||||
| boolean checked = false; | |||||
| bis = new BufferedInputStream(new FileInputStream(fileName)); | |||||
| bis.mark(0); | |||||
| int read = bis.read(first3Bytes, 0, 3); | |||||
| if (read == -1) | |||||
| return charset; | |||||
| if (first3Bytes[0] == (byte) 0xEF | |||||
| && first3Bytes[1] == (byte) 0xBB | |||||
| && first3Bytes[2] == (byte) 0xBF) { | |||||
| charset = Charset.UTF8; | |||||
| checked = true; | |||||
| } | |||||
| /* | |||||
| * 不支持 UTF16LE 和 UTF16BE | |||||
| else if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) { | |||||
| charset = Charset.UTF16LE; | |||||
| checked = true; | |||||
| } else if (first3Bytes[0] == (byte) 0xFE | |||||
| && first3Bytes[1] == (byte) 0xFF) { | |||||
| charset = Charset.UTF16BE; | |||||
| checked = true; | |||||
| } else */ | |||||
| bis.mark(0); | |||||
| if (!checked) { | |||||
| while ((read = bis.read()) != -1) { | |||||
| if (read >= 0xF0) | |||||
| break; | |||||
| if (0x80 <= read && read <= 0xBF) // 单独出现BF以下的,也算是GBK | |||||
| break; | |||||
| if (0xC0 <= read && read <= 0xDF) { | |||||
| read = bis.read(); | |||||
| if (0x80 <= read && read <= 0xBF) // 双字节 (0xC0 - 0xDF) | |||||
| // (0x80 - 0xBF),也可能在GB编码内 | |||||
| continue; | |||||
| else | |||||
| break; | |||||
| } else if (0xE0 <= read && read <= 0xEF) {// 也有可能出错,但是几率较小 | |||||
| read = bis.read(); | |||||
| if (0x80 <= read && read <= 0xBF) { | |||||
| read = bis.read(); | |||||
| if (0x80 <= read && read <= 0xBF) { | |||||
| charset = Charset.UTF8; | |||||
| break; | |||||
| } else | |||||
| break; | |||||
| } else | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } catch (Exception e) { | |||||
| e.printStackTrace(); | |||||
| } finally { | |||||
| IOUtils.close(bis); | |||||
| } | |||||
| return charset; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,21 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import java.io.Closeable; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-11. | |||||
| */ | |||||
| public class IOUtils { | |||||
| public static void close(Closeable closeable){ | |||||
| if (closeable == null) return; | |||||
| try { | |||||
| closeable.close(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| //close error | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,222 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import android.content.Context; | |||||
| import android.os.Environment; | |||||
| import android.util.Log; | |||||
| import com.yzx.webebook.App; | |||||
| import java.io.BufferedWriter; | |||||
| import java.io.File; | |||||
| import java.io.FileWriter; | |||||
| import java.io.IOException; | |||||
| import java.text.SimpleDateFormat; | |||||
| import java.util.Calendar; | |||||
| import java.util.Date; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-27. | |||||
| */ | |||||
| public class LogUtils { | |||||
| private static Boolean LOG_SWITCH = true; // 日志文件总开关 | |||||
| private static Boolean LOG_TO_FILE = false; // 日志写入文件开关 | |||||
| private static String LOG_TAG = "IReader"; // 默认的tag | |||||
| private static char LOG_TYPE = 'v';// 输入日志类型,v代表输出所有信息,w则只输出警告... | |||||
| private static int LOG_SAVE_DAYS = 7;// sd卡中日志文件的最多保存天数 | |||||
| private final static SimpleDateFormat LOG_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 日志的输出格式 | |||||
| private final static SimpleDateFormat FILE_SUFFIX = new SimpleDateFormat("yyyy-MM-dd");// 日志文件格式 | |||||
| private static String LOG_FILE_PATH; // 日志文件保存路径 | |||||
| private static String LOG_FILE_NAME;// 日志文件保存名称 | |||||
| public static void init(Context context) { // 在Application中初始化 | |||||
| LOG_FILE_PATH = Environment.getExternalStorageDirectory().getPath() + File.separator + App.Companion.getContext().getPackageName(); | |||||
| LOG_FILE_NAME = "Log"; | |||||
| } | |||||
| /**************************** | |||||
| * Warn | |||||
| *********************************/ | |||||
| public static void w(Object msg) { | |||||
| w(LOG_TAG, msg); | |||||
| } | |||||
| public static void w(String tag, Object msg) { | |||||
| w(tag, msg, null); | |||||
| } | |||||
| public static void w(String tag, Object msg, Throwable tr) { | |||||
| if (msg == null) return; | |||||
| log(tag, msg.toString(), tr, 'w'); | |||||
| } | |||||
| /*************************** | |||||
| * Error | |||||
| ********************************/ | |||||
| public static void e(Object msg) { | |||||
| e(LOG_TAG, msg); | |||||
| } | |||||
| public static void e(String tag, Object msg) { | |||||
| e(tag, msg, null); | |||||
| } | |||||
| public static void e(String tag, Object msg, Throwable tr) { | |||||
| if (msg == null) return; | |||||
| log(tag, msg.toString(), tr, 'e'); | |||||
| } | |||||
| /*************************** | |||||
| * Debug | |||||
| ********************************/ | |||||
| public static void d(Object msg) { | |||||
| d(LOG_TAG, msg); | |||||
| } | |||||
| public static void d(String tag, Object msg) {// 调试信息 | |||||
| d(tag, msg, null); | |||||
| } | |||||
| public static void d(String tag, Object msg, Throwable tr) { | |||||
| if (msg == null) return; | |||||
| log(tag, msg.toString(), tr, 'd'); | |||||
| } | |||||
| /**************************** | |||||
| * Info | |||||
| *********************************/ | |||||
| public static void i(Object msg) { | |||||
| i(LOG_TAG, msg); | |||||
| } | |||||
| public static void i(String tag, Object msg) { | |||||
| i(tag, msg, null); | |||||
| } | |||||
| public static void i(String tag, Object msg, Throwable tr) { | |||||
| if (msg == null) return; | |||||
| log(tag, msg.toString(), tr, 'i'); | |||||
| } | |||||
| /************************** | |||||
| * Verbose | |||||
| ********************************/ | |||||
| public static void v(Object msg) { | |||||
| v(LOG_TAG, msg); | |||||
| } | |||||
| public static void v(String tag, Object msg) { | |||||
| v(tag, msg, null); | |||||
| } | |||||
| public static void v(String tag, Object msg, Throwable tr) { | |||||
| if (msg == null) return; | |||||
| log(tag, msg.toString(), tr, 'v'); | |||||
| } | |||||
| /** | |||||
| * 根据tag, msg和等级,输出日志 | |||||
| * | |||||
| * @param tag | |||||
| * @param msg | |||||
| * @param level | |||||
| */ | |||||
| private static void log(String tag, String msg, Throwable tr, char level) { | |||||
| if (tag == null || msg == null || tr == null) return; | |||||
| if (LOG_SWITCH) { | |||||
| if ('e' == level && ('e' == LOG_TYPE || 'v' == LOG_TYPE)) { // 输出错误信息 | |||||
| Log.e(tag, createMessage(msg), tr); | |||||
| } else if ('w' == level && ('w' == LOG_TYPE || 'v' == LOG_TYPE)) { | |||||
| Log.w(tag, createMessage(msg), tr); | |||||
| } else if ('d' == level && ('d' == LOG_TYPE || 'v' == LOG_TYPE)) { | |||||
| Log.d(tag, createMessage(msg), tr); | |||||
| } else if ('i' == level && ('d' == LOG_TYPE || 'v' == LOG_TYPE)) { | |||||
| Log.i(tag, createMessage(msg), tr); | |||||
| } else { | |||||
| Log.v(tag, createMessage(msg), tr); | |||||
| } | |||||
| if (LOG_TO_FILE) | |||||
| log2File(String.valueOf(level), tag, msg + tr == null ? "" : "\n" + Log.getStackTraceString(tr)); | |||||
| } | |||||
| } | |||||
| private static String getFunctionName() { | |||||
| StackTraceElement[] sts = Thread.currentThread().getStackTrace(); | |||||
| if (sts == null) { | |||||
| return null; | |||||
| } | |||||
| for (StackTraceElement st : sts) { | |||||
| if (st.isNativeMethod()) { | |||||
| continue; | |||||
| } | |||||
| if (st.getClassName().equals(Thread.class.getName())) { | |||||
| continue; | |||||
| } | |||||
| if (st.getFileName().equals("LogUtils.java")) { | |||||
| continue; | |||||
| } | |||||
| return "[" + Thread.currentThread().getName() + "(" | |||||
| + Thread.currentThread().getId() + "): " + st.getFileName() | |||||
| + ":" + st.getLineNumber() + "]"; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| private static String createMessage(String msg) { | |||||
| String functionName = getFunctionName(); | |||||
| String message = (functionName == null ? msg | |||||
| : (functionName + " - " + msg)); | |||||
| return message; | |||||
| } | |||||
| /** | |||||
| * 打开日志文件并写入日志 | |||||
| * | |||||
| * @return | |||||
| **/ | |||||
| private synchronized static void log2File(String mylogtype, String tag, String text) { | |||||
| Date nowtime = new Date(); | |||||
| String date = FILE_SUFFIX.format(nowtime); | |||||
| String dateLogContent = LOG_FORMAT.format(nowtime) + ":" + mylogtype + ":" + tag + ":" + text; // 日志输出格式 | |||||
| File destDir = new File(LOG_FILE_PATH); | |||||
| if (!destDir.exists()) { | |||||
| destDir.mkdirs(); | |||||
| } | |||||
| File file = new File(LOG_FILE_PATH, LOG_FILE_NAME + date); | |||||
| try { | |||||
| FileWriter filerWriter = new FileWriter(file, true); | |||||
| BufferedWriter bufWriter = new BufferedWriter(filerWriter); | |||||
| bufWriter.write(dateLogContent); | |||||
| bufWriter.newLine(); | |||||
| bufWriter.close(); | |||||
| filerWriter.close(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 删除指定的日志文件 | |||||
| */ | |||||
| public static void delFile() {// 删除日志文件 | |||||
| String needDelFiel = FILE_SUFFIX.format(getDateBefore()); | |||||
| File file = new File(LOG_FILE_PATH, needDelFiel + LOG_FILE_NAME); | |||||
| if (file.exists()) { | |||||
| file.delete(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 得到LOG_SAVE_DAYS天前的日期 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| private static Date getDateBefore() { | |||||
| Date nowtime = new Date(); | |||||
| Calendar now = Calendar.getInstance(); | |||||
| now.setTime(nowtime); | |||||
| now.set(Calendar.DATE, now.get(Calendar.DATE) - LOG_SAVE_DAYS); | |||||
| return now.getTime(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| /** | |||||
| * Created by newbiechen on 2018/1/1. | |||||
| */ | |||||
| import java.security.MessageDigest; | |||||
| import java.security.NoSuchAlgorithmException; | |||||
| /** | |||||
| *@Description: 将字符串转化为MD5 | |||||
| */ | |||||
| public class MD5Utils { | |||||
| public static String strToMd5By32(String str){ | |||||
| String reStr = null; | |||||
| try { | |||||
| MessageDigest md5 = MessageDigest.getInstance("MD5"); | |||||
| byte[] bytes = md5.digest(str.getBytes()); | |||||
| StringBuffer stringBuffer = new StringBuffer(); | |||||
| for (byte b : bytes){ | |||||
| int bt = b&0xff; | |||||
| if (bt < 16){ | |||||
| stringBuffer.append(0); | |||||
| } | |||||
| stringBuffer.append(Integer.toHexString(bt)); | |||||
| } | |||||
| reStr = stringBuffer.toString(); | |||||
| } catch (NoSuchAlgorithmException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return reStr; | |||||
| } | |||||
| public static String strToMd5By16(String str){ | |||||
| String reStr = strToMd5By32(str); | |||||
| if (reStr != null){ | |||||
| reStr = reStr.substring(8, 24); | |||||
| } | |||||
| return reStr; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,63 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import android.content.Context; | |||||
| import android.net.ConnectivityManager; | |||||
| import android.net.NetworkInfo; | |||||
| import com.yzx.webebook.App; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-11. | |||||
| */ | |||||
| public class NetworkUtils { | |||||
| /** | |||||
| * 获取活动网络信息 | |||||
| * @return NetworkInfo | |||||
| */ | |||||
| public static NetworkInfo getNetworkInfo(){ | |||||
| ConnectivityManager cm = (ConnectivityManager) App.Companion | |||||
| .getContext() | |||||
| .getSystemService(Context.CONNECTIVITY_SERVICE); | |||||
| return cm.getActiveNetworkInfo(); | |||||
| } | |||||
| /** | |||||
| * 网络是否可用 | |||||
| * @return | |||||
| */ | |||||
| public static boolean isAvailable(){ | |||||
| NetworkInfo info = getNetworkInfo(); | |||||
| return info != null && info.isAvailable(); | |||||
| } | |||||
| /** | |||||
| * 网络是否连接 | |||||
| * @return | |||||
| */ | |||||
| public static boolean isConnected(){ | |||||
| NetworkInfo info = getNetworkInfo(); | |||||
| return info != null && info.isConnected(); | |||||
| } | |||||
| /** | |||||
| * 判断wifi是否连接状态 | |||||
| * <p>需添加权限 {@code <uses-permission android:name="android.permission | |||||
| * .ACCESS_NETWORK_STATE"/>}</p> | |||||
| * | |||||
| * @param context 上下文 | |||||
| * @return {@code true}: 连接<br>{@code false}: 未连接 | |||||
| */ | |||||
| public static boolean isWifiConnected(Context context) { | |||||
| ConnectivityManager cm = (ConnectivityManager) context | |||||
| .getSystemService(Context.CONNECTIVITY_SERVICE); | |||||
| return cm != null && cm.getActiveNetworkInfo() != null | |||||
| && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import android.content.Context; | |||||
| import android.content.pm.PackageManager; | |||||
| import androidx.core.content.ContextCompat; | |||||
| /** | |||||
| * Created by newbiechen on 2017/10/8. | |||||
| */ | |||||
| public class PermissionsChecker { | |||||
| private final Context mContext; | |||||
| public PermissionsChecker(Context context) { | |||||
| mContext = context.getApplicationContext(); | |||||
| } | |||||
| // 判断权限集合 | |||||
| public boolean lacksPermissions(String... permissions) { | |||||
| for (String permission : permissions) { | |||||
| if (lacksPermission(permission)) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| // 判断是否缺少权限 | |||||
| private boolean lacksPermission(String permission) { | |||||
| return ContextCompat.checkSelfPermission(mContext, permission) == | |||||
| PackageManager.PERMISSION_DENIED; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,60 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import com.yzx.webebook.model.bean.CommentBean; | |||||
| import com.yzx.webebook.model.bean.DetailBean; | |||||
| import java.util.List; | |||||
| import io.reactivex.Observable; | |||||
| import io.reactivex.ObservableSource; | |||||
| import io.reactivex.Single; | |||||
| import io.reactivex.SingleSource; | |||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | |||||
| import io.reactivex.functions.Function3; | |||||
| import io.reactivex.schedulers.Schedulers; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-29. | |||||
| */ | |||||
| public class RxUtils { | |||||
| public static <T> SingleSource<T> toSimpleSingle(Single<T> upstream){ | |||||
| return upstream.subscribeOn(Schedulers.io()) | |||||
| .observeOn(AndroidSchedulers.mainThread()); | |||||
| } | |||||
| public static <T> ObservableSource<T> toSimpleSingle(Observable<T> upstream){ | |||||
| return upstream.subscribeOn(Schedulers.io()) | |||||
| .observeOn(AndroidSchedulers.mainThread()); | |||||
| } | |||||
| public static <T,R> TwoTuple<T,R> twoTuple(T first,R second){ | |||||
| return new TwoTuple<T, R>(first, second); | |||||
| } | |||||
| public static <T> Single<DetailBean<T>> toCommentDetail(Single<T> detailSingle, | |||||
| Single<List<CommentBean>> bestCommentsSingle, | |||||
| Single<List<CommentBean>> commentsSingle){ | |||||
| return Single.zip(detailSingle, bestCommentsSingle, commentsSingle, | |||||
| new Function3<T, List<CommentBean>, List<CommentBean>, DetailBean<T>>() { | |||||
| @Override | |||||
| public DetailBean<T> apply(T t, List<CommentBean> commentBeen, | |||||
| List<CommentBean> commentBeen2) throws Exception { | |||||
| return new DetailBean<T>(t,commentBeen,commentBeen2); | |||||
| } | |||||
| }); | |||||
| } | |||||
| public static class TwoTuple<A, B> { | |||||
| public final A first; | |||||
| public final B second; | |||||
| public TwoTuple(A a, B b) { | |||||
| this.first = a; | |||||
| this.second = b; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,127 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import android.content.res.Resources; | |||||
| import android.util.DisplayMetrics; | |||||
| import android.util.TypedValue; | |||||
| import android.view.View; | |||||
| import androidx.appcompat.app.AppCompatActivity; | |||||
| import com.yzx.webebook.App; | |||||
| import java.lang.reflect.Method; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-1. | |||||
| */ | |||||
| public class ScreenUtils { | |||||
| public static int dpToPx(int dp) { | |||||
| DisplayMetrics metrics = getDisplayMetrics(); | |||||
| return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); | |||||
| } | |||||
| public static int pxToDp(int px) { | |||||
| DisplayMetrics metrics = getDisplayMetrics(); | |||||
| return (int) (px / metrics.density); | |||||
| } | |||||
| public static int spToPx(int sp) { | |||||
| DisplayMetrics metrics = getDisplayMetrics(); | |||||
| return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics); | |||||
| } | |||||
| public static int pxToSp(int px) { | |||||
| DisplayMetrics metrics = getDisplayMetrics(); | |||||
| return (int) (px / metrics.scaledDensity); | |||||
| } | |||||
| /** | |||||
| * 获取手机显示App区域的大小(头部导航栏+ActionBar+根布局),不包括虚拟按钮 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| public static int[] getAppSize() { | |||||
| int[] size = new int[2]; | |||||
| DisplayMetrics metrics = getDisplayMetrics(); | |||||
| size[0] = metrics.widthPixels; | |||||
| size[1] = metrics.heightPixels; | |||||
| return size; | |||||
| } | |||||
| /** | |||||
| * 获取整个手机屏幕的大小(包括虚拟按钮) | |||||
| * 必须在onWindowFocus方法之后使用 | |||||
| * | |||||
| * @param activity | |||||
| * @return | |||||
| */ | |||||
| public static int[] getScreenSize(AppCompatActivity activity) { | |||||
| int[] size = new int[2]; | |||||
| View decorView = activity.getWindow().getDecorView(); | |||||
| size[0] = decorView.getWidth(); | |||||
| size[1] = decorView.getHeight(); | |||||
| return size; | |||||
| } | |||||
| /** | |||||
| * 获取导航栏的高度 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| public static int getStatusBarHeight() { | |||||
| Resources resources = App.Companion.getContext().getResources(); | |||||
| int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); | |||||
| return resources.getDimensionPixelSize(resourceId); | |||||
| } | |||||
| /** | |||||
| * 获取虚拟按键的高度 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| public static int getNavigationBarHeight() { | |||||
| int navigationBarHeight = 0; | |||||
| Resources rs = App.Companion.getContext().getResources(); | |||||
| int id = rs.getIdentifier("navigation_bar_height", "dimen", "android"); | |||||
| if (id > 0 && hasNavigationBar()) { | |||||
| navigationBarHeight = rs.getDimensionPixelSize(id); | |||||
| } | |||||
| return navigationBarHeight; | |||||
| } | |||||
| /** | |||||
| * 是否存在虚拟按键 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| private static boolean hasNavigationBar() { | |||||
| boolean hasNavigationBar = false; | |||||
| Resources rs = App.Companion.getContext().getResources(); | |||||
| int id = rs.getIdentifier("config_showNavigationBar", "bool", "android"); | |||||
| if (id > 0) { | |||||
| hasNavigationBar = rs.getBoolean(id); | |||||
| } | |||||
| try { | |||||
| Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); | |||||
| Method m = systemPropertiesClass.getMethod("get", String.class); | |||||
| String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys"); | |||||
| if ("1".equals(navBarOverride)) { | |||||
| hasNavigationBar = false; | |||||
| } else if ("0".equals(navBarOverride)) { | |||||
| hasNavigationBar = true; | |||||
| } | |||||
| } catch (Exception e) { | |||||
| } | |||||
| return hasNavigationBar; | |||||
| } | |||||
| public static DisplayMetrics getDisplayMetrics() { | |||||
| DisplayMetrics metrics = App.Companion | |||||
| .getContext() | |||||
| .getResources() | |||||
| .getDisplayMetrics(); | |||||
| return metrics; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import android.content.Context; | |||||
| import android.content.SharedPreferences; | |||||
| import com.yzx.webebook.App; | |||||
| /** | |||||
| * Created by newbiechen on 17-4-16. | |||||
| */ | |||||
| public class SharedPreUtils { | |||||
| private static final String SHARED_NAME = "IReader_pref"; | |||||
| private static SharedPreUtils sInstance; | |||||
| private SharedPreferences sharedReadable; | |||||
| private SharedPreferences.Editor sharedWritable; | |||||
| private SharedPreUtils(){ | |||||
| sharedReadable = App.Companion.getContext() | |||||
| .getSharedPreferences(SHARED_NAME, Context.MODE_MULTI_PROCESS); | |||||
| sharedWritable = sharedReadable.edit(); | |||||
| } | |||||
| public static SharedPreUtils getInstance(){ | |||||
| if(sInstance == null){ | |||||
| synchronized (SharedPreUtils.class){ | |||||
| if (sInstance == null){ | |||||
| sInstance = new SharedPreUtils(); | |||||
| } | |||||
| } | |||||
| } | |||||
| return sInstance; | |||||
| } | |||||
| public String getString(String key){ | |||||
| return sharedReadable.getString(key,""); | |||||
| } | |||||
| public void putString(String key,String value){ | |||||
| sharedWritable.putString(key,value); | |||||
| sharedWritable.commit(); | |||||
| } | |||||
| public void putInt(String key,int value){ | |||||
| sharedWritable.putInt(key, value); | |||||
| sharedWritable.commit(); | |||||
| } | |||||
| public void putBoolean(String key,boolean value){ | |||||
| sharedWritable.putBoolean(key, value); | |||||
| sharedWritable.commit(); | |||||
| } | |||||
| public int getInt(String key,int def){ | |||||
| return sharedReadable.getInt(key, def); | |||||
| } | |||||
| public boolean getBoolean(String key,boolean def){ | |||||
| return sharedReadable.getBoolean(key, def); | |||||
| } | |||||
| } | |||||
| @@ -1,332 +1,190 @@ | |||||
| package com.yzx.webebook.utils; | package com.yzx.webebook.utils; | ||||
| import android.text.TextUtils; | |||||
| import android.content.Context; | |||||
| import java.io.UnsupportedEncodingException; | |||||
| import java.net.URLEncoder; | |||||
| import java.security.MessageDigest; | |||||
| import java.security.NoSuchAlgorithmException; | |||||
| import java.util.regex.Matcher; | |||||
| import java.util.regex.Pattern; | |||||
| import androidx.annotation.StringRes; | |||||
| /** | |||||
| * String Utils | |||||
| * | |||||
| * @author andrew 2015-9-2 | |||||
| */ | |||||
| public class StringUtils { | |||||
| private StringUtils() { | |||||
| throw new AssertionError(); | |||||
| } | |||||
| /** | |||||
| * is null or its length is 0 or it is made by space【字符串为空、长度为0、一个空格】 | |||||
| * | |||||
| * <pre> | |||||
| * isBlank(null) = true; | |||||
| * isBlank("") = true; | |||||
| * isBlank(" ") = true; | |||||
| * isBlank("a") = false; | |||||
| * isBlank("a ") = false; | |||||
| * isBlank(" a") = false; | |||||
| * isBlank("a b") = false; | |||||
| * </pre> | |||||
| * | |||||
| * @param str | |||||
| * @return if string is null or its size is 0 or it is made by space, return true, else return false. | |||||
| */ | |||||
| public static boolean isBlank(String str) { | |||||
| return (str == null || str.trim().length() == 0); | |||||
| } | |||||
| /** | |||||
| * is null or its length is 0【字符串为空、长度为0】 | |||||
| * | |||||
| * <pre> | |||||
| * isEmpty(null) = true; | |||||
| * isEmpty("") = true; | |||||
| * isEmpty(" ") = false; | |||||
| * </pre> | |||||
| * | |||||
| * @param str | |||||
| * @return if string is null or its size is 0, return true, else return false. | |||||
| */ | |||||
| public static boolean isEmpty(CharSequence str) { | |||||
| return (str == null || str.length() == 0); | |||||
| } | |||||
| public static boolean isEqual(String paramString1, String paramString2) { | |||||
| return paramString1.equals(paramString2); | |||||
| } | |||||
| /** | |||||
| * get length of CharSequence【字符串的长度】 | |||||
| * | |||||
| * <pre> | |||||
| * length(null) = 0; | |||||
| * length(\"\") = 0; | |||||
| * length(\"abc\") = 3; | |||||
| * </pre> | |||||
| * | |||||
| * @param str | |||||
| * @return if str is null or empty, return 0, else return {@link CharSequence#length()}. | |||||
| */ | |||||
| public static int length(CharSequence str) { | |||||
| return str == null ? 0 : str.length(); | |||||
| } | |||||
| /** | |||||
| * null Object to empty string | |||||
| * | |||||
| * <pre> | |||||
| * nullStrToEmpty(null) = ""; | |||||
| * nullStrToEmpty("") = ""; | |||||
| * nullStrToEmpty("aa") = "aa"; | |||||
| * </pre> | |||||
| * | |||||
| * @param str | |||||
| * @return | |||||
| */ | |||||
| public static String nullStrToEmpty(Object str) { | |||||
| return (str == null ? "" : (str instanceof String ? (String)str : str.toString())); | |||||
| } | |||||
| /** | |||||
| * capitalize first letter | |||||
| * | |||||
| * <pre> | |||||
| * capitalizeFirstLetter(null) = null; | |||||
| * capitalizeFirstLetter("") = ""; | |||||
| * capitalizeFirstLetter("2ab") = "2ab" | |||||
| * capitalizeFirstLetter("a") = "A" | |||||
| * capitalizeFirstLetter("ab") = "Ab" | |||||
| * capitalizeFirstLetter("Abc") = "Abc" | |||||
| * </pre> | |||||
| * | |||||
| * @param str | |||||
| * @return | |||||
| */ | |||||
| public static String capitalizeFirstLetter(String str) { | |||||
| if (isEmpty(str)) { | |||||
| return str; | |||||
| } | |||||
| char c = str.charAt(0); | |||||
| return (!Character.isLetter(c) || Character.isUpperCase(c)) ? str : new StringBuilder(str.length()) | |||||
| .append(Character.toUpperCase(c)).append(str.substring(1)).toString(); | |||||
| } | |||||
| /** | |||||
| * encoded in utf-8 | |||||
| * | |||||
| * <pre> | |||||
| * utf8Encode(null) = null | |||||
| * utf8Encode("") = ""; | |||||
| * utf8Encode("aa") = "aa"; | |||||
| * utf8Encode("啊啊啊啊") = "%E5%95%8A%E5%95%8A%E5%95%8A%E5%95%8A"; | |||||
| * </pre> | |||||
| * | |||||
| * @param str | |||||
| * @return | |||||
| * @throws UnsupportedEncodingException if an error occurs | |||||
| */ | |||||
| public static String utf8Encode(String str) { | |||||
| if (!isEmpty(str) && str.getBytes().length != str.length()) { | |||||
| try { | |||||
| return URLEncoder.encode(str, "UTF-8"); | |||||
| } catch (UnsupportedEncodingException e) { | |||||
| throw new RuntimeException("UnsupportedEncodingException occurred. ", e); | |||||
| } | |||||
| } | |||||
| return str; | |||||
| } | |||||
| import com.yzx.webebook.App; | |||||
| /** | |||||
| * encoded in utf-8, if exception, return defultReturn | |||||
| * | |||||
| * @param str | |||||
| * @param defultReturn | |||||
| * @return | |||||
| */ | |||||
| public static String utf8Encode(String str, String defultReturn) { | |||||
| if (!isEmpty(str) && str.getBytes().length != str.length()) { | |||||
| try { | |||||
| return URLEncoder.encode(str, "UTF-8"); | |||||
| } catch (UnsupportedEncodingException e) { | |||||
| return defultReturn; | |||||
| } | |||||
| } | |||||
| return str; | |||||
| } | |||||
| import java.text.DateFormat; | |||||
| import java.text.ParseException; | |||||
| import java.text.SimpleDateFormat; | |||||
| import java.util.Calendar; | |||||
| import java.util.Date; | |||||
| /** | |||||
| * get innerHtml from href | |||||
| * | |||||
| * <pre> | |||||
| * getHrefInnerHtml(null) = "" | |||||
| * getHrefInnerHtml("") = "" | |||||
| * getHrefInnerHtml("mp3") = "mp3"; | |||||
| * getHrefInnerHtml("<a innerHtml</a>") = "<a innerHtml</a>"; | |||||
| * getHrefInnerHtml("<a>innerHtml</a>") = "innerHtml"; | |||||
| * getHrefInnerHtml("<a<a>innerHtml</a>") = "innerHtml"; | |||||
| * getHrefInnerHtml("<a href="baidu.com">innerHtml</a>") = "innerHtml"; | |||||
| * getHrefInnerHtml("<a href="baidu.com" title="baidu">innerHtml</a>") = "innerHtml"; | |||||
| * getHrefInnerHtml(" <a>innerHtml</a> ") = "innerHtml"; | |||||
| * getHrefInnerHtml("<a>innerHtml</a></a>") = "innerHtml"; | |||||
| * getHrefInnerHtml("jack<a>innerHtml</a></a>") = "innerHtml"; | |||||
| * getHrefInnerHtml("<a>innerHtml1</a><a>innerHtml2</a>") = "innerHtml2"; | |||||
| * </pre> | |||||
| * | |||||
| * @param href | |||||
| * @return <ul> | |||||
| * <li>if href is null, return ""</li> | |||||
| * <li>if not match regx, return source</li> | |||||
| * <li>return the last string that match regx</li> | |||||
| * </ul> | |||||
| */ | |||||
| public static String getHrefInnerHtml(String href) { | |||||
| if (isEmpty(href)) { | |||||
| return ""; | |||||
| } | |||||
| //import com.zqc.opencc.android.lib.ChineseConverter; | |||||
| //import com.zqc.opencc.android.lib.ConversionType; | |||||
| String hrefReg = ".*<[\\s]*a[\\s]*.*>(.+?)<[\\s]*/a[\\s]*>.*"; | |||||
| Pattern hrefPattern = Pattern.compile(hrefReg, Pattern.CASE_INSENSITIVE); | |||||
| Matcher hrefMatcher = hrefPattern.matcher(href); | |||||
| if (hrefMatcher.matches()) { | |||||
| return hrefMatcher.group(1); | |||||
| } | |||||
| return href; | |||||
| } | |||||
| /** | |||||
| * process special char in html | |||||
| * | |||||
| * <pre> | |||||
| * htmlEscapeCharsToString(null) = null; | |||||
| * htmlEscapeCharsToString("") = ""; | |||||
| * htmlEscapeCharsToString("mp3") = "mp3"; | |||||
| * htmlEscapeCharsToString("mp3<") = "mp3<"; | |||||
| * htmlEscapeCharsToString("mp3>") = "mp3\>"; | |||||
| * htmlEscapeCharsToString("mp3&mp4") = "mp3&mp4"; | |||||
| * htmlEscapeCharsToString("mp3"mp4") = "mp3\"mp4"; | |||||
| * htmlEscapeCharsToString("mp3<>&"mp4") = "mp3\<\>&\"mp4"; | |||||
| * </pre> | |||||
| * | |||||
| * @param source | |||||
| * @return | |||||
| */ | |||||
| public static String htmlEscapeCharsToString(String source) { | |||||
| return StringUtils.isEmpty(source) ? source : source.replaceAll("<", "<").replaceAll(">", ">") | |||||
| .replaceAll("&", "&").replaceAll(""", "\""); | |||||
| } | |||||
| /** | |||||
| * transform half width char to full width char | |||||
| * | |||||
| * <pre> | |||||
| * fullWidthToHalfWidth(null) = null; | |||||
| * fullWidthToHalfWidth("") = ""; | |||||
| * fullWidthToHalfWidth(new String(new char[] {12288})) = " "; | |||||
| * fullWidthToHalfWidth("!"#$%&) = "!\"#$%&"; | |||||
| * </pre> | |||||
| * | |||||
| * @param s | |||||
| * @return | |||||
| */ | |||||
| public static String fullWidthToHalfWidth(String s) { | |||||
| if (isEmpty(s)) { | |||||
| return s; | |||||
| } | |||||
| char[] source = s.toCharArray(); | |||||
| for (int i = 0; i < source.length; i++) { | |||||
| if (source[i] == 12288) { | |||||
| source[i] = ' '; | |||||
| // } else if (source[i] == 12290) { | |||||
| // source[i] = '.'; | |||||
| } else if (source[i] >= 65281 && source[i] <= 65374) { | |||||
| source[i] = (char)(source[i] - 65248); | |||||
| } else { | |||||
| source[i] = source[i]; | |||||
| } | |||||
| } | |||||
| return new String(source); | |||||
| } | |||||
| /** | |||||
| * transform full width char to half width char | |||||
| * | |||||
| * <pre> | |||||
| * halfWidthToFullWidth(null) = null; | |||||
| * halfWidthToFullWidth("") = ""; | |||||
| * halfWidthToFullWidth(" ") = new String(new char[] {12288}); | |||||
| * halfWidthToFullWidth("!\"#$%&) = "!"#$%&"; | |||||
| * </pre> | |||||
| * | |||||
| * @param s | |||||
| * @return | |||||
| */ | |||||
| public static String halfWidthToFullWidth(String s) { | |||||
| if (isEmpty(s)) { | |||||
| return s; | |||||
| } | |||||
| char[] source = s.toCharArray(); | |||||
| for (int i = 0; i < source.length; i++) { | |||||
| if (source[i] == ' ') { | |||||
| source[i] = (char)12288; | |||||
| // } else if (source[i] == '.') { | |||||
| // source[i] = (char)12290; | |||||
| } else if (source[i] >= 33 && source[i] <= 126) { | |||||
| source[i] = (char)(source[i] + 65248); | |||||
| } else { | |||||
| source[i] = source[i]; | |||||
| } | |||||
| } | |||||
| return new String(source); | |||||
| } | |||||
| private static String bytesToHexString(byte[] paramArrayOfByte) { | |||||
| StringBuilder localStringBuilder = new StringBuilder(); | |||||
| for (int i = 0; i < paramArrayOfByte.length; i++) | |||||
| { | |||||
| String str = Integer.toHexString(0xFF & paramArrayOfByte[i]); | |||||
| if (str.length() == 1) | |||||
| localStringBuilder.append('0'); | |||||
| localStringBuilder.append(str); | |||||
| } | |||||
| return localStringBuilder.toString(); | |||||
| } | |||||
| public static String hashKey(String paramString) { | |||||
| String localObject = ""; | |||||
| if (!TextUtils.isEmpty(paramString)); | |||||
| try | |||||
| { | |||||
| MessageDigest localMessageDigest = MessageDigest.getInstance("MD5"); | |||||
| localMessageDigest.update(paramString.getBytes()); | |||||
| String str = bytesToHexString(localMessageDigest.digest()); | |||||
| localObject = str; | |||||
| return localObject; | |||||
| } | |||||
| catch (NoSuchAlgorithmException localNoSuchAlgorithmException) | |||||
| { | |||||
| } | |||||
| return String.valueOf(paramString.hashCode()); | |||||
| } | |||||
| public static String stripLeadingSlash(String paramString) { | |||||
| /** | |||||
| * Created by newbiechen on 17-4-22. | |||||
| * 对文字操作的工具类 | |||||
| */ | |||||
| if (TextUtils.isEmpty(paramString)) | |||||
| { | |||||
| return paramString; | |||||
| } | |||||
| int i = 0; | |||||
| while (paramString.charAt(i) == '/'){ | |||||
| i ++; | |||||
| } | |||||
| return paramString.substring(i); | |||||
| } | |||||
| public class StringUtils { | |||||
| private static final String TAG = "StringUtils"; | |||||
| private static final int HOUR_OF_DAY = 24; | |||||
| private static final int DAY_OF_YESTERDAY = 2; | |||||
| private static final int TIME_UNIT = 60; | |||||
| //将时间转换成日期 | |||||
| public static String dateConvert(long time,String pattern){ | |||||
| Date date = new Date(time); | |||||
| SimpleDateFormat format = new SimpleDateFormat(pattern); | |||||
| return format.format(date); | |||||
| } | |||||
| //将日期转换成昨天、今天、明天 | |||||
| public static String dateConvert(String source,String pattern){ | |||||
| DateFormat format = new SimpleDateFormat(pattern); | |||||
| Calendar calendar = Calendar.getInstance(); | |||||
| try { | |||||
| Date date = format.parse(source); | |||||
| long curTime = calendar.getTimeInMillis(); | |||||
| calendar.setTime(date); | |||||
| //将MISC 转换成 sec | |||||
| long difSec = Math.abs((curTime - date.getTime())/1000); | |||||
| long difMin = difSec/60; | |||||
| long difHour = difMin/60; | |||||
| long difDate = difHour/60; | |||||
| int oldHour = calendar.get(Calendar.HOUR); | |||||
| //如果没有时间 | |||||
| if (oldHour == 0){ | |||||
| //比日期:昨天今天和明天 | |||||
| if (difDate == 0){ | |||||
| return "今天"; | |||||
| } | |||||
| else if (difDate < DAY_OF_YESTERDAY){ | |||||
| return "昨天"; | |||||
| } | |||||
| else { | |||||
| DateFormat convertFormat = new SimpleDateFormat("yyyy-MM-dd"); | |||||
| String value = convertFormat.format(date); | |||||
| return value; | |||||
| } | |||||
| } | |||||
| if (difSec < TIME_UNIT){ | |||||
| return difSec+"秒前"; | |||||
| } | |||||
| else if (difMin < TIME_UNIT){ | |||||
| return difMin+"分钟前"; | |||||
| } | |||||
| else if (difHour < HOUR_OF_DAY){ | |||||
| return difHour+"小时前"; | |||||
| } | |||||
| else if (difDate < DAY_OF_YESTERDAY){ | |||||
| return "昨天"; | |||||
| } | |||||
| else { | |||||
| DateFormat convertFormat = new SimpleDateFormat("yyyy-MM-dd"); | |||||
| String value = convertFormat.format(date); | |||||
| return value; | |||||
| } | |||||
| } catch (ParseException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| public static String toFirstCapital(String str){ | |||||
| return str.substring(0,1).toUpperCase()+str.substring(1); | |||||
| } | |||||
| public static String getString(@StringRes int id){ | |||||
| return App.Companion.getContext().getResources().getString(id); | |||||
| } | |||||
| public static String getString(@StringRes int id, Object... formatArgs){ | |||||
| return App.Companion.getContext().getResources().getString(id,formatArgs); | |||||
| } | |||||
| /** | |||||
| * 将文本中的半角字符,转换成全角字符 | |||||
| * @param input | |||||
| * @return | |||||
| */ | |||||
| public static String halfToFull(String input) | |||||
| { | |||||
| char[] c = input.toCharArray(); | |||||
| for (int i = 0; i< c.length; i++) | |||||
| { | |||||
| if (c[i] == 32) //半角空格 | |||||
| { | |||||
| c[i] = (char) 12288; | |||||
| continue; | |||||
| } | |||||
| //根据实际情况,过滤不需要转换的符号 | |||||
| //if (c[i] == 46) //半角点号,不转换 | |||||
| // continue; | |||||
| if (c[i]> 32 && c[i]< 127) //其他符号都转换为全角 | |||||
| c[i] = (char) (c[i] + 65248); | |||||
| } | |||||
| return new String(c); | |||||
| } | |||||
| //功能:字符串全角转换为半角 | |||||
| public static String fullToHalf(String input) | |||||
| { | |||||
| char[] c = input.toCharArray(); | |||||
| for (int i = 0; i< c.length; i++) | |||||
| { | |||||
| if (c[i] == 12288) //全角空格 | |||||
| { | |||||
| c[i] = (char) 32; | |||||
| continue; | |||||
| } | |||||
| if (c[i]> 65280&& c[i]< 65375) | |||||
| c[i] = (char) (c[i] - 65248); | |||||
| } | |||||
| 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; | |||||
| return input; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,162 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import android.app.Activity; | |||||
| import android.os.Build; | |||||
| import android.view.View; | |||||
| import android.view.WindowManager; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-16. | |||||
| * 基于 Android 4.4 | |||||
| * | |||||
| * 主要参数说明: | |||||
| * | |||||
| * SYSTEM_UI_FLAG_FULLSCREEN : 隐藏StatusBar | |||||
| * SYSTEM_UI_FLAG_HIDE_NAVIGATION : 隐藏NavigationBar | |||||
| * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN: 视图扩展到StatusBar的位置,并且StatusBar不消失。 | |||||
| * 这里需要一些处理,一般是将StatusBar设置为全透明或者半透明。之后还需要使用fitSystemWindows=防止视图扩展到Status | |||||
| * Bar上面(会在StatusBar上加一层View,该View可被移动) | |||||
| * SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION: 视图扩展到NavigationBar的位置 | |||||
| * SYSTEM_UI_FLAG_LAYOUT_STABLE:稳定效果 | |||||
| * SYSTEM_UI_FLAG_IMMERSIVE_STICKY:保证点击任意位置不会退出 | |||||
| * | |||||
| * 可设置特效说明: | |||||
| * 1. 全屏特效 | |||||
| * 2. 全屏点击不退出特效 | |||||
| * 3. 注意在19 <=sdk <=21 时候,必须通过Window设置透明栏 | |||||
| */ | |||||
| public class SystemBarUtils { | |||||
| private static final int UNSTABLE_STATUS = View.SYSTEM_UI_FLAG_FULLSCREEN; | |||||
| private static final int UNSTABLE_NAV = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; | |||||
| private static final int STABLE_STATUS = View.SYSTEM_UI_FLAG_FULLSCREEN | | |||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | | |||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE | | |||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; | |||||
| private static final int STABLE_NAV = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | | |||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | | |||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE | | |||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; | |||||
| private static final int EXPAND_STATUS = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | |||||
| | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; | |||||
| private static final int EXPAND_NAV = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | |||||
| | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; | |||||
| //设置隐藏StatusBar(点击任意地方会恢复) | |||||
| public static void hideUnStableStatusBar(Activity activity){ | |||||
| //App全屏,隐藏StatusBar | |||||
| setFlag(activity,UNSTABLE_STATUS); | |||||
| } | |||||
| public static void showUnStableStatusBar(Activity activity){ | |||||
| clearFlag(activity,UNSTABLE_STATUS); | |||||
| } | |||||
| //隐藏NavigationBar(点击任意地方会恢复) | |||||
| public static void hideUnStableNavBar(Activity activity){ | |||||
| setFlag(activity,UNSTABLE_NAV); | |||||
| } | |||||
| public static void showUnStableNavBar(Activity activity){ | |||||
| clearFlag(activity,UNSTABLE_NAV); | |||||
| } | |||||
| public static void hideStableStatusBar(Activity activity){ | |||||
| //App全屏,隐藏StatusBar | |||||
| setFlag(activity,STABLE_STATUS); | |||||
| } | |||||
| public static void showStableStatusBar(Activity activity){ | |||||
| clearFlag(activity,STABLE_STATUS); | |||||
| } | |||||
| public static void hideStableNavBar(Activity activity){ | |||||
| //App全屏,隐藏StatusBar | |||||
| setFlag(activity,STABLE_NAV); | |||||
| } | |||||
| public static void showStableNavBar(Activity activity){ | |||||
| clearFlag(activity,STABLE_NAV); | |||||
| } | |||||
| /** | |||||
| * 视图扩充到StatusBar | |||||
| */ | |||||
| public static void expandStatusBar(Activity activity){ | |||||
| setFlag(activity, EXPAND_STATUS); | |||||
| } | |||||
| /** | |||||
| * 视图扩充到NavBar | |||||
| * @param activity | |||||
| */ | |||||
| public static void expandNavBar(Activity activity){ | |||||
| setFlag(activity, EXPAND_NAV); | |||||
| } | |||||
| public static void transparentStatusBar(Activity activity){ | |||||
| if (Build.VERSION.SDK_INT >= 21){ | |||||
| expandStatusBar(activity); | |||||
| activity.getWindow() | |||||
| .setStatusBarColor(activity.getResources().getColor(android.R.color.transparent)); | |||||
| } | |||||
| else if (Build.VERSION.SDK_INT >= 19){ | |||||
| WindowManager.LayoutParams attrs = activity.getWindow().getAttributes(); | |||||
| attrs.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | attrs.flags); | |||||
| activity.getWindow().setAttributes(attrs); | |||||
| } | |||||
| } | |||||
| public static void transparentNavBar(Activity activity){ | |||||
| if (Build.VERSION.SDK_INT >= 21){ | |||||
| expandNavBar(activity); | |||||
| //下面这个方法在sdk:21以上才有 | |||||
| activity.getWindow() | |||||
| .setNavigationBarColor(activity.getResources().getColor(android.R.color.transparent)); | |||||
| } | |||||
| } | |||||
| public static void setFlag(Activity activity, int flag){ | |||||
| if (Build.VERSION.SDK_INT >= 19){ | |||||
| View decorView = activity.getWindow().getDecorView(); | |||||
| int option = decorView.getSystemUiVisibility() | flag; | |||||
| decorView.setSystemUiVisibility(option); | |||||
| } | |||||
| } | |||||
| //取消flag | |||||
| public static void clearFlag(Activity activity, int flag){ | |||||
| if (Build.VERSION.SDK_INT >= 19){ | |||||
| View decorView = activity.getWindow().getDecorView(); | |||||
| int option = decorView.getSystemUiVisibility() & (~flag); | |||||
| decorView.setSystemUiVisibility(option); | |||||
| } | |||||
| } | |||||
| public static void setToggleFlag(Activity activity, int option){ | |||||
| if (Build.VERSION.SDK_INT >= 19){ | |||||
| if (isFlagUsed(activity,option)){ | |||||
| clearFlag(activity,option); | |||||
| } | |||||
| else { | |||||
| setFlag(activity,option); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * @param activity | |||||
| * @return flag是否已被使用 | |||||
| */ | |||||
| public static boolean isFlagUsed(Activity activity, int flag) { | |||||
| int currentFlag = activity.getWindow().getDecorView().getSystemUiVisibility(); | |||||
| if((currentFlag & flag) | |||||
| == flag) { | |||||
| return true; | |||||
| }else { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| package com.yzx.webebook.utils; | |||||
| import android.widget.Toast; | |||||
| import com.yzx.webebook.App; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-11. | |||||
| */ | |||||
| public class ToastUtils { | |||||
| public static void show(String msg){ | |||||
| Toast.makeText(App.Companion.getContext(), msg, Toast.LENGTH_SHORT).show(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| package com.yzx.webebook.utils.media; | |||||
| import android.content.Context; | |||||
| import android.os.Bundle; | |||||
| import androidx.loader.content.CursorLoader; | |||||
| /** | |||||
| * Created by newbiechen on 2018/1/14. | |||||
| */ | |||||
| public class LoaderCreator { | |||||
| public static final int ALL_BOOK_FILE = 1; | |||||
| public static CursorLoader create(Context context, int id, Bundle bundle) { | |||||
| LocalFileLoader loader = null; | |||||
| switch (id){ | |||||
| case ALL_BOOK_FILE: | |||||
| loader = new LocalFileLoader(context); | |||||
| break; | |||||
| default: | |||||
| loader = null; | |||||
| break; | |||||
| } | |||||
| if (loader != null) { | |||||
| return loader; | |||||
| } | |||||
| throw new IllegalArgumentException("The id of Loader is invalid!"); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,137 @@ | |||||
| package com.yzx.webebook.utils.media; | |||||
| import android.content.Context; | |||||
| import android.database.Cursor; | |||||
| import android.net.Uri; | |||||
| import android.provider.MediaStore; | |||||
| import android.text.TextUtils; | |||||
| import androidx.annotation.NonNull; | |||||
| import androidx.loader.content.CursorLoader; | |||||
| import java.io.File; | |||||
| import java.sql.Blob; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 2018/1/14. | |||||
| */ | |||||
| public class LocalFileLoader extends CursorLoader { | |||||
| private static final String TAG = "LocalFileLoader"; | |||||
| private static final Uri FILE_URI = Uri.parse("content://media/external/file"); | |||||
| private static final String SELECTION = MediaStore.Files.FileColumns.DATA + " like ?"; | |||||
| private static final String SEARCH_TYPE = "%.txt"; | |||||
| private static final String SORT_ORDER = MediaStore.Files.FileColumns.DISPLAY_NAME + " DESC"; | |||||
| private static final String[] FILE_PROJECTION = { | |||||
| MediaStore.Files.FileColumns.DATA, | |||||
| MediaStore.Files.FileColumns.DISPLAY_NAME | |||||
| }; | |||||
| public LocalFileLoader(Context context) { | |||||
| super(context); | |||||
| initLoader(); | |||||
| } | |||||
| /** | |||||
| * 为 Cursor 设置默认参数 | |||||
| */ | |||||
| private void initLoader() { | |||||
| setUri(FILE_URI); | |||||
| setProjection(FILE_PROJECTION); | |||||
| setSelection(SELECTION); | |||||
| setSelectionArgs(new String[]{SEARCH_TYPE}); | |||||
| setSortOrder(SORT_ORDER); | |||||
| } | |||||
| public void parseData(Cursor cursor, final MediaStoreHelper.MediaResultCallback resultCallback) { | |||||
| List<File> files = new ArrayList<>(); | |||||
| // 判断是否存在数据 | |||||
| if (cursor == null) { | |||||
| // TODO:当媒体库没有数据的时候,需要做相应的处理 | |||||
| // 暂时直接返回空数据 | |||||
| resultCallback.onResultCallback(files); | |||||
| return; | |||||
| } | |||||
| // 重复使用Loader时,需要重置cursor的position; | |||||
| cursor.moveToPosition(-1); | |||||
| while (cursor.moveToNext()) { | |||||
| String path; | |||||
| path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)); | |||||
| // 路径无效 | |||||
| if (TextUtils.isEmpty(path)) { | |||||
| continue; | |||||
| } else { | |||||
| File file = new File(path); | |||||
| if (file.isDirectory() || !file.exists()){ | |||||
| continue; | |||||
| } | |||||
| else { | |||||
| files.add(file); | |||||
| } | |||||
| } | |||||
| } | |||||
| if (resultCallback != null) { | |||||
| resultCallback.onResultCallback(files); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 从Cursor中读取对应columnName的值 | |||||
| * | |||||
| * @param cursor | |||||
| * @param columnName | |||||
| * @param defaultValue | |||||
| * @return 当columnName无效时返回默认值; | |||||
| */ | |||||
| protected Object getValueFromCursor(@NonNull Cursor cursor, String columnName, Object defaultValue) { | |||||
| try { | |||||
| int index = cursor.getColumnIndexOrThrow(columnName); | |||||
| int type = cursor.getType(index); | |||||
| switch (type) { | |||||
| case Cursor.FIELD_TYPE_STRING: | |||||
| // TO SOLVE:某些手机的数据库将数值类型存为String类型 | |||||
| String value = cursor.getString(index); | |||||
| try { | |||||
| if (defaultValue instanceof String) { | |||||
| return value; | |||||
| } else if (defaultValue instanceof Long) { | |||||
| return Long.valueOf(value); | |||||
| } else if (defaultValue instanceof Integer) { | |||||
| return Integer.valueOf(value); | |||||
| } else if (defaultValue instanceof Double) { | |||||
| return Double.valueOf(value); | |||||
| } else if (defaultValue instanceof Float) { | |||||
| return Float.valueOf(value); | |||||
| } | |||||
| } catch (NumberFormatException e) { | |||||
| return defaultValue; | |||||
| } | |||||
| case Cursor.FIELD_TYPE_INTEGER: | |||||
| if (defaultValue instanceof Long) { | |||||
| return cursor.getLong(index); | |||||
| } else if (defaultValue instanceof Integer) { | |||||
| return cursor.getInt(index); | |||||
| } | |||||
| case Cursor.FIELD_TYPE_FLOAT: | |||||
| if (defaultValue instanceof Float) { | |||||
| return cursor.getFloat(index); | |||||
| } else if (defaultValue instanceof Double) { | |||||
| return cursor.getDouble(index); | |||||
| } | |||||
| case Cursor.FIELD_TYPE_BLOB: | |||||
| if (defaultValue instanceof Blob) { | |||||
| return cursor.getBlob(index); | |||||
| } | |||||
| case Cursor.FIELD_TYPE_NULL: | |||||
| default: | |||||
| return defaultValue; | |||||
| } | |||||
| } catch (IllegalArgumentException e) { | |||||
| return defaultValue; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| package com.yzx.webebook.utils.media; | |||||
| import android.content.Context; | |||||
| import android.database.Cursor; | |||||
| import android.os.Bundle; | |||||
| import androidx.fragment.app.FragmentActivity; | |||||
| import androidx.loader.app.LoaderManager; | |||||
| import androidx.loader.content.Loader; | |||||
| import java.io.File; | |||||
| import java.lang.ref.WeakReference; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 2018/1/14. | |||||
| * 获取媒体库的数据。 | |||||
| */ | |||||
| public class MediaStoreHelper { | |||||
| /** | |||||
| * 获取媒体库中所有的书籍文件 | |||||
| * <p> | |||||
| * 暂时只支持 TXT | |||||
| * | |||||
| * @param activity | |||||
| * @param resultCallback | |||||
| */ | |||||
| public static void getAllBookFile(FragmentActivity activity, MediaResultCallback resultCallback) { | |||||
| // 将文件的获取处理交给 LoaderManager。 | |||||
| activity.getSupportLoaderManager() | |||||
| .initLoader(LoaderCreator.ALL_BOOK_FILE, null, new MediaLoaderCallbacks(activity, resultCallback)); | |||||
| } | |||||
| public interface MediaResultCallback { | |||||
| void onResultCallback(List<File> files); | |||||
| } | |||||
| /** | |||||
| * Loader 回调处理 | |||||
| */ | |||||
| static class MediaLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> { | |||||
| protected WeakReference<Context> mContext; | |||||
| protected MediaResultCallback mResultCallback; | |||||
| public MediaLoaderCallbacks(Context context, MediaResultCallback resultCallback) { | |||||
| mContext = new WeakReference<>(context); | |||||
| mResultCallback = resultCallback; | |||||
| } | |||||
| @Override | |||||
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { | |||||
| return LoaderCreator.create(mContext.get(), id, args); | |||||
| } | |||||
| @Override | |||||
| public void onLoadFinished(Loader<Cursor> loader, Cursor data) { | |||||
| LocalFileLoader localFileLoader = (LocalFileLoader) loader; | |||||
| localFileLoader.parseData(data, mResultCallback); | |||||
| } | |||||
| @Override | |||||
| public void onLoaderReset(Loader<Cursor> loader) { | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,333 @@ | |||||
| package com.yzx.webebook.widget; | |||||
| import android.app.Activity; | |||||
| import android.app.Dialog; | |||||
| import android.content.Intent; | |||||
| import android.graphics.drawable.Drawable; | |||||
| import android.os.Bundle; | |||||
| import android.view.Gravity; | |||||
| import android.view.Window; | |||||
| import android.view.WindowManager; | |||||
| import android.widget.CheckBox; | |||||
| import android.widget.ImageView; | |||||
| import android.widget.RadioButton; | |||||
| import android.widget.RadioGroup; | |||||
| import android.widget.SeekBar; | |||||
| import android.widget.TextView; | |||||
| import androidx.annotation.NonNull; | |||||
| import androidx.core.content.ContextCompat; | |||||
| import androidx.recyclerview.widget.GridLayoutManager; | |||||
| import androidx.recyclerview.widget.RecyclerView; | |||||
| import com.yzx.webebook.R; | |||||
| import com.yzx.webebook.adapter.PageStyleAdapter; | |||||
| import com.yzx.webebook.model.local.ReadSettingManager; | |||||
| import com.yzx.webebook.utils.BrightnessUtils; | |||||
| import com.yzx.webebook.utils.ScreenUtils; | |||||
| import com.yzx.webebook.widget.page.PageLoader; | |||||
| import com.yzx.webebook.widget.page.PageMode; | |||||
| import com.yzx.webebook.widget.page.PageStyle; | |||||
| import java.util.Arrays; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-18. | |||||
| */ | |||||
| public class ReadSettingDialog extends Dialog { | |||||
| private static final String TAG = "ReadSettingDialog"; | |||||
| private static final int DEFAULT_TEXT_SIZE = 16; | |||||
| ImageView mIvBrightnessMinus; | |||||
| SeekBar mSbBrightness; | |||||
| ImageView mIvBrightnessPlus; | |||||
| CheckBox mCbBrightnessAuto; | |||||
| TextView mTvFontMinus; | |||||
| TextView mTvFont; | |||||
| TextView mTvFontPlus; | |||||
| CheckBox mCbFontDefault; | |||||
| RadioGroup mRgPageMode; | |||||
| RadioButton mRbSimulation; | |||||
| RadioButton mRbCover; | |||||
| RadioButton mRbSlide; | |||||
| RadioButton mRbScroll; | |||||
| RadioButton mRbNone; | |||||
| RecyclerView mRvBg; | |||||
| TextView mTvMore; | |||||
| /************************************/ | |||||
| private PageStyleAdapter mPageStyleAdapter; | |||||
| private ReadSettingManager mSettingManager; | |||||
| private PageLoader mPageLoader; | |||||
| private Activity mActivity; | |||||
| private PageMode mPageMode; | |||||
| private PageStyle mPageStyle; | |||||
| private int mBrightness; | |||||
| private int mTextSize; | |||||
| private boolean isBrightnessAuto; | |||||
| private boolean isTextDefault; | |||||
| public ReadSettingDialog(@NonNull Activity activity, PageLoader mPageLoader) { | |||||
| super(activity, R.style.ReadSettingDialog); | |||||
| mActivity = activity; | |||||
| this.mPageLoader = mPageLoader; | |||||
| } | |||||
| @Override | |||||
| protected void onCreate(Bundle savedInstanceState) { | |||||
| super.onCreate(savedInstanceState); | |||||
| setContentView(R.layout.dialog_read_setting); | |||||
| initView(); | |||||
| setUpWindow(); | |||||
| initData(); | |||||
| initWidget(); | |||||
| initClick(); | |||||
| } | |||||
| private void initView() { | |||||
| mIvBrightnessMinus = findViewById(R.id.read_setting_iv_brightness_minus); | |||||
| mSbBrightness = findViewById(R.id.read_setting_sb_brightness); | |||||
| mIvBrightnessPlus = findViewById(R.id.read_setting_iv_brightness_plus); | |||||
| mCbBrightnessAuto = findViewById(R.id.read_setting_cb_brightness_auto); | |||||
| mTvFontMinus = findViewById(R.id.read_setting_tv_font_minus); | |||||
| mTvFont = findViewById(R.id.read_setting_tv_font); | |||||
| mTvFontPlus = findViewById(R.id.read_setting_tv_font_plus); | |||||
| mCbFontDefault = findViewById(R.id.read_setting_cb_font_default); | |||||
| mRgPageMode = findViewById(R.id.read_setting_rg_page_mode); | |||||
| mRbSimulation = findViewById(R.id.read_setting_rb_simulation); | |||||
| mRbCover = findViewById(R.id.read_setting_rb_cover); | |||||
| mRbSlide = findViewById(R.id.read_setting_rb_slide); | |||||
| mRbScroll = findViewById(R.id.read_setting_rb_scroll); | |||||
| mRbNone = findViewById(R.id.read_setting_rb_none); | |||||
| mRvBg = findViewById(R.id.read_setting_rv_bg); | |||||
| mTvMore = findViewById(R.id.read_setting_tv_more); | |||||
| } | |||||
| //设置Dialog显示的位置 | |||||
| private void setUpWindow() { | |||||
| Window window = getWindow(); | |||||
| WindowManager.LayoutParams lp = window.getAttributes(); | |||||
| lp.width = WindowManager.LayoutParams.MATCH_PARENT; | |||||
| lp.height = WindowManager.LayoutParams.WRAP_CONTENT; | |||||
| lp.gravity = Gravity.BOTTOM; | |||||
| window.setAttributes(lp); | |||||
| } | |||||
| private void initData() { | |||||
| mSettingManager = ReadSettingManager.getInstance(); | |||||
| isBrightnessAuto = mSettingManager.isBrightnessAuto(); | |||||
| mBrightness = mSettingManager.getBrightness(); | |||||
| mTextSize = mSettingManager.getTextSize(); | |||||
| isTextDefault = mSettingManager.isDefaultTextSize(); | |||||
| mPageMode = mSettingManager.getPageMode(); | |||||
| mPageStyle = mSettingManager.getPageStyle(); | |||||
| } | |||||
| private void initWidget() { | |||||
| mSbBrightness.setProgress(mBrightness); | |||||
| mTvFont.setText(mTextSize + ""); | |||||
| mCbBrightnessAuto.setChecked(isBrightnessAuto); | |||||
| mCbFontDefault.setChecked(isTextDefault); | |||||
| initPageMode(); | |||||
| //RecyclerView | |||||
| setUpAdapter(); | |||||
| } | |||||
| private void setUpAdapter() { | |||||
| Drawable[] drawables = { | |||||
| getDrawable(R.color.nb_read_bg_1) | |||||
| , getDrawable(R.color.nb_read_bg_2) | |||||
| , getDrawable(R.color.nb_read_bg_3) | |||||
| , getDrawable(R.color.nb_read_bg_4) | |||||
| , getDrawable(R.color.nb_read_bg_5)}; | |||||
| mPageStyleAdapter = new PageStyleAdapter(); | |||||
| mRvBg.setLayoutManager(new GridLayoutManager(getContext(), 5)); | |||||
| mRvBg.setAdapter(mPageStyleAdapter); | |||||
| mPageStyleAdapter.refreshItems(Arrays.asList(drawables)); | |||||
| mPageStyleAdapter.setPageStyleChecked(mPageStyle); | |||||
| } | |||||
| private void initPageMode() { | |||||
| switch (mPageMode) { | |||||
| case SIMULATION: | |||||
| mRbSimulation.setChecked(true); | |||||
| break; | |||||
| case COVER: | |||||
| mRbCover.setChecked(true); | |||||
| break; | |||||
| case SLIDE: | |||||
| mRbSlide.setChecked(true); | |||||
| break; | |||||
| case NONE: | |||||
| mRbNone.setChecked(true); | |||||
| break; | |||||
| case SCROLL: | |||||
| mRbScroll.setChecked(true); | |||||
| break; | |||||
| } | |||||
| } | |||||
| private Drawable getDrawable(int drawRes) { | |||||
| return ContextCompat.getDrawable(getContext(), drawRes); | |||||
| } | |||||
| private void initClick() { | |||||
| //亮度调节 | |||||
| mIvBrightnessMinus.setOnClickListener( | |||||
| (v) -> { | |||||
| if (mCbBrightnessAuto.isChecked()) { | |||||
| mCbBrightnessAuto.setChecked(false); | |||||
| } | |||||
| int progress = mSbBrightness.getProgress() - 1; | |||||
| if (progress < 0) return; | |||||
| mSbBrightness.setProgress(progress); | |||||
| BrightnessUtils.setBrightness(mActivity, progress); | |||||
| } | |||||
| ); | |||||
| mIvBrightnessPlus.setOnClickListener( | |||||
| (v) -> { | |||||
| if (mCbBrightnessAuto.isChecked()) { | |||||
| mCbBrightnessAuto.setChecked(false); | |||||
| } | |||||
| int progress = mSbBrightness.getProgress() + 1; | |||||
| if (progress > mSbBrightness.getMax()) return; | |||||
| mSbBrightness.setProgress(progress); | |||||
| BrightnessUtils.setBrightness(mActivity, progress); | |||||
| //设置进度 | |||||
| ReadSettingManager.getInstance().setBrightness(progress); | |||||
| } | |||||
| ); | |||||
| mSbBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { | |||||
| @Override | |||||
| public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { | |||||
| } | |||||
| @Override | |||||
| public void onStartTrackingTouch(SeekBar seekBar) { | |||||
| } | |||||
| @Override | |||||
| public void onStopTrackingTouch(SeekBar seekBar) { | |||||
| int progress = seekBar.getProgress(); | |||||
| if (mCbBrightnessAuto.isChecked()) { | |||||
| mCbBrightnessAuto.setChecked(false); | |||||
| } | |||||
| //设置当前 Activity 的亮度 | |||||
| BrightnessUtils.setBrightness(mActivity, progress); | |||||
| //存储亮度的进度条 | |||||
| ReadSettingManager.getInstance().setBrightness(progress); | |||||
| } | |||||
| }); | |||||
| mCbBrightnessAuto.setOnCheckedChangeListener( | |||||
| (buttonView, isChecked) -> { | |||||
| if (isChecked) { | |||||
| //获取屏幕的亮度 | |||||
| BrightnessUtils.setBrightness(mActivity, BrightnessUtils.getScreenBrightness(mActivity)); | |||||
| } else { | |||||
| //获取进度条的亮度 | |||||
| BrightnessUtils.setBrightness(mActivity, mSbBrightness.getProgress()); | |||||
| } | |||||
| ReadSettingManager.getInstance().setAutoBrightness(isChecked); | |||||
| } | |||||
| ); | |||||
| //字体大小调节 | |||||
| mTvFontMinus.setOnClickListener( | |||||
| (v) -> { | |||||
| if (mCbFontDefault.isChecked()) { | |||||
| mCbFontDefault.setChecked(false); | |||||
| } | |||||
| int fontSize = Integer.valueOf(mTvFont.getText().toString()) - 1; | |||||
| if (fontSize < 0) return; | |||||
| mTvFont.setText(fontSize + ""); | |||||
| mPageLoader.setTextSize(fontSize); | |||||
| } | |||||
| ); | |||||
| mTvFontPlus.setOnClickListener( | |||||
| (v) -> { | |||||
| if (mCbFontDefault.isChecked()) { | |||||
| mCbFontDefault.setChecked(false); | |||||
| } | |||||
| int fontSize = Integer.valueOf(mTvFont.getText().toString()) + 1; | |||||
| mTvFont.setText(fontSize + ""); | |||||
| mPageLoader.setTextSize(fontSize); | |||||
| } | |||||
| ); | |||||
| mCbFontDefault.setOnCheckedChangeListener( | |||||
| (buttonView, isChecked) -> { | |||||
| if (isChecked) { | |||||
| int fontSize = ScreenUtils.dpToPx(DEFAULT_TEXT_SIZE); | |||||
| mTvFont.setText(fontSize + ""); | |||||
| mPageLoader.setTextSize(fontSize); | |||||
| } | |||||
| } | |||||
| ); | |||||
| //Page Mode 切换 | |||||
| mRgPageMode.setOnCheckedChangeListener( | |||||
| (group, checkedId) -> { | |||||
| PageMode pageMode; | |||||
| switch (checkedId) { | |||||
| case R.id.read_setting_rb_simulation: | |||||
| pageMode = PageMode.SIMULATION; | |||||
| break; | |||||
| case R.id.read_setting_rb_cover: | |||||
| pageMode = PageMode.COVER; | |||||
| break; | |||||
| case R.id.read_setting_rb_slide: | |||||
| pageMode = PageMode.SLIDE; | |||||
| break; | |||||
| case R.id.read_setting_rb_scroll: | |||||
| pageMode = PageMode.SCROLL; | |||||
| break; | |||||
| case R.id.read_setting_rb_none: | |||||
| pageMode = PageMode.NONE; | |||||
| break; | |||||
| default: | |||||
| pageMode = PageMode.SIMULATION; | |||||
| break; | |||||
| } | |||||
| mPageLoader.setPageMode(pageMode); | |||||
| } | |||||
| ); | |||||
| //背景的点击事件 | |||||
| mPageStyleAdapter.setOnItemClickListener( | |||||
| (view, pos) -> mPageLoader.setPageStyle(PageStyle.values()[pos]) | |||||
| ); | |||||
| //更多设置 | |||||
| mTvMore.setOnClickListener( | |||||
| (v) -> { | |||||
| // Intent intent = new Intent(getContext(), MoreSettingActivity.class); | |||||
| // mActivity.startActivityForResult(intent, ReadActivity.REQUEST_MORE_SETTING); | |||||
| //关闭当前设置 | |||||
| dismiss(); | |||||
| } | |||||
| ); | |||||
| } | |||||
| public boolean isBrightFollowSystem() { | |||||
| if (mCbBrightnessAuto == null) { | |||||
| return false; | |||||
| } | |||||
| return mCbBrightnessAuto.isChecked(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,98 @@ | |||||
| package com.yzx.webebook.widget.animation; | |||||
| import android.graphics.Bitmap; | |||||
| import android.graphics.Canvas; | |||||
| import android.graphics.PointF; | |||||
| import android.widget.Scroller; | |||||
| /** | |||||
| * Created by Administrator on 2016/8/1 0001. | |||||
| */ | |||||
| public abstract class AnimationProvider { | |||||
| public enum Direction { | |||||
| NONE(true),NEXT(true), PRE(true), UP(false), DOWN(false); | |||||
| public final boolean isHorizontal; | |||||
| Direction(boolean isHorizontal) { | |||||
| this.isHorizontal = isHorizontal; | |||||
| } | |||||
| } | |||||
| protected Bitmap mCurPageBitmap,mNextPageBitmap; | |||||
| protected float myStartX; | |||||
| protected float myStartY; | |||||
| protected int myEndX; | |||||
| protected int myEndY; | |||||
| protected Direction myDirection; | |||||
| protected int mScreenWidth; | |||||
| protected int mScreenHeight; | |||||
| protected PointF mTouch = new PointF(); // 拖拽点 | |||||
| private Direction direction = Direction.NONE; | |||||
| private boolean isCancel = false; | |||||
| public AnimationProvider(int width,int height) { | |||||
| mCurPageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); | |||||
| mNextPageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); | |||||
| this.mScreenWidth = width; | |||||
| this.mScreenHeight = height; | |||||
| } | |||||
| //绘制滑动页面 | |||||
| public abstract void drawMove(Canvas canvas); | |||||
| //绘制不滑动页面 | |||||
| public abstract void drawStatic(Canvas canvas); | |||||
| //设置开始拖拽点 | |||||
| public void setStartPoint(float x,float y){ | |||||
| myStartX = x; | |||||
| myStartY = y; | |||||
| } | |||||
| //设置拖拽点 | |||||
| public void setTouchPoint(float x,float y){ | |||||
| mTouch.x = x; | |||||
| mTouch.y = y; | |||||
| } | |||||
| //设置方向 | |||||
| public void setDirection(Direction direction){ | |||||
| this.direction = direction; | |||||
| } | |||||
| public Direction getDirection(){ | |||||
| return direction; | |||||
| } | |||||
| public void setCancel(boolean isCancel){ | |||||
| this.isCancel = isCancel; | |||||
| } | |||||
| public abstract void startAnimation(Scroller scroller); | |||||
| /** | |||||
| * 转换页面,在显示下一章的时候,必须首先调用此方法 | |||||
| */ | |||||
| public void changePage(){ | |||||
| Bitmap bitmap = mCurPageBitmap; | |||||
| mCurPageBitmap = mNextPageBitmap; | |||||
| mNextPageBitmap = bitmap; | |||||
| } | |||||
| public Bitmap getNextBitmap(){ | |||||
| return mNextPageBitmap; | |||||
| } | |||||
| public Bitmap getBgBitmap(){ | |||||
| return mNextPageBitmap; | |||||
| } | |||||
| public boolean getCancel(){ | |||||
| return isCancel; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,100 @@ | |||||
| package com.yzx.webebook.widget.animation; | |||||
| import android.graphics.Bitmap; | |||||
| import android.graphics.Canvas; | |||||
| import android.graphics.Rect; | |||||
| import android.graphics.drawable.GradientDrawable; | |||||
| import android.view.View; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-24. | |||||
| */ | |||||
| 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 drawStatic(Canvas canvas) { | |||||
| if (isCancel){ | |||||
| mNextBitmap = mCurBitmap.copy(Bitmap.Config.RGB_565, true); | |||||
| canvas.drawBitmap(mCurBitmap, 0, 0, null); | |||||
| }else { | |||||
| canvas.drawBitmap(mNextBitmap, 0, 0, null); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void drawMove(Canvas canvas) { | |||||
| switch (mDirection){ | |||||
| case NEXT: | |||||
| int dis = (int) (mViewWidth - mStartX + mTouchX); | |||||
| if (dis > mViewWidth){ | |||||
| dis = mViewWidth; | |||||
| } | |||||
| //计算bitmap截取的区域 | |||||
| mSrcRect.left = mViewWidth - dis; | |||||
| //计算bitmap在canvas显示的区域 | |||||
| mDestRect.right = dis; | |||||
| canvas.drawBitmap(mNextBitmap,0,0,null); | |||||
| canvas.drawBitmap(mCurBitmap,mSrcRect,mDestRect,null); | |||||
| addShadow(dis,canvas); | |||||
| break; | |||||
| default: | |||||
| mSrcRect.left = (int) (mViewWidth - mTouchX); | |||||
| mDestRect.right = (int) mTouchX; | |||||
| canvas.drawBitmap(mCurBitmap,0,0,null); | |||||
| canvas.drawBitmap(mNextBitmap,mSrcRect,mDestRect,null); | |||||
| addShadow((int) mTouchX,canvas); | |||||
| break; | |||||
| } | |||||
| } | |||||
| //添加阴影 | |||||
| public void addShadow(int left,Canvas canvas) { | |||||
| mBackShadowDrawableLR.setBounds(left, 0, left + 30 , mScreenHeight); | |||||
| mBackShadowDrawableLR.draw(canvas); | |||||
| } | |||||
| @Override | |||||
| public void startAnim() { | |||||
| super.startAnim(); | |||||
| int dx = 0; | |||||
| switch (mDirection){ | |||||
| case NEXT: | |||||
| if (isCancel){ | |||||
| int dis = (int) ((mViewWidth - mStartX) + mTouchX); | |||||
| if (dis > mViewWidth){ | |||||
| dis = mViewWidth; | |||||
| } | |||||
| dx = mViewWidth - dis; | |||||
| }else{ | |||||
| dx = (int) -(mTouchX + (mViewWidth - mStartX)); | |||||
| } | |||||
| break; | |||||
| default: | |||||
| if (isCancel){ | |||||
| dx = (int) -mTouchX; | |||||
| }else{ | |||||
| dx = (int) (mViewWidth - mTouchX); | |||||
| } | |||||
| break; | |||||
| } | |||||
| //滑动速度保持一致 | |||||
| int duration = (400 * Math.abs(dx)) / mViewWidth; | |||||
| mScroller.startScroll((int) mTouchX, 0, dx, 0, duration); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,230 @@ | |||||
| package com.yzx.webebook.widget.animation; | |||||
| import android.graphics.Bitmap; | |||||
| import android.graphics.Canvas; | |||||
| import android.view.MotionEvent; | |||||
| import android.view.View; | |||||
| import android.view.ViewConfiguration; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-24. | |||||
| * 横向动画的模板 | |||||
| */ | |||||
| public abstract class HorizonPageAnim 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 HorizonPageAnim(int w, int h, View view, OnPageChangeListener listener) { | |||||
| this(w, h, 0, 0, view, listener); | |||||
| } | |||||
| public HorizonPageAnim(int w, int h, int marginWidth, int marginHeight, | |||||
| View view, OnPageChangeListener listener) { | |||||
| super(w, h, marginWidth, marginHeight, 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 (x - mStartX > 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 (x - mMoveX > 0){ | |||||
| isCancel = true; | |||||
| }else { | |||||
| isCancel = false; | |||||
| } | |||||
| }else{ | |||||
| if (x - mMoveX < 0){ | |||||
| isCancel = true; | |||||
| }else { | |||||
| isCancel = false; | |||||
| } | |||||
| } | |||||
| } | |||||
| mMoveX = x; | |||||
| mMoveY = y; | |||||
| isRunning = true; | |||||
| mView.invalidate(); | |||||
| } | |||||
| break; | |||||
| case MotionEvent.ACTION_UP: | |||||
| if (!isMove){ | |||||
| if (x < mScreenWidth / 2){ | |||||
| isNext = false; | |||||
| }else{ | |||||
| isNext = true; | |||||
| } | |||||
| 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; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| package com.yzx.webebook.widget.animation; | |||||
| import android.graphics.Canvas; | |||||
| import android.view.View; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-24. | |||||
| */ | |||||
| public class NonePageAnim extends HorizonPageAnim{ | |||||
| public NonePageAnim(int w, int h, View view, OnPageChangeListener listener) { | |||||
| super(w, h, view, listener); | |||||
| } | |||||
| @Override | |||||
| public void drawStatic(Canvas canvas) { | |||||
| if (isCancel){ | |||||
| canvas.drawBitmap(mCurBitmap, 0, 0, null); | |||||
| }else { | |||||
| canvas.drawBitmap(mNextBitmap, 0, 0, null); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void drawMove(Canvas canvas) { | |||||
| if (isCancel){ | |||||
| canvas.drawBitmap(mCurBitmap, 0, 0, null); | |||||
| }else { | |||||
| canvas.drawBitmap(mNextBitmap, 0, 0, null); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void startAnim() { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,157 @@ | |||||
| package com.yzx.webebook.widget.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; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-24. | |||||
| * 翻页动画抽象类 | |||||
| */ | |||||
| public abstract class PageAnimation { | |||||
| //正在使用的View | |||||
| protected View mView; | |||||
| //滑动装置 | |||||
| protected Scroller mScroller; | |||||
| //监听器 | |||||
| protected OnPageChangeListener mListener; | |||||
| //移动方向 | |||||
| protected Direction mDirection = Direction.NONE; | |||||
| protected boolean isRunning = false; | |||||
| //屏幕的尺寸 | |||||
| protected int mScreenWidth; | |||||
| protected int mScreenHeight; | |||||
| //屏幕的间距 | |||||
| protected int mMarginWidth; | |||||
| protected int mMarginHeight; | |||||
| //视图的尺寸 | |||||
| protected int mViewWidth; | |||||
| protected int mViewHeight; | |||||
| //起始点 | |||||
| protected float mStartX; | |||||
| protected float mStartY; | |||||
| //触碰点 | |||||
| protected float mTouchX; | |||||
| protected float mTouchY; | |||||
| //上一个触碰点 | |||||
| protected float mLastX; | |||||
| protected float mLastY; | |||||
| public PageAnimation(int w, int h,View view,OnPageChangeListener listener){ | |||||
| this(w, h, 0, 0, view,listener); | |||||
| } | |||||
| public PageAnimation(int w, int h, int marginWidth, int marginHeight, View view,OnPageChangeListener listener){ | |||||
| mScreenWidth = w; | |||||
| mScreenHeight = h; | |||||
| mMarginWidth = marginWidth; | |||||
| mMarginHeight = marginHeight; | |||||
| mViewWidth = mScreenWidth - mMarginWidth * 2; | |||||
| mViewHeight = mScreenHeight - mMarginHeight * 2; | |||||
| mView = view; | |||||
| mListener = listener; | |||||
| mScroller = new Scroller(mView.getContext(), new LinearInterpolator()); | |||||
| } | |||||
| 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 boolean isRunning(){ | |||||
| return isRunning; | |||||
| } | |||||
| /** | |||||
| * 开启翻页动画 | |||||
| */ | |||||
| public void startAnim(){ | |||||
| if (isRunning){ | |||||
| return; | |||||
| } | |||||
| isRunning = true; | |||||
| } | |||||
| public void setDirection(Direction direction){ | |||||
| mDirection = direction; | |||||
| } | |||||
| public Direction getDirection(){ | |||||
| return mDirection; | |||||
| } | |||||
| public void clear(){ | |||||
| mView = null; | |||||
| } | |||||
| /** | |||||
| * 点击事件的处理 | |||||
| * @param event | |||||
| */ | |||||
| public abstract boolean onTouchEvent(MotionEvent event); | |||||
| /** | |||||
| * 绘制图形 | |||||
| * @param canvas | |||||
| */ | |||||
| public abstract void draw(Canvas canvas); | |||||
| /** | |||||
| * 滚动动画 | |||||
| * 必须放在computeScroll()方法中执行 | |||||
| */ | |||||
| public abstract void scrollAnim(); | |||||
| /** | |||||
| * 取消动画 | |||||
| */ | |||||
| public abstract void abortAnim(); | |||||
| /** | |||||
| * 获取背景板 | |||||
| * @return | |||||
| */ | |||||
| public abstract Bitmap getBgBitmap(); | |||||
| /** | |||||
| * 获取内容显示版面 | |||||
| */ | |||||
| public abstract Bitmap getNextBitmap(); | |||||
| public enum Direction { | |||||
| NONE(true),NEXT(true), PRE(true), UP(false), DOWN(false); | |||||
| public final boolean isHorizontal; | |||||
| Direction(boolean isHorizontal) { | |||||
| this.isHorizontal = isHorizontal; | |||||
| } | |||||
| } | |||||
| public interface OnPageChangeListener { | |||||
| boolean hasPrev(); | |||||
| boolean hasNext(); | |||||
| void pageCancel(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,408 @@ | |||||
| package com.yzx.webebook.widget.animation; | |||||
| import android.graphics.Bitmap; | |||||
| import android.graphics.Canvas; | |||||
| import android.graphics.Rect; | |||||
| import android.view.MotionEvent; | |||||
| import android.view.VelocityTracker; | |||||
| import android.view.View; | |||||
| import java.util.ArrayDeque; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Iterator; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-23. | |||||
| * 原理:仿照ListView源码实现的上下滑动效果 | |||||
| * Alter by: zeroAngus | |||||
| * <p> | |||||
| * 问题: | |||||
| * 1. 向上翻页,重复的问题 (完成) | |||||
| * 2. 滑动卡顿的问题。原因:由于绘制的数据过多造成的卡顿问题。 (主要是文字绘制需要的时长比较多) 解决办法:做文字缓冲 | |||||
| * 3. 弱网环境下,显示的问题 | |||||
| */ | |||||
| 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; | |||||
| // 下一个展示的图片 | |||||
| private Bitmap mNextBitmap; | |||||
| // 被废弃的图片列表 | |||||
| private ArrayDeque<BitmapView> mScrapViews; | |||||
| // 正在被利用的图片列表 | |||||
| private ArrayList<BitmapView> mActiveViews = new ArrayList<>(2); | |||||
| // 是否处于刷新阶段 | |||||
| private boolean isRefresh = true; | |||||
| public ScrollPageAnim(int w, int h, int marginWidth, int marginHeight, | |||||
| View view, OnPageChangeListener listener) { | |||||
| super(w, h, marginWidth, marginHeight, view, listener); | |||||
| // 创建两个BitmapView | |||||
| initWidget(); | |||||
| } | |||||
| private void initWidget() { | |||||
| mBgBitmap = Bitmap.createBitmap(mScreenWidth, mScreenHeight, Bitmap.Config.RGB_565); | |||||
| mScrapViews = new ArrayDeque<>(2); | |||||
| for (int i = 0; i < 2; ++i) { | |||||
| BitmapView view = new BitmapView(); | |||||
| view.bitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565); | |||||
| view.srcRect = new Rect(0, 0, mViewWidth, mViewHeight); | |||||
| view.destRect = new Rect(0, 0, mViewWidth, mViewHeight); | |||||
| view.top = 0; | |||||
| view.bottom = view.bitmap.getHeight(); | |||||
| mScrapViews.push(view); | |||||
| } | |||||
| onLayout(); | |||||
| isRefresh = false; | |||||
| } | |||||
| // 修改布局,填充内容 | |||||
| private void onLayout() { | |||||
| // 如果还没有开始加载,则从上到下进行绘制 | |||||
| if (mActiveViews.size() == 0) { | |||||
| fillDown(0, 0); | |||||
| mDirection = Direction.NONE; | |||||
| } else { | |||||
| int offset = (int) (mTouchY - mLastY); | |||||
| // 判断是下滑还是上拉 (下滑) | |||||
| if (offset > 0) { | |||||
| int topEdge = mActiveViews.get(0).top; | |||||
| fillUp(topEdge, offset); | |||||
| } | |||||
| // 上拉 | |||||
| else { | |||||
| // 底部的距离 = 当前底部的距离 + 滑动的距离 (因为上滑,得到的值肯定是负的) | |||||
| int bottomEdge = mActiveViews.get(mActiveViews.size() - 1).bottom; | |||||
| fillDown(bottomEdge, offset); | |||||
| } | |||||
| } | |||||
| } | |||||
| // 底部填充 | |||||
| private Iterator<BitmapView> downIt; | |||||
| /** | |||||
| * 创建View填充底部空白部分 | |||||
| * | |||||
| * @param bottomEdge :当前最后一个View的底部,在整个屏幕上的位置,即相对于屏幕顶部的距离 | |||||
| * @param offset :滑动的偏移量 | |||||
| */ | |||||
| private void fillDown(int bottomEdge, int offset) { | |||||
| downIt = mActiveViews.iterator(); | |||||
| BitmapView view; | |||||
| // 进行删除 | |||||
| while (downIt.hasNext()) { | |||||
| view = downIt.next(); | |||||
| view.top = view.top + offset; | |||||
| view.bottom = view.bottom + offset; | |||||
| // 设置允许显示的范围 | |||||
| view.destRect.top = view.top; | |||||
| view.destRect.bottom = view.bottom; | |||||
| // 判断是否越界了 | |||||
| if (view.bottom <= 0) { | |||||
| // 添加到废弃的View中 | |||||
| mScrapViews.add(view); | |||||
| // 从Active中移除 | |||||
| downIt.remove(); | |||||
| // 如果原先是从上加载,现在变成从下加载,则表示取消 | |||||
| if (mDirection == Direction.UP) { | |||||
| mListener.pageCancel(); | |||||
| mDirection = Direction.NONE; | |||||
| } | |||||
| } | |||||
| } | |||||
| // 滑动之后的最后一个 View 的距离屏幕顶部上的实际位置 | |||||
| int realEdge = bottomEdge + offset; | |||||
| // 进行填充 | |||||
| while (realEdge < mViewHeight && mActiveViews.size() < 2) { | |||||
| // 从废弃的Views中获取一个 | |||||
| view = mScrapViews.getFirst(); | |||||
| /* //擦除其Bitmap(重新创建会不会更好一点) | |||||
| eraseBitmap(view.bitmap,view.bitmap.getWidth(),view.bitmap.getHeight(),0,0);*/ | |||||
| if (view == null) return; | |||||
| Bitmap cancelBitmap = mNextBitmap; | |||||
| mNextBitmap = view.bitmap; | |||||
| if (!isRefresh) { | |||||
| boolean hasNext = mListener.hasNext(); //如果不成功则无法滑动 | |||||
| // 如果不存在next,则进行还原 | |||||
| if (!hasNext) { | |||||
| mNextBitmap = cancelBitmap; | |||||
| for (BitmapView activeView : mActiveViews) { | |||||
| activeView.top = 0; | |||||
| activeView.bottom = mViewHeight; | |||||
| // 设置允许显示的范围 | |||||
| activeView.destRect.top = activeView.top; | |||||
| activeView.destRect.bottom = activeView.bottom; | |||||
| } | |||||
| abortAnim(); | |||||
| return; | |||||
| } | |||||
| } | |||||
| // 如果加载成功,那么就将View从ScrapViews中移除 | |||||
| mScrapViews.removeFirst(); | |||||
| // 添加到存活的Bitmap中 | |||||
| mActiveViews.add(view); | |||||
| mDirection = Direction.DOWN; | |||||
| // 设置Bitmap的范围 | |||||
| view.top = realEdge; | |||||
| view.bottom = realEdge + view.bitmap.getHeight(); | |||||
| // 设置允许显示的范围 | |||||
| view.destRect.top = view.top; | |||||
| view.destRect.bottom = view.bottom; | |||||
| realEdge += view.bitmap.getHeight(); | |||||
| } | |||||
| } | |||||
| private Iterator<BitmapView> upIt; | |||||
| /** | |||||
| * 创建View填充顶部空白部分 | |||||
| * | |||||
| * @param topEdge : 当前第一个View的顶部,到屏幕顶部的距离 | |||||
| * @param offset : 滑动的偏移量 | |||||
| */ | |||||
| private void fillUp(int topEdge, int offset) { | |||||
| // 首先进行布局的调整 | |||||
| upIt = mActiveViews.iterator(); | |||||
| BitmapView view; | |||||
| while (upIt.hasNext()) { | |||||
| view = upIt.next(); | |||||
| view.top = view.top + offset; | |||||
| view.bottom = view.bottom + offset; | |||||
| //设置允许显示的范围 | |||||
| view.destRect.top = view.top; | |||||
| view.destRect.bottom = view.bottom; | |||||
| // 判断是否越界了 | |||||
| if (view.top >= mViewHeight) { | |||||
| // 添加到废弃的View中 | |||||
| mScrapViews.add(view); | |||||
| // 从Active中移除 | |||||
| upIt.remove(); | |||||
| // 如果原先是下,现在变成从上加载了,则表示取消加载 | |||||
| if (mDirection == Direction.DOWN) { | |||||
| mListener.pageCancel(); | |||||
| mDirection = Direction.NONE; | |||||
| } | |||||
| } | |||||
| } | |||||
| // 滑动之后,第一个 View 的顶部距离屏幕顶部的实际位置。 | |||||
| int realEdge = topEdge + offset; | |||||
| // 对布局进行View填充 | |||||
| while (realEdge > 0 && mActiveViews.size() < 2) { | |||||
| // 从废弃的Views中获取一个 | |||||
| view = mScrapViews.getFirst(); | |||||
| if (view == null) return; | |||||
| // 判断是否存在上一章节 | |||||
| Bitmap cancelBitmap = mNextBitmap; | |||||
| mNextBitmap = view.bitmap; | |||||
| if (!isRefresh) { | |||||
| boolean hasPrev = mListener.hasPrev(); // 如果不成功则无法滑动 | |||||
| // 如果不存在next,则进行还原 | |||||
| if (!hasPrev) { | |||||
| mNextBitmap = cancelBitmap; | |||||
| for (BitmapView activeView : mActiveViews) { | |||||
| activeView.top = 0; | |||||
| activeView.bottom = mViewHeight; | |||||
| // 设置允许显示的范围 | |||||
| activeView.destRect.top = activeView.top; | |||||
| activeView.destRect.bottom = activeView.bottom; | |||||
| } | |||||
| abortAnim(); | |||||
| return; | |||||
| } | |||||
| } | |||||
| // 如果加载成功,那么就将View从ScrapViews中移除 | |||||
| mScrapViews.removeFirst(); | |||||
| // 加入到存活的对象中 | |||||
| mActiveViews.add(0, view); | |||||
| mDirection = Direction.UP; | |||||
| // 设置Bitmap的范围 | |||||
| view.top = realEdge - view.bitmap.getHeight(); | |||||
| view.bottom = realEdge; | |||||
| // 设置允许显示的范围 | |||||
| view.destRect.top = view.top; | |||||
| view.destRect.bottom = view.bottom; | |||||
| realEdge -= view.bitmap.getHeight(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 对Bitmap进行擦除 | |||||
| * | |||||
| * @param b | |||||
| * @param width | |||||
| * @param height | |||||
| * @param paddingLeft | |||||
| * @param paddingTop | |||||
| */ | |||||
| private void eraseBitmap(Bitmap b, int width, int height, | |||||
| int paddingLeft, int paddingTop) { | |||||
| /* if (mInitBitmapPix == null) return; | |||||
| b.setPixels(mInitBitmapPix, 0, width, paddingLeft, paddingTop, width, height);*/ | |||||
| } | |||||
| /** | |||||
| * 重置位移 | |||||
| */ | |||||
| public void resetBitmap() { | |||||
| isRefresh = true; | |||||
| // 将所有的Active加入到Scrap中 | |||||
| for (BitmapView view : mActiveViews) { | |||||
| mScrapViews.add(view); | |||||
| } | |||||
| // 清除所有的Active | |||||
| mActiveViews.clear(); | |||||
| // 重新进行布局 | |||||
| onLayout(); | |||||
| isRefresh = false; | |||||
| } | |||||
| @Override | |||||
| public boolean onTouchEvent(MotionEvent event) { | |||||
| 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: | |||||
| isRunning = false; | |||||
| // 设置起始点 | |||||
| setStartPoint(x, y); | |||||
| // 停止动画 | |||||
| abortAnim(); | |||||
| break; | |||||
| case MotionEvent.ACTION_MOVE: | |||||
| mVelocity.computeCurrentVelocity(VELOCITY_DURATION); | |||||
| isRunning = true; | |||||
| // 进行刷新 | |||||
| mView.postInvalidate(); | |||||
| break; | |||||
| case MotionEvent.ACTION_UP: | |||||
| isRunning = false; | |||||
| // 开启动画 | |||||
| startAnim(); | |||||
| // 删除检测器 | |||||
| mVelocity.recycle(); | |||||
| mVelocity = null; | |||||
| break; | |||||
| case MotionEvent.ACTION_CANCEL: | |||||
| try { | |||||
| mVelocity.recycle(); // if velocityTracker won't be used should be recycled | |||||
| mVelocity = null; | |||||
| } catch (Exception e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| break; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| BitmapView tmpView; | |||||
| @Override | |||||
| public void draw(Canvas canvas) { | |||||
| //进行布局 | |||||
| onLayout(); | |||||
| //绘制背景 | |||||
| canvas.drawBitmap(mBgBitmap, 0, 0, null); | |||||
| //绘制内容 | |||||
| canvas.save(); | |||||
| //移动位置 | |||||
| canvas.translate(0, mMarginHeight); | |||||
| //裁剪显示区域 | |||||
| canvas.clipRect(0, 0, mViewWidth, mViewHeight); | |||||
| /* //设置背景透明 | |||||
| canvas.drawColor(0x40);*/ | |||||
| //绘制Bitmap | |||||
| for (int i = 0; i < mActiveViews.size(); ++i) { | |||||
| tmpView = mActiveViews.get(i); | |||||
| canvas.drawBitmap(tmpView.bitmap, tmpView.srcRect, tmpView.destRect, null); | |||||
| } | |||||
| canvas.restore(); | |||||
| } | |||||
| @Override | |||||
| public synchronized void startAnim() { | |||||
| isRunning = true; | |||||
| mScroller.fling(0, (int) mTouchY, 0, (int) mVelocity.getYVelocity() | |||||
| , 0, 0, Integer.MAX_VALUE * -1, Integer.MAX_VALUE); | |||||
| } | |||||
| @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; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public Bitmap getBgBitmap() { | |||||
| return mBgBitmap; | |||||
| } | |||||
| @Override | |||||
| public Bitmap getNextBitmap() { | |||||
| return mNextBitmap; | |||||
| } | |||||
| private static class BitmapView { | |||||
| Bitmap bitmap; | |||||
| Rect srcRect; | |||||
| Rect destRect; | |||||
| int top; | |||||
| int bottom; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,668 @@ | |||||
| package com.yzx.webebook.widget.animation; | |||||
| import android.graphics.Bitmap; | |||||
| import android.graphics.Canvas; | |||||
| import android.graphics.Color; | |||||
| 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; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-24. | |||||
| */ | |||||
| 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 mIsRTandLB; // 是否属于右上左下 | |||||
| private float mMaxLength; | |||||
| 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; | |||||
| // 适配 android 高版本无法使用 XOR 的问题 | |||||
| private Path mXORPath; | |||||
| public SimulationPageAnim(int w, int h, View view, OnPageChangeListener listener) { | |||||
| super(w, h, view, listener); | |||||
| mPath0 = new Path(); | |||||
| mPath1 = new Path(); | |||||
| mXORPath = 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 drawMove(Canvas canvas) { | |||||
| switch (mDirection) { | |||||
| case NEXT: | |||||
| calcPoints(); | |||||
| drawCurrentPageArea(canvas, mCurBitmap, mPath0); | |||||
| drawNextPageAreaAndShadow(canvas, mNextBitmap); | |||||
| drawCurrentPageShadow(canvas); | |||||
| drawCurrentBackArea(canvas, mCurBitmap); | |||||
| break; | |||||
| default: | |||||
| calcPoints(); | |||||
| drawCurrentPageArea(canvas, mNextBitmap, mPath0); | |||||
| drawNextPageAreaAndShadow(canvas, mCurBitmap); | |||||
| drawCurrentPageShadow(canvas); | |||||
| drawCurrentBackArea(canvas, mNextBitmap); | |||||
| break; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void drawStatic(Canvas canvas) { | |||||
| if (isCancel) { | |||||
| mNextBitmap = mCurBitmap.copy(Bitmap.Config.RGB_565, true); | |||||
| canvas.drawBitmap(mCurBitmap, 0, 0, null); | |||||
| } else { | |||||
| canvas.drawBitmap(mNextBitmap, 0, 0, null); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void startAnim() { | |||||
| super.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, 400); | |||||
| } | |||||
| @Override | |||||
| public void setDirection(Direction direction) { | |||||
| super.setDirection(direction); | |||||
| switch (direction) { | |||||
| case PRE: | |||||
| //上一页滑动不出现对角 | |||||
| if (mStartX > mScreenWidth / 2) { | |||||
| calcCornerXY(mStartX, mScreenHeight); | |||||
| } else { | |||||
| calcCornerXY(mScreenWidth - mStartX, mScreenHeight); | |||||
| } | |||||
| break; | |||||
| case NEXT: | |||||
| if (mScreenWidth / 2 > mStartX) { | |||||
| calcCornerXY(mScreenWidth - mStartX, mStartY); | |||||
| } | |||||
| break; | |||||
| } | |||||
| } | |||||
| @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 && mStartY < mScreenHeight * 2 / 3) || mDirection.equals(Direction.PRE)) { | |||||
| mTouchY = mScreenHeight; | |||||
| } | |||||
| if (mStartY > mScreenHeight / 3 && mStartY < mScreenHeight / 2 && mDirection.equals(Direction.NEXT)) { | |||||
| mTouchY = 1; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 创建阴影的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); | |||||
| // 背面颜色组 | |||||
| int[] 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); | |||||
| // 前面颜色组 | |||||
| int[] 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); | |||||
| } | |||||
| /** | |||||
| * 是否能够拖动过去 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| public boolean canDragOver() { | |||||
| return mTouchToCornerDis > mScreenWidth / 10; | |||||
| } | |||||
| public boolean right() { | |||||
| return mCornerX <= -4; | |||||
| } | |||||
| /** | |||||
| * 绘制翻起页背面 | |||||
| * | |||||
| * @param canvas | |||||
| * @param bitmap | |||||
| */ | |||||
| 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 (mIsRTandLB) { | |||||
| 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); | |||||
| canvas.clipPath(mPath1, Region.Op.INTERSECT); | |||||
| } catch (Exception e) { | |||||
| } | |||||
| mPaint.setColorFilter(mColorMatrixFilter); | |||||
| //对Bitmap进行取色 | |||||
| int color = bitmap.getPixel(1, 1); | |||||
| //获取对应的三色 | |||||
| int red = (color & 0xff0000) >> 16; | |||||
| int green = (color & 0x00ff00) >> 8; | |||||
| int blue = (color & 0x0000ff); | |||||
| //转换成含有透明度的颜色 | |||||
| int tempColor = Color.argb(200, red, green, blue); | |||||
| 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); | |||||
| //背景叠加 | |||||
| canvas.drawColor(tempColor); | |||||
| 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(); | |||||
| } | |||||
| /** | |||||
| * 绘制翻起页的阴影 | |||||
| * | |||||
| * @param canvas | |||||
| */ | |||||
| private void drawCurrentPageShadow(Canvas canvas) { | |||||
| double degree; | |||||
| if (mIsRTandLB) { | |||||
| 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 (mIsRTandLB) { | |||||
| 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(); | |||||
| float rotateDegrees; | |||||
| canvas.save(); | |||||
| try { | |||||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | |||||
| mXORPath.reset(); | |||||
| mXORPath.moveTo(0f, 0f); | |||||
| mXORPath.lineTo(canvas.getWidth(), 0f); | |||||
| mXORPath.lineTo(canvas.getWidth(), canvas.getHeight()); | |||||
| mXORPath.lineTo(0f, canvas.getHeight()); | |||||
| mXORPath.close(); | |||||
| // 取 path 的补集,作为 canvas 的交集 | |||||
| mXORPath.op(mPath0, Path.Op.XOR); | |||||
| canvas.clipPath(mXORPath); | |||||
| } else { | |||||
| canvas.clipPath(mPath0, Region.Op.XOR); | |||||
| } | |||||
| canvas.clipPath(mPath1, Region.Op.INTERSECT); | |||||
| } catch (Exception e) { | |||||
| // TODO: handle exception | |||||
| } | |||||
| int leftx; | |||||
| int rightx; | |||||
| GradientDrawable mCurrentPageShadow; | |||||
| if (mIsRTandLB) { | |||||
| leftx = (int) (mBezierControl1.x); | |||||
| rightx = (int) mBezierControl1.x + 25; | |||||
| mCurrentPageShadow = mFrontShadowDrawableVLR; | |||||
| } else { | |||||
| leftx = (int) (mBezierControl1.x - 25); | |||||
| rightx = (int) mBezierControl1.x + 1; | |||||
| mCurrentPageShadow = mFrontShadowDrawableVRL; | |||||
| } | |||||
| 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.P) { | |||||
| mXORPath.reset(); | |||||
| mXORPath.moveTo(0f, 0f); | |||||
| mXORPath.lineTo(canvas.getWidth(), 0f); | |||||
| mXORPath.lineTo(canvas.getWidth(), canvas.getHeight()); | |||||
| mXORPath.lineTo(0f, canvas.getHeight()); | |||||
| mXORPath.close(); | |||||
| // 取 path 的补给,作为 canvas 的交集 | |||||
| mXORPath.op(mPath0, Path.Op.XOR); | |||||
| canvas.clipPath(mXORPath); | |||||
| } else { | |||||
| canvas.clipPath(mPath0, Region.Op.XOR); | |||||
| } | |||||
| canvas.clipPath(mPath1, Region.Op.INTERSECT); | |||||
| } catch (Exception e) { | |||||
| } | |||||
| if (mIsRTandLB) { | |||||
| 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 (mIsRTandLB) { //左下及右上 | |||||
| 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); | |||||
| canvas.clipPath(mPath1, Region.Op.INTERSECT); | |||||
| } catch (Exception e) { | |||||
| } | |||||
| 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, Path path) { | |||||
| 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.P) { | |||||
| mXORPath.reset(); | |||||
| mXORPath.moveTo(0f, 0f); | |||||
| mXORPath.lineTo(canvas.getWidth(), 0f); | |||||
| mXORPath.lineTo(canvas.getWidth(), canvas.getHeight()); | |||||
| mXORPath.lineTo(0f, canvas.getHeight()); | |||||
| mXORPath.close(); | |||||
| // 取 path 的补给,作为 canvas 的交集 | |||||
| mXORPath.op(path, Path.Op.XOR); | |||||
| canvas.clipPath(mXORPath); | |||||
| } else { | |||||
| canvas.clipPath(path, Region.Op.XOR); | |||||
| } | |||||
| canvas.drawBitmap(bitmap, 0, 0, null); | |||||
| try { | |||||
| canvas.restore(); | |||||
| } catch (Exception e) { | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 计算拖拽点对应的拖拽脚 | |||||
| * | |||||
| * @param x | |||||
| * @param y | |||||
| */ | |||||
| public void calcCornerXY(float x, float y) { | |||||
| if (x <= mScreenWidth / 2) { | |||||
| mCornerX = 0; | |||||
| } else { | |||||
| mCornerX = mScreenWidth; | |||||
| } | |||||
| if (y <= mScreenHeight / 2) { | |||||
| mCornerY = 0; | |||||
| } else { | |||||
| mCornerY = mScreenHeight; | |||||
| } | |||||
| if ((mCornerX == 0 && mCornerY == mScreenHeight) | |||||
| || (mCornerX == mScreenWidth && mCornerY == 0)) { | |||||
| mIsRTandLB = true; | |||||
| } else { | |||||
| mIsRTandLB = false; | |||||
| } | |||||
| } | |||||
| 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; | |||||
| float f4 = mCornerY - mMiddleY; | |||||
| if (f4 == 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; | |||||
| // 当mBezierStart1.x < 0或者mBezierStart1.x > 480时 | |||||
| // 如果继续翻页,会出现BUG故在此限制 | |||||
| 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的交点坐标 | |||||
| * | |||||
| * @param P1 | |||||
| * @param P2 | |||||
| * @param P3 | |||||
| * @param P4 | |||||
| * @return | |||||
| */ | |||||
| 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; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,101 @@ | |||||
| package com.yzx.webebook.widget.animation; | |||||
| import android.graphics.Canvas; | |||||
| import android.graphics.Rect; | |||||
| import android.view.View; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-24. | |||||
| */ | |||||
| 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 drawStatic(Canvas canvas) { | |||||
| if (isCancel){ | |||||
| canvas.drawBitmap(mCurBitmap, 0, 0, null); | |||||
| }else { | |||||
| canvas.drawBitmap(mNextBitmap, 0, 0, null); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void drawMove(Canvas canvas) { | |||||
| int dis = 0; | |||||
| switch (mDirection){ | |||||
| case 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(mNextBitmap,mNextSrcRect,mNextDestRect,null); | |||||
| canvas.drawBitmap(mCurBitmap,mSrcRect,mDestRect,null); | |||||
| break; | |||||
| default: | |||||
| 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(mCurBitmap,mNextSrcRect,mNextDestRect,null); | |||||
| canvas.drawBitmap(mNextBitmap,mSrcRect,mDestRect,null); | |||||
| break; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void startAnim() { | |||||
| super.startAnim(); | |||||
| int dx = 0; | |||||
| switch (mDirection){ | |||||
| case NEXT: | |||||
| if (isCancel){ | |||||
| int dis = (int)((mScreenWidth - mStartX) + mTouchX); | |||||
| if (dis > mScreenWidth){ | |||||
| dis = mScreenWidth; | |||||
| } | |||||
| dx = mScreenWidth - dis; | |||||
| }else{ | |||||
| dx = (int) -(mTouchX + (mScreenWidth - mStartX)); | |||||
| } | |||||
| break; | |||||
| default: | |||||
| if (isCancel){ | |||||
| dx = (int)-Math.abs(mTouchX - mStartX); | |||||
| }else{ | |||||
| dx = (int) (mScreenWidth - (mTouchX - mStartX)); | |||||
| } | |||||
| break; | |||||
| } | |||||
| //滑动速度保持一致 | |||||
| int duration = (400 * Math.abs(dx)) / mScreenWidth; | |||||
| mScroller.startScroll((int) mTouchX, 0, dx, 0, duration); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,440 @@ | |||||
| package com.yzx.webebook.widget.page; | |||||
| import com.yzx.webebook.model.bean.BookChapterBean; | |||||
| import com.yzx.webebook.model.bean.CollBookBean; | |||||
| import com.yzx.webebook.model.local.BookRepository; | |||||
| import com.yzx.webebook.model.local.Void; | |||||
| import com.yzx.webebook.utils.Charset; | |||||
| import com.yzx.webebook.utils.Constant; | |||||
| import com.yzx.webebook.utils.FileUtils; | |||||
| import com.yzx.webebook.utils.IOUtils; | |||||
| import com.yzx.webebook.utils.LogUtils; | |||||
| import com.yzx.webebook.utils.MD5Utils; | |||||
| import com.yzx.webebook.utils.RxUtils; | |||||
| import com.yzx.webebook.utils.StringUtils; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.ByteArrayInputStream; | |||||
| import java.io.File; | |||||
| import java.io.FileNotFoundException; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStreamReader; | |||||
| import java.io.RandomAccessFile; | |||||
| 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.SingleEmitter; | |||||
| import io.reactivex.SingleObserver; | |||||
| import io.reactivex.SingleOnSubscribe; | |||||
| import io.reactivex.disposables.Disposable; | |||||
| /** | |||||
| * 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 Charset mCharset; | |||||
| private Disposable mChapterDisp = null; | |||||
| public LocalPageLoader(PageView pageView, CollBookBean collBook) { | |||||
| super(pageView, collBook); | |||||
| mStatus = STATUS_PARING; | |||||
| } | |||||
| private List<TxtChapter> convertTxtChapter(List<BookChapterBean> bookChapters) { | |||||
| List<TxtChapter> txtChapters = new ArrayList<>(bookChapters.size()); | |||||
| for (BookChapterBean bean : bookChapters) { | |||||
| TxtChapter chapter = new TxtChapter(); | |||||
| chapter.title = bean.getTitle(); | |||||
| chapter.start = bean.getStart(); | |||||
| chapter.end = bean.getEnd(); | |||||
| txtChapters.add(chapter); | |||||
| } | |||||
| return txtChapters; | |||||
| } | |||||
| /** | |||||
| * 未完成的部分: | |||||
| * 1. 序章的添加 | |||||
| * 2. 章节存在的书本的虚拟分章效果 | |||||
| * | |||||
| * @throws IOException | |||||
| */ | |||||
| private void loadChapters() throws IOException { | |||||
| List<TxtChapter> chapters = new ArrayList<>(); | |||||
| //获取文件流 | |||||
| RandomAccessFile bookStream = new RandomAccessFile(mBookFile, "r"); | |||||
| //寻找匹配文章标题的正则表达式,判断是否存在章节名 | |||||
| boolean 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.getName()); | |||||
| //当前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) { | |||||
| //创建序章 | |||||
| TxtChapter preChapter = new TxtChapter(); | |||||
| preChapter.title = "序章"; | |||||
| preChapter.start = 0; | |||||
| preChapter.end = chapterContent.getBytes(mCharset.getName()).length; //获取String的byte值,作为最终值 | |||||
| //如果序章大小大于30才添加进去 | |||||
| if (preChapter.end - preChapter.start > 30) { | |||||
| chapters.add(preChapter); | |||||
| } | |||||
| //创建当前章节 | |||||
| TxtChapter curChapter = new TxtChapter(); | |||||
| curChapter.title = matcher.group(); | |||||
| curChapter.start = preChapter.end; | |||||
| chapters.add(curChapter); | |||||
| } | |||||
| //否则就block分割之后,上一个章节的剩余内容 | |||||
| else { | |||||
| //获取上一章节 | |||||
| TxtChapter lastChapter = chapters.get(chapters.size() - 1); | |||||
| //将当前段落添加上一章去 | |||||
| lastChapter.end += chapterContent.getBytes(mCharset.getName()).length; | |||||
| //如果章节内容太小,则移除 | |||||
| if (lastChapter.end - lastChapter.start < 30) { | |||||
| chapters.remove(lastChapter); | |||||
| } | |||||
| //创建当前章节 | |||||
| TxtChapter curChapter = new TxtChapter(); | |||||
| 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(); | |||||
| //获取上一章节 | |||||
| TxtChapter lastChapter = chapters.get(chapters.size() - 1); | |||||
| lastChapter.end = lastChapter.start + chapterContent.getBytes(mCharset.getName()).length; | |||||
| //如果章节内容太小,则移除 | |||||
| if (lastChapter.end - lastChapter.start < 30) { | |||||
| chapters.remove(lastChapter); | |||||
| } | |||||
| //创建当前章节 | |||||
| TxtChapter curChapter = new TxtChapter(); | |||||
| curChapter.title = matcher.group(); | |||||
| curChapter.start = lastChapter.end; | |||||
| chapters.add(curChapter); | |||||
| } | |||||
| //如果章节不存在则创建章节 | |||||
| else { | |||||
| TxtChapter curChapter = new TxtChapter(); | |||||
| 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; | |||||
| } | |||||
| } | |||||
| TxtChapter chapter = new TxtChapter(); | |||||
| chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")"; | |||||
| chapter.start = curOffset + chapterOffset + 1; | |||||
| chapter.end = curOffset + end; | |||||
| chapters.add(chapter); | |||||
| //减去已经被分配的长度 | |||||
| strLength = strLength - (end - chapterOffset); | |||||
| //设置偏移的位置 | |||||
| chapterOffset = end; | |||||
| } else { | |||||
| TxtChapter chapter = new TxtChapter(); | |||||
| chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")"; | |||||
| chapter.start = curOffset + chapterOffset + 1; | |||||
| chapter.end = curOffset + length; | |||||
| chapters.add(chapter); | |||||
| strLength = 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| //block的偏移点 | |||||
| curOffset += length; | |||||
| if (hasChapter) { | |||||
| //设置上一章的结尾 | |||||
| TxtChapter lastChapter = chapters.get(chapters.size() - 1); | |||||
| lastChapter.end = curOffset; | |||||
| } | |||||
| //当添加的block太多的时候,执行GC | |||||
| if (blockPos % 15 == 0) { | |||||
| System.gc(); | |||||
| System.runFinalization(); | |||||
| } | |||||
| } | |||||
| mChapterList = chapters; | |||||
| IOUtils.close(bookStream); | |||||
| System.gc(); | |||||
| System.runFinalization(); | |||||
| } | |||||
| /** | |||||
| * 从文件中提取一章的内容 | |||||
| * | |||||
| * @param chapter | |||||
| * @return | |||||
| */ | |||||
| private byte[] getChapterContent(TxtChapter 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 content; | |||||
| } catch (FileNotFoundException e) { | |||||
| e.printStackTrace(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| } finally { | |||||
| IOUtils.close(bookStream); | |||||
| } | |||||
| return new byte[0]; | |||||
| } | |||||
| /** | |||||
| * 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.getName())); | |||||
| //如果匹配存在,那么就表示当前章节使用这种匹配方式 | |||||
| if (matcher.find()) { | |||||
| mChapterPattern = pattern; | |||||
| //重置指针位置 | |||||
| bookStream.seek(0); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| //重置指针位置 | |||||
| bookStream.seek(0); | |||||
| return false; | |||||
| } | |||||
| @Override | |||||
| public void saveRecord() { | |||||
| super.saveRecord(); | |||||
| //修改当前COllBook记录 | |||||
| if (mCollBook != null && isChapterListPrepare) { | |||||
| //表示当前CollBook已经阅读 | |||||
| mCollBook.setIsUpdate(false); | |||||
| mCollBook.setLastChapter(mChapterList.get(mCurChapterPos).getTitle()); | |||||
| mCollBook.setLastRead(StringUtils. | |||||
| dateConvert(System.currentTimeMillis(), Constant.FORMAT_BOOK_DATE)); | |||||
| //直接更新 | |||||
| BookRepository.getInstance() | |||||
| .saveCollBook(mCollBook); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void closeBook() { | |||||
| super.closeBook(); | |||||
| if (mChapterDisp != null) { | |||||
| mChapterDisp.dispose(); | |||||
| mChapterDisp = null; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void refreshChapterList() { | |||||
| // 对于文件是否存在,或者为空的判断,不作处理。 ==> 在文件打开前处理过了。 | |||||
| mBookFile = new File(mCollBook.getCover()); | |||||
| //获取文件编码 | |||||
| mCharset = FileUtils.getCharset(mBookFile.getAbsolutePath()); | |||||
| String lastModified = StringUtils.dateConvert(mBookFile.lastModified(), Constant.FORMAT_BOOK_DATE); | |||||
| // 判断文件是否已经加载过,并具有缓存 | |||||
| if (!mCollBook.isUpdate() && mCollBook.getUpdated() != null | |||||
| && mCollBook.getUpdated().equals(lastModified) | |||||
| && mCollBook.getBookChapters() != null) { | |||||
| mChapterList = convertTxtChapter(mCollBook.getBookChapters()); | |||||
| isChapterListPrepare = true; | |||||
| //提示目录加载完成 | |||||
| if (mPageChangeListener != null) { | |||||
| mPageChangeListener.onCategoryFinish(mChapterList); | |||||
| } | |||||
| // 加载并显示当前章节 | |||||
| openChapter(); | |||||
| return; | |||||
| } | |||||
| // 通过RxJava异步处理分章事件 | |||||
| Single.create(new SingleOnSubscribe<Void>() { | |||||
| @Override | |||||
| public void subscribe(SingleEmitter<Void> e) throws Exception { | |||||
| loadChapters(); | |||||
| e.onSuccess(new Void()); | |||||
| } | |||||
| }).compose(RxUtils::toSimpleSingle) | |||||
| .subscribe(new SingleObserver<Void>() { | |||||
| @Override | |||||
| public void onSubscribe(Disposable d) { | |||||
| mChapterDisp = d; | |||||
| } | |||||
| @Override | |||||
| public void onSuccess(Void value) { | |||||
| mChapterDisp = null; | |||||
| isChapterListPrepare = true; | |||||
| // 提示目录加载完成 | |||||
| if (mPageChangeListener != null) { | |||||
| mPageChangeListener.onCategoryFinish(mChapterList); | |||||
| } | |||||
| // 存储章节到数据库 | |||||
| List<BookChapterBean> bookChapterBeanList = new ArrayList<>(); | |||||
| for (int i = 0; i < mChapterList.size(); ++i) { | |||||
| TxtChapter chapter = mChapterList.get(i); | |||||
| BookChapterBean bean = new BookChapterBean(); | |||||
| bean.setId(MD5Utils.strToMd5By16(mBookFile.getAbsolutePath() | |||||
| + File.separator + chapter.title)); // 将路径+i 作为唯一值 | |||||
| bean.setTitle(chapter.getTitle()); | |||||
| bean.setStart(chapter.getStart()); | |||||
| bean.setUnreadble(false); | |||||
| bean.setEnd(chapter.getEnd()); | |||||
| bookChapterBeanList.add(bean); | |||||
| } | |||||
| mCollBook.setBookChapters(bookChapterBeanList); | |||||
| mCollBook.setUpdated(lastModified); | |||||
| BookRepository.getInstance().saveBookChaptersWithAsync(bookChapterBeanList); | |||||
| BookRepository.getInstance().saveCollBook(mCollBook); | |||||
| // 加载并显示当前章节 | |||||
| openChapter(); | |||||
| } | |||||
| @Override | |||||
| public void onError(Throwable e) { | |||||
| chapterError(); | |||||
| LogUtils.d(TAG, "file load error:" + e.toString()); | |||||
| } | |||||
| }); | |||||
| } | |||||
| @Override | |||||
| protected BufferedReader getChapterReader(TxtChapter chapter) throws Exception { | |||||
| //从文件中获取数据 | |||||
| byte[] content = getChapterContent(chapter); | |||||
| ByteArrayInputStream bais = new ByteArrayInputStream(content); | |||||
| BufferedReader br = new BufferedReader(new InputStreamReader(bais, mCharset.getName())); | |||||
| return br; | |||||
| } | |||||
| @Override | |||||
| protected boolean hasChapterData(TxtChapter chapter) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,226 @@ | |||||
| package com.yzx.webebook.widget.page; | |||||
| import com.yzx.webebook.model.bean.BookChapterBean; | |||||
| import com.yzx.webebook.model.bean.CollBookBean; | |||||
| import com.yzx.webebook.model.local.BookRepository; | |||||
| import com.yzx.webebook.utils.BookManager; | |||||
| import com.yzx.webebook.utils.Constant; | |||||
| import com.yzx.webebook.utils.FileUtils; | |||||
| import com.yzx.webebook.utils.StringUtils; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.File; | |||||
| import java.io.FileReader; | |||||
| import java.io.Reader; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 17-5-29. | |||||
| * 网络页面加载器 | |||||
| */ | |||||
| public class NetPageLoader extends PageLoader { | |||||
| private static final String TAG = "PageFactory"; | |||||
| public NetPageLoader(PageView pageView, CollBookBean collBook) { | |||||
| super(pageView, collBook); | |||||
| } | |||||
| private List<TxtChapter> convertTxtChapter(List<BookChapterBean> bookChapters) { | |||||
| List<TxtChapter> txtChapters = new ArrayList<>(bookChapters.size()); | |||||
| for (BookChapterBean bean : bookChapters) { | |||||
| TxtChapter chapter = new TxtChapter(); | |||||
| chapter.bookId = bean.getBookId(); | |||||
| chapter.title = bean.getTitle(); | |||||
| chapter.link = bean.getLink(); | |||||
| txtChapters.add(chapter); | |||||
| } | |||||
| return txtChapters; | |||||
| } | |||||
| @Override | |||||
| public void refreshChapterList() { | |||||
| if (mCollBook.getBookChapters() == null) return; | |||||
| // 将 BookChapter 转换成当前可用的 Chapter | |||||
| mChapterList = convertTxtChapter(mCollBook.getBookChapters()); | |||||
| isChapterListPrepare = true; | |||||
| // 目录加载完成,执行回调操作。 | |||||
| if (mPageChangeListener != null) { | |||||
| mPageChangeListener.onCategoryFinish(mChapterList); | |||||
| } | |||||
| // 如果章节未打开 | |||||
| if (!isChapterOpen()) { | |||||
| // 打开章节 | |||||
| openChapter(); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| protected BufferedReader getChapterReader(TxtChapter chapter) throws Exception { | |||||
| File file = new File(Constant.BOOK_CACHE_PATH + mCollBook.get_id() | |||||
| + File.separator + chapter.title + FileUtils.SUFFIX_NB); | |||||
| if (!file.exists()) return null; | |||||
| Reader reader = new FileReader(file); | |||||
| BufferedReader br = new BufferedReader(reader); | |||||
| return br; | |||||
| } | |||||
| @Override | |||||
| protected boolean hasChapterData(TxtChapter chapter) { | |||||
| return BookManager.isChapterCached(mCollBook.get_id(), chapter.title); | |||||
| } | |||||
| // 装载上一章节的内容 | |||||
| @Override | |||||
| boolean parsePrevChapter() { | |||||
| boolean isRight = super.parsePrevChapter(); | |||||
| if (mStatus == STATUS_FINISH) { | |||||
| loadPrevChapter(); | |||||
| } else if (mStatus == STATUS_LOADING) { | |||||
| loadCurrentChapter(); | |||||
| } | |||||
| return isRight; | |||||
| } | |||||
| // 装载当前章内容。 | |||||
| @Override | |||||
| boolean parseCurChapter() { | |||||
| boolean isRight = super.parseCurChapter(); | |||||
| if (mStatus == STATUS_LOADING) { | |||||
| loadCurrentChapter(); | |||||
| } | |||||
| return isRight; | |||||
| } | |||||
| // 装载下一章节的内容 | |||||
| @Override | |||||
| boolean parseNextChapter() { | |||||
| boolean isRight = super.parseNextChapter(); | |||||
| if (mStatus == STATUS_FINISH) { | |||||
| loadNextChapter(); | |||||
| } else if (mStatus == STATUS_LOADING) { | |||||
| loadCurrentChapter(); | |||||
| } | |||||
| return isRight; | |||||
| } | |||||
| /** | |||||
| * 加载当前页的前面两个章节 | |||||
| */ | |||||
| private void loadPrevChapter() { | |||||
| if (mPageChangeListener != null) { | |||||
| int end = mCurChapterPos; | |||||
| int begin = end - 2; | |||||
| if (begin < 0) { | |||||
| begin = 0; | |||||
| } | |||||
| requestChapters(begin, end); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 加载前一页,当前页,后一页。 | |||||
| */ | |||||
| private void loadCurrentChapter() { | |||||
| if (mPageChangeListener != null) { | |||||
| int begin = mCurChapterPos; | |||||
| int end = mCurChapterPos; | |||||
| // 是否当前不是最后一章 | |||||
| if (end < mChapterList.size()) { | |||||
| end = end + 1; | |||||
| if (end >= mChapterList.size()) { | |||||
| end = mChapterList.size() - 1; | |||||
| } | |||||
| } | |||||
| // 如果当前不是第一章 | |||||
| if (begin != 0) { | |||||
| begin = begin - 1; | |||||
| if (begin < 0) { | |||||
| begin = 0; | |||||
| } | |||||
| } | |||||
| requestChapters(begin, end); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 加载当前页的后两个章节 | |||||
| */ | |||||
| private void loadNextChapter() { | |||||
| if (mPageChangeListener != null) { | |||||
| // 提示加载后两章 | |||||
| int begin = mCurChapterPos + 1; | |||||
| int end = begin + 1; | |||||
| // 判断是否大于最后一章 | |||||
| if (begin >= mChapterList.size()) { | |||||
| // 如果下一章超出目录了,就没有必要加载了 | |||||
| return; | |||||
| } | |||||
| if (end > mChapterList.size()) { | |||||
| end = mChapterList.size() - 1; | |||||
| } | |||||
| requestChapters(begin, end); | |||||
| } | |||||
| } | |||||
| private void requestChapters(int start, int end) { | |||||
| // 检验输入值 | |||||
| if (start < 0) { | |||||
| start = 0; | |||||
| } | |||||
| if (end >= mChapterList.size()) { | |||||
| end = mChapterList.size() - 1; | |||||
| } | |||||
| List<TxtChapter> chapters = new ArrayList<>(); | |||||
| // 过滤,哪些数据已经加载了 | |||||
| for (int i = start; i <= end; ++i) { | |||||
| TxtChapter txtChapter = mChapterList.get(i); | |||||
| if (!hasChapterData(txtChapter)) { | |||||
| chapters.add(txtChapter); | |||||
| } | |||||
| } | |||||
| if (!chapters.isEmpty()) { | |||||
| mPageChangeListener.requestChapters(chapters); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void saveRecord() { | |||||
| super.saveRecord(); | |||||
| if (mCollBook != null && isChapterListPrepare) { | |||||
| //表示当前CollBook已经阅读 | |||||
| mCollBook.setIsUpdate(false); | |||||
| mCollBook.setLastRead(StringUtils. | |||||
| dateConvert(System.currentTimeMillis(), Constant.FORMAT_BOOK_DATE)); | |||||
| //直接更新 | |||||
| BookRepository.getInstance() | |||||
| .saveCollBook(mCollBook); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| package com.yzx.webebook.widget.page; | |||||
| /** | |||||
| * Created by newbiechen on 2018/2/5. | |||||
| * 作用:翻页动画的模式 | |||||
| */ | |||||
| public enum PageMode { | |||||
| SIMULATION, COVER, SLIDE, NONE, SCROLL | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package com.yzx.webebook.widget.page; | |||||
| import androidx.annotation.ColorRes; | |||||
| import com.yzx.webebook.R; | |||||
| /** | |||||
| * Created by newbiechen on 2018/2/5. | |||||
| * 作用:页面的展示风格。 | |||||
| */ | |||||
| public enum PageStyle { | |||||
| BG_0(R.color.nb_read_font_1, R.color.nb_read_bg_1), | |||||
| BG_1(R.color.nb_read_font_2, R.color.nb_read_bg_2), | |||||
| BG_2(R.color.nb_read_font_3, R.color.nb_read_bg_3), | |||||
| BG_3(R.color.nb_read_font_4, R.color.nb_read_bg_4), | |||||
| BG_4(R.color.nb_read_font_5, R.color.nb_read_bg_5), | |||||
| NIGHT(R.color.nb_read_font_night, R.color.nb_read_bg_night),; | |||||
| private int fontColor; | |||||
| private int bgColor; | |||||
| PageStyle(@ColorRes int fontColor, @ColorRes int bgColor) { | |||||
| this.fontColor = fontColor; | |||||
| this.bgColor = bgColor; | |||||
| } | |||||
| public int getFontColor() { | |||||
| return fontColor; | |||||
| } | |||||
| public int getBgColor() { | |||||
| return bgColor; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,378 @@ | |||||
| package com.yzx.webebook.widget.page; | |||||
| 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.yzx.webebook.model.bean.CollBookBean; | |||||
| import com.yzx.webebook.widget.animation.CoverPageAnim; | |||||
| import com.yzx.webebook.widget.animation.HorizonPageAnim; | |||||
| import com.yzx.webebook.widget.animation.NonePageAnim; | |||||
| import com.yzx.webebook.widget.animation.PageAnimation; | |||||
| import com.yzx.webebook.widget.animation.ScrollPageAnim; | |||||
| import com.yzx.webebook.widget.animation.SimulationPageAnim; | |||||
| import com.yzx.webebook.widget.animation.SlidePageAnim; | |||||
| /** | |||||
| * Created by Administrator on 2016/8/29 0029. | |||||
| * 原作者的GitHub Project Path:(https://github.com/PeachBlossom/treader) | |||||
| * 绘制页面显示内容的类 | |||||
| */ | |||||
| public class PageView extends View { | |||||
| private final static String TAG = "BookPageWidget"; | |||||
| private int mViewWidth = 0; // 当前View的宽 | |||||
| private int mViewHeight = 0; // 当前View的高 | |||||
| private int mStartX = 0; | |||||
| private int mStartY = 0; | |||||
| private boolean isMove = false; | |||||
| // 初始化参数 | |||||
| private int mBgColor = 0xFFCEC29C; | |||||
| private PageMode mPageMode = PageMode.SIMULATION; | |||||
| // 是否允许点击 | |||||
| private boolean canTouch = true; | |||||
| // 唤醒菜单的区域 | |||||
| private RectF mCenterRect = null; | |||||
| private boolean isPrepare; | |||||
| // 动画类 | |||||
| private PageAnimation mPageAnim; | |||||
| // 动画监听类 | |||||
| private PageAnimation.OnPageChangeListener mPageAnimListener = new PageAnimation.OnPageChangeListener() { | |||||
| @Override | |||||
| public boolean hasPrev() { | |||||
| return PageView.this.hasPrevPage(); | |||||
| } | |||||
| @Override | |||||
| public boolean hasNext() { | |||||
| return PageView.this.hasNextPage(); | |||||
| } | |||||
| @Override | |||||
| public void pageCancel() { | |||||
| PageView.this.pageCancel(); | |||||
| } | |||||
| }; | |||||
| //点击监听 | |||||
| private TouchListener mTouchListener; | |||||
| //内容加载器 | |||||
| private PageLoader mPageLoader; | |||||
| 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); | |||||
| } | |||||
| @Override | |||||
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |||||
| super.onSizeChanged(w, h, oldw, oldh); | |||||
| mViewWidth = w; | |||||
| mViewHeight = h; | |||||
| isPrepare = true; | |||||
| if (mPageLoader != null) { | |||||
| mPageLoader.prepareDisplay(w, h); | |||||
| } | |||||
| } | |||||
| //设置翻页的模式 | |||||
| void setPageMode(PageMode pageMode) { | |||||
| mPageMode = pageMode; | |||||
| //视图未初始化的时候,禁止调用 | |||||
| if (mViewWidth == 0 || mViewHeight == 0) return; | |||||
| switch (mPageMode) { | |||||
| case SIMULATION: | |||||
| mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); | |||||
| break; | |||||
| case COVER: | |||||
| mPageAnim = new CoverPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); | |||||
| break; | |||||
| case SLIDE: | |||||
| mPageAnim = new SlidePageAnim(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); | |||||
| break; | |||||
| default: | |||||
| mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); | |||||
| } | |||||
| } | |||||
| public Bitmap getNextBitmap() { | |||||
| if (mPageAnim == null) return null; | |||||
| return mPageAnim.getNextBitmap(); | |||||
| } | |||||
| public Bitmap getBgBitmap() { | |||||
| if (mPageAnim == null) return null; | |||||
| return mPageAnim.getBgBitmap(); | |||||
| } | |||||
| public boolean autoPrevPage() { | |||||
| //滚动暂时不支持自动翻页 | |||||
| if (mPageAnim instanceof ScrollPageAnim) { | |||||
| return false; | |||||
| } else { | |||||
| startPageAnim(PageAnimation.Direction.PRE); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| public boolean autoNextPage() { | |||||
| if (mPageAnim instanceof ScrollPageAnim) { | |||||
| return false; | |||||
| } else { | |||||
| startPageAnim(PageAnimation.Direction.NEXT); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| private void startPageAnim(PageAnimation.Direction direction) { | |||||
| if (mTouchListener == null) return; | |||||
| //是否正在执行动画 | |||||
| abortAnimation(); | |||||
| if (direction == PageAnimation.Direction.NEXT) { | |||||
| int x = mViewWidth; | |||||
| int y = mViewHeight; | |||||
| //初始化动画 | |||||
| mPageAnim.setStartPoint(x, y); | |||||
| //设置点击点 | |||||
| mPageAnim.setTouchPoint(x, y); | |||||
| //设置方向 | |||||
| Boolean hasNext = hasNextPage(); | |||||
| mPageAnim.setDirection(direction); | |||||
| if (!hasNext) { | |||||
| return; | |||||
| } | |||||
| } else { | |||||
| int x = 0; | |||||
| int y = mViewHeight; | |||||
| //初始化动画 | |||||
| mPageAnim.setStartPoint(x, y); | |||||
| //设置点击点 | |||||
| mPageAnim.setTouchPoint(x, y); | |||||
| mPageAnim.setDirection(direction); | |||||
| //设置方向方向 | |||||
| Boolean hashPrev = hasPrevPage(); | |||||
| if (!hashPrev) { | |||||
| return; | |||||
| } | |||||
| } | |||||
| mPageAnim.startAnim(); | |||||
| this.postInvalidate(); | |||||
| } | |||||
| public void setBgColor(int color) { | |||||
| mBgColor = color; | |||||
| } | |||||
| @Override | |||||
| protected void onDraw(Canvas canvas) { | |||||
| //绘制背景 | |||||
| canvas.drawColor(mBgColor); | |||||
| //绘制动画 | |||||
| mPageAnim.draw(canvas); | |||||
| } | |||||
| @Override | |||||
| public boolean onTouchEvent(MotionEvent event) { | |||||
| super.onTouchEvent(event); | |||||
| if (!canTouch && event.getAction() != MotionEvent.ACTION_DOWN) return true; | |||||
| int x = (int) event.getX(); | |||||
| int y = (int) event.getY(); | |||||
| switch (event.getAction()) { | |||||
| case MotionEvent.ACTION_DOWN: | |||||
| mStartX = x; | |||||
| mStartY = y; | |||||
| isMove = false; | |||||
| canTouch = mTouchListener.onTouch(); | |||||
| mPageAnim.onTouchEvent(event); | |||||
| break; | |||||
| case MotionEvent.ACTION_MOVE: | |||||
| // 判断是否大于最小滑动值。 | |||||
| int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); | |||||
| if (!isMove) { | |||||
| isMove = Math.abs(mStartX - event.getX()) > slop || Math.abs(mStartY - event.getY()) > slop; | |||||
| } | |||||
| // 如果滑动了,则进行翻页。 | |||||
| if (isMove) { | |||||
| mPageAnim.onTouchEvent(event); | |||||
| } | |||||
| break; | |||||
| case MotionEvent.ACTION_UP: | |||||
| if (!isMove) { | |||||
| //设置中间区域范围 | |||||
| if (mCenterRect == null) { | |||||
| mCenterRect = new RectF(mViewWidth / 5, mViewHeight / 3, | |||||
| mViewWidth * 4 / 5, mViewHeight * 2 / 3); | |||||
| } | |||||
| //是否点击了中间 | |||||
| if (mCenterRect.contains(x, y)) { | |||||
| if (mTouchListener != null) { | |||||
| mTouchListener.center(); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| mPageAnim.onTouchEvent(event); | |||||
| break; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| /** | |||||
| * 判断是否存在上一页 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| private boolean hasPrevPage() { | |||||
| mTouchListener.prePage(); | |||||
| return mPageLoader.prev(); | |||||
| } | |||||
| /** | |||||
| * 判断是否下一页存在 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| private boolean hasNextPage() { | |||||
| mTouchListener.nextPage(); | |||||
| return mPageLoader.next(); | |||||
| } | |||||
| private void pageCancel() { | |||||
| mTouchListener.cancel(); | |||||
| mPageLoader.pageCancel(); | |||||
| } | |||||
| @Override | |||||
| public void computeScroll() { | |||||
| //进行滑动 | |||||
| mPageAnim.scrollAnim(); | |||||
| super.computeScroll(); | |||||
| } | |||||
| //如果滑动状态没有停止就取消状态,重新设置Anim的触碰点 | |||||
| public void abortAnimation() { | |||||
| mPageAnim.abortAnim(); | |||||
| } | |||||
| public boolean isRunning() { | |||||
| if (mPageAnim == null) { | |||||
| return false; | |||||
| } | |||||
| return mPageAnim.isRunning(); | |||||
| } | |||||
| public boolean isPrepare() { | |||||
| return isPrepare; | |||||
| } | |||||
| public void setTouchListener(TouchListener mTouchListener) { | |||||
| this.mTouchListener = mTouchListener; | |||||
| } | |||||
| public void drawNextPage() { | |||||
| if (!isPrepare) return; | |||||
| if (mPageAnim instanceof HorizonPageAnim) { | |||||
| ((HorizonPageAnim) mPageAnim).changePage(); | |||||
| } | |||||
| mPageLoader.drawPage(getNextBitmap(), false); | |||||
| } | |||||
| /** | |||||
| * 绘制当前页。 | |||||
| * | |||||
| * @param isUpdate | |||||
| */ | |||||
| public void drawCurPage(boolean isUpdate) { | |||||
| if (!isPrepare) return; | |||||
| if (!isUpdate){ | |||||
| if (mPageAnim instanceof ScrollPageAnim) { | |||||
| ((ScrollPageAnim) mPageAnim).resetBitmap(); | |||||
| } | |||||
| } | |||||
| mPageLoader.drawPage(getNextBitmap(), isUpdate); | |||||
| } | |||||
| @Override | |||||
| protected void onDetachedFromWindow() { | |||||
| super.onDetachedFromWindow(); | |||||
| mPageAnim.abortAnim(); | |||||
| mPageAnim.clear(); | |||||
| mPageLoader = null; | |||||
| mPageAnim = null; | |||||
| } | |||||
| /** | |||||
| * 获取 PageLoader | |||||
| * | |||||
| * @param collBook | |||||
| * @return | |||||
| */ | |||||
| public PageLoader getPageLoader(CollBookBean collBook) { | |||||
| // 判是否已经存在 | |||||
| if (mPageLoader != null) { | |||||
| return mPageLoader; | |||||
| } | |||||
| // 根据书籍类型,获取具体的加载器 | |||||
| if (collBook.isLocal()) { | |||||
| mPageLoader = new LocalPageLoader(this, collBook); | |||||
| } else { | |||||
| mPageLoader = new NetPageLoader(this, collBook); | |||||
| } | |||||
| // 判断是否 PageView 已经初始化完成 | |||||
| if (mViewWidth != 0 || mViewHeight != 0) { | |||||
| // 初始化 PageLoader 的屏幕大小 | |||||
| mPageLoader.prepareDisplay(mViewWidth, mViewHeight); | |||||
| } | |||||
| Log.e(TAG, "getPageLoader: "+mPageLoader.toString() ); | |||||
| return mPageLoader; | |||||
| } | |||||
| public interface TouchListener { | |||||
| boolean onTouch(); | |||||
| void center(); | |||||
| void prePage(); | |||||
| void nextPage(); | |||||
| void cancel(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,70 @@ | |||||
| package com.yzx.webebook.widget.page; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-1. | |||||
| */ | |||||
| public class TxtChapter{ | |||||
| //章节所属的小说(网络) | |||||
| String bookId; | |||||
| //章节的链接(网络) | |||||
| String link; | |||||
| //章节名(共用) | |||||
| String title; | |||||
| //章节内容在文章中的起始位置(本地) | |||||
| long start; | |||||
| //章节内容在文章中的终止位置(本地) | |||||
| long end; | |||||
| public String getBookId() { | |||||
| return bookId; | |||||
| } | |||||
| public void setBookId(String id) { | |||||
| this.bookId = id; | |||||
| } | |||||
| public String getLink() { | |||||
| return link; | |||||
| } | |||||
| public void setLink(String link) { | |||||
| this.link = link; | |||||
| } | |||||
| public String getTitle() { | |||||
| return title; | |||||
| } | |||||
| public void setTitle(String title) { | |||||
| this.title = title; | |||||
| } | |||||
| public long getStart() { | |||||
| return start; | |||||
| } | |||||
| public void setStart(long start) { | |||||
| this.start = start; | |||||
| } | |||||
| public long getEnd() { | |||||
| return end; | |||||
| } | |||||
| public void setEnd(long end) { | |||||
| this.end = end; | |||||
| } | |||||
| @Override | |||||
| public String toString() { | |||||
| return "TxtChapter{" + | |||||
| "title='" + title + '\'' + | |||||
| ", start=" + start + | |||||
| ", end=" + end + | |||||
| '}'; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| package com.yzx.webebook.widget.page; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by newbiechen on 17-7-1. | |||||
| */ | |||||
| public class TxtPage { | |||||
| int position; | |||||
| String title; | |||||
| int titleLines; //当前 lines 中为 title 的行数。 | |||||
| List<String> lines; | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <rotate android:fromDegrees="0" | |||||
| android:toDegrees="180" | |||||
| android:pivotX="50%" | |||||
| android:pivotY="50%" | |||||
| android:duration="300"/> | |||||
| </set> | |||||
| @@ -0,0 +1,8 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android" | |||||
| android:duration="300"> | |||||
| <rotate android:fromDegrees="180" | |||||
| android:toDegrees="360" | |||||
| android:pivotX="50%" | |||||
| android:pivotY="50%"/> | |||||
| </set> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <translate | |||||
| android:fromYDelta="100%" | |||||
| android:toYDelta="0" | |||||
| android:duration="400"/> | |||||
| </set> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <translate | |||||
| android:fromYDelta="0" | |||||
| android:toYDelta="100%" | |||||
| android:duration="400"/> | |||||
| </set> | |||||
| @@ -0,0 +1,6 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <translate android:fromXDelta="-100%p" | |||||
| android:toXDelta="0" | |||||
| android:duration="500"/> | |||||
| </set> | |||||
| @@ -0,0 +1,6 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <translate android:fromXDelta="0" | |||||
| android:toXDelta="-100%p" | |||||
| android:duration="500"/> | |||||
| </set> | |||||
| @@ -0,0 +1,6 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <translate android:fromXDelta="100%p" | |||||
| android:toXDelta="0" | |||||
| android:duration="500"/> | |||||
| </set> | |||||
| @@ -0,0 +1,6 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <translate android:fromXDelta="0" | |||||
| android:toXDelta="100%p" | |||||
| android:duration="500"/> | |||||
| </set> | |||||
| @@ -0,0 +1,8 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <translate android:fromXDelta="0" | |||||
| android:fromYDelta="-100%" | |||||
| android:toXDelta="0" | |||||
| android:toYDelta="0" | |||||
| android:duration="400"/> | |||||
| </set> | |||||
| @@ -0,0 +1,6 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <translate android:fromYDelta="0" | |||||
| android:toYDelta="-100%" | |||||
| android:duration="400"/> | |||||
| </set> | |||||
| @@ -0,0 +1,8 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <item android:color="@color/light_red" android:state_checked="true" /> | |||||
| <item android:color="@color/light_red" android:state_pressed="true" /> | |||||
| <item android:color="@color/light_red" android:state_selected="true" /> | |||||
| <!--必须放在最后一行,否则上面的效果全都不能用--> | |||||
| <item android:color="@color/white"/> | |||||
| </selector> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <item android:color="@color/light_red" android:state_pressed="true" /> | |||||
| <item android:color="@color/light_red" android:state_selected="true" /> | |||||
| <item android:color="@color/nb.text.default"/> | |||||
| </selector> | |||||
| @@ -0,0 +1,25 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <item android:id="@android:id/background"> | |||||
| <shape> | |||||
| <size android:height="1dp" /> | |||||
| <gradient | |||||
| android:endColor="@android:color/darker_gray" | |||||
| android:startColor="@android:color/darker_gray" /> | |||||
| </shape> | |||||
| </item> | |||||
| <item android:id="@android:id/progress"> | |||||
| <clip> | |||||
| <shape> | |||||
| <size android:height="3dp" /> | |||||
| <gradient | |||||
| android:endColor="@color/orange" | |||||
| android:startColor="@color/orange" /> | |||||
| </shape> | |||||
| </clip> | |||||
| </item> | |||||
| </layer-list> | |||||
| @@ -0,0 +1,8 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <item android:drawable="@mipmap/seekbar_thumb_selected" android:state_pressed="true"/> | |||||
| <item android:drawable="@mipmap/seekbar_thumb_selected" android:state_focused="true"/> | |||||
| <item android:drawable="@mipmap/seekbar_thumb_normal"></item> | |||||
| </selector> | |||||
| @@ -0,0 +1,6 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <item android:state_pressed="true" android:drawable="@drawable/shape_btn_read_setting_checked" /> | |||||
| <item android:state_checked="true" android:drawable="@drawable/shape_btn_read_setting_checked" /> | |||||
| <item android:drawable="@drawable/shape_btn_read_setting_normal" /> | |||||
| </selector> | |||||