├── .gitignore ├── .idea ├── codeStyles │ └── codeStyleConfig.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── glong │ │ └── sample │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── glong │ │ │ └── sample │ │ │ ├── Constant.java │ │ │ ├── MainActivity.java │ │ │ ├── MyApplication.java │ │ │ ├── ScreenUtils.java │ │ │ ├── activities │ │ │ ├── CustomReaderActivity.java │ │ │ ├── ExtendReaderActivity.java │ │ │ ├── NormalReaderActivity.java │ │ │ ├── PaperActivity.java │ │ │ ├── SimpleReaderActivity.java │ │ │ └── WebViewActivity.java │ │ │ ├── adpater │ │ │ ├── CatalogueAdapter.java │ │ │ └── MyReaderAdapter.java │ │ │ ├── api │ │ │ ├── Api.java │ │ │ └── Service.java │ │ │ ├── custom │ │ │ └── MyReaderResolve.java │ │ │ ├── entry │ │ │ ├── ChapterContent2Bean.java │ │ │ ├── ChapterContentBean.java │ │ │ ├── ChapterItemBean.java │ │ │ ├── Result.java │ │ │ └── ResultData.java │ │ │ ├── localtest │ │ │ ├── LocalConstant.java │ │ │ └── LocalServer.java │ │ │ └── view │ │ │ ├── MenuView.java │ │ │ ├── MyView.java │ │ │ ├── SettingView.java │ │ │ └── SimpleOnSeekBarChangeListener.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── ic_read_categue_day.png │ │ ├── ic_read_categue_night.png │ │ ├── ic_read_comment_day.png │ │ ├── ic_read_comment_night.png │ │ ├── ic_read_day.png │ │ ├── ic_read_night.png │ │ ├── ic_read_setting_day.png │ │ └── ic_read_setting_night.png │ │ ├── drawable-xxhdpi │ │ ├── ic_read_categue_day.png │ │ ├── ic_read_categue_night.png │ │ ├── ic_read_comment_day.png │ │ ├── ic_read_comment_night.png │ │ ├── ic_read_day.png │ │ ├── ic_read_night.png │ │ ├── ic_read_setting_day.png │ │ └── ic_read_setting_night.png │ │ ├── drawable │ │ ├── battery.png │ │ ├── ic_launcher_background.xml │ │ ├── progress_bar_states.xml │ │ ├── reader_bg.jpg │ │ ├── test1.jpg │ │ ├── test2.jpg │ │ └── text_background.xml │ │ ├── layout │ │ ├── activity_custom_reader.xml │ │ ├── activity_extend_reader.xml │ │ ├── activity_main.xml │ │ ├── activity_normal_reader.xml │ │ ├── activity_paper.xml │ │ ├── activity_simple_reader.xml │ │ ├── activity_web_view.xml │ │ ├── first_page_view_layout.xml │ │ ├── item_catalogue.xml │ │ ├── last_page_view_layout.xml │ │ ├── reader_menu_layout.xml │ │ └── reader_setting_layout.xml │ │ ├── menu │ │ ├── extend_reader_menu.xml │ │ └── my_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-sw300dp │ │ └── dimens.xml │ │ ├── values-sw320dp │ │ └── dimens.xml │ │ ├── values-sw360dp │ │ └── dimens.xml │ │ ├── values-sw411dp │ │ └── dimens.xml │ │ ├── values-sw450dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── glong │ └── sample │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── 20181123_204344.gif ├── QQ截图20181123223124.png ├── QQ截图20181123224234.png ├── QQ截图20181123224412.png ├── QQ截图20181123224610.png ├── QQ截图20181123225002.png ├── QQ截图20181123225319.png ├── addView1.gif ├── gushu.png ├── non.png ├── pager0.png ├── pager1.png ├── pager2.png ├── video2gif_20181123_183236.gif ├── 微信图片_20181126135424.jpg └── 菜单.gif ├── reader ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── glong │ │ └── reader │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── glong │ │ │ └── reader │ │ │ ├── ReaderSparseBooleanArray.java │ │ │ ├── TestBean.java │ │ │ ├── TestGson.java │ │ │ ├── TurnStatus.java │ │ │ ├── cache │ │ │ ├── ACache.java │ │ │ ├── Cache.java │ │ │ └── DiskCache.java │ │ │ ├── config │ │ │ ├── ColorsConfig.java │ │ │ └── ReaderConfig.java │ │ │ ├── textconvert │ │ │ ├── BreakResult.java │ │ │ ├── ShowChar.java │ │ │ ├── ShowLine.java │ │ │ └── TextBreakUtils.java │ │ │ ├── util │ │ │ ├── DLog.java │ │ │ ├── FilePair.java │ │ │ ├── NetUtil.java │ │ │ └── Request.java │ │ │ └── widget │ │ │ ├── Effect.java │ │ │ ├── EffectOfCover.java │ │ │ ├── EffectOfNon.java │ │ │ ├── EffectOfRealBothWay.java │ │ │ ├── EffectOfRealOneWay.java │ │ │ ├── EffectOfSlide.java │ │ │ ├── IDownload.java │ │ │ ├── IReaderManager.java │ │ │ ├── OnReaderWatcherListener.java │ │ │ ├── PageChangedCallback.java │ │ │ ├── PageDrawingCallback.java │ │ │ ├── ReaderResolve.java │ │ │ └── ReaderView.java │ └── res │ │ ├── drawable │ │ └── paper.png │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── glong │ └── reader │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | signingConfigs { 5 | release { 6 | keyAlias 'test' 7 | keyPassword 'test123' 8 | storeFile file('../debug.jks') 9 | storePassword 'test123' 10 | } 11 | 12 | debug { 13 | keyAlias 'test' 14 | keyPassword 'test123' 15 | storeFile file('../debug.jks') 16 | storePassword 'test123' 17 | } 18 | } 19 | 20 | compileSdkVersion 28 21 | defaultConfig { 22 | applicationId "com.glong.sample" 23 | minSdkVersion 21 24 | targetSdkVersion 28 25 | versionCode 1 26 | versionName "1.0" 27 | testInstrumentationRunner "android.support.reader_bg.runner.AndroidJUnitRunner" 28 | } 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation fileTree(include: ['*.jar'], dir: 'libs') 39 | implementation 'com.android.support:appcompat-v7:28.0.0' 40 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 41 | implementation 'com.android.support:design:28.0.0' 42 | testImplementation 'junit:junit:4.12' 43 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 44 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 45 | implementation 'com.android.support:recyclerview-v7:28.0.0' 46 | implementation project(':reader') 47 | 48 | // 网络请求 49 | implementation 'com.squareup.okhttp3:okhttp:3.11.0' 50 | // 网络请求框架Retrofit2 51 | implementation 'com.squareup.retrofit2:retrofit:2.4.0' 52 | implementation 'com.squareup.retrofit2:converter-gson:2.4.0' 53 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' 54 | // 异步框架RxJava2 RxAndroid2 55 | implementation 'io.reactivex.rxjava2:rxjava:2.2.7' 56 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 57 | } 58 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/androidTest/java/com/glong/sample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented reader_bg, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under reader_bg. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.glong.sample", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/Constant.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample; 2 | 3 | /** 4 | * Created by Garrett on 2018/11/20. 5 | * contact me krouky@outlook.com 6 | */ 7 | public class Constant { 8 | public static final String STR_CONTENT = " 写作的时间长了之后,就有很多的小伙伴,都会问我,说,格格,你平时上班很忙、每天上下班通勤时间那么长,怎么还有时间做那么多事情呢?\n" + 9 | "\n" + 10 | "我的答案,特别简单,就是早起。

我是从去春节时,开始每天5点起床的,到现在1年多了。\n" + 11 | "\n" + 12 | "好多人都对我能坚持早起,表示很佩服。

其实,我自己并不觉得,这是一件多么厉害的事情。

因为有很多人,都是早起一族,甚至比我起得更早。\n" + 13 | "\n" + 14 | "不过在早起之后,我发现,我对时间的管理确实更加高效了。\n" + 15 | "\n" + 16 | "那么,我当初为什么想要早起呢?

\n" + 17 | "\n" + 18 | "其实,我当时是每天6:30起。但是我发现,自己想做的事情特别多,比如读书、写作、跑步、学习,可是老是觉得时间不够用。

我仔细想了想,每一样我又都不想放弃。

\n" + 19 | "\n" + 20 | "于是,我就读了一些时间管理的书,比如李笑来老师的书《把时间当作朋友》,还听了一些时间管理的课,比如古典老师的橙子学院的课,希望能解决时间管理的问题。\n" + 21 | "\n" + 22 | "在读书和听课后,我就果断决定,我要每天早上5点起床。睡觉时间,依然是23:00—24:00,并且,尽量在23:00前睡觉。\n" + 23 | "\n" + 24 | "当时,特别巧,有一个朋友也想每天5点起。

于是,我们俩就商量好,每天早上起床后,就在我的读书会群里,发一个表情,算是早起打卡了。\n" + 25 | "\n" + 26 | "刚开始早起时,身体也不适应,

会犯困,

但慢慢身体就适应了。\n" + 27 | "\n" + 28 | "但是,我的意志也不是一直特别强大,也有过退缩、想放弃的时候。就是去年10月,我刚报无戒老师的写作训练营的时候。\n" + 29 | "\n" + 30 | "那时候北京5点起来,外面漆黑一片,天气也有点凉,我就有点不想早起了,不自觉就把生物钟,给调成了5:30起。\n" + 31 | "\n" + 32 | "但是,为了多一些学习的时间,

我还是继续咬牙坚持每天5点起来听课。

每天一边听课、一边记笔记。\n" + 33 | "\n" + 34 | "就这样,我算是经受了革命的考验,养成了早起的习惯。\n" + 35 | "\n" + 36 | "早起之后,你会发现,早起真的是非常棒的一件事。通过早起,你会发现你多出了很多的时间。而且,这个时间是整块的时间,能够不被别人打扰,非常高效地去做事情。\n" + 37 | "\n" + 38 | "如果你也想早起的话,那么,早起之后,你做什么,一定要提前规划好,才不至于浪费时间。\n" + 39 | "\n" + 40 | "有人喜欢早起后写作,写作其实是输出性的事情。\n" + 41 | "\n" + 42 | "我个人呢,更愿意在早起后做一些输入类的、重要而不紧急的事情。\n" + 43 | "\n" + 44 | "总结一下,我在早起后、上班之前的这段时间,大概有4个小时的时间。我除了听课学习外,还做了以下5件事情:\n" + 45 | "\n" + 46 | "(1)听音频:除了听无戒老师的写作课,我还购买了其他的音频产品,比如一些听书的APP,光听就可以了。我通常是在清晨起床后,一边洗漱、一边听音频,充分利用碎片时间。\n" + 47 | "\n" + 48 | "(2)读书:我除了利用通勤时间,在地铁上读书外,也会在早起后读书。如果早起后读书,就会有意识地读一些比较重要或比较烧脑的书。\n" + 49 | "\n" + 50 | "(3)写作:有时,我清晨起来,会有一些写作灵感。那我就会把这些灵感写下来,当作写作素材。等到中午休息或者晚上的时间,再把文章写出来。\n" + 51 | "\n" + 52 | "(4)跑步:在平时,我主要是早起去跑步。跑步的时候,我还会听书。每周跑步3次左右。冬天呢,则改为中午跑步。\n" + 53 | "\n" + 54 | "(5)学习:我每天都会背100个单词、看CHINA DAILY的新闻,以保持英语的语感。\n" + 55 | "\n" + 56 | "我用实践证明,只要你想,每个人都可以充分利用自己的时间,和时间作朋友,过上高效地生活。\n" + 57 | "\n" + 58 | "有些人可能觉得,我每天5起床,除了上班外,还要读书、跑步、写作、学英语,这样做太辛苦了。\n" + 59 | "\n" + 60 | "但是,我一点也不觉得辛苦。因为这些都是我真心喜欢、真心想做的事情,如果不去做,我反而会觉得很难受,好像生活中少了点什么。\n" + 61 | "\n" + 62 | "我知道,对于有些人而言,可能觉我做得这些努力,都微不足道。\n" + 63 | "\n" + 64 | "但是,对于我而言,正是这些点滴的努力,让我觉得特别地踏实。\n" + 65 | "\n" + 66 | "通过不断突破自己的舒适区,我感受到自己在一点点变化、一点点进步。虽然这变化和进步是那么地缓慢、不易察觉,但却可以被我真实的感知,让我走向一个越来越宽广的世界。\n" + 67 | "\n" + 68 | "我知道,自己智商不高,没有天赋,甚至有时会显得特别笨拙。所以,我就需要默默地努力,只有这样,才有可能实现我在疲惫生活中的英雄梦想。\n" + 69 | "\n" + 70 | "据说,陈道明老师在某个节目担任嘉宾时,节目里一群打鼓的孩子,当场受到了一些人的质疑。陈道明老师就以自己成名前7年跑龙套的经历,来鼓舞那些孩子们,对他们说:“你们一定要努力,但千万别着急。”\n" + 71 | "\n" + 72 | "我相信,凡是今天来听我分享的小伙伴,也一定都有自己的梦想。
\n" + 73 | "\n" + 74 | "那么,现在,我也借花献佛,把同样的一句话,送给所有坚持努力的你:“你一定要努力,但千万别着急”。
\n" + 75 | "\n" + 76 | "这就是我今天的分享。
\n" + 77 | "\n" + 78 | "我希望在未来的日子里,能和大家一起努力、一起交流、一起进步,慢慢成长为更好的自己。
\n" + 79 | "\n" + 80 | "谢谢大家!
\n" + 81 | "\n" + 82 | "作者:修心的格格
\n" + 83 | "链接:https://www.jianshu.com/p/49fd60b5393f
\n" + 84 | "來源:简书
\n" + 85 | "简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。"; 86 | 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | 9 | import com.glong.sample.activities.CustomReaderActivity; 10 | import com.glong.sample.activities.ExtendReaderActivity; 11 | import com.glong.sample.activities.NormalReaderActivity; 12 | import com.glong.sample.activities.PaperActivity; 13 | import com.glong.sample.activities.SimpleReaderActivity; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | @Override 18 | protected void onCreate(@Nullable Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | 22 | findViewById(R.id.simple_reader).setOnClickListener(new View.OnClickListener() { 23 | @Override 24 | public void onClick(View v) { 25 | startActivity(new Intent(MainActivity.this, SimpleReaderActivity.class)); 26 | } 27 | }); 28 | 29 | findViewById(R.id.normal_reader).setOnClickListener(new View.OnClickListener() { 30 | @Override 31 | public void onClick(View v) { 32 | startActivity(new Intent(MainActivity.this, NormalReaderActivity.class)); 33 | } 34 | }); 35 | 36 | findViewById(R.id.extend_reader).setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View v) { 39 | startActivity(new Intent(MainActivity.this, ExtendReaderActivity.class)); 40 | } 41 | }); 42 | 43 | findViewById(R.id.custom_reader).setOnClickListener(new View.OnClickListener() { 44 | @Override 45 | public void onClick(View v) { 46 | startActivity(new Intent(MainActivity.this, CustomReaderActivity.class)); 47 | } 48 | }); 49 | 50 | findViewById(R.id.paper_reader).setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | startActivity(new Intent(MainActivity.this, PaperActivity.class)); 54 | } 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * Created by Garrett on 2018/12/6. 7 | * contact me krouky@outlook.com 8 | */ 9 | public class MyApplication extends Application { 10 | 11 | private static Application sApplication; 12 | 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | sApplication = this; 17 | } 18 | 19 | public static Application getApplication() { 20 | return sApplication; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/ScreenUtils.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.provider.Settings; 6 | import android.view.Window; 7 | import android.view.WindowManager; 8 | 9 | /** 10 | * Created by ${Garrett} on 2018/9/8. 11 | * Contact me krouky@outlook.com 12 | */ 13 | public class ScreenUtils { 14 | /** 15 | * 获取屏幕宽度 16 | * 17 | * @param context 18 | * @return 19 | */ 20 | public static int getScreenWidth(Context context) { 21 | int width; 22 | WindowManager wm = (WindowManager) context 23 | .getSystemService(Context.WINDOW_SERVICE); 24 | width = wm.getDefaultDisplay().getWidth(); 25 | return width; 26 | } 27 | 28 | /** 29 | * 获取屏幕高度 30 | * 31 | * @param context 32 | * @return 33 | */ 34 | public static int getScreenHeight(Context context) { 35 | int height; 36 | WindowManager wm = (WindowManager) context 37 | .getSystemService(Context.WINDOW_SERVICE); 38 | height = wm.getDefaultDisplay().getHeight(); 39 | return height; 40 | } 41 | 42 | 43 | /** 44 | * 获得系统亮度 45 | */ 46 | public static int getSystemBrightness(Context context) { 47 | int systemBrightness = 0; 48 | try { 49 | systemBrightness = Settings.System.getInt(context.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS); 50 | } catch (Settings.SettingNotFoundException e) { 51 | e.printStackTrace(); 52 | } 53 | return systemBrightness; 54 | } 55 | 56 | /** 57 | * 改变App当前Window亮度 58 | * 59 | * @param brightness a 60 | */ 61 | public static void changeAppBrightness(Activity activity, int brightness) { 62 | Window window = activity.getWindow(); 63 | WindowManager.LayoutParams lp = window.getAttributes(); 64 | if (brightness == -1) { 65 | lp.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE; 66 | } else { 67 | lp.screenBrightness = (brightness <= 0 ? 1 : brightness) / 255f; 68 | } 69 | window.setAttributes(lp); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/activities/CustomReaderActivity.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.activities; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.widget.Toast; 9 | 10 | import com.glong.reader.widget.ReaderView; 11 | import com.glong.sample.R; 12 | import com.glong.sample.custom.MyReaderResolve; 13 | import com.glong.sample.entry.ChapterContentBean; 14 | import com.glong.sample.entry.ChapterItemBean; 15 | import com.glong.sample.localtest.LocalServer; 16 | 17 | import java.util.List; 18 | 19 | public class CustomReaderActivity extends AppCompatActivity { 20 | 21 | private ReaderView mReaderView; 22 | private ReaderView.Adapter mAdapter; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_custom_reader); 28 | 29 | initReaderView(); 30 | initData(); 31 | } 32 | 33 | private void initData() { 34 | LocalServer.getChapterList("1", new LocalServer.OnResponseCallback() { 35 | @Override 36 | public void onSuccess(List chapters) { 37 | mAdapter.setChapterList(chapters); 38 | mAdapter.notifyDataSetChanged(); 39 | } 40 | 41 | @Override 42 | public void onError(Exception e) { 43 | 44 | } 45 | }); 46 | } 47 | 48 | private void initReaderView() { 49 | mReaderView = findViewById(R.id.custom_reader_view); 50 | ReaderView.ReaderManager readerManager = new ReaderView.ReaderManager(); 51 | mReaderView.setReaderManager(readerManager); 52 | 53 | mAdapter = new ReaderView.Adapter() { 54 | @Override 55 | public String obtainCacheKey(ChapterItemBean chapterItemBean) { 56 | return chapterItemBean.getChapterId() + "custom"; 57 | } 58 | 59 | @Override 60 | public String obtainChapterName(ChapterItemBean chapterItemBean) { 61 | return "龟虽寿";//chapterItemBean.getChapterName(); 62 | } 63 | 64 | @Override 65 | public String obtainChapterContent(ChapterContentBean chapterContentBean) { 66 | return "曹操

