| @@ -31,5 +31,10 @@ | |||
| <option name="name" value="maven2" /> | |||
| <option name="url" value="https://dl.bintray.com/umsdk/release" /> | |||
| </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> | |||
| </project> | |||
| @@ -4,16 +4,25 @@ apply plugin: 'kotlin-android' | |||
| 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 { | |||
| compileSdkVersion 30 | |||
| buildToolsVersion "30.0.3" | |||
| defaultConfig { | |||
| applicationId "com.yzx.webebook" | |||
| minSdkVersion 26 | |||
| targetSdkVersion 30 | |||
| // minSdkVersion 26 | |||
| minSdkVersion 21 | |||
| targetSdkVersion 25 | |||
| versionCode 3 | |||
| versionName "2.0.1" | |||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
| ndk { | |||
| abiFilters "armeabi-v7a", "arm64-v8a", "x86" | |||
| } | |||
| } | |||
| signingConfigs { | |||
| yzx { | |||
| @@ -59,10 +68,10 @@ android { | |||
| debug { | |||
| minifyEnabled false | |||
| proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |||
| // debuggable true | |||
| debuggable true | |||
| signingConfig signingConfigs.release | |||
| // 移除无用的resource文件 | |||
| // shrinkResources false | |||
| shrinkResources false | |||
| buildConfigField "String", "BASE_URL", '"http://192.168.69.99:9009"' | |||
| buildConfigField "String", "M_URL", '"http://192.168.69.112:8098"' | |||
| buildConfigField "String", "APP_NAME", '"家校互通(开发)"' | |||
| @@ -96,6 +105,24 @@ android { | |||
| // but continue the build even when errors are found: | |||
| 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 { | |||
| @@ -127,4 +154,16 @@ dependencies { | |||
| implementation 'com.umeng.umsdk:asms:1.2.1' // 必选 | |||
| implementation 'com.github.thomhurst:RoundImageView:1.0.2' | |||
| 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_MEDIA_STORAGE" /> | |||
| <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 | |||
| android:name=".App" | |||
| android:allowBackup="true" | |||
| android:icon="@mipmap/ic_launcher" | |||
| android:label="@string/app_name" | |||
| android:networkSecurityConfig="@xml/network_security_config" | |||
| android:roundIcon="@mipmap/ic_launcher_round" | |||
| 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 | |||
| android:name=".activity.HomeActivity" | |||
| android:configChanges="orientation|keyboard" | |||
| @@ -51,10 +59,6 @@ | |||
| android:name=".activity.BookActivity" | |||
| android:configChanges="orientation|keyboard" | |||
| android:screenOrientation="portrait" /> | |||
| <activity | |||
| android:name=".activity.Main1Activity" | |||
| android:configChanges="orientation|keyboard" | |||
| android:screenOrientation="portrait" /> | |||
| <provider | |||
| 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 | |||
| import android.app.Application | |||
| import android.content.Context | |||
| import android.util.Log | |||
| import com.blankj.utilcode.util.Utils | |||
| import com.lzy.okgo.OkGo | |||
| import com.umeng.analytics.MobclickAgent | |||
| 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() { | |||
| 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) | |||
| 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) | |||
| } | |||
| } | |||
| @@ -4,13 +4,17 @@ import android.annotation.SuppressLint | |||
| import android.app.Activity | |||
| import android.content.Intent | |||
| import android.text.TextUtils | |||
| import android.util.Log | |||
| import com.blankj.utilcode.util.StringUtils | |||
| import com.bumptech.glide.Glide | |||
| import com.yzx.webebook.activity.* | |||
| import com.yzx.webebook.activity.base.BaseActivity | |||
| import com.yzx.webebook.config.Config | |||
| 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.utils.MD5Utils | |||
| import kotlinx.android.synthetic.main.activity_main.* | |||
| import org.jetbrains.anko.startActivity | |||
| import org.jetbrains.anko.toast | |||
| @@ -84,7 +88,7 @@ class MainActivity : BaseActivity<BasePresenter<*>>() { | |||
| } | |||
| btn7.setOnClickListener { | |||
| startActivity<Main1Activity>() | |||
| startActivity<ReadActivity>() | |||
| } | |||
| /*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() { | |||
| 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 | |||
| @@ -1,5 +1,6 @@ | |||
| package com.yzx.webebook.activity | |||
| import android.Manifest | |||
| import android.annotation.SuppressLint | |||
| import android.app.AlertDialog | |||
| import android.app.Dialog | |||
| @@ -8,6 +9,7 @@ import android.content.DialogInterface | |||
| import android.util.Log | |||
| import android.view.View | |||
| import android.widget.TextView | |||
| import androidx.core.app.ActivityCompat | |||
| import androidx.core.widget.ContentLoadingProgressBar | |||
| import androidx.recyclerview.widget.GridLayoutManager | |||
| 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.widget.BaseDialog | |||
| import kotlinx.android.synthetic.main.activity_home.* | |||
| import kotlinx.android.synthetic.main.activity_home.titleTv | |||
| import org.jetbrains.anko.find | |||
| import org.jetbrains.anko.startActivity | |||
| 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_notebook, "/notebook/list")) | |||
| 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_classwork)) | |||
| list.add(HomeItem("我的", R.mipmap.ic_home_my, "/mine/ebook")) | |||
| 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<*>? { | |||
| 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.yzx.webebook.R | |||
| import com.yzx.webebook.presenter.base.BasePresenter | |||
| import io.reactivex.disposables.CompositeDisposable | |||
| import io.reactivex.disposables.Disposable | |||
| /** | |||
| * 类名:BaseActivity | |||
| @@ -38,7 +40,7 @@ abstract class BaseActivity<out P : BasePresenter<*>> : AppCompatActivity() { | |||
| abstract fun initPresenter(): P? | |||
| protected var mDisposable: CompositeDisposable? = null | |||
| override fun onCreate(savedInstanceState: Bundle?) { | |||
| 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() { | |||
| @@ -68,6 +75,9 @@ abstract class BaseActivity<out P : BasePresenter<*>> : AppCompatActivity() { | |||
| override fun 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; | |||
| 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.File; | |||
| import java.io.FileInputStream; | |||
| import java.io.FileNotFoundException; | |||
| import java.io.FileOutputStream; | |||
| import java.io.FileWriter; | |||
| import java.io.FileReader; | |||
| 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.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 { | |||
| 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; | |||
| 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> | |||