" + "神龟虽寿,犹有竟时;

" + "腾蛇乘雾,终为土灰。

" 67 | + "老骥伏枥,志在千里;

" + "烈士暮年,壮心不已。

" 68 | + "盈缩之期,不但在天;

" + "养怡之福,可得永年。

" 69 | + "幸甚至哉,歌以咏志。

" 70 | + 71 | "曹操

" + "神龟虽寿,犹有竟时;

" + "腾蛇乘雾,终为土灰。

" 72 | + "老骥伏枥,志在千里;

" + "烈士暮年,壮心不已。

" 73 | + "盈缩之期,不但在天;

" + "养怡之福,可得永年。

" 74 | + "幸甚至哉,歌以咏志。

" 75 | + 76 | "曹操

" + "神龟虽寿,犹有竟时;

" + "腾蛇乘雾,终为土灰。

" 77 | + "老骥伏枥,志在千里;

" + "烈士暮年,壮心不已。

" 78 | + "盈缩之期,不但在天;

" + "养怡之福,可得永年。

" 79 | + "幸甚至哉,歌以咏志。

" 80 | + 81 | "曹操

" + "神龟虽寿,犹有竟时;

" + "腾蛇乘雾,终为土灰。

" 82 | + "老骥伏枥,志在千里;

" + "烈士暮年,壮心不已。

" 83 | + "盈缩之期,不但在天;

" + "养怡之福,可得永年。

" 84 | + "幸甚至哉,歌以咏志。

"; 85 | } 86 | 87 | @Override 88 | public ChapterContentBean downLoad(ChapterItemBean chapterItemBean) { 89 | return LocalServer.syncDownloadContent(chapterItemBean); 90 | } 91 | }; 92 | 93 | mReaderView.setAdapter(mAdapter); 94 | readerManager.setCustomReaderResolve(new MyReaderResolve()); 95 | mReaderView.setLineSpace(50); 96 | 97 | View firstPageView = LayoutInflater.from(this).inflate(R.layout.first_page_view_layout, null); 98 | firstPageView.findViewById(R.id.imageView).setOnClickListener(new View.OnClickListener() { 99 | @Override 100 | public void onClick(View v) { 101 | Toast.makeText(CustomReaderActivity.this, "You clicked ImageView!", Toast.LENGTH_SHORT).show(); 102 | } 103 | }); 104 | mReaderView.addView(firstPageView, ReaderView.ChildInPage.FIRST_PAGE); 105 | 106 | View lastPageView = LayoutInflater.from(this).inflate(R.layout.last_page_view_layout, null); 107 | lastPageView.findViewById(R.id.imageView2).setOnClickListener(new View.OnClickListener() { 108 | @Override 109 | public void onClick(View v) { 110 | startActivity(new Intent(CustomReaderActivity.this, WebViewActivity.class)); 111 | } 112 | }); 113 | mReaderView.addView(lastPageView, ReaderView.ChildInPage.LAST_PAGE); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/activities/NormalReaderActivity.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | import android.widget.SeekBar; 7 | import android.widget.Toast; 8 | 9 | import com.glong.reader.TurnStatus; 10 | import com.glong.reader.widget.EffectOfCover; 11 | import com.glong.reader.widget.EffectOfNon; 12 | import com.glong.reader.widget.EffectOfRealBothWay; 13 | import com.glong.reader.widget.EffectOfRealOneWay; 14 | import com.glong.reader.widget.EffectOfSlide; 15 | import com.glong.reader.widget.PageChangedCallback; 16 | import com.glong.reader.widget.ReaderView; 17 | import com.glong.sample.R; 18 | import com.glong.sample.entry.ChapterContentBean; 19 | import com.glong.sample.entry.ChapterItemBean; 20 | import com.glong.sample.localtest.LocalServer; 21 | 22 | import java.util.List; 23 | 24 | public class NormalReaderActivity extends AppCompatActivity implements View.OnClickListener { 25 | 26 | private ReaderView mReaderView; 27 | private ReaderView.Adapter mAdapter; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_normal_reader); 33 | 34 | initReader(); 35 | initData(); 36 | initViews(); 37 | } 38 | 39 | private void initViews() { 40 | SeekBar textSizeSeek = findViewById(R.id.text_size_seek_bar); 41 | textSizeSeek.setMax(100); 42 | textSizeSeek.setProgress(mReaderView.getTextSize()); 43 | textSizeSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 44 | @Override 45 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 46 | mReaderView.setTextSize(progress); 47 | } 48 | 49 | @Override 50 | public void onStartTrackingTouch(SeekBar seekBar) { 51 | 52 | } 53 | 54 | @Override 55 | public void onStopTrackingTouch(SeekBar seekBar) { 56 | } 57 | }); 58 | 59 | SeekBar lineSpaceSeek = findViewById(R.id.line_space_seek_bar); 60 | lineSpaceSeek.setMax(100); 61 | lineSpaceSeek.setProgress(mReaderView.getLineSpace()); 62 | lineSpaceSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 63 | @Override 64 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 65 | mReaderView.setLineSpace(seekBar.getProgress()); 66 | } 67 | 68 | @Override 69 | public void onStartTrackingTouch(SeekBar seekBar) { 70 | 71 | } 72 | 73 | @Override 74 | public void onStopTrackingTouch(SeekBar seekBar) { 75 | } 76 | }); 77 | 78 | findViewById(R.id.reader_bg_0).setOnClickListener(this); 79 | findViewById(R.id.reader_bg_1).setOnClickListener(this); 80 | findViewById(R.id.reader_bg_2).setOnClickListener(this); 81 | findViewById(R.id.reader_bg_3).setOnClickListener(this); 82 | 83 | findViewById(R.id.effect_real_one_way).setOnClickListener(this); 84 | findViewById(R.id.effect_real_both_way).setOnClickListener(this); 85 | findViewById(R.id.effect_cover).setOnClickListener(this); 86 | findViewById(R.id.effect_slide).setOnClickListener(this); 87 | findViewById(R.id.effect_non).setOnClickListener(this); 88 | findViewById(R.id.effect_default).setOnClickListener(this); 89 | } 90 | 91 | private void initReader() { 92 | final String userId = "123"; 93 | mReaderView = findViewById(R.id.normal_reader_view); 94 | 95 | final ReaderView.ReaderManager readerManager = new ReaderView.ReaderManager(); 96 | mReaderView.setReaderManager(readerManager); 97 | 98 | mAdapter = new ReaderView.Adapter() { 99 | 100 | @Override 101 | public String obtainCacheKey(ChapterItemBean chapterItemBean) { 102 | return chapterItemBean.getChapterId() + userId; 103 | } 104 | 105 | @Override 106 | public String obtainChapterName(ChapterItemBean chapterItemBean) { 107 | return chapterItemBean.getChapterName(); 108 | } 109 | 110 | @Override 111 | public String obtainChapterContent(ChapterContentBean contentBean) { 112 | return contentBean.getChapterContent(); 113 | } 114 | 115 | /** 116 | * 这个方法运行在子线程中,同步返回章节内容 117 | */ 118 | @Override 119 | public ChapterContentBean downLoad(ChapterItemBean chapterItemBean) { 120 | return LocalServer.syncDownloadContent(chapterItemBean); 121 | } 122 | }; 123 | 124 | mReaderView.setAdapter(mAdapter); 125 | 126 | mReaderView.setPageChangedCallback(new PageChangedCallback() { 127 | @Override 128 | public TurnStatus toPrevPage() { 129 | TurnStatus turnStatus = readerManager.toPrevPage(); 130 | if (turnStatus == TurnStatus.NO_PREV_CHAPTER) { 131 | Toast.makeText(NormalReaderActivity.this, "没有上一页啦", Toast.LENGTH_SHORT).show(); 132 | } 133 | return turnStatus; 134 | } 135 | 136 | @Override 137 | public TurnStatus toNextPage() { 138 | TurnStatus turnStatus = readerManager.toNextPage(); 139 | if (turnStatus == TurnStatus.NO_NEXT_CHAPTER) { 140 | Toast.makeText(NormalReaderActivity.this, "没有下一页啦", Toast.LENGTH_SHORT).show(); 141 | } 142 | return turnStatus; 143 | } 144 | }); 145 | } 146 | 147 | private void initData() { 148 | /* 149 | * 获取章节列表 150 | */ 151 | LocalServer.getChapterList("123", new LocalServer.OnResponseCallback() { 152 | @Override 153 | public void onSuccess(final List chapters) { 154 | mAdapter.setChapterList(chapters); 155 | mAdapter.notifyDataSetChanged(); 156 | } 157 | 158 | @Override 159 | public void onError(Exception e) { 160 | 161 | } 162 | }); 163 | } 164 | 165 | @Override 166 | public void onClick(View v) { 167 | switch (v.getId()) { 168 | case R.id.reader_bg_0: 169 | mReaderView.setBackgroundColor(getResources().getColor(R.color.reader_bg_0)); 170 | break; 171 | case R.id.reader_bg_1: 172 | mReaderView.setBackgroundColor(getResources().getColor(R.color.reader_bg_1)); 173 | break; 174 | case R.id.reader_bg_2: 175 | mReaderView.setBackgroundColor(getResources().getColor(R.color.reader_bg_2)); 176 | break; 177 | case R.id.reader_bg_3: 178 | mReaderView.setBackgroundColor(getResources().getColor(R.color.reader_bg_3)); 179 | break; 180 | case R.id.effect_real_one_way: 181 | case R.id.effect_default: 182 | mReaderView.setEffect(new EffectOfRealOneWay(this)); 183 | break; 184 | case R.id.effect_real_both_way: 185 | mReaderView.setEffect(new EffectOfRealBothWay(this)); 186 | break; 187 | case R.id.effect_cover: 188 | mReaderView.setEffect(new EffectOfCover(this)); 189 | break; 190 | case R.id.effect_slide: 191 | mReaderView.setEffect(new EffectOfSlide(this)); 192 | break; 193 | case R.id.effect_non: 194 | mReaderView.setEffect(new EffectOfNon(this)); 195 | break; 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/activities/PaperActivity.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.widget.SeekBar; 6 | 7 | import com.glong.sample.R; 8 | import com.glong.sample.view.MyView; 9 | 10 | public class PaperActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_paper); 16 | 17 | final MyView myView = findViewById(R.id.myView); 18 | SeekBar seekBar = findViewById(R.id.seekBar); 19 | 20 | seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 21 | @Override 22 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 23 | myView.setPaperIntensity(progress * 1f / seekBar.getMax()); 24 | } 25 | 26 | @Override 27 | public void onStartTrackingTouch(SeekBar seekBar) { 28 | 29 | } 30 | 31 | @Override 32 | public void onStopTrackingTouch(SeekBar seekBar) { 33 | 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/activities/SimpleReaderActivity.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.glong.reader.widget.ReaderView; 7 | import com.glong.sample.R; 8 | import com.glong.sample.entry.ChapterContentBean; 9 | import com.glong.sample.entry.ChapterItemBean; 10 | import com.glong.sample.localtest.LocalServer; 11 | 12 | import java.util.List; 13 | 14 | public class SimpleReaderActivity extends AppCompatActivity { 15 | 16 | private ReaderView.Adapter mAdapter; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_simple_reader); 22 | 23 | initReader(); 24 | initData(); 25 | } 26 | 27 | private void initReader() { 28 | final String userId = "123"; 29 | ReaderView readerView = findViewById(R.id.simple_reader_view); 30 | 31 | ReaderView.ReaderManager readerManager = new ReaderView.ReaderManager(); 32 | readerView.setReaderManager(readerManager); 33 | 34 | mAdapter = new ReaderView.Adapter() { 35 | 36 | @Override 37 | public String obtainCacheKey(ChapterItemBean chapterItemBean) { 38 | return chapterItemBean.getChapterId() + userId; 39 | } 40 | 41 | @Override 42 | public String obtainChapterName(ChapterItemBean chapterItemBean) { 43 | return chapterItemBean.getChapterName(); 44 | } 45 | 46 | @Override 47 | public String obtainChapterContent(ChapterContentBean contentBean) { 48 | return contentBean.getChapterContent(); 49 | } 50 | 51 | /** 52 | * 这个方法运行在子线程中,同步返回章节内容 53 | */ 54 | @Override 55 | public ChapterContentBean downLoad(ChapterItemBean chapterItemBean) { 56 | return LocalServer.syncDownloadContent(chapterItemBean); 57 | } 58 | }; 59 | 60 | readerView.setAdapter(mAdapter); 61 | } 62 | 63 | private void initData() { 64 | /* 65 | * 获取章节列表 66 | */ 67 | LocalServer.getChapterList("123", new LocalServer.OnResponseCallback() { 68 | @Override 69 | public void onSuccess(final List chapters) { 70 | mAdapter.setChapterList(chapters); 71 | mAdapter.notifyDataSetChanged(); 72 | } 73 | 74 | @Override 75 | public void onError(Exception e) { 76 | 77 | } 78 | }); 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/activities/WebViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | import android.webkit.WebChromeClient; 7 | import android.webkit.WebView; 8 | import android.widget.ProgressBar; 9 | 10 | import com.glong.sample.R; 11 | 12 | public class WebViewActivity extends AppCompatActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_web_view); 18 | WebView webView = findViewById(R.id.web_view); 19 | final ProgressBar progressBar = findViewById(R.id.progressBar); 20 | 21 | webView.loadUrl("https://github.com/GarrettLance/Reader"); 22 | 23 | webView.setWebChromeClient(new WebChromeClient() { 24 | @Override 25 | public void onProgressChanged(WebView view, int newProgress) { 26 | //显示进度条 27 | progressBar.setProgress(newProgress); 28 | if (newProgress == 100) { 29 | //加载完毕隐藏进度条 30 | progressBar.setVisibility(View.GONE); 31 | } 32 | super.onProgressChanged(view, newProgress); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/adpater/CatalogueAdapter.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.adpater; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import com.glong.sample.R; 11 | import com.glong.sample.entry.ChapterItemBean; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Created by Garrett on 2018/12/5. 17 | * contact me krouky@outlook.com 18 | */ 19 | public class CatalogueAdapter extends RecyclerView.Adapter { 20 | 21 | private List mList; 22 | 23 | private OnItemClickListener mOnItemClickListener; 24 | 25 | public CatalogueAdapter(OnItemClickListener listener) { 26 | this.mOnItemClickListener = listener; 27 | } 28 | 29 | @NonNull 30 | @Override 31 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { 32 | return new ViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_catalogue, viewGroup, false)); 33 | } 34 | 35 | @Override 36 | public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) { 37 | viewHolder.mTextView.setText(mList.get(i).getChapterName()); 38 | } 39 | 40 | @Override 41 | public int getItemCount() { 42 | return mList == null ? 0 : mList.size(); 43 | } 44 | 45 | public void setList(List list) { 46 | this.mList = list; 47 | notifyDataSetChanged(); 48 | } 49 | 50 | class ViewHolder extends RecyclerView.ViewHolder { 51 | 52 | TextView mTextView; 53 | 54 | ViewHolder(@NonNull View itemView) { 55 | super(itemView); 56 | mTextView = (TextView) itemView; 57 | mTextView.setOnClickListener(new View.OnClickListener() { 58 | @Override 59 | public void onClick(View v) { 60 | if (mOnItemClickListener != null) { 61 | mOnItemClickListener.onClicked(getAdapterPosition()); 62 | } 63 | } 64 | }); 65 | } 66 | } 67 | 68 | public interface OnItemClickListener { 69 | void onClicked(int position); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/adpater/MyReaderAdapter.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.adpater; 2 | 3 | import com.glong.reader.util.Request; 4 | import com.glong.reader.widget.ReaderView; 5 | import com.glong.sample.api.Api; 6 | import com.glong.sample.entry.ChapterContent2Bean; 7 | import com.glong.sample.entry.ChapterItemBean; 8 | 9 | /** 10 | * Created by Garrett on 2018/11/28. 11 | * contact me krouky@outlook.com 12 | */ 13 | public class MyReaderAdapter extends ReaderView.Adapter { 14 | @Override 15 | public String obtainCacheKey(ChapterItemBean chapterItemBean) { 16 | return chapterItemBean.getChapterId() + "userId"; 17 | } 18 | 19 | @Override 20 | public String obtainChapterName(ChapterItemBean chapterItemBean) { 21 | return chapterItemBean.getChapterName(); 22 | } 23 | 24 | @Override 25 | public String obtainChapterContent(ChapterContent2Bean ChapterContent2Bean) { 26 | return ChapterContent2Bean.getResult().getData().get(0).getSub2(); 27 | } 28 | 29 | @Override 30 | public ChapterContent2Bean downLoad(ChapterItemBean chapterItemBean) { 31 | return null;//LocalServer.syncDownloadContent(chapterItemBean); 32 | } 33 | 34 | @Override 35 | public Request requestParams(ChapterItemBean chapterItemBean) { 36 | return new Request.Builder().baseUrl("http://apis.juhe.cn/goodbook/query") 37 | .addUrlParams("key", Api.KEY).addUrlParams("catalog_id", chapterItemBean.getChapterId()) 38 | .addUrlParams("pn", 0).addUrlParams("rn", 1) 39 | .get().build(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/api/Api.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.api; 2 | 3 | import retrofit2.Retrofit; 4 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 5 | import retrofit2.converter.gson.GsonConverterFactory; 6 | 7 | /** 8 | * Created by Garrett on 2018/12/6. 9 | * contact me krouky@outlook.com 10 | */ 11 | public class Api { 12 | 13 | public static final String KEY = "d96c770925b95fb3d0404a4df89c3aa5"; 14 | private static Api sApi; 15 | private Retrofit retrofit; 16 | 17 | private Api() { 18 | initRetrofit(); 19 | } 20 | 21 | public static Api getInstance() { 22 | if (sApi == null) { 23 | synchronized (Api.class) { 24 | if (sApi == null) { 25 | sApi = new Api(); 26 | } 27 | } 28 | } 29 | return sApi; 30 | } 31 | 32 | private void initRetrofit() { 33 | retrofit = new Retrofit.Builder().baseUrl("http://apis.juhe.cn") 34 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 35 | .addConverterFactory(GsonConverterFactory.create()) 36 | .build(); 37 | } 38 | 39 | public T getService(Class clazz) { 40 | return retrofit.create(clazz); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/api/Service.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.api; 2 | 3 | import com.glong.sample.entry.ChapterContentBean; 4 | import com.glong.sample.entry.ChapterItemBean; 5 | import com.glong.sample.entry.Result; 6 | import com.glong.sample.entry.ResultData; 7 | 8 | import java.util.List; 9 | 10 | import io.reactivex.Observable; 11 | import retrofit2.http.GET; 12 | import retrofit2.http.Query; 13 | 14 | /** 15 | * Created by Garrett on 2018/12/6. 16 | * contact me krouky@outlook.com 17 | */ 18 | public interface Service { 19 | 20 | /** 21 | * 获取图书目录 22 | */ 23 | @GET("/goodbook/catalog") 24 | Observable>> catalog(@Query("key") String key); 25 | 26 | /** 27 | * 获取图书内容 28 | */ 29 | @GET("/goodbook/query") 30 | Observable>>> query(@Query("key") String key, 31 | @Query("catalog_id") String catalog, 32 | @Query("pn") int start, @Query("rn") int end); 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/custom/MyReaderResolve.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.custom; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.support.annotation.Nullable; 9 | 10 | import com.glong.reader.textconvert.TextBreakUtils; 11 | import com.glong.reader.widget.ReaderResolve; 12 | import com.glong.sample.MyApplication; 13 | import com.glong.sample.R; 14 | 15 | /** 16 | * Created by Garrett on 2018/12/6. 17 | * contact me krouky@outlook.com 18 | */ 19 | public class MyReaderResolve extends ReaderResolve { 20 | 21 | private int mChapterTitleWidth; 22 | 23 | @Override 24 | public void calculateChapterParameter() { 25 | int paddingLeft = mReaderConfig.getPadding()[0]; 26 | int paddingTop = mReaderConfig.getPadding()[1]; 27 | int paddingRight = mReaderConfig.getPadding()[2]; 28 | int paddingBottom = mReaderConfig.getPadding()[3]; 29 | //除边缘区域外的区域宽度 30 | int usableWidth = mAreaWidth - paddingLeft - paddingRight; 31 | //除边缘区域外的区域高度 32 | int usableHeight = mAreaHeight - paddingTop - paddingBottom; 33 | 34 | //计算大标题转换为行List 35 | mChapterNameLines = TextBreakUtils.breakToLineList(mTitle, usableHeight, 0, mChapterPaint); 36 | 37 | //计算第0页大章节标题所占宽度 38 | float titleWidth = mChapterPaint.measureText("正"); 39 | float bodyWidth = mMainBodyPaint.measureText("正"); 40 | mChapterTitleWidth = (int) (mChapterNameLines.size() * (titleWidth + mReaderConfig.getLineSpace()) * 1.5f); 41 | 42 | //第0页 能展示多少行正文 43 | mLineNumPerPageInFirstPage = (int) ((usableWidth - mChapterTitleWidth) / (bodyWidth + mReaderConfig.getLineSpace())); 44 | 45 | //正常情况下(除第0页)一页能展示多少行 46 | mLineNumPerPageWithoutFirstPage = (int) (usableWidth / (bodyWidth + mReaderConfig.getLineSpace())); 47 | 48 | //正文所占行数 49 | if (!android.text.TextUtils.isEmpty(mContent)) { 50 | mShowLines = TextBreakUtils.breakToLineList(mContent, usableHeight, 0, mMainBodyPaint); 51 | } 52 | //计算总页数 53 | if (mShowLines != null) { 54 | mPageSum = mShowLines.size() - mLineNumPerPageInFirstPage <= 0 ? 55 | 1 : ((mShowLines.size() - mLineNumPerPageInFirstPage) - 1) / Math.max(1, mLineNumPerPageWithoutFirstPage) + 1 + 1; 56 | calculatePageIndex(); 57 | } else { 58 | mPageSum = -1; 59 | } 60 | } 61 | 62 | @Override 63 | protected void maybeDrawChapterTitle(Canvas canvas) { 64 | if (mPageIndex == 0) { 65 | mChapterPaint.setFakeBoldText(true); 66 | Paint.FontMetrics pfm = mChapterPaint.getFontMetrics(); 67 | 68 | float x = mAreaWidth - (mReaderConfig.getPadding()[2] + mChapterPaint.measureText("正") + mReaderConfig.getLineSpace() / 2); 69 | for (int i = 0; i < mChapterNameLines.size(); i++) { 70 | float y = mReaderConfig.getPadding()[1] + pfm.bottom - pfm.top; 71 | String lineData = mChapterNameLines.get(i).getLineData(); 72 | for (int j = 0; j < lineData.length(); j++) { 73 | canvas.drawText(lineData.substring(j, j + 1), x, y, mChapterPaint); 74 | y += (pfm.bottom - pfm.top); 75 | } 76 | x -= (mChapterPaint.measureText("正") + mReaderConfig.getLineSpace()); 77 | } 78 | } 79 | } 80 | 81 | @Override 82 | protected void drawMainBodyArea(Canvas canvas) { 83 | //画正文 84 | if (mShowLines == null || mShowLines.size() <= 0) 85 | return; 86 | 87 | Paint.FontMetrics fm = mMainBodyPaint.getFontMetrics(); 88 | float textHeight = fm.bottom - fm.top; 89 | 90 | float x = mAreaWidth - (mReaderConfig.getPadding()[2] + mMainBodyPaint.measureText("正") + mReaderConfig.getLineSpace() / 2); 91 | if (mPageIndex == 0) { 92 | x -= mChapterTitleWidth; 93 | } 94 | 95 | for (int i = 0; i < (mPageIndex == 0 ? mLineNumPerPageInFirstPage : mLineNumPerPageWithoutFirstPage); i++) { 96 | //要画的这行在整章中的索引(那么在整章中前面就有 索引数 行数) 97 | float y = mReaderConfig.getPadding()[1] + textHeight; 98 | 99 | int lineInChapter; 100 | if (mPageIndex == 0) { 101 | lineInChapter = i; 102 | } else { 103 | lineInChapter = mLineNumPerPageWithoutFirstPage * (mPageIndex - 1) + mLineNumPerPageInFirstPage + i; 104 | } 105 | //如果是最后一页可能当前行数超过总行数 106 | if (lineInChapter > mShowLines.size() - 1) { 107 | break; 108 | } 109 | 110 | String lineData = mShowLines.get(lineInChapter).getLineData(); 111 | for (int j = 0; j < lineData.length(); j++) { 112 | canvas.drawText(lineData.substring(j, j + 1), x, y, mMainBodyPaint); 113 | y += textHeight; 114 | } 115 | x -= (mReaderConfig.getLineSpace() + mMainBodyPaint.measureText("正")); 116 | } 117 | } 118 | 119 | @Override 120 | protected void drawMarginTitle(Canvas canvas, @Nullable String title, int x, int y, Paint paint) { 121 | // super.drawMarginTitle(canvas, title, x, y, paint); 122 | } 123 | 124 | @Override 125 | protected void drawPercentage(Canvas canvas, float percent, float x, float y, Paint marginPaint) { 126 | // super.drawPercentage(canvas, percent, x, y, marginPaint); 127 | // 不画页码只需要注掉super.drawPagination()既可. 128 | } 129 | 130 | @Override 131 | protected void drawBattery(Canvas canvas, Paint batteryPaint, int battery, Rect outRect, Rect innerRect) { 132 | Bitmap batteryBp = BitmapFactory.decodeResource(MyApplication.getApplication().getResources(), R.drawable.battery); 133 | int center = outRect.top + outRect.height() / 2; 134 | Rect rect = new Rect(outRect.left, center - 30, outRect.left + 60, center + 30); 135 | canvas.drawBitmap(batteryBp, null, rect, null); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/entry/ChapterContent2Bean.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.entry; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by Garrett on 2018/12/6. 7 | * contact me krouky@outlook.com 8 | */ 9 | public class ChapterContent2Bean { 10 | 11 | /** 12 | * resultcode : 200 13 | * reason : Success 14 | * result : {"data":[{"title":"微观历史","catalog":"中国文学 历史 ","tags":"一本好书 中国历史 历史好书推荐 历史日记 当代中国 ","sub1":"《微观历史》:1957\u20141965","sub2":"《微观历史》是中国作家祝伟坡著作的一部历史笔记。作品从个人视角出发,记录了1957年至1965年期间中国大跃进人民公社化运动、整风整社运动、四清运动等建国初期的历史事件。《微观历史》以震撼人心的坦率直播个人命运的进退,用亲身经历的事实投影社会历史的浮沉,堪称一部微观的共和国史。\n《微观历史》作者祝伟坡于1935年出生在河北省魏县,是河北师范大学副教授、中国历史学者。他早年曾研究外国文学,后主要研究中国近现代史,经常在多处报刊发表学术论文和散文,文学代表作有《踏步集》、《微观历史》等。\n新中国建国初期,那是一段怎样的岁月呢?小时候,爷爷奶奶偶尔会给我们讲那个时代的故事;历史课本上,我们也能了解个大概。但是,那段岁月始终如一个蒙着面纱的女子,朦朦胧胧的,瞧不见她的真容。而《微观历史》,却帮助我们轻轻撩起了那层神秘的面纱,再现了那个时期真实的社会风貌。\n《微观历史》以日记的形式,记录了历次运动中的所见所闻、所思所感,带领我们走进那段不平凡的岁月,让我们重温历史,体会当时人们迷茫的心理和曲折坎坷的人生,感受时代脉搏的跳动。这些字字真诚的日记于细微处见证了一个时代的理想、迷失和荒诞,它既是火热年代平民的心灵史,更是时代的活化石。《微观历史》中大量珍贵的历史照片,让我们直击历史场景,让那段岁月的故事如电影般播放在我们的眼前,清清楚楚。\n翻开《微观历史》,阅读珍藏的日记,让我们体味到一个青年的心灵独白。拂去历史的尘埃,《微观历史》再现了一个国家的时代风貌。著名学者单正平说:\u201c这是一个普通知识分子的心路历程。一切都似曾相识,一切都陌生新鲜,一切都惊心动魄,一切都微波细澜。家史即国史,心史即信史。大一统下的破碎人生,最宜用日记也只有用日记做记录。时过境未迁,后生堪镜鉴。\u201d\n《微观历史》真实地为读者们展现了那段不平凡年代的历史,对于年轻一代正确地总结过去、珍惜今天,有着十分积极的意义。\n《微观历史》","img":"http://apis.juhe.cn/goodbook/img/93b469cf22ef86b40ce84a6c63b95e82.jpg","reading":"2737人阅读","online":"京东商城:http://item.jd.com/11174489.html 当当网:http://product.dangdang.com/product.aspx?product_id=23181422 亚马逊:http://www.amazon.cn/%E5%BE%AE%E8%A7%82%E5%8E%86%E5%8F%B2-1957-1965-%E7%A5%9D%E4%BC%9F%E5%9D%A1/dp/B00B20MZ3M/ref=sr_1_1","bytime":"2013年8月6日"}],"totalNum":"39","pn":0,"rn":"1"} 15 | * error_code : 0 16 | */ 17 | 18 | private String resultcode; 19 | private String reason; 20 | private ResultBean result; 21 | private int error_code; 22 | 23 | public String getResultcode() { 24 | return resultcode; 25 | } 26 | 27 | public void setResultcode(String resultcode) { 28 | this.resultcode = resultcode; 29 | } 30 | 31 | public String getReason() { 32 | return reason; 33 | } 34 | 35 | public void setReason(String reason) { 36 | this.reason = reason; 37 | } 38 | 39 | public ResultBean getResult() { 40 | return result; 41 | } 42 | 43 | public void setResult(ResultBean result) { 44 | this.result = result; 45 | } 46 | 47 | public int getError_code() { 48 | return error_code; 49 | } 50 | 51 | public void setError_code(int error_code) { 52 | this.error_code = error_code; 53 | } 54 | 55 | public static class ResultBean { 56 | /** 57 | * data : [{"title":"微观历史","catalog":"中国文学 历史 ","tags":"一本好书 中国历史 历史好书推荐 历史日记 当代中国 ","sub1":"《微观历史》:1957\u20141965","sub2":"《微观历史》是中国作家祝伟坡著作的一部历史笔记。作品从个人视角出发,记录了1957年至1965年期间中国大跃进人民公社化运动、整风整社运动、四清运动等建国初期的历史事件。《微观历史》以震撼人心的坦率直播个人命运的进退,用亲身经历的事实投影社会历史的浮沉,堪称一部微观的共和国史。\n《微观历史》作者祝伟坡于1935年出生在河北省魏县,是河北师范大学副教授、中国历史学者。他早年曾研究外国文学,后主要研究中国近现代史,经常在多处报刊发表学术论文和散文,文学代表作有《踏步集》、《微观历史》等。\n新中国建国初期,那是一段怎样的岁月呢?小时候,爷爷奶奶偶尔会给我们讲那个时代的故事;历史课本上,我们也能了解个大概。但是,那段岁月始终如一个蒙着面纱的女子,朦朦胧胧的,瞧不见她的真容。而《微观历史》,却帮助我们轻轻撩起了那层神秘的面纱,再现了那个时期真实的社会风貌。\n《微观历史》以日记的形式,记录了历次运动中的所见所闻、所思所感,带领我们走进那段不平凡的岁月,让我们重温历史,体会当时人们迷茫的心理和曲折坎坷的人生,感受时代脉搏的跳动。这些字字真诚的日记于细微处见证了一个时代的理想、迷失和荒诞,它既是火热年代平民的心灵史,更是时代的活化石。《微观历史》中大量珍贵的历史照片,让我们直击历史场景,让那段岁月的故事如电影般播放在我们的眼前,清清楚楚。\n翻开《微观历史》,阅读珍藏的日记,让我们体味到一个青年的心灵独白。拂去历史的尘埃,《微观历史》再现了一个国家的时代风貌。著名学者单正平说:\u201c这是一个普通知识分子的心路历程。一切都似曾相识,一切都陌生新鲜,一切都惊心动魄,一切都微波细澜。家史即国史,心史即信史。大一统下的破碎人生,最宜用日记也只有用日记做记录。时过境未迁,后生堪镜鉴。\u201d\n《微观历史》真实地为读者们展现了那段不平凡年代的历史,对于年轻一代正确地总结过去、珍惜今天,有着十分积极的意义。\n《微观历史》","img":"http://apis.juhe.cn/goodbook/img/93b469cf22ef86b40ce84a6c63b95e82.jpg","reading":"2737人阅读","online":"京东商城:http://item.jd.com/11174489.html 当当网:http://product.dangdang.com/product.aspx?product_id=23181422 亚马逊:http://www.amazon.cn/%E5%BE%AE%E8%A7%82%E5%8E%86%E5%8F%B2-1957-1965-%E7%A5%9D%E4%BC%9F%E5%9D%A1/dp/B00B20MZ3M/ref=sr_1_1","bytime":"2013年8月6日"}] 58 | * totalNum : 39 59 | * pn : 0 60 | * rn : 1 61 | */ 62 | 63 | private String totalNum; 64 | private int pn; 65 | private String rn; 66 | private List data; 67 | 68 | public String getTotalNum() { 69 | return totalNum; 70 | } 71 | 72 | public void setTotalNum(String totalNum) { 73 | this.totalNum = totalNum; 74 | } 75 | 76 | public int getPn() { 77 | return pn; 78 | } 79 | 80 | public void setPn(int pn) { 81 | this.pn = pn; 82 | } 83 | 84 | public String getRn() { 85 | return rn; 86 | } 87 | 88 | public void setRn(String rn) { 89 | this.rn = rn; 90 | } 91 | 92 | public List getData() { 93 | return data; 94 | } 95 | 96 | public void setData(List data) { 97 | this.data = data; 98 | } 99 | 100 | public static class DataBean { 101 | /** 102 | * title : 微观历史 103 | * catalog : 中国文学 历史 104 | * tags : 一本好书 中国历史 历史好书推荐 历史日记 当代中国 105 | * sub1 : 《微观历史》:1957—1965 106 | * sub2 : 《微观历史》是中国作家祝伟坡著作的一部历史笔记。作品从个人视角出发,记录了1957年至1965年期间中国大跃进人民公社化运动、整风整社运动、四清运动等建国初期的历史事件。《微观历史》以震撼人心的坦率直播个人命运的进退,用亲身经历的事实投影社会历史的浮沉,堪称一部微观的共和国史。 107 | 《微观历史》作者祝伟坡于1935年出生在河北省魏县,是河北师范大学副教授、中国历史学者。他早年曾研究外国文学,后主要研究中国近现代史,经常在多处报刊发表学术论文和散文,文学代表作有《踏步集》、《微观历史》等。 108 | 新中国建国初期,那是一段怎样的岁月呢?小时候,爷爷奶奶偶尔会给我们讲那个时代的故事;历史课本上,我们也能了解个大概。但是,那段岁月始终如一个蒙着面纱的女子,朦朦胧胧的,瞧不见她的真容。而《微观历史》,却帮助我们轻轻撩起了那层神秘的面纱,再现了那个时期真实的社会风貌。 109 | 《微观历史》以日记的形式,记录了历次运动中的所见所闻、所思所感,带领我们走进那段不平凡的岁月,让我们重温历史,体会当时人们迷茫的心理和曲折坎坷的人生,感受时代脉搏的跳动。这些字字真诚的日记于细微处见证了一个时代的理想、迷失和荒诞,它既是火热年代平民的心灵史,更是时代的活化石。《微观历史》中大量珍贵的历史照片,让我们直击历史场景,让那段岁月的故事如电影般播放在我们的眼前,清清楚楚。 110 | 翻开《微观历史》,阅读珍藏的日记,让我们体味到一个青年的心灵独白。拂去历史的尘埃,《微观历史》再现了一个国家的时代风貌。著名学者单正平说:“这是一个普通知识分子的心路历程。一切都似曾相识,一切都陌生新鲜,一切都惊心动魄,一切都微波细澜。家史即国史,心史即信史。大一统下的破碎人生,最宜用日记也只有用日记做记录。时过境未迁,后生堪镜鉴。” 111 | 《微观历史》真实地为读者们展现了那段不平凡年代的历史,对于年轻一代正确地总结过去、珍惜今天,有着十分积极的意义。 112 | 《微观历史》 113 | * img : http://apis.juhe.cn/goodbook/img/93b469cf22ef86b40ce84a6c63b95e82.jpg 114 | * reading : 2737人阅读 115 | * online : 京东商城:http://item.jd.com/11174489.html 当当网:http://product.dangdang.com/product.aspx?product_id=23181422 亚马逊:http://www.amazon.cn/%E5%BE%AE%E8%A7%82%E5%8E%86%E5%8F%B2-1957-1965-%E7%A5%9D%E4%BC%9F%E5%9D%A1/dp/B00B20MZ3M/ref=sr_1_1 116 | * bytime : 2013年8月6日 117 | */ 118 | 119 | private String title; 120 | private String catalog; 121 | private String tags; 122 | private String sub1; 123 | private String sub2; 124 | private String img; 125 | private String reading; 126 | private String online; 127 | private String bytime; 128 | 129 | public String getTitle() { 130 | return title; 131 | } 132 | 133 | public void setTitle(String title) { 134 | this.title = title; 135 | } 136 | 137 | public String getCatalog() { 138 | return catalog; 139 | } 140 | 141 | public void setCatalog(String catalog) { 142 | this.catalog = catalog; 143 | } 144 | 145 | public String getTags() { 146 | return tags; 147 | } 148 | 149 | public void setTags(String tags) { 150 | this.tags = tags; 151 | } 152 | 153 | public String getSub1() { 154 | return sub1; 155 | } 156 | 157 | public void setSub1(String sub1) { 158 | this.sub1 = sub1; 159 | } 160 | 161 | public String getSub2() { 162 | return sub2; 163 | } 164 | 165 | public void setSub2(String sub2) { 166 | this.sub2 = sub2; 167 | } 168 | 169 | public String getImg() { 170 | return img; 171 | } 172 | 173 | public void setImg(String img) { 174 | this.img = img; 175 | } 176 | 177 | public String getReading() { 178 | return reading; 179 | } 180 | 181 | public void setReading(String reading) { 182 | this.reading = reading; 183 | } 184 | 185 | public String getOnline() { 186 | return online; 187 | } 188 | 189 | public void setOnline(String online) { 190 | this.online = online; 191 | } 192 | 193 | public String getBytime() { 194 | return bytime; 195 | } 196 | 197 | public void setBytime(String bytime) { 198 | this.bytime = bytime; 199 | } 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/entry/ChapterContentBean.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.entry; 2 | 3 | /** 4 | * Created by Garrett on 2018/11/17. 5 | * contact me krouky@outlook.com 6 | */ 7 | public class ChapterContentBean { 8 | 9 | private String chapterId; 10 | 11 | private String chapterName; 12 | 13 | private String chapterContent; 14 | 15 | public String getChapterId() { 16 | return chapterId; 17 | } 18 | 19 | public void setChapterId(String chapterId) { 20 | this.chapterId = chapterId; 21 | } 22 | 23 | public String getChapterName() { 24 | return chapterName; 25 | } 26 | 27 | public void setChapterName(String chapterName) { 28 | this.chapterName = chapterName; 29 | } 30 | 31 | public String getChapterContent() { 32 | return chapterContent; 33 | } 34 | 35 | public void setChapterContent(String chapterContent) { 36 | this.chapterContent = chapterContent; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/entry/ChapterItemBean.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.entry; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by Garrett on 2018/11/17. 7 | * contact me krouky@outlook.com 8 | */ 9 | public class ChapterItemBean { 10 | 11 | @SerializedName("id") 12 | private String chapterId; 13 | 14 | @SerializedName("catalog") 15 | private String chapterName; 16 | 17 | public String getChapterId() { 18 | return chapterId; 19 | } 20 | 21 | public void setChapterId(String chapterId) { 22 | this.chapterId = chapterId; 23 | } 24 | 25 | public String getChapterName() { 26 | return chapterName; 27 | } 28 | 29 | public void setChapterName(String chapterName) { 30 | this.chapterName = chapterName; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/entry/Result.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.entry; 2 | 3 | /** 4 | * Created by Garrett on 2018/12/6. 5 | * contact me krouky@outlook.com 6 | */ 7 | public class Result { 8 | private int resultcode; 9 | private String reason; 10 | private T result; 11 | 12 | public int getResultcode() { 13 | return resultcode; 14 | } 15 | 16 | public void setResultcode(int resultcode) { 17 | this.resultcode = resultcode; 18 | } 19 | 20 | public String getReason() { 21 | return reason; 22 | } 23 | 24 | public void setReason(String reason) { 25 | this.reason = reason; 26 | } 27 | 28 | public T getResult() { 29 | return result; 30 | } 31 | 32 | public void setResult(T result) { 33 | this.result = result; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/entry/ResultData.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.entry; 2 | 3 | /** 4 | * Created by Garrett on 2018/12/6. 5 | * contact me krouky@outlook.com 6 | */ 7 | public class ResultData { 8 | 9 | private T data; 10 | 11 | public T getData() { 12 | return data; 13 | } 14 | 15 | public void setData(T data) { 16 | this.data = data; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/localtest/LocalServer.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.localtest; 2 | 3 | 4 | import com.glong.sample.entry.ChapterContentBean; 5 | import com.glong.sample.entry.ChapterItemBean; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by Garrett on 2018/11/23. 12 | * contact me krouky@outlook.com 13 | */ 14 | public class LocalServer { 15 | 16 | /** 17 | * 模拟网络请求 18 | * 19 | * @param bookId 书ID 20 | */ 21 | public static void getChapterList(String bookId, final OnResponseCallback onResponseCallback) { 22 | new Thread(new Runnable() { 23 | @Override 24 | public void run() { 25 | List chapters = new ArrayList<>(); 26 | while (chapters.size() < 100) { 27 | ChapterItemBean chapterItemBean = new ChapterItemBean(); 28 | chapterItemBean.setChapterId("id" + (chapters.size() + 1)); 29 | chapterItemBean.setChapterName("第" + (chapters.size() + 1) + "章 名言名句"); 30 | chapters.add(chapterItemBean); 31 | } 32 | onResponseCallback.onSuccess(chapters); 33 | } 34 | }).start(); 35 | } 36 | 37 | /** 38 | * 模拟网络同步下载 39 | * 40 | * @return 章节内容 41 | */ 42 | public static ChapterContentBean syncDownloadContent(ChapterItemBean chapterItemBean) { 43 | ChapterContentBean chapterContentBean = new ChapterContentBean(); 44 | chapterContentBean.setChapterId(chapterItemBean.getChapterId()); 45 | chapterContentBean.setChapterName(chapterItemBean.getChapterName()); 46 | StringBuilder contentBuilder = new StringBuilder(); 47 | while (contentBuilder.length() < 1000) { 48 | // contentBuilder.append(String.valueOf(chapterItemBean.getChapterName() + "

" + 49 | // "  

" + 50 | // "  世界上一成不变的东西,只有“任何事物都是在不断变化的”这条真理。

—— 斯里兰卡

" + 51 | // "  

" + 52 | // "  我需要三件东西:爱情友谊和图书。然而这三者之间何其相通!炽热的爱情可以充实图书的内容,图书又是人们最忠实的朋友。

—— 蒙田

" + 53 | // "  

" + 54 | // "  生活有度,人生添寿。

—— 书摘

")); 55 | contentBuilder.append(LocalConstant.CONTENT); 56 | 57 | } 58 | chapterContentBean.setChapterContent(contentBuilder.toString()); 59 | return chapterContentBean; 60 | } 61 | 62 | public interface OnResponseCallback { 63 | void onSuccess(List chapters); 64 | 65 | void onError(Exception e); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/view/MenuView.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.app.Activity; 8 | import android.content.Context; 9 | import android.support.annotation.NonNull; 10 | import android.support.annotation.Nullable; 11 | import android.support.v7.widget.Toolbar; 12 | import android.util.AttributeSet; 13 | import android.view.LayoutInflater; 14 | import android.view.MotionEvent; 15 | import android.view.View; 16 | import android.widget.FrameLayout; 17 | 18 | import com.glong.sample.R; 19 | 20 | /** 21 | * Created by Garrett on 2018/11/29. 22 | * contact me krouky@outlook.com 23 | */ 24 | public class MenuView extends FrameLayout { 25 | 26 | private boolean isShowing; 27 | private static final int ANIMATION_DURATION = 200; 28 | 29 | private Toolbar mToolbar; 30 | private FrameLayout mBottomMenu; 31 | 32 | public MenuView(@NonNull Context context) { 33 | this(context, null); 34 | } 35 | 36 | public MenuView(@NonNull Context context, @Nullable AttributeSet attrs) { 37 | this(context, attrs, 0); 38 | } 39 | 40 | public MenuView(@NonNull final Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | LayoutInflater.from(context).inflate(R.layout.reader_menu_layout, this); 43 | mToolbar = findViewById(R.id.tool_bar); 44 | mBottomMenu = findViewById(R.id.bottom_menu); 45 | 46 | ((Activity) context).getWindow().getDecorView().addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 47 | @Override 48 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 49 | ((Activity) context).getWindow().getDecorView().removeOnLayoutChangeListener(this); 50 | mToolbar.setTranslationY(-mToolbar.getHeight()); 51 | mBottomMenu.setTranslationY(mBottomMenu.getHeight()); 52 | setVisibility(GONE); 53 | } 54 | }); 55 | setClickable(true); 56 | } 57 | 58 | private AnimatorSet mShowAnim; 59 | 60 | public void show() { 61 | setVisibility(View.VISIBLE); 62 | post(new Runnable() { 63 | @Override 64 | public void run() { 65 | showAnim(); 66 | } 67 | }); 68 | } 69 | 70 | private void showAnim() { 71 | if (mShowAnim == null) { 72 | ObjectAnimator toolbarAnim = ObjectAnimator.ofFloat(mToolbar, "translationY", -mToolbar.getHeight(), 0); 73 | ObjectAnimator bottomMenuAnim = ObjectAnimator.ofFloat(mBottomMenu, "translationY", mBottomMenu.getHeight(), 0); 74 | mShowAnim = new AnimatorSet(); 75 | mShowAnim.play(toolbarAnim).with(bottomMenuAnim); 76 | mShowAnim.setDuration(ANIMATION_DURATION); 77 | mShowAnim.addListener(new AnimatorListenerAdapter() { 78 | @Override 79 | public void onAnimationEnd(Animator animation) { 80 | isShowing = true; 81 | } 82 | }); 83 | } 84 | if (!mShowAnim.isRunning()) { 85 | mShowAnim.start(); 86 | } 87 | } 88 | 89 | private AnimatorSet mDismissAnim; 90 | 91 | public void dismiss() { 92 | if (mDismissAnim == null) { 93 | ObjectAnimator toolbarAnim = ObjectAnimator.ofFloat(mToolbar, "translationY", -mToolbar.getHeight()); 94 | ObjectAnimator bottomMenuAnim = ObjectAnimator.ofFloat(mBottomMenu, "translationY", mBottomMenu.getHeight()); 95 | mDismissAnim = new AnimatorSet(); 96 | mDismissAnim.play(toolbarAnim).with(bottomMenuAnim); 97 | mDismissAnim.setDuration(ANIMATION_DURATION); 98 | mDismissAnim.addListener(new AnimatorListenerAdapter() { 99 | @Override 100 | public void onAnimationEnd(Animator animation) { 101 | setVisibility(View.GONE); 102 | isShowing = false; 103 | } 104 | }); 105 | } 106 | if (!mDismissAnim.isRunning()) { 107 | mDismissAnim.start(); 108 | } 109 | } 110 | 111 | public boolean isShowing() { 112 | return isShowing; 113 | } 114 | 115 | private boolean tempShowing; 116 | 117 | @Override 118 | public boolean onTouchEvent(MotionEvent event) { 119 | switch (event.getAction()) { 120 | case MotionEvent.ACTION_DOWN: 121 | tempShowing = isShowing; 122 | break; 123 | case MotionEvent.ACTION_UP: 124 | if (tempShowing && isShowing) 125 | dismiss(); 126 | break; 127 | } 128 | return super.onTouchEvent(event); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/view/MyView.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.BitmapShader; 7 | import android.graphics.Canvas; 8 | import android.graphics.Matrix; 9 | import android.graphics.Paint; 10 | import android.graphics.PorterDuff; 11 | import android.graphics.PorterDuffXfermode; 12 | import android.graphics.Shader; 13 | import android.support.annotation.Nullable; 14 | import android.util.AttributeSet; 15 | import android.view.View; 16 | 17 | import com.glong.sample.R; 18 | 19 | /** 20 | * Created by Garrett on 2018/12/7. 21 | * contact me krouky@outlook.com 22 | */ 23 | public class MyView extends View { 24 | private Paint paint = new Paint(); 25 | BitmapShader shader; 26 | Matrix matrix; 27 | 28 | public MyView(Context context) { 29 | this(context, null); 30 | } 31 | 32 | public MyView(Context context, @Nullable AttributeSet attrs) { 33 | this(context, attrs, 0); 34 | } 35 | 36 | public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 37 | super(context, attrs, defStyleAttr); 38 | Bitmap paperRes = BitmapFactory.decodeResource(getResources(), R.drawable.paper); 39 | shader = new BitmapShader(paperRes, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 40 | matrix = new Matrix(); 41 | 42 | shader.setLocalMatrix(matrix); 43 | paint.setShader(shader); 44 | paint.setAlpha(120); 45 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN)); 46 | } 47 | 48 | @Override 49 | protected void onDraw(Canvas canvas) { 50 | super.onDraw(canvas); 51 | canvas.drawPaint(paint); 52 | } 53 | 54 | public void setPaperIntensity(float noiseIntensity) { 55 | paint.setAlpha((int) (255 * noiseIntensity)); 56 | postInvalidate(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/view/SettingView.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.app.Activity; 7 | import android.content.Context; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.widget.FrameLayout; 15 | import android.widget.LinearLayout; 16 | 17 | import com.glong.sample.R; 18 | 19 | /** 20 | * Created by Garrett on 2018/12/5. 21 | * contact me krouky@outlook.com 22 | */ 23 | public class SettingView extends FrameLayout { 24 | 25 | private boolean isShowing; 26 | private LinearLayout mSettingContainer; 27 | 28 | public SettingView(@NonNull Context context) { 29 | this(context, null); 30 | } 31 | 32 | public SettingView(@NonNull Context context, @Nullable AttributeSet attrs) { 33 | this(context, attrs, 0); 34 | } 35 | 36 | public SettingView(@NonNull final Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 37 | super(context, attrs, defStyleAttr); 38 | LayoutInflater.from(context).inflate(R.layout.reader_setting_layout, this); 39 | mSettingContainer = findViewById(R.id.setting_container); 40 | 41 | ((Activity) context).getWindow().getDecorView().addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 42 | @Override 43 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 44 | ((Activity) context).getWindow().getDecorView().removeOnLayoutChangeListener(this); 45 | mSettingContainer.setTranslationY(mSettingContainer.getHeight()); 46 | setVisibility(GONE); 47 | } 48 | }); 49 | 50 | setOnClickListener(new OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | dismiss(); 54 | } 55 | }); 56 | } 57 | 58 | public void dismiss() { 59 | ObjectAnimator anim = ObjectAnimator.ofFloat(mSettingContainer, "translationY", mSettingContainer.getHeight()); 60 | anim.setDuration(200).start(); 61 | anim.addListener(new AnimatorListenerAdapter() { 62 | @Override 63 | public void onAnimationEnd(Animator animation) { 64 | setVisibility(View.GONE); 65 | isShowing = false; 66 | } 67 | }); 68 | } 69 | 70 | public void show() { 71 | setVisibility(View.VISIBLE); 72 | isShowing = true; 73 | post(new Runnable() { 74 | @Override 75 | public void run() { 76 | Log.d(SettingView.class.getSimpleName(), "showAnim translationY:" + mSettingContainer.getTranslationY()); 77 | ObjectAnimator.ofFloat(mSettingContainer, "translationY", 0) 78 | .setDuration(200).start(); 79 | } 80 | }); 81 | } 82 | 83 | public boolean isShowing() { 84 | return isShowing; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/glong/sample/view/SimpleOnSeekBarChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample.view; 2 | 3 | import android.widget.SeekBar; 4 | 5 | /** 6 | * Created by Garrett on 2018/12/5. 7 | * contact me krouky@outlook.com 8 | */ 9 | public class SimpleOnSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener { 10 | @Override 11 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 12 | 13 | } 14 | 15 | @Override 16 | public void onStartTrackingTouch(SeekBar seekBar) { 17 | 18 | } 19 | 20 | @Override 21 | public void onStopTrackingTouch(SeekBar seekBar) { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_read_categue_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xhdpi/ic_read_categue_day.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_read_categue_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xhdpi/ic_read_categue_night.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_read_comment_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xhdpi/ic_read_comment_day.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_read_comment_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xhdpi/ic_read_comment_night.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_read_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xhdpi/ic_read_day.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_read_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xhdpi/ic_read_night.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_read_setting_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xhdpi/ic_read_setting_day.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_read_setting_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xhdpi/ic_read_setting_night.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_read_categue_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xxhdpi/ic_read_categue_day.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_read_categue_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xxhdpi/ic_read_categue_night.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_read_comment_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xxhdpi/ic_read_comment_day.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_read_comment_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xxhdpi/ic_read_comment_night.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_read_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xxhdpi/ic_read_day.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_read_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xxhdpi/ic_read_night.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_read_setting_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xxhdpi/ic_read_setting_day.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_read_setting_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable-xxhdpi/ic_read_setting_night.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable/battery.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/progress_bar_states.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/reader_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable/reader_bg.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable/test1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/test2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/drawable/test2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_custom_reader.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_extend_reader.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | 20 | 21 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 48 | 49 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 32 | 33 | 42 | 43 | 52 | 53 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_normal_reader.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 28 | 32 | 33 | 42 | 43 | 52 | 53 | 62 | 63 | 72 | 73 | 74 | 75 | 79 | 80 | 89 | 90 | 99 | 100 | 110 | 111 | 112 | 113 | 117 | 118 | 127 | 128 | 137 | 138 | 148 | 149 | 150 | 151 | 152 | 156 | 157 | 164 | 165 | 172 | 173 | 174 | 175 | 179 | 180 | 187 | 188 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_paper.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_simple_reader.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_web_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/first_page_view_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_catalogue.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/layout/last_page_view_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 22 | 23 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/reader_menu_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 18 | 19 | 27 | 28 | 34 | 35 | 44 | 45 | 52 | 53 | 62 | 63 | 64 | 70 | 71 | 82 | 83 | 93 | 94 | 105 | 106 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /app/src/main/res/layout/reader_setting_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 20 | 21 | 28 | 29 | 35 | 36 | 37 | 38 | 43 | 44 | 51 | 52 | 59 | 60 | 67 | 68 | 75 | 76 | 83 | 84 | 85 | 90 | 91 | 98 | 99 | 105 | 106 | 107 | 112 | 113 | 120 | 121 | 127 | 128 | 129 | 134 | 135 | 142 | 143 | 152 | 153 | 162 | 163 | 172 | 173 | 182 | 183 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /app/src/main/res/menu/extend_reader_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/my_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2196F3 4 | #1976D2 5 | #2196F3536DFE 6 | 7 | 8 | #f7f3ef 9 | #d8c9aa 10 | #cce8cf 11 | #b69b94 12 | 13 | #FE282828 14 | #ffffff 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Sample 3 | Main2Activity 4 | Home 5 | Dashboard 6 | Notifications 7 | 8 | 分享 9 | 删除缓存 10 | 目录 11 | 设置 12 | 日/夜 13 | 评论 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/test/java/com/glong/sample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.glong.sample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit reader_bg, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | maven { 9 | name 'Google' 10 | url 'https://maven.google.com' 11 | } 12 | } 13 | 14 | dependencies { 15 | classpath 'com.android.tools.build:gradle:3.4.0' 16 | // classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' 17 | // classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 18 | classpath 'com.novoda:bintray-release:0.9' 19 | 20 | // NOTE: Do not place your application dependencies here; they belong 21 | // in the individual module build.gradle files 22 | } 23 | } 24 | 25 | allprojects { 26 | repositories { 27 | google() 28 | jcenter() 29 | maven { url 'https://jitpack.io' } 30 | } 31 | 32 | // tasks.withType(Javadoc) { 33 | // options.addStringOption('Xdoclint:none', '-quiet') //忽略javadoc错误 34 | // options{ 35 | // encoding "UTF-8" 36 | // charSet 'UTF-8' 37 | // links "http://docs.oracle.com/javase/7/docs/api" 38 | // } 39 | // } 40 | tasks.withType(Javadoc).all { 41 | enabled = false 42 | } 43 | } 44 | 45 | task clean(type: Delete) { 46 | delete rootProject.buildDir 47 | } 48 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 08 12:10:21 GMT+08:00 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /images/20181123_204344.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/20181123_204344.gif -------------------------------------------------------------------------------- /images/QQ截图20181123223124.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/QQ截图20181123223124.png -------------------------------------------------------------------------------- /images/QQ截图20181123224234.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/QQ截图20181123224234.png -------------------------------------------------------------------------------- /images/QQ截图20181123224412.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/QQ截图20181123224412.png -------------------------------------------------------------------------------- /images/QQ截图20181123224610.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/QQ截图20181123224610.png -------------------------------------------------------------------------------- /images/QQ截图20181123225002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/QQ截图20181123225002.png -------------------------------------------------------------------------------- /images/QQ截图20181123225319.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/QQ截图20181123225319.png -------------------------------------------------------------------------------- /images/addView1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/addView1.gif -------------------------------------------------------------------------------- /images/gushu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/gushu.png -------------------------------------------------------------------------------- /images/non.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/non.png -------------------------------------------------------------------------------- /images/pager0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/pager0.png -------------------------------------------------------------------------------- /images/pager1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/pager1.png -------------------------------------------------------------------------------- /images/pager2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/pager2.png -------------------------------------------------------------------------------- /images/video2gif_20181123_183236.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/video2gif_20181123_183236.gif -------------------------------------------------------------------------------- /images/微信图片_20181126135424.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/微信图片_20181126135424.jpg -------------------------------------------------------------------------------- /images/菜单.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/images/菜单.gif -------------------------------------------------------------------------------- /reader/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /reader/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //添加这两行 3 | //apply plugin: 'com.github.dcendents.android-maven' 4 | //apply plugin: 'com.jfrog.bintray' 5 | apply plugin: 'com.novoda.bintray-release' 6 | android { 7 | compileSdkVersion 28 8 | 9 | defaultConfig { 10 | minSdkVersion 21 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(include: ['*.jar'], dir: 'libs') 30 | implementation 'com.android.support:appcompat-v7:28.0.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | implementation 'com.google.code.gson:gson:2.8.5' 35 | implementation 'com.android.support:support-annotations:28.0.0' 36 | } 37 | 38 | ////项目主页 39 | //def siteUrl = 'https://github.com/GarrettLance/Reader' 40 | ////项目的git地址 41 | //def gitUrl = 'https://github.com/GarrettLance/Reader.git' 42 | ////发布到JCenter上的项目名字 43 | //def libName = "Reader" 44 | // 45 | ////发布到组织名称名字,必须填写 46 | //group = "com.glong.reader" 47 | //// 版本号,下次更新是只需要更改版本号即可 48 | //version = "1.0.0" 49 | ////上面配置后上传至JCenter后的编译路径是这样的: compile 'me.songning.CircleView:library:1.0.0' 50 | // 51 | ////生成源文件 52 | //task sourcesJar(type: Jar) { 53 | // from android.sourceSets.main.java.srcDirs 54 | // classifier = 'sources' 55 | //} 56 | // 57 | ////生成Javadoc文档 58 | //task javadoc(type: Javadoc) { 59 | // source = android.sourceSets.main.java.srcDirs 60 | // options.encoding = "utf-8" 61 | // classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 62 | // classpath += project.files(configurations.compile.files,android.getBootClasspath().join(File.pathSeparator)) 63 | //} 64 | // 65 | ////文档打包成jar 66 | //task javadocJar(type: Jar, dependsOn: javadoc) { 67 | // classifier = 'javadoc' 68 | // from javadoc.destinationDir 69 | //} 70 | // 71 | ////拷贝javadoc文件 72 | //task copyDoc(type: Copy) { 73 | // from "${buildDir}/docs/" 74 | // into "docs" 75 | //} 76 | // 77 | ////上传到JCenter所需要的源码文件 78 | //artifacts { 79 | // archives javadocJar 80 | // archives sourcesJar 81 | //} 82 | // 83 | //// 配置maven库,生成POM.xml文件 84 | //install { 85 | // repositories.mavenInstaller { 86 | // // This generates POM.xml with proper parameters 87 | // pom { 88 | // project { 89 | // packaging 'aar' 90 | // //项目描述,随意填 91 | // name 'A reader that is easy to integrate and extend..' 92 | // url siteUrl 93 | // licenses { 94 | // license { 95 | // //开源协议 96 | // name 'The Apache Software License, Version 2.0' 97 | // url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 98 | // } 99 | // } 100 | // developers { 101 | // developer { 102 | // //开发者的个人信息 103 | // id 'glong' 104 | // name 'GuoLong' 105 | // email 'krouky@outlook.com' 106 | // } 107 | // } 108 | // scm { 109 | // connection gitUrl 110 | // developerConnection gitUrl 111 | // url siteUrl 112 | // } 113 | // } 114 | // } 115 | // } 116 | //} 117 | // 118 | ////上传到JCenter 119 | //Properties properties = new Properties() 120 | //properties.load(project.rootProject.file('local.properties').newDataInputStream()) 121 | // 122 | //bintray { 123 | // user = properties.getProperty("bintray.user") //读取 local.properties 文件里面的 bintray.user 124 | // key = properties.getProperty("bintray.apikey") //读取 local.properties 文件里面的 bintray.apikey 125 | // configurations = ['archives'] 126 | // pkg { 127 | // //这里的repo值必须要和你创建Maven仓库的时候的名字一样 128 | // repo = "maven" 129 | // //发布到JCenter上的项目名字 130 | // name = "Reader" 131 | // //项目描述 132 | // desc = 'A reader that is easy to integrate and extend.' 133 | // websiteUrl = siteUrl 134 | // vcsUrl = gitUrl 135 | // licenses = ["Apache-2.0"] 136 | // publish = true 137 | // } 138 | //} 139 | // 140 | //javadoc { 141 | // options { 142 | // //如果你的项目里面有中文注释的话,必须将格式设置为UTF-8,不然会出现乱码 143 | // encoding "UTF-8" 144 | // charSet 'UTF-8' 145 | // author true 146 | // version true 147 | // links "http://docs.oracle.com/javase/7/docs/api" 148 | // } 149 | //} 150 | publish { 151 | userOrg = 'glong'//garrettlance //bintray注册的用户名(不是邮箱) 152 | groupId = 'com.glong.reader' 153 | artifactId = 'Reader' //项目名称 154 | publishVersion = '1.0.0' //版本号 155 | desc = 'A reader that is easy to integrate and extend.' //项目描述。可空。 156 | website = 'https://github.com/GarrettLance/Reader.git' //项目地址,一般填github地址。可空。 157 | } 158 | -------------------------------------------------------------------------------- /reader/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /reader/src/androidTest/java/com/glong/reader/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.glong.reader.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /reader/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/ReaderSparseBooleanArray.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader; 2 | 3 | import android.util.SparseBooleanArray; 4 | 5 | /** 6 | * Created by Garrett on 2018/11/20. 7 | * contact me krouky@outlook.com 8 | */ 9 | public class ReaderSparseBooleanArray { 10 | 11 | private SparseBooleanArray mSparseBooleanArray = new SparseBooleanArray(); 12 | 13 | public boolean get(int key) { 14 | return mSparseBooleanArray.get(key); 15 | } 16 | 17 | public void delete(int key) { 18 | synchronized (ReaderSparseBooleanArray.this) { 19 | 20 | mSparseBooleanArray.delete(key); 21 | } 22 | } 23 | 24 | public void put(int key, boolean value) { 25 | synchronized (ReaderSparseBooleanArray.this) { 26 | mSparseBooleanArray.put(key, value); 27 | } 28 | } 29 | 30 | public synchronized void clear() { 31 | synchronized (ReaderSparseBooleanArray.this) { 32 | mSparseBooleanArray.clear(); 33 | } 34 | } 35 | 36 | public int size() { 37 | synchronized (ReaderSparseBooleanArray.this) { 38 | return mSparseBooleanArray.size(); 39 | } 40 | } 41 | 42 | public boolean contains(int key) { 43 | synchronized (ReaderSparseBooleanArray.this) { 44 | for (int i = 0; i < size(); i++) { 45 | int indexKey = mSparseBooleanArray.keyAt(i); 46 | if (indexKey == key) { 47 | return true; 48 | } 49 | } 50 | return false; 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/TestBean.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader; 2 | 3 | /** 4 | * Created by Garrett on 2018/11/22. 5 | * contact me krouky@outlook.com 6 | */ 7 | public class TestBean { 8 | private int code; 9 | private String msg; 10 | private Data data; 11 | 12 | public int getCode() { 13 | return code; 14 | } 15 | 16 | public void setCode(int code) { 17 | this.code = code; 18 | } 19 | 20 | public String getMsg() { 21 | return msg; 22 | } 23 | 24 | public void setMsg(String msg) { 25 | this.msg = msg; 26 | } 27 | 28 | public Data getData() { 29 | return data; 30 | } 31 | 32 | public void setData(Data data) { 33 | this.data = data; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "TestBean{" + 39 | "code=" + code + 40 | ", msg='" + msg + '\'' + 41 | ", data=" + data + 42 | '}'; 43 | } 44 | 45 | public static class Data{ 46 | private String id; 47 | private String name; 48 | private String content; 49 | 50 | public String getId() { 51 | return id; 52 | } 53 | 54 | public void setId(String id) { 55 | this.id = id; 56 | } 57 | 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | public void setName(String name) { 63 | this.name = name; 64 | } 65 | 66 | public String getContent() { 67 | return content; 68 | } 69 | 70 | public void setContent(String content) { 71 | this.content = content; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return "Data{" + 77 | "id='" + id + '\'' + 78 | ", name='" + name + '\'' + 79 | ", content='" + content + '\'' + 80 | '}'; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/TestGson.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader; 2 | 3 | import com.google.gson.Gson; 4 | 5 | /** 6 | * Created by Garrett on 2018/11/21. 7 | * contact me krouky@outlook.com 8 | */ 9 | public class TestGson { 10 | 11 | public static String json = "{\n" + 12 | " \"code\":0,\n" + 13 | " \"msg\":\"success\",\n" + 14 | " \"data\":{\n" + 15 | " \"id\":\"123\",\n" + 16 | " \"name\":\"aldkf\",\n" + 17 | " \"content\":\"dlajfljaflfjlajldfjlasglajldfjafl\"\n" + 18 | " }\n" + 19 | "}"; 20 | 21 | public static void main(String[] args) { 22 | Gson gson = new Gson(); 23 | TestBean testBean = gson.fromJson(json, TestBean.class); 24 | System.out.println("testBean:" + testBean.toString()); 25 | 26 | System.out.println(Method.GET.toString()); 27 | } 28 | } 29 | 30 | enum Method { 31 | POST, 32 | GET 33 | } 34 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/TurnStatus.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader; 2 | 3 | /** 4 | * Created by Garrett on 2018/11/17. 5 | * contact me krouky@outlook.com 6 | */ 7 | public enum TurnStatus { 8 | 9 | /** 10 | * 未加载 11 | */ 12 | IDLE, 13 | 14 | /** 15 | * 加载成功 16 | */ 17 | LOAD_SUCCESS, 18 | 19 | /** 20 | * 加载失败 21 | */ 22 | LOAD_FAILURE, 23 | 24 | /** 25 | * 下载中 26 | */ 27 | DOWNLOADING, 28 | 29 | /** 30 | * 没有下一章 31 | */ 32 | NO_NEXT_CHAPTER, 33 | 34 | /** 35 | * 没有上一章 36 | */ 37 | NO_PREV_CHAPTER 38 | 39 | // /** 40 | // * 等待中,代表上次的下载还未完成 41 | // * 这个时候不处理翻页请求 42 | // */ 43 | // LOADING, 44 | 45 | // /** 46 | // * 没有下一页 47 | // */ 48 | // NO_NEXT_PAGE, 49 | // 50 | // /** 51 | // * 没有上一页 52 | // */ 53 | // NO_PREV_PAGE, 54 | 55 | } 56 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/cache/Cache.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.cache; 2 | 3 | import java.io.File; 4 | import java.lang.reflect.Type; 5 | 6 | /** 7 | * Created by Garrett on 2018/11/17. 8 | * contact me krouky@outlook.com 9 | */ 10 | public abstract class Cache { 11 | 12 | private File mCacheDir; 13 | 14 | /** 15 | * 缓存章节数(默认前面3章,后面3章) 16 | */ 17 | private int mCacheAmount = 3; 18 | 19 | public Cache(File cacheDir) { 20 | checkIsDirectory(cacheDir); 21 | this.mCacheDir = cacheDir; 22 | } 23 | 24 | private void checkIsDirectory(File cacheDir) { 25 | if (!cacheDir.isDirectory()) { 26 | throw new IllegalArgumentException("cacheDir must be a directory!"); 27 | } 28 | } 29 | 30 | public abstract void put(String key, String value); 31 | 32 | public abstract void put(String key, T t); 33 | 34 | public abstract String get(String key); 35 | 36 | public abstract T get(String key, Type clazz); 37 | 38 | public abstract boolean remove(String key); 39 | 40 | public abstract boolean removeAll(); 41 | 42 | /** 43 | * 指定key是否已经有缓存 44 | * 45 | * @param key 缓存key 46 | * @return 已经有缓存返回true,否则返回false 47 | */ 48 | public abstract boolean isCached(String key); 49 | 50 | public File getCacheDir() { 51 | return mCacheDir; 52 | } 53 | 54 | public void setCacheDir(File cacheDir) { 55 | checkIsDirectory(cacheDir); 56 | this.mCacheDir = cacheDir; 57 | } 58 | 59 | public int getCacheAmount() { 60 | return mCacheAmount; 61 | } 62 | 63 | public void setCacheAmount(int cacheAmount) { 64 | mCacheAmount = cacheAmount; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/cache/DiskCache.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.cache; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import java.io.File; 8 | import java.lang.reflect.Type; 9 | 10 | /** 11 | * Created by Garrett on 2018/11/17. 12 | * contact me krouky@outlook.com 13 | */ 14 | public class DiskCache extends Cache { 15 | 16 | private Gson mGson; 17 | 18 | public DiskCache(@NonNull File cacheDir) { 19 | super(cacheDir); 20 | mGson = new Gson(); 21 | } 22 | 23 | @Override 24 | public void put(String key, String value) { 25 | ACache.get(getCacheDir()).put(key, value); 26 | } 27 | 28 | @Override 29 | public void put(String key, T t) { 30 | put(key, mGson.toJson(t)); 31 | } 32 | 33 | @Override 34 | public String get(String key) { 35 | return ACache.get(getCacheDir()).getAsString(key); 36 | } 37 | 38 | @Override 39 | public T get(String key, Type type) { 40 | String cache = ACache.get(getCacheDir()).getAsString(key); 41 | return mGson.fromJson(cache, type); 42 | } 43 | 44 | @Override 45 | public boolean remove(String key) { 46 | return ACache.get(getCacheDir()).remove(key); 47 | } 48 | 49 | @Override 50 | public boolean removeAll() { 51 | boolean result = true; 52 | for (File childFile : getCacheDir().listFiles()) { 53 | if (!childFile.delete()) { 54 | result = false; 55 | } 56 | } 57 | return result; 58 | } 59 | 60 | @Override 61 | public boolean isCached(String key) { 62 | return get(key) != null; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/config/ColorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.config; 2 | 3 | import android.support.annotation.ColorInt; 4 | 5 | /** 6 | * Created by Garrett on 2018/11/18. 7 | * contact me krouky@outlook.com 8 | */ 9 | public class ColorsConfig { 10 | 11 | /** 12 | * 阅读器文字颜色 13 | */ 14 | private int mTextColor; 15 | 16 | /** 17 | * 画电池的画笔颜色 18 | */ 19 | private int mBatteryColor; 20 | 21 | public ColorsConfig() { 22 | } 23 | 24 | public ColorsConfig( @ColorInt int textColor, @ColorInt int batteryColor) { 25 | this.mTextColor = textColor; 26 | this.mBatteryColor = batteryColor; 27 | } 28 | 29 | public int getTextColor() { 30 | return mTextColor; 31 | } 32 | 33 | public void setTextColor(int textColor) { 34 | mTextColor = textColor; 35 | } 36 | 37 | public int getBatteryColor() { 38 | return mBatteryColor; 39 | } 40 | 41 | public void setBatteryColor(int batteryColor) { 42 | mBatteryColor = batteryColor; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/config/ReaderConfig.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.config; 2 | 3 | import android.graphics.Color; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Size; 6 | 7 | /** 8 | * Created by Garrett on 2018/11/17. 9 | * contact me krouky@outlook.com 10 | */ 11 | public class ReaderConfig { 12 | 13 | /** 14 | * 正文文字大小 15 | */ 16 | private int mTextSize; 17 | 18 | /** 19 | * 正文的边界 20 | */ 21 | private int[] mPadding; 22 | 23 | /** 24 | * 电池宽度和高度 25 | */ 26 | private int[] mBatteryWidthAndHeight; 27 | 28 | /** 29 | * 正文行间距 30 | */ 31 | private int mLineSpace; 32 | 33 | /** 34 | * 阅读界面所有颜色相关 35 | */ 36 | private ColorsConfig mColorsConfig; 37 | 38 | private ReaderConfig() { 39 | } 40 | 41 | private ReaderConfig(ReaderConfig config) { 42 | this.mBatteryWidthAndHeight = config.mBatteryWidthAndHeight; 43 | this.mLineSpace = config.mLineSpace; 44 | this.mPadding = config.mPadding; 45 | this.mTextSize = config.mTextSize; 46 | this.mColorsConfig = config.mColorsConfig; 47 | } 48 | 49 | public static ReaderConfig newInstance(ReaderConfig config) { 50 | return new ReaderConfig(config); 51 | } 52 | 53 | public int getTextSize() { 54 | return mTextSize; 55 | } 56 | 57 | public void setTextSize(int textSize) { 58 | mTextSize = textSize; 59 | } 60 | 61 | public int[] getPadding() { 62 | return mPadding; 63 | } 64 | 65 | public void setPadding(int[] padding) { 66 | mPadding = padding; 67 | } 68 | 69 | public int[] getBatteryWidthAndHeight() { 70 | return mBatteryWidthAndHeight; 71 | } 72 | 73 | public void setBatteryWidthAndHeight(int[] batteryWidthAndHeight) { 74 | mBatteryWidthAndHeight = batteryWidthAndHeight; 75 | } 76 | 77 | public int getLineSpace() { 78 | return mLineSpace; 79 | } 80 | 81 | public void setLineSpace(int lineSpace) { 82 | mLineSpace = lineSpace; 83 | } 84 | 85 | public ColorsConfig getColorsConfig() { 86 | return mColorsConfig; 87 | } 88 | 89 | public void setColorsConfig(ColorsConfig colorsConfig) { 90 | mColorsConfig = colorsConfig; 91 | } 92 | 93 | public static class Builder { 94 | 95 | private int mTextSize = 60; 96 | 97 | private int[] mPadding = {40, 70, 40, 70}; 98 | 99 | private int mLineSpace = 10; 100 | 101 | private int[] mBatteryWidthAndHeight = new int[]{60, 30}; 102 | 103 | private ColorsConfig mColorsConfig = new ColorsConfig(Color.BLACK, Color.BLACK); 104 | 105 | public Builder setTextSize(@Size int textSize) { 106 | this.mTextSize = textSize; 107 | return this; 108 | } 109 | 110 | public Builder setPadding(int[] padding) { 111 | this.mPadding = padding; 112 | return this; 113 | } 114 | 115 | public Builder setLineSpace(@Size int lineSpace) { 116 | this.mLineSpace = lineSpace; 117 | return this; 118 | } 119 | 120 | public Builder setColorsConfig(ColorsConfig colorsConfig) { 121 | this.mColorsConfig = colorsConfig; 122 | return this; 123 | } 124 | 125 | public Builder setBatteryWidthAndHeight(@NonNull int[] widthAndHeight) { 126 | if (widthAndHeight.length < 2) { 127 | throw new IllegalArgumentException("int[] widthAndHeight length must == 2"); 128 | } 129 | this.mBatteryWidthAndHeight = widthAndHeight; 130 | return this; 131 | } 132 | 133 | void apply(ReaderConfig readerConfig) { 134 | readerConfig.mTextSize = this.mTextSize; 135 | readerConfig.mPadding = this.mPadding; 136 | readerConfig.mLineSpace = this.mLineSpace; 137 | readerConfig.mColorsConfig = this.mColorsConfig; 138 | readerConfig.mBatteryWidthAndHeight = this.mBatteryWidthAndHeight; 139 | } 140 | 141 | public ReaderConfig build() { 142 | ReaderConfig readerConfig = new ReaderConfig(); 143 | apply(readerConfig); 144 | return readerConfig; 145 | } 146 | 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/textconvert/BreakResult.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.textconvert; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 对图书内容测量之后的封装 7 | */ 8 | public class BreakResult { 9 | 10 | /** 11 | * 测量了的字符数 12 | */ 13 | public int chartNums; 14 | 15 | /** 16 | * 是否满一行了 17 | */ 18 | public boolean isFullLine; 19 | 20 | /** 21 | * 是否以分段符标志结束的 22 | */ 23 | public boolean endWithWrapMark; 24 | 25 | /** 26 | * 测量了的字符数据 27 | */ 28 | public List showChars = null; 29 | 30 | public boolean hasData() { 31 | return showChars != null && showChars.size() > 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/textconvert/ShowChar.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.textconvert; 2 | 3 | import android.graphics.RectF; 4 | 5 | /** 6 | * 可见字符数据封装 7 | */ 8 | public class ShowChar { 9 | /** 10 | * 字符数据 11 | */ 12 | public char charData; 13 | 14 | /** 15 | * 当前字符是否被选中 16 | */ 17 | public boolean selected = false; 18 | 19 | /** 20 | * 字符宽度 21 | */ 22 | public float charWidth = 0; 23 | 24 | /** 25 | * 当前字符在当前章节中的索引 26 | */ 27 | public int indexInChapter; 28 | 29 | /** 30 | * 当前字符 上下左右四个位置 31 | */ 32 | public RectF rectF; 33 | // public Point topLeftPosition = null; 34 | // public Point topRightPosition = null; 35 | // public Point bottomLeftPosition = null; 36 | // public Point bottomRightPosition = null; 37 | 38 | } -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/textconvert/ShowLine.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.textconvert; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 显示的行数据 7 | */ 8 | public class ShowLine { 9 | 10 | public List charsData; 11 | 12 | /** 13 | * 当前行在当前章节所有行中的索引 14 | */ 15 | public int indexInChapter; 16 | 17 | /** 18 | * 是否满一行了 19 | */ 20 | public boolean isFullLine; 21 | 22 | /** 23 | * 是否以分段符标志结束的 24 | */ 25 | public boolean endWithWrapMark; 26 | 27 | public String getLineData() { 28 | String lineData = ""; 29 | if (charsData == null || charsData.size() == 0) return lineData; 30 | for (ShowChar c : charsData) { 31 | lineData = lineData + c.charData; 32 | } 33 | return lineData; 34 | } 35 | 36 | public int getLineFirstIndexInChapter() { 37 | if (charsData == null || charsData.size() <= 0) { 38 | return -1; 39 | } 40 | return charsData.get(0).indexInChapter; 41 | } 42 | 43 | public int getLineLastIndexInChapter() { 44 | if (charsData == null || charsData.size() <= 0) { 45 | return -1; 46 | } 47 | return charsData.get(charsData.size() - 1).indexInChapter; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/textconvert/TextBreakUtils.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.textconvert; 2 | 3 | import android.graphics.Paint; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | /** 11 | * 测量工具 12 | */ 13 | public class TextBreakUtils { 14 | 15 | public static Set sParagraph = new HashSet<>();//换行符 16 | public static Set sRetract = new HashSet<>();// 缩进符 17 | 18 | static { 19 | sParagraph.add("

"); 20 | sParagraph.add("
"); 21 | sParagraph.add("

"); 22 | sParagraph.add("\n"); 23 | 24 | sRetract.add(" ");// 这个类似空格但并不是空格的缩进符,长度刚好是一个汉字的长度(推荐使用这个作为缩进符) 25 | sRetract.add(" ");// 以空格为段开头 26 | } 27 | 28 | public static boolean isStartWithRetract(String src) { 29 | if (src == null) { 30 | return false; 31 | } else { 32 | return true; 33 | } 34 | } 35 | 36 | /** 37 | * 截取一行的Char 38 | * 39 | * @param cs 字符串源 40 | * @param measureWidth 行测量的最大宽度 41 | * @param textPadding 字符间距 42 | * @param paint 测量的画笔 43 | * @return 如果cs为空或者长度为0,返回null 44 | * -------------------- 45 | * TODO 46 | * -------------------- 47 | */ 48 | public static BreakResult breakText(int fromIndex, char[] cs, float measureWidth, float textPadding, Paint paint) { 49 | // if (cs == null || cs.length == 0) { 50 | // return null; 51 | // } 52 | BreakResult breakResult = new BreakResult(); 53 | breakResult.showChars = new ArrayList<>(); 54 | float width = 0; 55 | 56 | for (int i = 0, size = cs.length; i < size; i++) { 57 | String measureStr = String.valueOf(cs[i]); 58 | float charWidth = paint.measureText(measureStr); 59 | 60 | for (String paragraph : sParagraph) { 61 | if (paragraph != null && paragraph.length() > 0 && size - i >= paragraph.length()) { 62 | char[] paragraphArray = paragraph.toCharArray(); 63 | int length = paragraphArray.length; 64 | boolean isLineFeed = true; 65 | for (int j = 0; j < length; j++) { 66 | if (paragraphArray[j] != cs[i + j]) { 67 | isLineFeed = false; 68 | break; 69 | } 70 | } 71 | if (isLineFeed) { 72 | breakResult.chartNums = i + length; 73 | breakResult.isFullLine = true; 74 | breakResult.endWithWrapMark = true; 75 | return breakResult; 76 | } 77 | } 78 | } 79 | 80 | if (width <= measureWidth && (width + textPadding + charWidth) > measureWidth) { 81 | breakResult.chartNums = i; 82 | breakResult.isFullLine = true; 83 | return breakResult; 84 | } 85 | 86 | ShowChar showChar = new ShowChar(); 87 | showChar.charData = cs[i]; 88 | showChar.charWidth = charWidth; 89 | showChar.indexInChapter = fromIndex + i; 90 | breakResult.showChars.add(showChar); 91 | width += charWidth + textPadding; 92 | } 93 | 94 | breakResult.chartNums = cs.length; 95 | return breakResult; 96 | } 97 | 98 | public static BreakResult breakText(int fromIndex, String text, float measureWidth, float textPadding, Paint paint) { 99 | return breakText(fromIndex, text.toCharArray(), measureWidth, textPadding, paint); 100 | } 101 | 102 | /** 103 | * 测量能显示多少行 104 | * 105 | * @param measureHeight 最大高度 106 | * @param lineSpace 行间距 107 | * @param paint 画笔 108 | * @return 行数 109 | */ 110 | public static int measureLines(float measureHeight, float lineSpace, Paint paint) { 111 | Paint.FontMetrics fm = paint.getFontMetrics(); 112 | float textHeight = fm.bottom - fm.top; 113 | float heightEveryLine = textHeight + lineSpace; 114 | return (int) (measureHeight / heightEveryLine); 115 | } 116 | 117 | /** 118 | * 截取字符串 119 | * 120 | * @param src 源字符串 121 | * @param measureWidth 文字展示长度 122 | * @param textPadding 文字padding 123 | * @param paint Paint 124 | * @return 125 | */ 126 | public static List breakToLineList(String src, float measureWidth, float textPadding, Paint paint) { 127 | String textData = src; 128 | List showLines = new ArrayList<>(); 129 | 130 | int lineIndex = 0; 131 | while (textData.length() > 0) { 132 | BreakResult breakResult = breakText(src.length() - textData.length(), textData, measureWidth, textPadding, paint); 133 | ShowLine showLine = new ShowLine(); 134 | showLine.charsData = breakResult.showChars; 135 | showLine.isFullLine = breakResult.isFullLine; 136 | showLine.endWithWrapMark = breakResult.endWithWrapMark; 137 | 138 | if (showLine.getLineFirstIndexInChapter() != -1) { 139 | showLine.indexInChapter = lineIndex; 140 | showLines.add(showLine); 141 | lineIndex++; 142 | } 143 | textData = textData.substring(breakResult.chartNums); 144 | } 145 | 146 | //给每个字符添加当前章节中的索引(即所有字符串中的索引) 147 | // 11.28 将添加索引的计算放在了BreakResult的获取时。 148 | // int indexCharInChapter = 0; 149 | // int indexLineInChapter = 0; 150 | // for (ShowLine everyLine : showLines) { 151 | // everyLine.indexInChapter = indexLineInChapter; 152 | // indexLineInChapter++; 153 | // for (ShowChar everyChar : everyLine.charsData) { 154 | // everyChar.indexInChapter = indexCharInChapter; 155 | // indexCharInChapter++; 156 | // } 157 | // } 158 | return showLines; 159 | } 160 | } -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/util/DLog.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.util; 2 | 3 | /** 4 | * Created by Garrett on 2018/11/20. 5 | * contact me krouky@outlook.com 6 | */ 7 | public class DLog { 8 | 9 | public static final boolean DEBUG = false; 10 | 11 | public static int v(String tag, String msg) { 12 | if (DEBUG) { 13 | return android.util.Log.v(tag, msg); 14 | } else { 15 | return -1; 16 | } 17 | } 18 | 19 | public static int d(String tag, String msg) { 20 | if (DEBUG) { 21 | return android.util.Log.d(tag, msg); 22 | } else { 23 | return -1; 24 | } 25 | } 26 | 27 | public static int i(String tag, String msg) { 28 | if (DEBUG) { 29 | return android.util.Log.i(tag, msg); 30 | } else { 31 | return -1; 32 | } 33 | } 34 | 35 | public static int w(String tag, String msg) { 36 | if (DEBUG) { 37 | return android.util.Log.w(tag, msg); 38 | } else { 39 | return -1; 40 | } 41 | } 42 | 43 | public static int e(String tag, String msg) { 44 | if (DEBUG) { 45 | return android.util.Log.e(tag, msg); 46 | } else { 47 | return -1; 48 | } 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/util/FilePair.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.util; 2 | 3 | /** 4 | * Created by Garrett on 2018/11/22. 5 | * contact me krouky@outlook.com 6 | */ 7 | class FilePair { 8 | String mFileName; 9 | byte[] mBinaryData; 10 | 11 | public FilePair(String fileName, byte[] data) { 12 | this.mFileName = fileName; 13 | this.mBinaryData = data; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/util/Request.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by Garrett on 2018/11/22. 8 | * contact me krouky@outlook.com 9 | */ 10 | public class Request { 11 | 12 | private final String mBaseUrl; 13 | private final String mMethod; 14 | private final Map mUrlParams; 15 | private final Map mBody; 16 | private final Map mFiles; 17 | private final Map mHeaders; 18 | 19 | private Request(Builder builder) { 20 | this.mBaseUrl = builder.mBaseUrl; 21 | this.mMethod = builder.mMethod; 22 | this.mUrlParams = builder.mUrlParams; 23 | this.mBody = builder.mBody; 24 | this.mFiles = builder.mFiles; 25 | this.mHeaders = builder.mHeaders; 26 | } 27 | 28 | String getBaseUrl() { 29 | return this.mBaseUrl; 30 | } 31 | 32 | String getMethod() { 33 | return mMethod; 34 | } 35 | 36 | Map getParam() { 37 | return mUrlParams; 38 | } 39 | 40 | Map getFilePair() { 41 | return mFiles; 42 | } 43 | 44 | Map getBody() { 45 | return mBody; 46 | } 47 | 48 | Map getHeaders() { 49 | return mHeaders; 50 | } 51 | 52 | public static class Builder { 53 | String mBaseUrl; 54 | String mMethod; 55 | Map mUrlParams; 56 | Map mBody; 57 | Map mFiles; 58 | Map mHeaders; 59 | 60 | public Builder() { 61 | mMethod = "GET"; 62 | } 63 | 64 | public Builder baseUrl(String baseUrl) { 65 | if (baseUrl == null) throw new NullPointerException("baseUrl == null"); 66 | this.mBaseUrl = baseUrl; 67 | return this; 68 | } 69 | 70 | public Builder addUrlParams(String key, Object value) { 71 | if (key == null) throw new NullPointerException("key == null"); 72 | if (value == null) throw new NullPointerException("value == null"); 73 | if (this.mUrlParams == null) { 74 | mUrlParams = new HashMap<>(); 75 | } 76 | mUrlParams.put(key, value); 77 | return this; 78 | } 79 | 80 | public Builder addBody(String key, Object value) { 81 | if (key == null) throw new NullPointerException("key == null"); 82 | if (value == null) throw new NullPointerException("value == null"); 83 | if (mBody == null) { 84 | mBody = new HashMap<>(); 85 | } 86 | mBody.put(key, value); 87 | return this; 88 | } 89 | 90 | public Builder addFile(String key, FilePair filePair) { 91 | if (key == null) throw new NullPointerException("key == null"); 92 | if (filePair == null) throw new NullPointerException("filePair == null"); 93 | if (mFiles == null) { 94 | mFiles = new HashMap<>(); 95 | } 96 | mFiles.put(key, filePair); 97 | return this; 98 | } 99 | 100 | public Builder addHeader(String key, String value) { 101 | if (key == null) throw new NullPointerException("key == null"); 102 | if (value == null) throw new NullPointerException("value == null"); 103 | if (mHeaders == null) 104 | mHeaders = new HashMap<>(); 105 | mHeaders.put(key, value); 106 | return this; 107 | } 108 | 109 | public Builder post() { 110 | mMethod = "POST"; 111 | return this; 112 | } 113 | 114 | public Builder get() { 115 | mMethod = "GET"; 116 | return this; 117 | } 118 | 119 | public Request build() { 120 | if (mBaseUrl == null) throw new NullPointerException("mBaseUrl == null"); 121 | return new Request(this); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/Effect.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.view.MotionEvent; 7 | import android.view.VelocityTracker; 8 | import android.view.ViewConfiguration; 9 | import android.widget.Scroller; 10 | 11 | public abstract class Effect { 12 | 13 | protected int mEffectWidth; 14 | protected int mEffectHeight; 15 | 16 | protected Bitmap mCurrPageBitmap; 17 | protected Bitmap mNextPageBitmap; 18 | 19 | protected PageChangedCallback mPageChangedCallback; 20 | protected PageDrawingCallback mPageDrawingCallback; 21 | 22 | protected Scroller mScroller; 23 | protected VelocityTracker mVelocityTracker = VelocityTracker.obtain(); 24 | 25 | protected int mLongClickTime; 26 | protected int mTouchSlop; 27 | 28 | public Effect(Context context) { 29 | initViewConfig(ViewConfiguration.get(context)); 30 | mScroller = new Scroller(context); 31 | } 32 | 33 | private void initViewConfig(ViewConfiguration configuration) { 34 | mLongClickTime = configuration.getLongPressTimeout(); 35 | mTouchSlop = configuration.getScaledPagingTouchSlop(); 36 | } 37 | 38 | void config(int effectWidth, int effectHeight, Bitmap currPageBitmap, Bitmap nextPageBitmap) { 39 | this.mEffectWidth = effectWidth; 40 | this.mEffectHeight = effectHeight; 41 | this.mCurrPageBitmap = currPageBitmap; 42 | this.mNextPageBitmap = nextPageBitmap; 43 | } 44 | 45 | public PageChangedCallback getPageChangedCallback() { 46 | return mPageChangedCallback; 47 | } 48 | 49 | public void setPageChangedCallback(PageChangedCallback pageChangedCallback) { 50 | mPageChangedCallback = pageChangedCallback; 51 | } 52 | 53 | public PageDrawingCallback getPageDrawingCallback() { 54 | return mPageDrawingCallback; 55 | } 56 | 57 | public void setPageDrawingCallback(PageDrawingCallback pageDrawingCallback) { 58 | mPageDrawingCallback = pageDrawingCallback; 59 | } 60 | 61 | /** 62 | * 承接View的onTouchEvent(MotionEvent event) 63 | */ 64 | public abstract boolean onTouchEvent(MotionEvent event); 65 | 66 | /** 67 | * 承接View的onDraw(Canvas canvas) 68 | */ 69 | public abstract void onDraw(Canvas canvas); 70 | 71 | /** 72 | * 承接View的computeScroll() 73 | */ 74 | public void computeScroll() { 75 | } 76 | 77 | public void abortAnimation() { 78 | if (!mScroller.isFinished()) { 79 | mScroller.abortAnimation(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/EffectOfCover.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.LinearGradient; 6 | import android.graphics.Paint; 7 | import android.view.MotionEvent; 8 | 9 | import com.glong.reader.TurnStatus; 10 | import com.glong.reader.util.DLog; 11 | 12 | /** 13 | * Created by Garrett on 2018/11/20. 14 | * contact me krouky@outlook.com 15 | */ 16 | public class EffectOfCover extends Effect { 17 | 18 | private static final int ANIM_DURATION = 1200; 19 | private float mMoveVector; 20 | private Paint mShadowPaint; 21 | private int mShadowWidth; 22 | 23 | public EffectOfCover(Context context) { 24 | super(context); 25 | initShadowPaint(); 26 | } 27 | 28 | private void initShadowPaint() { 29 | mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 30 | mShadowWidth = 35; 31 | LinearGradient linearGradient = new LinearGradient(0, 0, mShadowWidth, 0, 0x00111111, 0x55111111, LinearGradient.TileMode.MIRROR); 32 | mShadowPaint.setShader(linearGradient); 33 | } 34 | 35 | private float mDownX; 36 | private long mDownTime; 37 | private float mStartMoveX; 38 | private TurnStatus mTurnStatus = TurnStatus.IDLE; 39 | 40 | @Override 41 | public boolean onTouchEvent(MotionEvent event) { 42 | // 如果是多点触控 ,不处理 43 | if (event.getPointerCount() > 1) { 44 | return true; 45 | } 46 | 47 | float x = event.getX(); 48 | mVelocityTracker.addMovement(event); 49 | switch (event.getAction()) { 50 | case MotionEvent.ACTION_DOWN: 51 | mTurnStatus = TurnStatus.IDLE; 52 | this.mDownX = x; 53 | this.mDownTime = System.currentTimeMillis(); 54 | 55 | abortAnimation(); 56 | mPageDrawingCallback.drawCurrPage(); 57 | mPageDrawingCallback.invalidate(); 58 | return true; 59 | case MotionEvent.ACTION_MOVE: 60 | float dxFromDown = x - mDownX; 61 | 62 | // 如果滑动需要处理翻页 63 | if (Math.abs(dxFromDown) >= mTouchSlop) { 64 | if (mTurnStatus == TurnStatus.IDLE) { 65 | mStartMoveX = x; 66 | if (dxFromDown > 0) { 67 | mTurnStatus = mPageChangedCallback.toPrevPage(); 68 | } else { 69 | mTurnStatus = mPageChangedCallback.toNextPage(); 70 | } 71 | if (mTurnStatus == TurnStatus.LOAD_SUCCESS) { 72 | DLog.d(getClass().getSimpleName(), "drawNextPage -- "); 73 | mPageDrawingCallback.drawNextPage(); 74 | } 75 | } 76 | } 77 | 78 | //如果已经翻页成功,需要处理移动时页面的刷新 79 | if (mTurnStatus == TurnStatus.LOAD_SUCCESS) { 80 | float dxMove; 81 | if (mStartMoveX - mDownX < 0) { // 向左滑 --> 下一页 82 | dxMove = x - mStartMoveX; 83 | } else { 84 | dxMove = x; 85 | } 86 | if (dxMove * (mStartMoveX - mDownX) >= 0) { 87 | mMoveVector = dxMove; 88 | mPageDrawingCallback.invalidate(); 89 | } 90 | } 91 | break; 92 | case MotionEvent.ACTION_UP: 93 | default: 94 | // 单击 事件 95 | long upTime = System.currentTimeMillis(); 96 | if (mTurnStatus == TurnStatus.IDLE && upTime - mDownTime <= mLongClickTime) { 97 | if (x > mEffectWidth / 2) { 98 | //向左滑 --> 下一页 99 | if (mPageChangedCallback.toNextPage() == TurnStatus.LOAD_SUCCESS) { 100 | DLog.d(getClass().getSimpleName(), "start scroll to next page!"); 101 | mPageDrawingCallback.drawNextPage(); 102 | mScroller.startScroll(0, 0, -mEffectWidth - mShadowWidth, 0, ANIM_DURATION); 103 | mPageDrawingCallback.invalidate(); 104 | } 105 | } else { 106 | // 向右滑 --> 上一页 107 | if (mPageChangedCallback.toPrevPage() == TurnStatus.LOAD_SUCCESS) { 108 | mPageDrawingCallback.drawNextPage(); 109 | mScroller.startScroll(0, 0, mEffectWidth, 0, ANIM_DURATION); 110 | mPageDrawingCallback.invalidate(); 111 | } 112 | } 113 | } 114 | 115 | // 翻页成功,手指抬起来时,处理自动翻页 116 | else if (mTurnStatus == TurnStatus.LOAD_SUCCESS) { 117 | mVelocityTracker.computeCurrentVelocity(1000); 118 | DLog.d(getClass().getSimpleName(), "xVelocity == " + mVelocityTracker.getXVelocity()); 119 | if (mMoveVector <= 0) { 120 | // 向左滑 --> 下一页 121 | // 分两种情况,顺利到达下一页 and 取消到下一页 122 | if (mVelocityTracker.getXVelocity() >= 500) {// 取消至下一页 123 | mScroller.startScroll((int) mMoveVector, 0, (int) -mMoveVector, 0, ANIM_DURATION); 124 | mPageChangedCallback.toPrevPage();// 数据回滚 125 | } else { 126 | mScroller.startScroll((int) mMoveVector, 0, (int) (-mEffectWidth - mMoveVector - mShadowWidth), 0, ANIM_DURATION); 127 | } 128 | mPageDrawingCallback.invalidate(); 129 | } else { 130 | // 向右滑 --> 上一页 131 | // 分两种情况,顺利到达上一页 and 取消到上一页 132 | if (mVelocityTracker.getXVelocity() <= -500) {// 取消至上一页 133 | mScroller.startScroll((int) mMoveVector, 0, (int) -mMoveVector, 0, ANIM_DURATION / 4); 134 | mPageChangedCallback.toNextPage();// 数据回滚 135 | } else { 136 | mScroller.startScroll((int) mMoveVector, 0, (int) (mEffectWidth - mMoveVector), 0, ANIM_DURATION); 137 | } 138 | mPageDrawingCallback.invalidate(); 139 | } 140 | } 141 | break; 142 | } 143 | return false; 144 | } 145 | 146 | @Override 147 | public void abortAnimation() { 148 | if (!mScroller.isFinished()) { 149 | mScroller.abortAnimation(); 150 | mMoveVector = mScroller.getCurrX(); 151 | } 152 | } 153 | 154 | @Override 155 | public void computeScroll() { 156 | if (mScroller.computeScrollOffset()) { 157 | mMoveVector = mScroller.getCurrX(); 158 | mPageDrawingCallback.invalidate(); 159 | } 160 | } 161 | 162 | @Override 163 | public void onDraw(Canvas canvas) { 164 | drawCurrPage(canvas); 165 | drawNextPage(canvas); 166 | drawShadow(canvas); 167 | } 168 | 169 | private void drawCurrPage(Canvas canvas) { 170 | canvas.save(); 171 | // 向左滑 --> 下一页 172 | if (mMoveVector <= 0) { 173 | canvas.translate(mMoveVector, 0); 174 | } else { 175 | canvas.clipRect(mMoveVector, 0, mEffectWidth, mEffectHeight); 176 | } 177 | canvas.drawBitmap(mCurrPageBitmap, 0, 0, null); 178 | canvas.restore(); 179 | } 180 | 181 | private void drawNextPage(Canvas canvas) { 182 | canvas.save(); 183 | // 向左滑 --> 下一页 184 | if (mMoveVector <= 0) { 185 | canvas.clipRect(mEffectWidth + mMoveVector, 0, mEffectWidth, mEffectHeight); 186 | } else { 187 | canvas.translate(mMoveVector - mEffectWidth, 0); 188 | } 189 | canvas.drawBitmap(mNextPageBitmap, 0, 0, null); 190 | canvas.restore(); 191 | } 192 | 193 | private void drawShadow(Canvas canvas) { 194 | canvas.save(); 195 | if (mMoveVector <= 0) { 196 | canvas.translate(mMoveVector, 0); 197 | } else { 198 | canvas.translate(mMoveVector - mEffectWidth, 0); 199 | } 200 | canvas.drawRect(mEffectWidth, 0, mEffectWidth + mShadowWidth, mEffectHeight, mShadowPaint); 201 | canvas.restore(); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/EffectOfNon.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.view.MotionEvent; 6 | 7 | import com.glong.reader.TurnStatus; 8 | 9 | /** 10 | * Created by Garrett on 2018/11/18. 11 | * contact me krouky@outlook.com 12 | */ 13 | public class EffectOfNon extends Effect { 14 | 15 | private float mDownX; 16 | 17 | public EffectOfNon(Context context) { 18 | super(context); 19 | } 20 | 21 | @Override 22 | public boolean onTouchEvent(MotionEvent event) { 23 | 24 | float x = event.getX(); 25 | 26 | switch (event.getAction()) { 27 | case MotionEvent.ACTION_DOWN: 28 | mDownX = x; 29 | return true; 30 | case MotionEvent.ACTION_UP: 31 | TurnStatus turnStatus; 32 | if (mDownX >= mEffectWidth / 2) { 33 | turnStatus = mPageChangedCallback.toNextPage(); 34 | } else { 35 | turnStatus = mPageChangedCallback.toPrevPage(); 36 | } 37 | if (turnStatus == TurnStatus.LOAD_SUCCESS) { 38 | mPageDrawingCallback.drawCurrPage(); 39 | mPageDrawingCallback.invalidate(); 40 | } 41 | } 42 | return false; 43 | } 44 | 45 | @Override 46 | public void onDraw(Canvas canvas) { 47 | canvas.drawBitmap(mCurrPageBitmap, 0, 0, null); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/EffectOfSlide.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.view.MotionEvent; 6 | 7 | import com.glong.reader.TurnStatus; 8 | import com.glong.reader.util.DLog; 9 | 10 | /** 11 | * Created by Garrett on 2018/11/20. 12 | * contact me krouky@outlook.com 13 | */ 14 | public class EffectOfSlide extends Effect { 15 | private static final int ANIM_DURATION = 800; 16 | private float mMoveVector; 17 | 18 | public EffectOfSlide(Context context) { 19 | super(context); 20 | } 21 | 22 | private float mDownX; 23 | private long mDownTime; 24 | private float mStartMoveX; 25 | private TurnStatus mTurnStatus = TurnStatus.IDLE; 26 | 27 | @Override 28 | public boolean onTouchEvent(MotionEvent event) { 29 | // 如果是多点触控 ,不处理 30 | if (event.getPointerCount() > 1) { 31 | return true; 32 | } 33 | 34 | float x = event.getX(); 35 | mVelocityTracker.addMovement(event); 36 | switch (event.getAction()) { 37 | case MotionEvent.ACTION_DOWN: 38 | mTurnStatus = TurnStatus.IDLE; 39 | this.mDownX = x; 40 | this.mDownTime = System.currentTimeMillis(); 41 | 42 | abortAnimation(); 43 | mPageDrawingCallback.drawCurrPage(); 44 | mPageDrawingCallback.invalidate(); 45 | return true; 46 | case MotionEvent.ACTION_MOVE: 47 | float dxFromDown = x - mDownX; 48 | 49 | // 如果滑动需要处理翻页 50 | if (Math.abs(dxFromDown) >= mTouchSlop) { 51 | if (mTurnStatus == TurnStatus.IDLE) { 52 | mStartMoveX = x; 53 | if (dxFromDown > 0) { 54 | mTurnStatus = mPageChangedCallback.toPrevPage(); 55 | } else { 56 | mTurnStatus = mPageChangedCallback.toNextPage(); 57 | } 58 | if (mTurnStatus == TurnStatus.LOAD_SUCCESS) { 59 | DLog.d(getClass().getSimpleName(), "drawNextPage -- "); 60 | mPageDrawingCallback.drawNextPage(); 61 | } 62 | } 63 | } 64 | 65 | //如果已经翻页成功,需要处理移动时页面的刷新 66 | if (mTurnStatus == TurnStatus.LOAD_SUCCESS) { 67 | float dxMove = x - mStartMoveX; 68 | // if (mStartMoveX - mDownX < 0) { // 向左滑 --> 下一页 69 | // dxMove = x - mStartMoveX; 70 | // } else { 71 | // dxMove = x; 72 | // } 73 | if (dxMove * (mStartMoveX - mDownX) >= 0) { 74 | mMoveVector = dxMove; 75 | mPageDrawingCallback.invalidate(); 76 | } 77 | } 78 | break; 79 | case MotionEvent.ACTION_UP: 80 | default: 81 | // 单击 事件 82 | long upTime = System.currentTimeMillis(); 83 | if (mTurnStatus == TurnStatus.IDLE && upTime - mDownTime <= mLongClickTime) { 84 | if (x > mEffectWidth / 2) { 85 | //向左滑 --> 下一页 86 | if (mPageChangedCallback.toNextPage() == TurnStatus.LOAD_SUCCESS) { 87 | DLog.d(getClass().getSimpleName(), "start scroll to next page!"); 88 | mPageDrawingCallback.drawNextPage(); 89 | mScroller.startScroll(0, 0, -mEffectWidth, 0, ANIM_DURATION); 90 | mPageDrawingCallback.invalidate(); 91 | } 92 | } else { 93 | // 向右滑 --> 上一页 94 | if (mPageChangedCallback.toPrevPage() == TurnStatus.LOAD_SUCCESS) { 95 | mPageDrawingCallback.drawNextPage(); 96 | mScroller.startScroll(0, 0, mEffectWidth, 0, ANIM_DURATION); 97 | mPageDrawingCallback.invalidate(); 98 | } 99 | } 100 | } 101 | 102 | // 翻页成功,手指抬起来时,处理自动翻页 103 | else if (mTurnStatus == TurnStatus.LOAD_SUCCESS) { 104 | mVelocityTracker.computeCurrentVelocity(1000); 105 | DLog.d(getClass().getSimpleName(), "xVelocity == " + mVelocityTracker.getXVelocity()); 106 | if (mMoveVector <= 0) { 107 | // 向左滑 --> 下一页 108 | // 分两种情况,顺利到达下一页 and 取消到下一页 109 | if (mVelocityTracker.getXVelocity() >= 500) {// 取消至下一页 110 | mScroller.startScroll((int) mMoveVector, 0, (int) -mMoveVector, 0, ANIM_DURATION); 111 | mPageChangedCallback.toPrevPage();// 数据回滚 112 | } else { 113 | mScroller.startScroll((int) mMoveVector, 0, (int) (-mEffectWidth - mMoveVector), 0, ANIM_DURATION); 114 | } 115 | mPageDrawingCallback.invalidate(); 116 | } else { 117 | // 向右滑 --> 上一页 118 | // 分两种情况,顺利到达上一页 and 取消到上一页 119 | if (mVelocityTracker.getXVelocity() <= -500) {// 取消至上一页 120 | mScroller.startScroll((int) mMoveVector, 0, (int) -mMoveVector, 0, ANIM_DURATION); 121 | mPageChangedCallback.toNextPage();// 数据回滚 122 | } else { 123 | mScroller.startScroll((int) mMoveVector, 0, (int) (mEffectWidth - mMoveVector), 0, ANIM_DURATION); 124 | } 125 | mPageDrawingCallback.invalidate(); 126 | } 127 | } 128 | break; 129 | } 130 | return false; 131 | } 132 | 133 | @Override 134 | public void abortAnimation() { 135 | if (!mScroller.isFinished()) { 136 | mScroller.abortAnimation(); 137 | mMoveVector = mScroller.getCurrX(); 138 | } 139 | } 140 | 141 | @Override 142 | public void computeScroll() { 143 | if (mScroller.computeScrollOffset()) { 144 | mMoveVector = mScroller.getCurrX(); 145 | mPageDrawingCallback.invalidate(); 146 | } 147 | } 148 | 149 | @Override 150 | public void onDraw(Canvas canvas) { 151 | rawCurrPage(canvas); 152 | drawNextPage(canvas); 153 | } 154 | 155 | private void rawCurrPage(Canvas canvas) { 156 | canvas.save(); 157 | // 向左滑 --> 下一页 158 | if (mMoveVector <= 0) { 159 | canvas.translate(mMoveVector, 0); 160 | } else { 161 | canvas.translate(mMoveVector, 0); 162 | } 163 | canvas.drawBitmap(mCurrPageBitmap, 0, 0, null); 164 | canvas.restore(); 165 | } 166 | 167 | private void drawNextPage(Canvas canvas) { 168 | canvas.save(); 169 | if (mMoveVector <= 0) { 170 | canvas.translate(mMoveVector + mEffectWidth, 0); 171 | } else { 172 | canvas.translate(mMoveVector - mEffectWidth, 0); 173 | } 174 | canvas.drawBitmap(mNextPageBitmap, 0, 0, null); 175 | canvas.restore(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/IDownload.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | import com.glong.reader.util.Request; 4 | 5 | /** 6 | * Created by Garrett on 2018/11/17. 7 | * contact me krouky@outlook.com 8 | */ 9 | interface IDownload { 10 | /** 11 | * 同步下载 12 | * 13 | * @param k 下载参数 14 | * @return 下载内容 15 | */ 16 | T downLoad(K k); 17 | 18 | /** 19 | * 获取同步下载参数 20 | * 如果这个方法返回不为null,则下载使用该方法,忽略{@link #downLoad(Object)}方法,即使它被实现。 21 | * 如果该方法返回为null,则使用{@link #downLoad(Object)}方法下载。 22 | * 23 | * @param k 下载参数 24 | * @return 下载参数 25 | */ 26 | Request requestParams(K k); 27 | } 28 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/IReaderManager.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | import android.graphics.Canvas; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.glong.reader.TurnStatus; 7 | 8 | import java.io.File; 9 | 10 | /** 11 | * Created by Garrett on 2018/11/17. 12 | * contact me krouky@outlook.com 13 | */ 14 | interface IReaderManager { 15 | 16 | /** 17 | * 上一页 18 | * 19 | * @return 跳转结果 20 | */ 21 | TurnStatus toPrevPage(); 22 | 23 | /** 24 | * 下一页 25 | * 26 | * @return 跳转结果 27 | */ 28 | TurnStatus toNextPage(); 29 | 30 | /** 31 | * 上一章 32 | * 33 | * @return 跳转结果 34 | */ 35 | TurnStatus toPrevChapter(); 36 | 37 | /** 38 | * 下一章 39 | * 40 | * @return 跳转结果 41 | */ 42 | TurnStatus toNextChapter(); 43 | 44 | /** 45 | * 跳转到指定章节 46 | * 47 | * @param chapterIndex 指定章节的索引 48 | * @param charIndex 指定跳转到该章节的第charIndex个字符处 49 | * @return 跳转结果 50 | */ 51 | TurnStatus toSpecifiedChapter(int chapterIndex, int charIndex); 52 | 53 | /** 54 | * 从缓存启动阅读器 55 | * 56 | * @param key 缓存键 57 | * @param chapterIndex 章节索引 58 | * @param charIndex 跳转到缓存的那个字符 59 | */ 60 | void startFromCache(String key, int chapterIndex, int charIndex, @NonNull String chapterName); 61 | 62 | /** 63 | * 从缓存启动阅读器 64 | * 65 | * @param cacheDir 缓存路径 66 | * @param key 缓存键 67 | * @param chapterIndex 章节索引 68 | * @param charIndex 跳转到缓存的那个字符 69 | */ 70 | void startFromCache(File cacheDir, String key, int chapterIndex, int charIndex, @NonNull String chapterName); 71 | 72 | void drawPage(Canvas canvas); 73 | } 74 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/OnReaderWatcherListener.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | /** 4 | * 当页码、章节发生变化时的回调接口 5 | * Created by Garrett on 2018/11/19. 6 | * contact me krouky@outlook.com 7 | */ 8 | public interface OnReaderWatcherListener { 9 | 10 | /** 11 | * 页码发生了变化 12 | * 13 | * @param pageIndex 第pageIndex页(从第0页开始) 14 | */ 15 | void onPageChanged(int pageIndex); 16 | 17 | /** 18 | * 章节发生了变化 19 | * 20 | * @param chapterIndex 跳转到了第chapterIndex章 21 | * @param pageIndex 跳转到了这章的第pageIndex页(从第0页开始) 22 | */ 23 | void onChapterChanged(int chapterIndex, int pageIndex); 24 | 25 | /** 26 | * 开始下载当前所需章节时调用(方便弹出提示等等) 27 | * 当下载缓存时不会回调 28 | * 29 | * @param chapterIndex 章节索引 30 | */ 31 | void onChapterDownloadStart(int chapterIndex); 32 | 33 | /** 34 | * 当前所需章节下载成功后回调 35 | * 仅下载缓存时不会回调 36 | * 37 | * @param chapterIndex 章节索引 38 | */ 39 | void onChapterDownloadSuccess(int chapterIndex); 40 | 41 | /** 42 | * 当前所需章节下载成功后回调 43 | * 仅下载缓存时不会回调 44 | * 45 | * @param chapterIndex 章节索引 46 | */ 47 | void onChapterDownloadError(int chapterIndex); 48 | } 49 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/PageChangedCallback.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | import com.glong.reader.TurnStatus; 4 | 5 | /** 6 | * Created by Garrett on 2018/11/18. 7 | * contact me krouky@outlook.com 8 | */ 9 | public interface PageChangedCallback { 10 | 11 | /** 12 | * 获取上一页的时候调用 13 | * 14 | * @return 获取结果 15 | */ 16 | TurnStatus toPrevPage(); 17 | 18 | /** 19 | * 获取下一页的时候调用 20 | * 21 | * @return 获取结果 22 | */ 23 | TurnStatus toNextPage(); 24 | } 25 | -------------------------------------------------------------------------------- /reader/src/main/java/com/glong/reader/widget/PageDrawingCallback.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader.widget; 2 | 3 | /** 4 | * Created by Garrett on 2018/11/24. 5 | * contact me krouky@outlook.com 6 | */ 7 | public interface PageDrawingCallback { 8 | /** 9 | * 刷新ReaderView时调用 10 | */ 11 | void invalidate(); 12 | 13 | /** 14 | * 画当前页的时候调用 {@link ReaderView#mCurrPageCanvas} 15 | */ 16 | void drawCurrPage(); 17 | 18 | /** 19 | * 画下一页的时候调用 {@link ReaderView#mNextPageCanvas} 20 | */ 21 | void drawNextPage(); 22 | } 23 | -------------------------------------------------------------------------------- /reader/src/main/res/drawable/paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glongdev/Reader/09d6339d5e968ca3e9d70abb70c37919a748e552/reader/src/main/res/drawable/paper.png -------------------------------------------------------------------------------- /reader/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Reader 3 | 4 | -------------------------------------------------------------------------------- /reader/src/test/java/com/glong/reader/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.glong.reader; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':reader' 2 | --------------------------------------------------------------------------------