diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index a2d7c21..2cbbe00 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/build.gradle b/app/build.gradle
index bc9196e..7fe70a5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,14 +9,13 @@ android {
defaultConfig {
applicationId "com.zjgsu.jianshu"
- minSdk 21
- targetSdk 31
+ minSdk 19
+ targetSdkVersion 31
versionCode 1
versionName "1.0"
-
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ multiDexEnabled true
}
-
buildTypes {
release {
minifyEnabled false
@@ -57,4 +56,6 @@ dependencies {
implementation 'junit:junit:4.12'
implementation 'com.jakewharton:butterknife:7.0.1'
implementation 'com.facebook.fresco:fresco:0.9.0+'
+ implementation project(':hwtxtreaderlib')
+ implementation 'androidx.multidex:multidex:2.0.1'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cb193e6..2decb61 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,13 +12,9 @@
-
-
-
-
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
@@ -50,23 +46,23 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/zjgsu/jianshu/Activity/BookInformationActivity.kt b/app/src/main/java/com/zjgsu/jianshu/Activity/BookInformationActivity.kt
index cb1c4d6..37fe275 100644
--- a/app/src/main/java/com/zjgsu/jianshu/Activity/BookInformationActivity.kt
+++ b/app/src/main/java/com/zjgsu/jianshu/Activity/BookInformationActivity.kt
@@ -18,14 +18,21 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import cn.bmob.v3.listener.SaveListener
+import com.bifan.txtreaderlib.ui.HwTxtPlayActivity
import com.bumptech.glide.Glide
import com.zjgsu.jianshu.Adapter.GoodperceptionAdapter
import com.zjgsu.jianshu.Bmob.BookShelf
import com.zjgsu.jianshu.Bmob.Book_info_bmob
+import com.zjgsu.jianshu.Bmob.Bookcontent_bmob
import com.zjgsu.jianshu.Bmob.Perception_bmob
import kotlinx.android.synthetic.main.activity_book_info.*
import kotlinx.android.synthetic.main.activity_sendspecific.*
import kotlinx.android.synthetic.main.bookinfo_title.*
+import okhttp3.*
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
class BookInformationActivity : AppCompatActivity() {
@@ -38,6 +45,7 @@ class BookInformationActivity : AppCompatActivity() {
private lateinit var picurl:String
private var sourceActivityClass: Class<*>? = null
private lateinit var bookInfoTextView: TextView
+ private lateinit var bookPath:String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_book_info)
@@ -102,7 +110,6 @@ class BookInformationActivity : AppCompatActivity() {
val str: String = String.format("%.2f", tuijian_res)
textView10.text = "简书推荐值 " + str + "%"
}
- updateBookRankings(book)
bookinfo_bookname.text = book.name
bookinfo_authorname.text = book.author_name
bookinfo_introduce.text=book.introduce
@@ -181,9 +188,8 @@ class BookInformationActivity : AppCompatActivity() {
showBookIntroDialog(bookInfoTextView.text.toString())
}
startToRead.setOnClickListener {
- val intent = Intent(this, BookIntroActivity::class.java)
- intent.putExtra("Book_name", bookName)
- startActivity(intent)
+ downloadFile(bookName)
+
}
}
//TODO:以后可以考虑再次点击这个“已加入的按钮”后,弹出一个对话框:是否将该书移除,然后将该书从书架中移除。而不是弹:该书已在您的书架中
@@ -268,21 +274,6 @@ class BookInformationActivity : AppCompatActivity() {
// }
// }
- private fun updateBookRankings(book: Book_info_bmob, isRecommended: Boolean = false) {
- if (isRecommended) {
- book.settuijian(book.tuijian + 1)
- } else {
- book.setyiban(book.yiban + 1)
- book.setbuxing(book.buxing + 1)
- }
- book.update(book.objectId, object : UpdateListener() {
- override fun done(e: BmobException?) {
- if (e != null) {
- Log.e("BookInfo", "Error updating book rankings: ${e.message}")
- }
- }
- })
- }
private fun judgeComment(){
val query=BmobQuery()
query.addWhereEqualTo("userid",userId)
@@ -313,4 +304,59 @@ class BookInformationActivity : AppCompatActivity() {
.error(R.drawable.fail_load) // 设置加载失败时显示的图片
.into(bookinfo_img) // 将图片加载到指定的 ImageView 中
}
+ private fun downloadFile(bookName: String) {
+ val localPath = getExternalFilesDir(null)?.absolutePath + "/$bookName.txt"
+ val file = File(localPath)
+
+ // 检查本地是否已经存在该书籍的 TXT 文件
+ if (file.exists()) {
+ // 如果存在,则直接打开该文件
+ runOnUiThread {
+ HwTxtPlayActivity.loadTxtFile(this@BookInformationActivity, file.absolutePath)
+ }
+ } else {
+ // 如果不存在,则从网上下载
+ val querybook = BmobQuery()
+ querybook.addWhereEqualTo("bookname", bookName)
+ querybook.findObjects(object : FindListener() {
+ override fun done(books: MutableList?, e: BmobException?) {
+ if (e == null && books != null && books.isNotEmpty()) {
+ val book = books[0]
+ bookPath = book.bookpath.url
+ downloadFromUrl(bookPath, localPath)
+ } else {
+ Log.e("BookInfo", "Error loading book info: ${e?.message}")
+ }
+ }
+ })
+ }
+ }
+
+ private fun downloadFromUrl(bookPath: String, localPath: String) {
+ val client = OkHttpClient()
+ val request = Request.Builder().url(bookPath).build()
+ client.newCall(request).enqueue(object : Callback {
+ override fun onFailure(call: Call, e: IOException) {
+ Log.e("Download", "Download failed: ${e.message}")
+ runOnUiThread {
+ Toast.makeText(this@BookInformationActivity, "Download failed", Toast.LENGTH_SHORT).show()
+ }
+ }
+ override fun onResponse(call: Call, response: Response) {
+ response.body?.let { body ->
+ val inputStream: InputStream = body.byteStream()
+ val file = File(localPath)
+ val outputStream = FileOutputStream(file)
+ inputStream.use { input ->
+ outputStream.use { output ->
+ input.copyTo(output)
+ runOnUiThread {
+ HwTxtPlayActivity.loadTxtFile(this@BookInformationActivity, file.absolutePath)
+ }
+ }
+ }
+ }
+ }
+ })
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/zjgsu/jianshu/Activity/BookShelfActivity.kt b/app/src/main/java/com/zjgsu/jianshu/Activity/BookShelfActivity.kt
index 79aeab9..36425ba 100644
--- a/app/src/main/java/com/zjgsu/jianshu/Activity/BookShelfActivity.kt
+++ b/app/src/main/java/com/zjgsu/jianshu/Activity/BookShelfActivity.kt
@@ -86,9 +86,6 @@ class BookShelfActivity : AppCompatActivity() {
bookcity.setOnClickListener {
NavigationHelper.navigateTo(this, MainActivity::class.java)
}
- bookshelf.setOnClickListener {
- NavigationHelper.navigateTo(this, BookShelfActivity::class.java)
- }
reading_comprehension.setOnClickListener {
NavigationHelper.navigateTo(this, PerceptionActivity::class.java)
}
diff --git a/app/src/main/java/com/zjgsu/jianshu/Activity/MyActivity.kt b/app/src/main/java/com/zjgsu/jianshu/Activity/MyActivity.kt
index 538e491..ead7356 100644
--- a/app/src/main/java/com/zjgsu/jianshu/Activity/MyActivity.kt
+++ b/app/src/main/java/com/zjgsu/jianshu/Activity/MyActivity.kt
@@ -107,9 +107,6 @@ class MyActivity : AppCompatActivity() {
reading_comprehension.setOnClickListener {
NavigationHelper.navigateTo(this, PerceptionActivity::class.java)
}
- homepage.setOnClickListener {
- NavigationHelper.navigateTo(this, MyActivity::class.java)
- }
}
}
diff --git a/app/src/main/java/com/zjgsu/jianshu/Activity/PerceptionActivity.kt b/app/src/main/java/com/zjgsu/jianshu/Activity/PerceptionActivity.kt
index bf02d27..4f9f6bd 100644
--- a/app/src/main/java/com/zjgsu/jianshu/Activity/PerceptionActivity.kt
+++ b/app/src/main/java/com/zjgsu/jianshu/Activity/PerceptionActivity.kt
@@ -67,9 +67,6 @@ class PerceptionActivity : AppCompatActivity() {
bookshelf.setOnClickListener {
NavigationHelper.navigateTo(this, BookShelfActivity::class.java)
}
- reading_comprehension.setOnClickListener {
- NavigationHelper.navigateTo(this, PerceptionActivity::class.java)
- }
homepage.setOnClickListener {
NavigationHelper.navigateTo(this, MyActivity::class.java)
}
diff --git a/app/src/main/java/com/zjgsu/jianshu/Adapter/BillboardAdapter.kt b/app/src/main/java/com/zjgsu/jianshu/Adapter/BillboardAdapter.kt
index 9005118..9495959 100644
--- a/app/src/main/java/com/zjgsu/jianshu/Adapter/BillboardAdapter.kt
+++ b/app/src/main/java/com/zjgsu/jianshu/Adapter/BillboardAdapter.kt
@@ -42,7 +42,6 @@ class BillboardAdapter(var listOfbookRankList: List>) : Recycler
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val bookRankList = listOfbookRankList[position]
- Log.d("BillboardAdapter", "Size of bookRankList at position $position")
if(position==0) {
holder.bookRank_title.text="热门图书榜"
holder.bookRank_decr1.text = "在读人数:"+bookRankList[0].courtOfReaders.toString()
diff --git a/app/src/main/java/com/zjgsu/jianshu/Adapter/PerceptionAdapter.kt b/app/src/main/java/com/zjgsu/jianshu/Adapter/PerceptionAdapter.kt
index 13a1d92..09b2483 100644
--- a/app/src/main/java/com/zjgsu/jianshu/Adapter/PerceptionAdapter.kt
+++ b/app/src/main/java/com/zjgsu/jianshu/Adapter/PerceptionAdapter.kt
@@ -71,8 +71,6 @@ class PerceptionAdapter(val perceptionList: List) :
for (i in list) {
if (p.userid.equals(i.objectId)) {
holder.perception_posetPerson.text = i.nickName
-// flag = true //找到
- Log.d("perception", "find sucessflly")
//加载图片
Glide.with(holder.itemView.context)
.load(i.face.url) // 确保你的Book对象有正确的图片URL
@@ -110,55 +108,6 @@ class PerceptionAdapter(val perceptionList: List) :
}
}
})
- //加载书籍封面
-// query.findObjects(object : FindListener() {
-// override fun done(p0: MutableList?, p1: BmobException?) {
-// if (p1 == null) {
-// if (p0 != null && p0.size > 0) {
-// for (p in p0) {
-// Log.d("myLog", p.b_name)
-// bookName=p.b_name
-// holder.perceptionList_bookName.text=bookName
-// val queryBook = BmobQuery()
-// queryBook.findObjects(object : FindListener() {
-// override fun done(list: List, e: BmobException?) {
-// if (e == null) {
-// for (i in list) {
-// if (p.b_name.equals(i.name)) {
-// holder.perceptionList_bookAuthor.text=i.author_name
-// //加载图片
-// object : Thread() {
-// override fun run() {
-// try {
-// val url = URL(i!!.picture?.url)
-// val connection: HttpURLConnection =
-// url.openConnection() as HttpURLConnection
-// connection.setRequestMethod("GET")
-// connection.setConnectTimeout(3000)
-// val `in`: InputStream = connection.getInputStream()
-// val bitmap: Bitmap =
-// BitmapFactory.decodeStream(`in`)
-// holder.perceptionList_bookPic.setImageBitmap(bitmap)
-// } catch (e: Exception) {
-// e.printStackTrace()
-// }
-// }
-// }.start()
-//
-//
-// }
-// }
-// } else {
-// Log.d("myLog", "error")
-// }
-// }
-// })
-// }
-// }
-// }
-// }
-// })
-
holder.perception_bookInf.setOnClickListener() {
val intent = Intent(holder.itemView.context, BookInformationActivity::class.java)
intent.putExtra("Book_name", bookName)
diff --git a/app/src/main/java/com/zjgsu/jianshu/Bmob/Bookcontent_bmob.kt b/app/src/main/java/com/zjgsu/jianshu/Bmob/Bookcontent_bmob.kt
new file mode 100644
index 0000000..95baf8f
--- /dev/null
+++ b/app/src/main/java/com/zjgsu/jianshu/Bmob/Bookcontent_bmob.kt
@@ -0,0 +1,9 @@
+package com.zjgsu.jianshu.Bmob
+
+import cn.bmob.v3.BmobObject
+import cn.bmob.v3.datatype.BmobFile
+
+class Bookcontent_bmob:BmobObject() {
+ val bookpath:BmobFile=BmobFile()
+ val bookname:String=""
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zjgsu/jianshu/Fragment/ContentFragment.kt b/app/src/main/java/com/zjgsu/jianshu/Fragment/ContentFragment.kt
new file mode 100644
index 0000000..7048bff
--- /dev/null
+++ b/app/src/main/java/com/zjgsu/jianshu/Fragment/ContentFragment.kt
@@ -0,0 +1,50 @@
+package com.zjgsu.jianshu.Fragment
+
+import android.graphics.Typeface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import com.zjgsu.jianshu.R
+
+class BookPageFragment : Fragment() {
+ private var bookContent: String? = null
+ private var pageIndex: Int = 0
+
+ companion object {
+ private const val ARG_BOOK_CONTENT = "book_content"
+ private const val ARG_PAGE_INDEX = "page_index"
+
+ fun newInstance(bookContent: String, pageIndex: Int): BookPageFragment {
+ return BookPageFragment().apply {
+ arguments = Bundle().apply {
+ putString(ARG_BOOK_CONTENT, bookContent)
+ putInt(ARG_PAGE_INDEX, pageIndex)
+ }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ bookContent = it.getString(ARG_BOOK_CONTENT)
+ pageIndex = it.getInt(ARG_PAGE_INDEX)
+ }
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ val view = inflater.inflate(R.layout.fragment_content, container, false)
+ val textViewContent: TextView = view.findViewById(R.id.textViewContent)
+
+ // 计算当前页的内容
+ val startIndex = pageIndex * (10 * 100)
+ val endIndex = minOf((pageIndex + 1) * (10 * 100), bookContent?.length ?: 0)
+ val currentPageContent = bookContent?.substring(startIndex, endIndex) ?: ""
+ textViewContent.text = currentPageContent
+
+ return view
+ }
+}
diff --git a/app/src/main/res/drawable/chatbot.png b/app/src/main/res/drawable/chatbot.png
new file mode 100644
index 0000000..92f77f6
Binary files /dev/null and b/app/src/main/res/drawable/chatbot.png differ
diff --git a/app/src/main/res/drawable/ic_copy.png b/app/src/main/res/drawable/ic_copy.png
new file mode 100644
index 0000000..bbe1f5d
Binary files /dev/null and b/app/src/main/res/drawable/ic_copy.png differ
diff --git a/app/src/main/res/drawable/ic_regenerate.png b/app/src/main/res/drawable/ic_regenerate.png
new file mode 100644
index 0000000..bb76d4f
Binary files /dev/null and b/app/src/main/res/drawable/ic_regenerate.png differ
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 723b403..a1af26d 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -29,8 +29,10 @@
+
+ android:layout_height="match_parent" />
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
diff --git a/app/src/main/res/layout/fragment_content.xml b/app/src/main/res/layout/fragment_content.xml
new file mode 100644
index 0000000..b8ad181
--- /dev/null
+++ b/app/src/main/res/layout/fragment_content.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..a6b3dae
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 22dc47a..f19806a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,6 +3,7 @@ buildscript {
repositories {
google()
mavenCentral()
+ maven { url 'https://jitpack.io' }
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.2"
@@ -12,8 +13,6 @@ buildscript {
}
}
-
-
task clean(type: Delete) {
delete rootProject.buildDir
}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2f839a1..e2819d1 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -4,3 +4,4 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
+
diff --git a/hwtxtreaderlib/.gitignore b/hwtxtreaderlib/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/hwtxtreaderlib/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/hwtxtreaderlib/build.gradle b/hwtxtreaderlib/build.gradle
new file mode 100644
index 0000000..5c16d85
--- /dev/null
+++ b/hwtxtreaderlib/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'com.android.library'
+
+
+android {
+ compileSdkVersion 31
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 30
+ versionCode 6
+ versionName "4.5"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation files('libs/chardet.jar')
+ implementation 'com.google.android.material:material:1.3.0'
+ implementation 'io.github.bmob:android-sdk:3.9.4'
+ implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
+ implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
+ implementation 'com.squareup.okhttp3:okhttp:4.8.1'
+ implementation 'com.squareup.okio:okio:2.2.2'
+ implementation 'com.google.code.gson:gson:2.8.5'
+ implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+ implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
+
+}
diff --git a/hwtxtreaderlib/libs/chardet.jar b/hwtxtreaderlib/libs/chardet.jar
new file mode 100644
index 0000000..69f6503
Binary files /dev/null and b/hwtxtreaderlib/libs/chardet.jar differ
diff --git a/hwtxtreaderlib/proguard-rules.pro b/hwtxtreaderlib/proguard-rules.pro
new file mode 100644
index 0000000..f66c05e
--- /dev/null
+++ b/hwtxtreaderlib/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:\developtool\sdk\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/hwtxtreaderlib/src/main/AndroidManifest.xml b/hwtxtreaderlib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4db255e
--- /dev/null
+++ b/hwtxtreaderlib/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hwtxtreaderlib/src/main/assets/config.properties b/hwtxtreaderlib/src/main/assets/config.properties
new file mode 100644
index 0000000..4b2ebb9
--- /dev/null
+++ b/hwtxtreaderlib/src/main/assets/config.properties
@@ -0,0 +1 @@
+auth_key=sk-No44eOPA2V17ZQmuD897C61b905743B9Aa2d83B6EeF2C38a
diff --git a/hwtxtreaderlib/src/main/assets/fonts/text_style.TTF b/hwtxtreaderlib/src/main/assets/fonts/text_style.TTF
new file mode 100644
index 0000000..6b87c1b
Binary files /dev/null and b/hwtxtreaderlib/src/main/assets/fonts/text_style.TTF differ
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/adapter/MessageAdapter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/adapter/MessageAdapter.java
new file mode 100644
index 0000000..ecde23b
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/adapter/MessageAdapter.java
@@ -0,0 +1,69 @@
+package com.bifan.txtreaderlib.adapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bifan.txtreaderlib.R;
+import com.bifan.txtreaderlib.bean.Message;
+
+import java.util.List;
+
+public class MessageAdapter extends RecyclerView.Adapter {
+ private List messages;
+
+ public MessageAdapter(List messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ Message message = messages.get(position);
+ return message.getIsuser();
+ }
+
+ @NonNull
+ @Override
+ public MessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ View view;
+ if (viewType == 1) {
+ view = inflater.inflate(R.layout.item_message_user, parent, false);
+ } else {
+ view = inflater.inflate(R.layout.item_message_bot, parent, false);
+ }
+ return new MessageViewHolder(view, viewType);
+ }
+
+
+ @Override
+ public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
+ Message message = messages.get(position);
+ if (message.getIsuser() == 1) {
+ holder.textViewMessage.setText(message.getContent());
+ } else {
+ holder.textViewMessage.setText(message.getContent());
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return messages.size();
+ }
+
+ static class MessageViewHolder extends RecyclerView.ViewHolder {
+ TextView textViewMessage;
+
+ MessageViewHolder(View itemView, int viewType) {
+ super(itemView);
+ if (viewType == 1) { // 假设 1 是用户消息
+ textViewMessage = itemView.findViewById(R.id.textViewMessageUser);
+ } else { // 非 1 即机器人消息
+ textViewMessage = itemView.findViewById(R.id.textViewMessageBot);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Chapter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Chapter.java
new file mode 100644
index 0000000..4182507
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Chapter.java
@@ -0,0 +1,99 @@
+package com.bifan.txtreaderlib.bean;
+
+import com.bifan.txtreaderlib.interfaces.IChapter;
+
+/**
+ * @description
+ * @author bifan-wei
+ * @time 2021/11/13 16:05
+ */
+
+public class Chapter implements IChapter {
+ public String Title;
+ public int Index;//章节位置
+ public int StartParagraphIndex;
+ public int EndParagraphIndex;
+ public int StartCharIndex;//字符开始位置
+ public int EndCharIndex;//字符结束位置
+ public int StartIndex;//第一个字符在全文字符的位置
+
+ public Chapter(String title, int startParagraphIndex, int endParagraphIndex) {
+ Title = title;
+ StartParagraphIndex = startParagraphIndex;
+ EndParagraphIndex = endParagraphIndex;
+ }
+
+ public Chapter(int startIndex, int index, String title, int startParagraphIndex, int endParagraphIndex, int startCharIndex, int endCharIndex) {
+ StartIndex = startIndex;
+ Title = title;
+ Index = index;
+ StartParagraphIndex = startParagraphIndex;
+ EndParagraphIndex = endParagraphIndex;
+ StartCharIndex = startCharIndex;
+ EndCharIndex = endCharIndex;
+ }
+
+ public Chapter() {
+ }
+
+ public void setTitle(String title) {
+ Title = title;
+ }
+
+ @Override
+ public int getStartIndex() {
+ return StartIndex;
+ }
+
+ @Override
+ public int getStartCharIndex() {
+ return StartCharIndex;
+ }
+
+ @Override
+ public int getEndCharIndex() {
+ return EndCharIndex;
+ }
+
+ @Override
+ public int getStartParagraphIndex() {
+ return StartParagraphIndex;
+ }
+
+ @Override
+ public int getEndParagraphIndex() {
+ return EndParagraphIndex;
+ }
+
+ @Override
+ public String getTitle() {
+ return Title;
+ }
+
+ @Override
+ public void setStartParagraphIndex(int index) {
+ StartParagraphIndex = index;
+ }
+
+ @Override
+ public void setEndParagraphIndex(int index) {
+ EndParagraphIndex = index;
+ }
+
+ @Override
+ public int getIndex() {
+ return Index;
+ }
+
+ @Override
+ public String toString() {
+ return "Chapter{" +
+ "Title='" + Title + '\'' +
+ ", Index=" + Index +
+ ", StartParagraphIndex=" + StartParagraphIndex +
+ ", EndParagraphIndex=" + EndParagraphIndex +
+ ", StartCharIndex=" + StartCharIndex +
+ ", EndCharIndex=" + EndCharIndex +
+ '}';
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatMessage_bmob.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatMessage_bmob.java
new file mode 100644
index 0000000..9111466
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatMessage_bmob.java
@@ -0,0 +1,49 @@
+package com.bifan.txtreaderlib.bean;
+
+import cn.bmob.v3.BmobObject;
+
+public class ChatMessage_bmob extends BmobObject {
+ private String content;
+ private Integer isuser;
+ private String relatedBook;
+ private String userid;
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public Integer getIsuser() {
+ return isuser;
+ }
+
+ public void setIsuser(Integer isuser) {
+ this.isuser = isuser;
+ }
+
+ public String getRelatedBook() {
+ return relatedBook;
+ }
+
+ public void setRelatedBook(String relatedBook) {
+ this.relatedBook = relatedBook;
+ }
+
+ public String getUserid() {
+ return userid;
+ }
+
+ public void setUserid(String userid) {
+ this.userid = userid;
+ }
+
+ public ChatMessage_bmob(String content, Integer isuser, String relatedBook, String userid) {
+ this.content = content;
+ this.isuser = isuser;
+ this.relatedBook = relatedBook;
+ this.userid = userid;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatRequest.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatRequest.java
new file mode 100644
index 0000000..86e20da
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatRequest.java
@@ -0,0 +1,71 @@
+package com.bifan.txtreaderlib.bean;
+
+import java.util.List;
+
+
+//curl https://api.openai.com/v1/chat/completions \
+// -H "Content-Type: application/json" \
+// -H "Authorization: Bearer $OPENAI_API_KEY" \
+// -d '{
+// "model": "gpt-3.5-turbo",
+// "messages": [
+// {
+// "role": "system",
+// "content": "You are a helpful assistant."
+// },
+// {
+// "role": "user",
+// "content": "Hello!"
+// }
+// ]
+// }'
+public class ChatRequest {
+ private String model;
+ private List messages;
+ private Integer maxTokens;
+ private Double temperature;
+
+ public ChatRequest(List messages) {
+ this.messages = messages;
+ this.model = "gpt-3.5-turbo"; // 默认模型
+ this.maxTokens = 150; // 默认的最大 token 数
+ this.temperature = 0.7;
+ }
+ public void setMaxTokens(Integer maxTokens) {
+ this.maxTokens = maxTokens;
+ }
+ public Integer getMaxTokens() {
+ return maxTokens;
+ }
+ public void setTemperature(Double temperature) {
+ this.temperature = temperature;
+ }
+
+ public Double getTemperature() {
+ return temperature;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ public List getMessages() {
+ return messages;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ // Message 类
+ public static class Message {
+ String role;
+ String content;
+
+ public Message(String role, String content) {
+ this.role = role;
+ this.content = content;
+ }
+ }
+}
+
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatResponse.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatResponse.java
new file mode 100644
index 0000000..22a2800
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatResponse.java
@@ -0,0 +1,47 @@
+package com.bifan.txtreaderlib.bean;
+
+import java.util.List;
+
+//{
+// "id": "chatcmpl-123",
+// "object": "chat.completion",
+// "created": 1677652288,
+// "model": "gpt-3.5-turbo-0125",
+// "system_fingerprint": "fp_44709d6fcb",
+// "choices": [{
+// "index": 0,
+// "message": {
+// "role": "assistant",
+// "content": "\n\nHello there, how may I assist you today?",
+// },
+// "logprobs": null,
+// "finish_reason": "stop"
+// }],
+// "usage": {
+// "prompt_tokens": 9,
+// "completion_tokens": 12,
+// "total_tokens": 21
+// }
+// }
+
+
+public class ChatResponse {
+ String id;
+ String object;
+ long created;
+ String model;
+ public List choices;
+
+ public static class Choice {
+ int index;
+ public Message message;
+ // 你可以根据需要继续添加其他字段如 logprobs, finish_reason 等
+ }
+
+ public static class Message {
+ String role;
+ public String content;
+ }
+
+ // 假设你已经有 getter 方法或者你可以自行添加
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultLeftSlider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultLeftSlider.java
new file mode 100644
index 0000000..81cb8d4
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultLeftSlider.java
@@ -0,0 +1,37 @@
+package com.bifan.txtreaderlib.bean;
+
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * created by : bifan-wei
+ */
+
+public class DefaultLeftSlider extends Slider{
+ @Override
+ public float getX(float dx) {
+ return Right + dx +5;
+ }
+
+ @Override
+ public float getY(float dy) {
+ return Top + dy - 5;
+ }
+
+ @Override
+ public Path getPath(TxtChar txtChar,Path path) {
+ if (txtChar != null) {
+ Path p = path;
+ p.reset();
+ p.moveTo(txtChar.Left, txtChar.Bottom);
+ p.lineTo(txtChar.Left, txtChar.Bottom + SliderWidth);
+ Rect rect = new Rect(txtChar.Left - SliderWidth * 2, txtChar.Bottom, txtChar.Left, txtChar.Bottom + SliderWidth * 2);
+ p.addArc(new RectF(rect), 0, 270);
+ p.lineTo(txtChar.Left, txtChar.Bottom);
+ return p;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultRightSlider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultRightSlider.java
new file mode 100644
index 0000000..08caec0
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultRightSlider.java
@@ -0,0 +1,37 @@
+package com.bifan.txtreaderlib.bean;
+
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * created by : bifan-wei
+ */
+
+public class DefaultRightSlider extends Slider{
+ @Override
+ public float getX(float dx) {
+ return Left + dx - 5;
+ }
+
+ @Override
+ public float getY(float dy) {
+ return Top + dy - 5;
+ }
+
+ @Override
+ public Path getPath(TxtChar txtChar,Path path) {
+ if (txtChar != null) {
+ Path p = path;
+ p.reset();
+ p.moveTo(txtChar.Right, txtChar.Bottom + SliderWidth);
+ p.lineTo(txtChar.Right, txtChar.Bottom);
+ p.lineTo(txtChar.Right + SliderWidth, txtChar.Bottom);
+ Rect rect = new Rect(txtChar.Right, txtChar.Bottom, txtChar.Right + SliderWidth * 2, txtChar.Bottom + SliderWidth * 2);
+ p.addArc(new RectF(rect), -90, 270);
+ return p;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/EnChar.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/EnChar.java
new file mode 100644
index 0000000..553f6d1
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/EnChar.java
@@ -0,0 +1,23 @@
+package com.bifan.txtreaderlib.bean;
+
+import android.graphics.Color;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class EnChar extends TxtChar {
+ public static int DefaultTextColor = Color.parseColor("#45a1cd");
+ public EnChar(char aChar) {
+ super(aChar);
+ }
+ @Override
+ public int getTextColor() {
+ return DefaultTextColor;
+ }
+
+ @Override
+ public int getCharType() {
+ return Char_En;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/FileReadRecordBean.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/FileReadRecordBean.java
new file mode 100644
index 0000000..e0a5f10
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/FileReadRecordBean.java
@@ -0,0 +1,26 @@
+package com.bifan.txtreaderlib.bean;
+
+/**
+ * created by : bifan-wei
+ */
+
+public class FileReadRecordBean {
+ public String fileHashName;
+ public int id;
+ public String fileName;
+ public String filePath;
+ public int paragraphIndex;
+ public int chartIndex;
+
+ @Override
+ public String toString() {
+ return "FileReadRecordBean{" +
+ "fileHashName='" + fileHashName + '\'' +
+ ", id=" + id +
+ ", fileName='" + fileName + '\'' +
+ ", filePath='" + filePath + '\'' +
+ ", paragraphIndex='" + paragraphIndex + '\'' +
+ ", chartIndex='" + chartIndex + '\'' +
+ '}';
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Message.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Message.java
new file mode 100644
index 0000000..00ccf38
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Message.java
@@ -0,0 +1,27 @@
+package com.bifan.txtreaderlib.bean;
+
+public class Message {
+ private String content;
+ private Integer isuser;
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public Integer getIsuser() {
+ return isuser;
+ }
+
+ public void setIsuser(Integer isuser) {
+ this.isuser = isuser;
+ }
+
+ public Message(String content, Integer isuser) {
+ this.content = content;
+ this.isuser = isuser;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiLeftSlider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiLeftSlider.java
new file mode 100644
index 0000000..ac10ce8
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiLeftSlider.java
@@ -0,0 +1,47 @@
+package com.bifan.txtreaderlib.bean;
+
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * created by : bifan-wei
+ */
+
+public class MuiLeftSlider extends Slider {
+ @Override
+ public float getX(float dx) {
+ return Right + dx - 5;
+ }
+
+ @Override
+ public float getY(float dy) {
+ return Top + dy - 5;
+ }
+
+ @Override
+ public Path getPath(TxtChar txtChar, Path path) {
+ if (txtChar != null) {
+
+ int r = SliderWidth;
+ int leftWidth = (int) (Math.cos(30) * r);
+ int heightWidth = r * 3 / 2;
+ Path p = path;
+ p.reset();
+ p.moveTo(txtChar.Left, txtChar.Top);
+ p.lineTo(txtChar.Left - leftWidth, txtChar.Top - heightWidth);
+
+ Rect rect = new Rect(
+ txtChar.Left - r,
+ txtChar.Top - 3 * r,
+ txtChar.Left + r,
+ txtChar.Top - r);
+
+ p.addArc(new RectF(rect), 150, 240);
+ p.lineTo(txtChar.Left, txtChar.Top);
+ return p;
+ } else {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiRightSlider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiRightSlider.java
new file mode 100644
index 0000000..9a0f355
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiRightSlider.java
@@ -0,0 +1,46 @@
+package com.bifan.txtreaderlib.bean;
+
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * created by : bifan-wei
+ */
+
+public class MuiRightSlider extends Slider {
+ @Override
+ public float getX(float dx) {
+ return Left + dx + 5;
+ }
+
+ @Override
+ public float getY(float dy) {
+ return Bottom + dy + 5;
+ }
+
+ @Override
+ public Path getPath(TxtChar txtChar, Path path) {
+ if (txtChar != null) {
+ int r = SliderWidth;
+ int leftWidth = (int) (Math.cos(30) * r);
+ int heightWidth = r * 3 / 2;
+ Path p = path;
+ p.reset();
+ p.moveTo(txtChar.Right, txtChar.Bottom);
+ p.lineTo(txtChar.Right + leftWidth, txtChar.Bottom + heightWidth);
+
+ Rect rect = new Rect(
+ txtChar.Right - r,
+ txtChar.Bottom + r,
+ txtChar.Right + r,
+ txtChar.Bottom +3*r);
+
+ p.addArc(new RectF(rect), -30, 240);
+ p.lineTo(txtChar.Right, txtChar.Bottom);
+ return p;
+ } else {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/NumChar.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/NumChar.java
new file mode 100644
index 0000000..7586556
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/NumChar.java
@@ -0,0 +1,23 @@
+package com.bifan.txtreaderlib.bean;
+
+import android.graphics.Color;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class NumChar extends TxtChar {
+ public static int DefaultTextColor = Color.parseColor("#45a1cf");
+ public NumChar(char aChar) {
+ super(aChar);
+ }
+ @Override
+ public int getTextColor() {
+ return DefaultTextColor;
+ }
+
+ @Override
+ public int getCharType() {
+ return Char_Num;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Page.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Page.java
new file mode 100644
index 0000000..56b06cc
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Page.java
@@ -0,0 +1,236 @@
+package com.bifan.txtreaderlib.bean;
+
+import com.bifan.txtreaderlib.interfaces.ICursor;
+import com.bifan.txtreaderlib.interfaces.IPage;
+import com.bifan.txtreaderlib.interfaces.ITxtLine;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/*
+ * create by bifan-wei
+ * 2017-11-13
+ */
+public class Page implements IPage, ICursor {
+ private int CurrentIndex;
+ private List lines = null;
+ private boolean fullPage = false;
+
+ @Override
+ public int CurrentIndex() {
+ return CurrentIndex;
+ }
+
+ @Override
+ public void addLineTo(ITxtLine line, int index) {
+ if (line == null) {
+ throw new NullPointerException("line == null on addLine form Page");
+ }
+ if (lines == null) {
+ lines = new ArrayList<>();
+ }
+ lines.add(index, line);
+ }
+
+ @Override
+ public void addLine(ITxtLine line) {
+ if (line == null) {
+ throw new NullPointerException("line == null on addLine form Page");
+ }
+ if (lines == null) {
+ lines = new ArrayList<>();
+ }
+ lines.add(line);
+ }
+
+ @Override
+ public void setLines(List lines) {
+ this.lines = lines;
+ }
+
+ @Override
+ public int getCount() {
+ return lines == null ? 0 : lines.size();
+ }
+
+ @Override
+ public void moveToPosition(int index) {
+ if (HasData()) {
+ if (index < 0 || index >= getCount()) {
+ throw new ArrayIndexOutOfBoundsException
+ (" moveToPosition index OutOfBoundsException from page");
+ }
+ CurrentIndex = index;
+ Current();
+ }
+
+ }
+
+ @Override
+ public TxtChar getFirstChar() {
+ ITxtLine firstLine = getFirstLine();
+ if (!firstLine.HasData()) {
+ return null;
+ } else {
+ return firstLine.getFirstChar();
+ }
+ }
+
+ @Override
+ public void moveToFirst() {
+ moveToPosition(0);
+ }
+
+ @Override
+ public TxtChar getLastChar() {
+ ITxtLine lastLine = getLastLine();
+ if (!lastLine.HasData()) {
+ return null;
+ } else {
+ return lastLine.getLastChar();
+ }
+ }
+
+ @Override
+ public void moveToLast() {
+ CurrentIndex = getCount() - 1;
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ }
+
+ @Override
+ public ITxtLine getFirstLine() {
+ moveToFirst();
+ return Current();
+ }
+
+ @Override
+ public void moveToNext() {
+ CurrentIndex++;
+ if (CurrentIndex >= getCount()) {
+ CurrentIndex = getCount() - 1;
+ }
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ }
+
+ @Override
+ public ITxtLine getLastLine() {
+ moveToLast();
+ return Current();
+ }
+
+ @Override
+ public void moveToPrevious() {
+ CurrentIndex--;
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ }
+
+ @Override
+ public boolean isFirst() {
+ return CurrentIndex == 0;
+ }
+
+ @Override
+ public ICursor getLineCursor() {
+ return this;
+ }
+
+ @Override
+ public boolean isLast() {
+ return CurrentIndex == getCount() - 1;
+ }
+
+ @Override
+ public boolean isFullPage() {
+ return fullPage;
+ }
+
+ @Override
+ public void setFullPage(boolean fullPage) {
+ this.fullPage = fullPage;
+ }
+
+ @Override
+ public int getLineNum() {
+ return getCount();
+ }
+
+ private Boolean AfterLast = false;
+ private Boolean BeforeFirst = false;
+
+ @Override
+ public boolean isBeforeFirst() {
+ return BeforeFirst;
+ }
+
+
+ @Override
+ public boolean isAfterLast() {
+ return AfterLast;
+ }
+
+ @Override
+ public Boolean HasData() {
+ return getCount() > 0;
+ }
+
+ @Override
+ public ITxtLine Pre() {
+ CurrentIndex--;
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ return Current();
+ }
+
+ @Override
+ public ITxtLine Next() {
+ CurrentIndex++;
+ if (CurrentIndex >= getCount()) {
+ CurrentIndex = getCount() - 1;
+ }
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ return Current();
+ }
+
+ @Override
+ public ITxtLine Current() {
+ AfterLast = isLast();
+ BeforeFirst = isFirst();
+ return lines == null ? null : getLine(CurrentIndex);
+ }
+
+ @Override
+ public ITxtLine getLine(int index) {
+ return lines == null ? null : lines.get(index);
+ }
+
+ @Override
+ public String toString() {
+ String str = "";
+ if (HasData()) {
+ for (ITxtLine l : lines) {
+ str = str + l.getLineStr() + "\r\n";
+ }
+ }
+ return str;
+ }
+
+ @Override
+ public List getLines() {
+ return lines;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Slider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Slider.java
new file mode 100644
index 0000000..85e2a9d
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Slider.java
@@ -0,0 +1,31 @@
+package com.bifan.txtreaderlib.bean;
+
+import android.graphics.Path;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/5.
+ */
+
+public abstract class Slider {
+ public Boolean ShowBellow= true;
+ public int Left;
+ public int Right;
+ public int Top;
+ public int Bottom;
+ public int SliderWidth;
+
+ public abstract float getX(float dx);//手势判断位置x坐标
+ public abstract float getY(float dy);//手势判断位置y坐标
+ public abstract Path getPath(TxtChar txtChar,Path path);//获取当前滑动条Path
+ @Override
+ public String toString() {
+ return "Slider{" +
+ "ShowBellow=" + ShowBellow +
+ ", Left=" + Left +
+ ", Right=" + Right +
+ ", Top=" + Top +
+ ", Bottom=" + Bottom +
+ '}';
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtChar.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtChar.java
new file mode 100644
index 0000000..0526095
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtChar.java
@@ -0,0 +1,72 @@
+package com.bifan.txtreaderlib.bean;
+
+
+import android.graphics.Color;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class TxtChar {
+ public static final int Char_text = 0x01;
+ public static final int Char_Num = 0x02;
+ public static final int Char_En = 0x03;
+
+ public char Char;
+ public int ParagraphIndex;
+ public float CharWidth = 0;
+ public int CharIndex;
+ public int TextColor = Color.BLACK;
+ public int PositionX;
+ public int PositionY;
+ public int Left;
+ public int Right;
+ public int Bottom;
+ public int Top;
+
+ public TxtChar(char aChar) {
+ Char = aChar;
+ }
+
+ public int getTextColor() {
+ return TextColor;
+ }
+
+ public int getCharType() {
+ return Char_text;
+ }
+
+ public char getValue() {
+ return Char;
+ }
+
+ public String getValueStr() {
+ return String.valueOf(getValue());
+ }
+
+ @Override
+ public String toString() {
+ return "TxtChar{" +
+ "Char=" + Char +
+ ", ParagraphIndex=" + ParagraphIndex +
+ ", CharWidth=" + CharWidth +
+ ", CharIndex=" + CharIndex +
+ ", TextColor=" + TextColor +
+ ", PositionX=" + PositionX +
+ ", PositionY=" + PositionY +
+ ", Left=" + Left +
+ ", Right=" + Right +
+ ", Bottom=" + Bottom +
+ ", Top=" + Top +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ TxtChar to = (TxtChar) obj;
+ if (to != null) {
+ return ParagraphIndex == to.ParagraphIndex && CharIndex == to.CharIndex && Char == to.Char && Top == to.Top;
+ }
+ return false;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtFileMsg.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtFileMsg.java
new file mode 100644
index 0000000..d001e22
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtFileMsg.java
@@ -0,0 +1,18 @@
+package com.bifan.txtreaderlib.bean;
+
+/**
+ * created by : bifan-wei
+ */
+
+public class TxtFileMsg {
+ public String FilePath;
+ public String FileName;
+ public String HexName;
+ public long FileSize;
+ public int CurrentParagraphIndex;
+ public int CurrentCharIndex;
+ public int PreParagraphIndex =-1;
+ public int PreCharIndex = -1;
+ public String FileCode;
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtLine.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtLine.java
new file mode 100644
index 0000000..4a835a1
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtLine.java
@@ -0,0 +1,224 @@
+package com.bifan.txtreaderlib.bean;
+
+import com.bifan.txtreaderlib.interfaces.ICursor;
+import com.bifan.txtreaderlib.interfaces.ITxtLine;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class TxtLine implements ITxtLine, ICursor {
+ private int CurrentIndex;
+ private List chars = null;
+ private boolean isParagraphEndLine = false;
+
+ public TxtLine() {
+ }
+ @Override
+ public int CurrentIndex() {
+ return CurrentIndex;
+ }
+
+ @Override
+ public Boolean HasData() {
+ return getCount() != 0;
+ }
+
+ @Override
+ public List getTxtChars() {
+ return chars;
+ }
+
+ @Override
+ public void addChar(TxtChar txtChar) {
+ if (chars == null) {
+ chars = new ArrayList<>();
+ }
+ chars.add(txtChar);
+
+ }
+
+
+ @Override
+ public int getCount() {
+ return chars == null ? 0 : chars.size();
+ }
+
+ @Override
+ public void moveToPosition(int index) {
+ if (index < 0 || index >= getCount()) {
+ throw new ArrayIndexOutOfBoundsException(" moveToPosition index OutOfBoundsException");
+ }
+ CurrentIndex = index;
+ }
+
+ @Override
+ public int getCharNum() {
+ return getCount();
+ }
+
+ @Override
+ public void moveToFirst() {
+ CurrentIndex = 0;
+ Current();
+ }
+
+ @Override
+ public TxtChar getFirstChar() {
+ CurrentIndex = 0;
+ return Current();
+ }
+
+ @Override
+ public void moveToLast() {
+ CurrentIndex = getCount() - 1;
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ Current();
+ }
+
+ @Override
+ public TxtChar getLastChar() {
+ CurrentIndex = getCount() - 1;
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ return Current();
+ }
+
+ @Override
+ public void moveToNext() {
+ CurrentIndex++;
+ if (CurrentIndex >= getCount()) {
+ CurrentIndex = getCount() - 1;
+ }
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ }
+
+ @Override
+ public TxtChar getChar(int index) {
+ if (index < 0 || index >= getCount()) {
+ throw new ArrayIndexOutOfBoundsException(" moveToPosition index OutOfBoundsException");
+ }
+ return chars == null ? null : chars.get(index);
+ }
+
+ @Override
+ public void moveToPrevious() {
+ CurrentIndex--;
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ }
+
+ @Override
+ public boolean isFirst() {
+ return CurrentIndex == 0;
+ }
+
+ @Override
+ public ICursor getCharCursor() {
+ return this;
+ }
+
+ @Override
+ public boolean isLast() {
+ return CurrentIndex == getCount() - 1;
+ }
+
+ @Override
+ public String getLineStr() {
+ String str = "";
+ for (TxtChar txtChar : chars) {
+ str = str + txtChar.getValue();
+ }
+ return str;
+ }
+
+
+ @Override
+ public boolean isBeforeFirst() {
+ return BeforeFirst;
+ }
+
+ @Override
+ public char[] getLineChar() {
+ return (getLineStr() + "").toCharArray();
+ }
+
+ @Override
+ public boolean isAfterLast() {
+ return AfterLast;
+ }
+
+ private Boolean AfterLast = false;
+ private Boolean BeforeFirst = false;
+
+ @Override
+ public TxtChar Pre() {
+ CurrentIndex--;
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ return Current();
+ }
+
+ @Override
+ public TxtChar Next() {
+ CurrentIndex++;
+ if (CurrentIndex >= getCount()) {
+ CurrentIndex = getCount() - 1;
+ }
+ if (CurrentIndex < 0) {
+ CurrentIndex = 0;
+ }
+ moveToPosition(CurrentIndex);
+ return Current();
+ }
+
+ @Override
+ public TxtChar Current() {
+ if (isLast()) {
+ AfterLast = true;
+ } else {
+ AfterLast = false;
+ }
+ if (isFirst()) {
+ BeforeFirst = true;
+ } else {
+ BeforeFirst = false;
+ }
+ return chars == null ? null : getChar(CurrentIndex);
+ }
+
+ @Override
+ public boolean isParagraphEndLine() {
+ return isParagraphEndLine;
+ }
+
+ public void setParagraphEndLine(boolean paragraphEndLine) {
+ isParagraphEndLine = paragraphEndLine;
+ }
+
+ @Override
+ public void Clear() {
+ if (chars != null) {
+ chars.clear();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "" + getLineStr();
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtMsg.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtMsg.java
new file mode 100644
index 0000000..c367ba6
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtMsg.java
@@ -0,0 +1,10 @@
+package com.bifan.txtreaderlib.bean;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public enum TxtMsg {
+ InitError,
+ FileNoExist,
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/constant/Constants.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/constant/Constants.java
new file mode 100644
index 0000000..90e9c91
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/constant/Constants.java
@@ -0,0 +1,11 @@
+package com.bifan.txtreaderlib.constant;
+
+import com.bifan.txtreaderlib.utils.ConfigUtils;
+
+public class Constants {
+ public static final String AUTH_KEY;
+
+ static {
+ AUTH_KEY = ConfigUtils.getAuthKey();
+ }
+}
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ChatGPTService.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ChatGPTService.java
new file mode 100644
index 0000000..807779b
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ChatGPTService.java
@@ -0,0 +1,15 @@
+package com.bifan.txtreaderlib.interfaces;
+
+import com.bifan.txtreaderlib.bean.ChatRequest;
+import com.bifan.txtreaderlib.bean.ChatResponse;
+
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface ChatGPTService {
+ @Headers("Authorization: Bearer sk-uucSf3pIkBZSYHzQ6028C9FcCe4e4d61B457497b4d72C894")
+ @POST("v1/chat/completions")
+ Call postMessage(@Body ChatRequest request);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICenterAreaClickListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICenterAreaClickListener.java
new file mode 100644
index 0000000..88bc334
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICenterAreaClickListener.java
@@ -0,0 +1,19 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/**
+ * created by : bifan-wei
+ */
+
+public interface ICenterAreaClickListener {
+ /**
+ * @param widthPercentInView
+ * @return 返回是否处理了这个事件,如果已经处理了,将可能会执行翻页事件
+ */
+ boolean onCenterClick(float widthPercentInView);
+
+ /**
+ * @param widthPercentInView
+ * @return 返回是否处理了这个事件,如果已经处理了,将可能会执行翻页事件
+ */
+ boolean onOutSideCenterClick(float widthPercentInView);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapter.java
new file mode 100644
index 0000000..33b519e
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapter.java
@@ -0,0 +1,27 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/**
+ * Created by HP on 2017/11/15.
+ */
+
+public interface IChapter {
+
+ int getIndex();
+
+ int getStartParagraphIndex();
+
+ int getEndParagraphIndex();
+
+ int getStartCharIndex();
+
+ int getEndCharIndex();
+
+ int getStartIndex();
+
+ String getTitle();
+
+ void setStartParagraphIndex(int index);
+
+ void setEndParagraphIndex(int index);
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapterMatcher.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapterMatcher.java
new file mode 100644
index 0000000..943b3fe
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapterMatcher.java
@@ -0,0 +1,14 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/**
+ * created by : bifan-wei
+ */
+
+public interface IChapterMatcher {
+ /**
+ * @param paragraphData 段落数据
+ * @param ParagraphIndex 当前段落位置
+ * @return
+ */
+ IChapter match(String paragraphData, int ParagraphIndex);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICharpter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICharpter.java
new file mode 100644
index 0000000..9754894
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICharpter.java
@@ -0,0 +1,14 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/**
+ * Created by HP on 2017/11/15.
+ */
+
+public interface ICharpter {
+ int getParagraphNum();
+ int getStartParagraphIndex();
+ int getEndParagraphIndex();
+ String getTitle();
+ void setStartParagraphIndex(int index);
+ void setEndParagraphIndex(int index);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICursor.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICursor.java
new file mode 100644
index 0000000..c30d2b4
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICursor.java
@@ -0,0 +1,21 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public interface ICursor {
+ int getCount();
+ void moveToPosition(int var1);
+ void moveToFirst();
+ void moveToLast();
+ void moveToNext();
+ void moveToPrevious();
+ boolean isFirst();
+ boolean isLast();
+ boolean isBeforeFirst();
+ boolean isAfterLast();
+ T Pre();
+ T Next();
+ T Current();
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ILoadListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ILoadListener.java
new file mode 100644
index 0000000..1ca6ff8
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ILoadListener.java
@@ -0,0 +1,14 @@
+package com.bifan.txtreaderlib.interfaces;
+
+
+import com.bifan.txtreaderlib.bean.TxtMsg;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public interface ILoadListener {
+ void onSuccess();
+ void onFail(TxtMsg txtMsg);
+ void onMessage(String message);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPage.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPage.java
new file mode 100644
index 0000000..33a1d76
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPage.java
@@ -0,0 +1,28 @@
+package com.bifan.txtreaderlib.interfaces;
+
+import com.bifan.txtreaderlib.bean.TxtChar;
+
+import java.util.List;
+
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public interface IPage {
+ ITxtLine getLine(int index);
+ void addLine(ITxtLine line);
+ void addLineTo(ITxtLine line, int index);
+ void setLines(List lines);
+ TxtChar getFirstChar();
+ TxtChar getLastChar();
+ ITxtLine getFirstLine();
+ ITxtLine getLastLine();
+ List getLines();
+ ICursor getLineCursor();
+ int getLineNum();
+ boolean isFullPage();//是否满页了
+ void setFullPage(boolean fullPage);
+ int CurrentIndex();
+ Boolean HasData();
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageChangeListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageChangeListener.java
new file mode 100644
index 0000000..fa445a2
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageChangeListener.java
@@ -0,0 +1,10 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/8.
+ */
+
+public interface IPageChangeListener {
+ void onCurrentPage(float progress);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageDataPipeline.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageDataPipeline.java
new file mode 100644
index 0000000..f2aa5d4
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageDataPipeline.java
@@ -0,0 +1,10 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public interface IPageDataPipeline {
+ IPage getPageStartFromProgress(int paragraphIndex, int charIndex);
+ IPage getPageEndToProgress(int paragraphIndex, int charIndex);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageEdgeListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageEdgeListener.java
new file mode 100644
index 0000000..02dee5c
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageEdgeListener.java
@@ -0,0 +1,10 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/**
+ * created by : bifan-wei
+ */
+
+public interface IPageEdgeListener {
+ void onCurrentFirstPage();
+ void onCurrentLastPage();
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IParagraphData.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IParagraphData.java
new file mode 100644
index 0000000..8764d45
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IParagraphData.java
@@ -0,0 +1,31 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public interface IParagraphData {
+ void addParagraph(int ParaIndex, String p);
+ void addParagraph(String p);
+ int getParagraphNum();
+ int getCharNum();
+ int findParagraphIndexByCharIndex(int CharIndex);
+ int getParaStartCharIndex(int ParaIndex);
+ String getParagraphStr(int ParaIndex);
+ /**
+ * @param ParaIndex 段落位置
+ * @param CharIndex 字符位置
+ * @return 如果段落位置超出范围,报越界异常,如果字符位置超出段落范围,返回空字符
+ */
+ String getParagraphStrFromStart(int ParaIndex,int CharIndex);
+
+ /**
+ * @param ParaIndex
+ * @param CharIndex
+ * @return 如果段落位置超出范围,报越界异常,如果字符位置超出段落范围,返回空字符
+ */
+ String getParagraphStrToEnd(int ParaIndex,int CharIndex);
+
+ void Clear();
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IReaderViewDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IReaderViewDrawer.java
new file mode 100644
index 0000000..57fcd32
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IReaderViewDrawer.java
@@ -0,0 +1,61 @@
+package com.bifan.txtreaderlib.interfaces;
+
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/1.
+ */
+
+public interface IReaderViewDrawer {
+ void drawPageNextPageShadow(Canvas canvas);
+ //绘制执行下一页的页边阴影
+
+ void drawPageNextBottomPage(Canvas canvas);
+ //绘制执行下一页时的下面页部分
+
+
+ void drawPageNextTopPage(Canvas canvas);
+ //绘制执行下一页时的上面页部分
+
+
+ void drawPagePrePageShadow(Canvas canvas);
+ //绘制执行上一页的页边阴影
+
+
+ void drawPagePreBottomPage(Canvas canvas);
+ //绘制执行上一页时的下面页部分
+
+
+ void drawPagePreTopPage(Canvas canvas);
+ //绘制执行上一页时的上面页部分
+
+
+ void startPageStateBackAnimation();
+ //状态恢复动画
+
+ void startPageNextAnimation();
+ //执行下一页动画
+
+ void startPagePreAnimation();
+ //执行上一页动画
+
+ void onTextSelectMoveForward(MotionEvent event);
+ // 文字向前选中
+
+ void onTextSelectMoveBack(MotionEvent event);
+ //文字向后选中
+
+ void onPageMove(MotionEvent event);
+ //页面移动
+
+ void drawNote(Canvas canvas);
+ //绘制笔记
+
+ void drawSelectedText(Canvas canvas);
+ //文字选中
+
+ void computeScroll();
+ //完成移动
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ISliderListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ISliderListener.java
new file mode 100644
index 0000000..b73aad4
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ISliderListener.java
@@ -0,0 +1,13 @@
+package com.bifan.txtreaderlib.interfaces;
+
+import com.bifan.txtreaderlib.bean.TxtChar;
+
+/**
+ * created by : bifan-wei
+ */
+
+public interface ISliderListener {
+ void onShowSlider(TxtChar txtChar);
+ void onShowSlider(String CurrentSelectedText);
+ void onReleaseSlider();
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectDrawer.java
new file mode 100644
index 0000000..3ed3767
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectDrawer.java
@@ -0,0 +1,19 @@
+package com.bifan.txtreaderlib.interfaces;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.bifan.txtreaderlib.bean.TxtChar;
+
+import java.util.List;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/4.
+ */
+
+public interface ITextSelectDrawer {
+ void drawSelectedChar(TxtChar selectedChar, Canvas canvas, Paint paint);
+
+ void drawSelectedLines(List selectedLines, Canvas canvas, Paint paint);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectListener.java
new file mode 100644
index 0000000..951dad1
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectListener.java
@@ -0,0 +1,14 @@
+package com.bifan.txtreaderlib.interfaces;
+
+import com.bifan.txtreaderlib.bean.TxtChar;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/5.
+ */
+
+public interface ITextSelectListener {
+ void onTextChanging(TxtChar firstSelectedChar,TxtChar lastSelectedChar);
+ void onTextChanging(String selectText);
+ void onTextSelected(String selectText);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtLine.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtLine.java
new file mode 100644
index 0000000..819516b
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtLine.java
@@ -0,0 +1,27 @@
+package com.bifan.txtreaderlib.interfaces;
+
+import com.bifan.txtreaderlib.bean.TxtChar;
+
+import java.util.List;
+
+
+/*
+* create by bifan-wei
+* 2017-11-14
+*/
+public interface ITxtLine {
+ List getTxtChars();
+ int getCharNum();
+ TxtChar getFirstChar();
+ TxtChar getLastChar();
+ TxtChar getChar(int index);
+ ICursor getCharCursor();
+ String getLineStr();
+ char[] getLineChar();
+ Boolean HasData();
+ void addChar(TxtChar txtChar);
+ void Clear();
+ int CurrentIndex();
+ boolean isParagraphEndLine();//判断是否是段落最后一句
+ void setParagraphEndLine(boolean isParagraphEndLine);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtReaderLoggerListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtReaderLoggerListener.java
new file mode 100644
index 0000000..0caa756
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtReaderLoggerListener.java
@@ -0,0 +1,9 @@
+package com.bifan.txtreaderlib.interfaces;
+
+/**
+ * Created by HP on 2017/11/15.
+ */
+
+public interface ITxtReaderLoggerListener {
+ void onLog(String tag, String msg);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtTask.java
new file mode 100644
index 0000000..1ca7b97
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtTask.java
@@ -0,0 +1,11 @@
+package com.bifan.txtreaderlib.interfaces;
+
+import com.bifan.txtreaderlib.main.TxtReaderContext;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public interface ITxtTask {
+ void Run(ILoadListener callBack, TxtReaderContext readerContext);
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/BitmapData.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/BitmapData.java
new file mode 100644
index 0000000..e387123
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/BitmapData.java
@@ -0,0 +1,62 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Bitmap;
+
+/**
+ * Created by bifa-wei
+ * on 2017/11/27.
+ */
+
+public class BitmapData {
+ private final Bitmap[] pages = new Bitmap[3];
+ private Bitmap BgBitmap;
+
+ public void setFirstPage(Bitmap page) {
+ pages[0] = page;
+ }
+
+ public void setMidPage(Bitmap page) {
+ pages[1] = page;
+ }
+
+ public void setLastPage(Bitmap page) {
+ pages[2] = page;
+ }
+
+ public Bitmap FirstPage() {
+ return pages[0];
+ }
+
+ public Bitmap MidPage() {
+ return pages[1];
+ }
+
+ public Bitmap LastPage() {
+ return pages[2];
+ }
+
+ public void setBgBitmap(Bitmap bgBitmap) {
+ BgBitmap = bgBitmap;
+ }
+
+ public Bitmap getBgBitmap() {
+ return BgBitmap;
+ }
+
+ public Bitmap[] getPages() {
+ return pages;
+ }
+
+ public void onDestroy() {
+ recycle(getBgBitmap());
+ recycle(FirstPage());
+ recycle(MidPage());
+ recycle(LastPage());
+ }
+
+ private void recycle(Bitmap bitmap) {
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/FileReadRecordDB.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/FileReadRecordDB.java
new file mode 100644
index 0000000..8d63ffc
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/FileReadRecordDB.java
@@ -0,0 +1,222 @@
+package com.bifan.txtreaderlib.main;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import androidx.annotation.NonNull;
+import android.text.TextUtils;
+
+import com.bifan.txtreaderlib.bean.FileReadRecordBean;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FileReadRecordDB extends SQLiteOpenHelper {
+ private static final int Version_1 = 1;
+ private static final String DB_NAME = "hwTxtReader";
+ private String TABLE_NAME = "FileReadRecord";
+
+ private final String FileHashName = "fileHashName";
+ private final String SearchId = "searchId";
+ private final String FilePath = "filePath";
+ private final String FileName = "fileName";
+ private final String ParagraphIndex = "paragraphIndex";
+ private final String ChartIndex = "chartIndex";
+
+ private String sql = "create table if not exists " + TABLE_NAME + " (" + SearchId
+ + " integer primary key autoincrement," + FileHashName + " varchar(50),"
+ + FilePath + " varchar(100), " + FileName + " varchar(100),"
+ + ParagraphIndex + " integer, " + ChartIndex + " integer)";
+
+ public FileReadRecordDB(Context context) {
+ this(context, DB_NAME, null, Version_1);
+ }
+
+ private FileReadRecordDB(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
+ super(context, name, factory, version);
+ }
+
+ public void createTable() {
+ getWritableDatabase().execSQL(sql);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+ }
+
+ /**
+ * 关闭数据表
+ */
+ public void closeTable() {
+ close();
+ }
+
+ /**
+ * 删除数据表
+ */
+ public void delectTable() {
+ getWritableDatabase().execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ }
+
+ public void insertData(String fileHashName, String path, String fileName, int paragraphIndex, int chartIndex) {
+ if (!TextUtils.isEmpty(FileHashName)) {
+ if (IsHasData(FileHashName, fileHashName)) {//已经存在了,先删除
+ deleteRecordsByFileHashName(fileHashName);
+ }
+ String sql = " insert into " + TABLE_NAME + " (" + FileHashName + "," + FilePath + "," + FileName + "," + ParagraphIndex + "," + ChartIndex + ") values ('" + fileHashName
+ + "','" + path + "','" + fileName + "','" + paragraphIndex + "','" + chartIndex + "')";
+ getWritableDatabase().execSQL(sql);
+ }
+ }
+
+ public void insertData(@NonNull FileReadRecordBean recordBean) {
+ insertData(
+ recordBean.fileHashName
+ , recordBean.filePath
+ , recordBean.fileName
+ , recordBean.paragraphIndex
+ , recordBean.chartIndex);
+ }
+
+ /**
+ * @param fileHashName 文件hash值
+ * @return 如果没有记录,返回null
+ */
+ public FileReadRecordBean getRecordByHashName(String fileHashName) {
+ if (!TextUtils.isEmpty(fileHashName) && IsHasData(FileHashName, fileHashName)) {
+ Cursor cursor = getCursorByKeyValue(FileHashName, fileHashName);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ FileReadRecordBean bean = getRecordBeanFromCursor(cursor);
+ cursor.close();
+ return bean;
+ }
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param userId
+ * @return 如果userId 为空,不分用户返回的是所有下载的数据,否则返回指定用户的下载记录,不会返回空
+ */
+ public List getRecord(String userId) {
+ List searchBeans = new ArrayList<>();
+ Cursor cursor;
+ if (TextUtils.isEmpty(userId)) {
+ cursor = getWritableDatabase().rawQuery("select * from " + TABLE_NAME, null);
+ } else {
+ cursor = getCursorByKeyValue(FileHashName, userId);
+ }
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ while (!cursor.isAfterLast()) {
+ searchBeans.add(getRecordBeanFromCursor(cursor));
+ cursor.moveToNext();
+ }
+ }
+ cursor.close();
+ }
+ return searchBeans;
+
+ }
+
+ /**
+ * @return 获取所有记录
+ */
+ public List getAllFileRecord() {
+ return getRecord(null);
+ }
+
+
+ private FileReadRecordBean getRecordBeanFromCursor(Cursor cursor) {
+ FileReadRecordBean bean = new FileReadRecordBean();
+ int userIdIndex = cursor.getColumnIndex(FileHashName);
+ bean.fileHashName = cursor.getString(userIdIndex);
+
+ int searchIdIndex = cursor.getColumnIndex(SearchId);
+ bean.id = cursor.getInt(searchIdIndex);
+
+ int CachePathIndex = cursor.getColumnIndex(FilePath);
+ bean.filePath = cursor.getString(CachePathIndex);
+
+ int fileNameIndex = cursor.getColumnIndex(FileName);
+ bean.fileName = cursor.getString(fileNameIndex);
+
+ int paragraphIndex = cursor.getColumnIndex(ParagraphIndex);
+ bean.paragraphIndex = cursor.getInt(paragraphIndex);
+
+ int chartIndex = cursor.getColumnIndex(ChartIndex);
+ bean.chartIndex = cursor.getInt(chartIndex);
+
+ return bean;
+ }
+
+
+ /**
+ * @param recordBean 要删除的记录
+ */
+ public void deleteRecord(FileReadRecordBean recordBean) {
+ if (IsHasData(SearchId, recordBean.id + "")) {
+ delete(SearchId, recordBean.id + "");
+ }
+ }
+
+ /**
+ * @param fileHashName fileHashName
+ */
+ public void deleteRecordsByFileHashName(String fileHashName) {
+ if (IsHasData(FileHashName, fileHashName + "")) {
+ delete(FileHashName, fileHashName + "");
+ }
+
+ }
+
+ /**
+ * @param key
+ * @param value
+ * @return 是否有数据
+ */
+ private Boolean IsHasData(String key, String value) {
+ Cursor cursor = getCursorByKeyValue(key, value);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.close();
+ return true;
+ }
+ cursor.close();
+ }
+ return false;
+ }
+
+ /**
+ * @param key
+ * @param value
+ */
+ private void delete(String key, String value) {
+ String sql = "delete from " + TABLE_NAME + " where " + key + " = " + "'" + value + "'";
+ getWritableDatabase().execSQL(sql);
+ }
+
+ /**
+ * @param key
+ * @param value
+ * @return
+ */
+ private Cursor getCursorByKeyValue(String key, String value) {
+ Cursor cursor = getWritableDatabase()
+ .rawQuery("select * from " + TABLE_NAME + " where " + key + " = " + "'" + value + "'", null);
+ return cursor;
+ }
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/LoadListenerAdapter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/LoadListenerAdapter.java
new file mode 100644
index 0000000..7f8e30a
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/LoadListenerAdapter.java
@@ -0,0 +1,23 @@
+package com.bifan.txtreaderlib.main;
+
+import com.bifan.txtreaderlib.bean.TxtMsg;
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/11.
+ */
+
+public class LoadListenerAdapter implements ILoadListener{
+ @Override
+ public void onSuccess() {
+ }
+
+ @Override
+ public void onFail(TxtMsg txtMsg) {
+ }
+
+ @Override
+ public void onMessage(String message) {
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalPageDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalPageDrawer.java
new file mode 100644
index 0000000..c68c1c5
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalPageDrawer.java
@@ -0,0 +1,258 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Canvas;
+import android.graphics.Region;
+import android.graphics.drawable.GradientDrawable;
+import android.view.MotionEvent;
+import android.widget.Scroller;
+
+import com.bifan.txtreaderlib.interfaces.IReaderViewDrawer;
+
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/1.
+ */
+
+public class NormalPageDrawer extends PageDrawerBase implements IReaderViewDrawer {
+ private String tag = "NormalReaderViewDrawer";
+ private final static int BorderShadowWith = 20;
+
+ public NormalPageDrawer(TxtReaderView readerView, TxtReaderContext readerContext, Scroller scroller) {
+ super(readerView, readerContext, scroller);
+ }
+
+ @Override
+ public void drawPageNextPageShadow(Canvas canvas) {
+ //绘制执行下一页的页边阴影
+ mPath.reset();
+ int left = (int) getMoveDistance() + getWidth();
+ int top = 0;
+ int right = left + BorderShadowWith;
+ int bottom = getHeight();
+ if (left > BorderShadowWith) {
+ getPageNextBorderDrawable().setBounds(left, top, right, bottom);
+ getPageNextBorderDrawable().draw(canvas);
+ }
+ }
+
+ @Override
+ public void drawPageNextBottomPage(Canvas canvas) {
+ //绘制执行下一页时的下面页部分
+ /* float x =getWidth()+getMoveDistance();
+ mPath.reset();
+ mPath.moveTo(x, 0);
+ mPath.lineTo(getWidth(), 0);
+ mPath.lineTo(getWidth(), getHeight());
+ mPath.lineTo(x, getHeight());
+ mPath.lineTo(x, 0);
+ canvas.clipPath(mPath, Region.Op.INTERSECT);
+ canvas.drawBitmap(getBottomPage(), x, x, null);*/
+
+ float x =getWidth()+getMoveDistance();
+ mPath.reset();
+ mPath.moveTo(x, 0);
+ mPath.lineTo(getWidth(), 0);
+ mPath.lineTo(getWidth(), getHeight());
+ mPath.lineTo(x, getHeight());
+ mPath.lineTo(x, 0);
+ canvas.clipPath(mPath, Region.Op.INTERSECT);
+ canvas.drawBitmap(getBottomPage(), 0, 0, null);
+
+ }
+
+ @Override
+ public void drawPageNextTopPage(Canvas canvas) {
+ //绘制执行下一页时的上面页部分
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(getWidth() , 0);
+ mPath.lineTo(getWidth() , getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath,Region.Op.INTERSECT);
+ canvas.drawBitmap(getTopPage(), getMoveDistance()+1, 0, null);
+ }
+
+ @Override
+ public void drawPagePrePageShadow(Canvas canvas) {
+ //绘制执行上一页的页边阴影
+ mPath.reset();
+ int left = (int) getMoveDistance() - BorderShadowWith;
+ int top = 0;
+ int right = (int) getMoveDistance();
+ int bottom = getHeight();
+ if (right < getWidth() - BorderShadowWith) {
+ getPagePreBorderDrawable().setBounds(left, top, right, bottom);
+ getPagePreBorderDrawable().draw(canvas);
+ }
+
+ }
+
+ @Override
+ public void drawPagePreBottomPage(Canvas canvas) {
+ //绘制执行上一页时的下面页部分
+ float x = getMoveDistance();
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(x, 0);
+ mPath.lineTo(x, getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath, Region.Op.INTERSECT);
+ canvas.drawBitmap(getBottomPage(), 0, 0, null);
+ }
+
+ @Override
+ public void drawPagePreTopPage(Canvas canvas) {
+ //绘制执行上一页时的上面页部分
+ float distance = getMoveDistance();
+ float x = getWidth();
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(x , 0);
+ mPath.lineTo(x , getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath, Region.Op.INTERSECT);
+ canvas.drawBitmap(getTopPage(), distance, 0, null);
+ }
+
+ private Boolean onPageStateBackAnimation = false;
+
+ @Override
+ public void startPageStateBackAnimation() {
+ if (readerView.isPagePre() || readerView.isPageNext()) {
+ onPageStateBackAnimation = true;
+ scroller.startScroll((int) readerView.mTouch.x, 0, -(int) getMoveDistance(), 0, PageSwitchTime);
+ postInvalidate();
+ }
+ }
+
+
+ @Override
+ public void startPageNextAnimation() {
+ scroller.startScroll(getWidth() + (int) getMoveDistance(), 0, -(getWidth() + (int) getMoveDistance()), 0, PageSwitchTime);
+ readerView.mDown.x = getWidth();//从getWidth()开始
+ postInvalidate();
+ }
+
+ @Override
+ public void startPagePreAnimation() {
+ scroller.startScroll((int) getMoveDistance(), 0, getWidth() - (int) getMoveDistance(), 0, PageSwitchTime);
+ readerView.mDown.x = 0;
+ postInvalidate();
+ }
+
+ @Override
+ public void onTextSelectMoveForward(MotionEvent event) {
+
+ }
+
+ @Override
+ public void onTextSelectMoveBack(MotionEvent event) {
+
+ }
+
+
+ @Override
+ public void onPageMove(MotionEvent event) {
+ readerView.mTouch.x = event.getX();
+ readerView.mTouch.y = event.getY();
+ postInvalidate();
+ }
+
+ private void postInvalidate() {
+ readerView.postInvalidate();
+ }
+
+ @Override
+ public void drawNote(Canvas canvas) {
+
+ }
+
+
+ @Override
+ public void drawSelectedText(Canvas canvas) {
+ if (readerView.CurrentMode == TxtReaderBaseView.Mode.PressSelectText) {
+ drawPressSelectedText(canvas);
+ } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveBack) {
+ drawSelectedLinesText(canvas);
+ } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveForward) {
+ drawSelectedLinesText(canvas);
+ }
+
+ }
+
+ private void drawSelectedLinesText(Canvas canvas) {
+ getTextSelectDrawer().drawSelectedLines(readerView.getCurrentSelectTextLine(), canvas, readerContext.getPaintContext().selectTextPaint);
+ //draw slider
+ drawSlider(canvas);
+ }
+
+ private void drawPressSelectedText(Canvas canvas) {
+ getTextSelectDrawer().drawSelectedChar(readerView.FirstSelectedChar, canvas, readerContext.getPaintContext().selectTextPaint);
+ //draw slider
+ drawSlider(canvas);
+ }
+
+ private void drawSlider(Canvas canvas) {
+ if (readerView.getLeftSliderPath() != null && readerView.getRightSliderPath() != null) {
+ canvas.drawPath(readerView.getLeftSliderPath(), readerContext.getPaintContext().sliderPaint);
+ canvas.drawPath(readerView.getRightSliderPath(), readerContext.getPaintContext().sliderPaint);
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ if (scroller.computeScrollOffset()) {
+ readerView.mTouch.x = scroller.getCurrX();
+ readerView.invalidate();
+ checkPageData();
+ }
+ }
+
+ private synchronized void checkPageData() {
+ if (onPageStateBackAnimation) {
+ if ((getMoveDistance() > 0 && getMoveDistance() <= 3) || (getMoveDistance() < 0 && getMoveDistance() >= -3)) {
+ scroller.abortAnimation();
+ readerView.releaseTouch();
+ readerView.invalidate();
+ onPageStateBackAnimation = false;
+ }
+ } else {
+ if (readerView.mTouch.x == 0) {//执行下一页数据获取
+ readerView.doPageNextDone();
+ scroller.abortAnimation();
+
+ } else if (readerView.mTouch.x == getWidth()) {//执行上一页数据获取
+ readerView.doPagePreDone();
+ scroller.abortAnimation();
+
+ }
+ }
+
+ }
+
+
+ private GradientDrawable mPagePreBorderDrawable;
+ private GradientDrawable mPageNextBorderDrawable;
+
+ private GradientDrawable getPagePreBorderDrawable() {
+ if (mPagePreBorderDrawable == null) {
+ int[] color = new int[]{0xaa666666, 0x00666666};
+ mPagePreBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, color);
+ }
+ return mPagePreBorderDrawable;
+ }
+
+ private GradientDrawable getPageNextBorderDrawable() {
+ if (mPageNextBorderDrawable == null) {
+ int[] color = new int[]{0xaa666666, 0x00666666};
+ mPageNextBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, color);
+ }
+ return mPageNextBorderDrawable;
+ }
+
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalTextSelectDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalTextSelectDrawer.java
new file mode 100644
index 0000000..5a37fd2
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalTextSelectDrawer.java
@@ -0,0 +1,61 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+import com.bifan.txtreaderlib.bean.TxtChar;
+import com.bifan.txtreaderlib.interfaces.ITextSelectDrawer;
+import com.bifan.txtreaderlib.interfaces.ITxtLine;
+import com.bifan.txtreaderlib.utils.ELogger;
+
+import java.util.List;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/4.
+ */
+
+public class NormalTextSelectDrawer implements ITextSelectDrawer {
+ private final Path path = new Path();
+
+ @Override
+ public void drawSelectedChar(TxtChar selectedChar, Canvas canvas, Paint paint) {
+ if (selectedChar != null) {
+ ELogger.log("onPressSelectText","drawSelectedChar");
+ path.reset();
+ path.moveTo(selectedChar.Left, selectedChar.Top);
+ path.lineTo(selectedChar.Right, selectedChar.Top);
+ path.lineTo(selectedChar.Right, selectedChar.Bottom);
+ path.lineTo(selectedChar.Left, selectedChar.Bottom);
+ path.lineTo(selectedChar.Left, selectedChar.Top);
+ canvas.drawPath(path, paint);
+ }
+ }
+
+ @Override
+ public void drawSelectedLines(List selectedLines, Canvas canvas, Paint paint) {
+
+ for (ITxtLine line : selectedLines) {
+ ELogger.log("onPressSelectText",line.getLineStr());
+ if (line.getTxtChars() != null && line.getTxtChars().size() > 0) {
+
+ TxtChar fistChar = line.getTxtChars().get(0);
+ TxtChar lastChar = line.getTxtChars().get(line.getTxtChars().size() - 1);
+
+ float fw = fistChar.CharWidth;
+ float lw = lastChar.CharWidth;
+
+ RectF rect = new RectF(fistChar.Left, fistChar.Top,
+ lastChar.Right, lastChar.Bottom);
+
+ canvas.drawRoundRect(rect, fw / 2,
+ paint.getTextSize() / 2, paint);
+
+ }
+ }
+ }
+
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageData.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageData.java
new file mode 100644
index 0000000..8477e60
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageData.java
@@ -0,0 +1,49 @@
+package com.bifan.txtreaderlib.main;
+
+import com.bifan.txtreaderlib.interfaces.IPage;
+
+/**
+ * Created by bifa-wei
+ * on 2017/11/27.
+ */
+
+public class PageData {
+ private final IPage[] pages = new IPage[3];
+ public final int[] refreshTag = new int[]{1, 1, 1};//1:需要刷新,0不需要刷新
+
+ public void setFirstPage(IPage page) {
+ pages[0] = page;
+ }
+
+ public void setMidPage(IPage page) {
+ pages[1] = page;
+ }
+
+ public void setLastPage(IPage page) {
+ pages[2] = page;
+ }
+
+ public IPage FirstPage() {
+ return pages[0];
+ }
+
+ public IPage MidPage() {
+ return pages[1];
+ }
+
+ public IPage LastPage() {
+ return pages[2];
+ }
+
+ public IPage[] getPages() {
+ return pages;
+ }
+
+ public void onDestroy() {
+ pages[0]=null;
+ pages[1]=null;
+ pages[2]=null;
+ }
+
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDataPipeline.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDataPipeline.java
new file mode 100644
index 0000000..bac3e26
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDataPipeline.java
@@ -0,0 +1,330 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Paint;
+
+import com.bifan.txtreaderlib.bean.EnChar;
+import com.bifan.txtreaderlib.bean.NumChar;
+import com.bifan.txtreaderlib.bean.Page;
+import com.bifan.txtreaderlib.bean.TxtChar;
+import com.bifan.txtreaderlib.bean.TxtLine;
+import com.bifan.txtreaderlib.interfaces.IPage;
+import com.bifan.txtreaderlib.interfaces.IPageDataPipeline;
+import com.bifan.txtreaderlib.interfaces.IParagraphData;
+import com.bifan.txtreaderlib.interfaces.ITxtLine;
+import com.bifan.txtreaderlib.utils.FormatUtil;
+import com.bifan.txtreaderlib.utils.TextBreakUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+public class PageDataPipeline implements IPageDataPipeline {
+ private TxtReaderContext readerContext;
+
+ public PageDataPipeline(TxtReaderContext readerContext) {
+ this.readerContext = readerContext;
+ }
+
+ @Override
+ public IPage getPageStartFromProgress(int paragraphIndex, int charIndex) {
+ IParagraphData data = readerContext.getParagraphData();
+ PageParam param = readerContext.getPageParam();
+
+ if(data==null) return null;
+
+ int PageLineNum = param.PageLineNum;
+ int PageHeight = param.PageHeight;
+ int LineHeight = (int) param.LineHeight;
+
+
+ int CurrentPaIndex = paragraphIndex;
+ int startIndex = charIndex;
+ int ParagraphNum = data.getParagraphNum();
+ float lineWidth = readerContext.getPageParam().LineWidth;
+ float textPadding = readerContext.getPageParam().TextPadding;
+ int paragraphMargin = param.ParagraphMargin;
+
+ if (CurrentPaIndex >= ParagraphNum || CurrentPaIndex < 0) return null;//超过返回null
+
+ IPage page = new Page();
+
+ //获取页面数据
+ while (page.getLineNum() < PageLineNum && CurrentPaIndex < ParagraphNum) {
+ String paragraphStr = data.getParagraphStr(CurrentPaIndex);
+ if (paragraphStr != null && paragraphStr.length() > 0) {
+ List lines = getLineFromParagraphStartOnBeginning(paragraphStr, CurrentPaIndex, startIndex, lineWidth, textPadding, readerContext.getPaintContext().textPaint);
+ for (ITxtLine line : lines) {
+ page.addLine(line);
+ if (page.getLineNum() >= PageLineNum) {
+ page.setFullPage(true);
+ break;
+ }
+ }
+
+ startIndex = 0;
+ }
+ CurrentPaIndex++;
+ }
+
+ //尝试识别是否需要加段落间距数据
+ PageHeight = PageHeight - param.PaddingTop - param.PaddingBottom;
+ int textHeight = readerContext.getTxtConfig().textSize;
+ if (paragraphMargin > 0 && LineHeight > 0 && PageHeight > LineHeight && page.getLineNum() > 0) {
+ int height = param.PaddingTop;
+ List lines = new ArrayList<>();
+ for (int i = 0; i < page.getLineNum(); i++) {
+ ITxtLine line = page.getLine(i);
+ height = height + LineHeight;
+ if (height > PageHeight) {
+ page.setFullPage(true);
+ if (height - LineHeight + textHeight <= PageHeight) {
+ lines.add(line);
+ }
+ break;
+ } else {
+ lines.add(line);
+ }
+
+ if (line.isParagraphEndLine()) {
+ // if (height + paragraphMargin < PageHeight) {
+ height = height + paragraphMargin;//说明还有行数据
+ // }
+ }
+
+ }
+ if (height >= PageHeight) {
+ page.setFullPage(true);
+ }
+ page.setLines(lines);
+ }
+
+ return page;
+ }
+
+
+ @Override
+ public IPage getPageEndToProgress(int paragraphIndex, int charIndex) {
+ IParagraphData data = readerContext.getParagraphData();
+ PageParam param = readerContext.getPageParam();
+
+ if(data==null) return null;
+
+ int PageLineNum = param.PageLineNum;
+ int PageHeight = param.PageHeight;
+ int LineHeight = (int) param.LineHeight;
+
+
+ int CurrentPaIndex = paragraphIndex;
+ int startIndex = charIndex;
+ int ParagraphNum = data.getParagraphNum();
+ float lineWidth = readerContext.getPageParam().LineWidth;
+ float textPadding = readerContext.getPageParam().TextPadding;
+
+
+ if (charIndex == 0) {//说明上页开始是段落开始位置,段落左移
+ CurrentPaIndex--;//
+ startIndex = 0;
+ }
+ if (CurrentPaIndex >= ParagraphNum || CurrentPaIndex < 0) return null;//超过返回null
+
+ IPage page = new Page();
+ PageHeight = PageHeight - param.PaddingTop - param.PaddingBottom;
+ int paragraphMargin = param.ParagraphMargin;
+
+
+ while (page.getLineNum() < PageLineNum && CurrentPaIndex >= 0) {
+ String paragraphStr = data.getParagraphStr(CurrentPaIndex);
+
+ if (paragraphStr != null && paragraphStr.length() > 0) {
+ if (startIndex == 0) {//说明上页开始是段落开始位置
+ startIndex = paragraphStr.length();
+ }
+ List lines = getLineFromParagraphOnEnd(paragraphStr, CurrentPaIndex, startIndex, lineWidth, textPadding, readerContext.getPaintContext().textPaint);
+ if (lines.size() > 0) {
+ for (int i = lines.size() - 1; i >= 0; i--) {
+ ITxtLine line = lines.get(i);
+ page.addLine(line);
+ if (page.getLineNum() >= PageLineNum) {
+ page.setFullPage(true);
+ break;
+ }
+ }
+ }
+ }
+ CurrentPaIndex--;
+ startIndex = 0;
+ }
+
+
+ if (page.HasData()) {
+ Collections.reverse(page.getLines());
+ }
+
+ //尝试识别是否需要加段落间距数据
+ PageHeight = PageHeight - param.PaddingTop - param.PaddingBottom;
+ if (paragraphMargin > 0 && LineHeight > 0 && PageHeight > LineHeight && page.getLineNum() > 0) {
+ int height = param.PaddingTop;
+ int textHeight = readerContext.getTxtConfig().textSize;
+
+ List lines = new ArrayList<>();
+
+ for (int i = page.getLineNum() - 1; i >= 0; i--) {
+ ITxtLine line = page.getLine(i);
+
+ if (i == page.getLineNum() - 1) {//底部那个不添加偏移量
+ lines.add(line);
+ height = height + textHeight;
+ } else {
+ if (height + LineHeight > PageHeight) {
+ page.setFullPage(true);
+ if (height + textHeight <= PageHeight) {
+ lines.add(line);
+ }
+ break;
+ } else {
+ lines.add(line);
+ height = height + LineHeight;
+ if (line.isParagraphEndLine()) {
+ // if (height + paragraphMargin < PageHeight) {
+ height = height + paragraphMargin;//说明还有行数据
+ // }
+ }
+ }
+ }
+
+ }
+ if (height >= PageHeight) {
+ page.setFullPage(true);
+ }
+ page.setLines(lines);
+
+ if (page.HasData()) {
+ Collections.reverse(page.getLines());
+ }
+ }
+ return page;
+ }
+
+ /**
+ * @param paragraphData 完整段落数据
+ * @param paragraphIndex 段落下标
+ * @param startCharIndex 开始字符位置
+ * @param lineWidth 行宽度
+ * @param textPadding 字体间距
+ * @param paint 画笔
+ * @return startCharIndex超过范围,返回空集合
+ */
+ //开始满行
+ private List getLineFromParagraphStartOnBeginning(String paragraphData, int paragraphIndex, int startCharIndex,
+ float lineWidth, float textPadding, Paint paint) {
+ List lines = new ArrayList<>();
+ int startIndex = startCharIndex;
+ startIndex = startIndex < 0 ? 0 : startIndex;
+ int paragraphLength = paragraphData.length();
+
+ if (startIndex >= paragraphLength) {
+ return lines;
+ }
+ if ( paragraphData.length() > 0) {
+ String s = paragraphData.substring(startIndex);//截取要的数据
+ while (s.length() > 0) {// is[0] 为个数 is[1] 为是否满一行
+ float[] is = TextBreakUtil.BreakText(s, lineWidth, textPadding, paint);
+ ITxtLine line;
+ if (is[1] != 1) {//不满一行
+ line = getLineFromString(s, paragraphIndex, startIndex, is);
+ if (s.length() + startIndex >= paragraphLength) {
+ line.setParagraphEndLine(true);
+ }
+ lines.add(line);
+ break;
+ }
+ int num = (int) is[0];
+ String lineStr = s.substring(0, num);
+ line = getLineFromString(lineStr, paragraphIndex, startIndex, is);
+ startIndex = startIndex + num;
+ if (line != null) {
+ lines.add(line);
+ }
+ s = s.substring(num);
+ }
+ }
+ return lines;
+ }
+
+ /**
+ * @param paragraphData
+ * @param paragraphIndex
+ * @param endCharIndex
+ * @param lineWidth
+ * @param textPadding
+ * @param paint
+ * @return startCharIndex等于0,返回空集合,超过范围,为段落长度数据
+ */
+ //结束满行
+ private List getLineFromParagraphOnEnd(String paragraphData, int paragraphIndex, int endCharIndex,
+ float lineWidth, float textPadding, Paint paint) {
+ List lines = new ArrayList<>();
+ int startIndex = 0;
+ int paragraphLength = paragraphData.length();
+ if (paragraphLength == 0 || endCharIndex <= 0) {
+ return lines;
+ }
+ endCharIndex = endCharIndex >= paragraphData.length() ? paragraphData.length() : endCharIndex;
+
+ if (endCharIndex > 0) {
+ String s = paragraphData.substring(startIndex, endCharIndex);//截取要的数据
+ while (s.length() > 0) {// is[0] 为个数 is[1] 为是否满一行
+ float[] is = TextBreakUtil.BreakText(s, lineWidth, textPadding, paint);
+ ITxtLine line;
+ int num = (int) is[0];
+ if (is[1] != 1) {//不满一行
+ line = getLineFromString(s, paragraphIndex, startIndex, is);
+ // ELogger.log("isParagraphEndLine11",endCharIndex+"/"+startIndex+"/"+paragraphLength+"/"+s.length()+"/"+s);
+ if (endCharIndex == paragraphLength && s.length() + startIndex >= paragraphLength) {
+ line.setParagraphEndLine(true);
+ }
+ lines.add(line);
+ return lines;
+ }
+
+ String lineStr = s.substring(0, num);
+ line = getLineFromString(lineStr, paragraphIndex, startIndex, is);
+ startIndex = startIndex + num;
+ if (line != null) {
+ lines.add(line);
+ }
+ s = s.substring(lineStr.length());
+ }
+ }
+ return lines;
+ }
+
+
+ private ITxtLine getLineFromString(String str, int ParagraphIndex, int charIndex, float[] is) {
+ ITxtLine line = null;
+ int index = charIndex;
+ int cIndex = 2;//找到字符宽度
+ if (str != null && str.length() > 0) {
+ line = new TxtLine();
+
+ char[] cs = str.toCharArray();
+ for (char c : cs) {
+ TxtChar Char;
+ if (FormatUtil.isDigital(c)) {
+ Char = new NumChar(c);
+ } else if (FormatUtil.isLetter(c)) {
+ Char = new EnChar(c);
+ } else {
+ Char = new TxtChar(c);
+ }
+ Char.ParagraphIndex = ParagraphIndex;
+ Char.CharIndex = index++;
+ Char.CharWidth = is[cIndex++];
+ line.addChar(Char);
+ }
+
+ }
+ return line;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDrawerBase.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDrawerBase.java
new file mode 100644
index 0000000..ce7cd15
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDrawerBase.java
@@ -0,0 +1,60 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Bitmap;
+import android.graphics.Path;
+import android.widget.Scroller;
+
+import com.bifan.txtreaderlib.interfaces.ITextSelectDrawer;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/2.
+ */
+
+public class PageDrawerBase {
+ protected int PageSwitchTime = 400;
+ protected TxtReaderView readerView;
+ protected TxtReaderContext readerContext;
+ protected Scroller scroller;
+ protected Path mPath = new Path();
+ protected ITextSelectDrawer textSelectDrawer;
+
+
+ public PageDrawerBase(TxtReaderView readerView, TxtReaderContext readerContext, Scroller scroller) {
+ this.readerView = readerView;
+ this.readerContext = readerContext;
+ this.scroller = scroller;
+ PageSwitchTime = TxtConfig.getPageSwitchDuration(readerContext.context);
+ }
+ protected int getWidth() {
+ return readerView.getWidth();
+ }
+ protected float getMoveDistance() {
+ return readerView.getMoveDistance();
+ }
+ protected int getHeight() {
+ return readerView.getHeight();
+ }
+ protected Bitmap getTopPage() {
+ return readerView.getTopPage();
+ }
+ protected Bitmap getBottomPage() {
+ return readerView.getBottomPage();
+ }
+
+
+
+ public ITextSelectDrawer getTextSelectDrawer() {
+ if (textSelectDrawer == null) {
+ textSelectDrawer = new NormalTextSelectDrawer();
+ }
+ return textSelectDrawer;
+ }
+
+ public void setTextSelectDrawer(ITextSelectDrawer textSelectDrawer) {
+ this.textSelectDrawer = textSelectDrawer;
+ }
+
+
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageParam.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageParam.java
new file mode 100644
index 0000000..1767289
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageParam.java
@@ -0,0 +1,22 @@
+package com.bifan.txtreaderlib.main;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class PageParam {
+ public int PaddingLeft = 20;
+ public int PaddingBottom = 0;
+ public int PaddingTop = 20;
+ public int PaddingRight = 20;
+ public int ParagraphMargin = 0;
+ public int VerticalLinePadding = 30;//横行距
+ public int HorizonalLinePadding = 30;//竖行距
+ public int LinePadding = 30 ;
+ public int PageLineNum = -1;
+ public float LineWidth = 0;
+ public float LineHeight = 0;
+ public float TextPadding = 5;
+ public int PageWidth = 0;
+ public int PageHeight = 0;
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PaintContext.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PaintContext.java
new file mode 100644
index 0000000..3211bb1
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PaintContext.java
@@ -0,0 +1,28 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Paint;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class PaintContext {
+ public Paint textPaint;
+ public Paint selectTextPaint;
+ public Paint sliderPaint;
+ public Paint notePaint;
+
+ public PaintContext() {
+ textPaint = new Paint();
+ selectTextPaint = new Paint();
+ notePaint = new Paint();
+ sliderPaint = new Paint();
+ }
+
+ public void onDestroy(){
+ textPaint = null;
+ selectTextPaint =null;
+ notePaint = null;
+ sliderPaint =null;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ParagraphData.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ParagraphData.java
new file mode 100644
index 0000000..847aee6
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ParagraphData.java
@@ -0,0 +1,98 @@
+package com.bifan.txtreaderlib.main;
+
+import com.bifan.txtreaderlib.interfaces.IParagraphData;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class ParagraphData implements IParagraphData {
+ private int CharNum = 0;
+ private HashMap paragraph = new HashMap<>();
+ private List charIndex = new ArrayList<>();//每个段落开始字符位置
+
+ @Override
+ public void addParagraph(int ParaIndex, String p) {
+ if (p != null) {
+ paragraph.put(ParaIndex, p);
+ charIndex.add(CharNum);
+ CharNum += p.length();
+ }
+ }
+
+ @Override
+ public void addParagraph(String p) {
+ addParagraph(paragraph.size(), p);
+ }
+
+ @Override
+ public int getParagraphNum() {
+ return paragraph.size();
+ }
+
+ @Override
+ public int getCharNum() {
+ return CharNum;
+ }
+
+ @Override
+ public int findParagraphIndexByCharIndex(int CharIndex) {
+ int pre = 0;
+ int next = 0;
+ int index = 0;
+
+ for (Integer v : charIndex) {
+ if (index == 0) {
+ pre = v;
+ } else {
+ if (CharIndex >= pre && CharIndex < v) {
+ return index;
+ } else {
+ pre = next;
+ next = v;
+ }
+ }
+ index++;
+ }
+ return index;
+ }
+
+
+ @Override
+ public int getParaStartCharIndex(int ParaIndex) {
+ return charIndex.get(ParaIndex);
+ }
+
+ @Override
+ public String getParagraphStr(int ParaIndex) {
+ return paragraph.get(ParaIndex);
+ }
+
+ @Override
+ public String getParagraphStrFromStart(int ParaIndex, int CharIndex) {
+ String S = getParagraphStr(ParaIndex) + "";
+ if (CharIndex < 0) CharIndex = 0;
+ if (CharIndex > S.length()) CharIndex = S.length();
+ return S.substring(CharIndex);
+ }
+
+ @Override
+ public String getParagraphStrToEnd(int ParaIndex, int CharIndex) {
+ String S = getParagraphStr(ParaIndex) + "";
+ if (CharIndex < 0) CharIndex = 0;
+ if (CharIndex > S.length()) CharIndex = S.length();
+ return S.substring(0, CharIndex);
+ }
+
+ @Override
+ public void Clear() {
+ charIndex.clear();
+ paragraph.clear();
+ CharNum = 0;
+ }
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/SerialPageDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/SerialPageDrawer.java
new file mode 100644
index 0000000..f13a80f
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/SerialPageDrawer.java
@@ -0,0 +1,252 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Canvas;
+import android.graphics.Region;
+import android.graphics.drawable.GradientDrawable;
+import android.view.MotionEvent;
+import android.widget.Scroller;
+
+import com.bifan.txtreaderlib.interfaces.IReaderViewDrawer;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/2.
+ */
+
+public class SerialPageDrawer extends PageDrawerBase implements IReaderViewDrawer {
+ private String tag = "SerialPageDrawer";
+ private final static int BorderShadowWith = 5;
+
+ public SerialPageDrawer(TxtReaderView readerView, TxtReaderContext readerContext, Scroller scroller) {
+ super(readerView, readerContext, scroller);
+ }
+
+ @Override
+ public void drawPageNextPageShadow(Canvas canvas) {
+ //绘制执行下一页的页边阴影
+ mPath.reset();
+ int left = (int) getMoveDistance() + getWidth();
+ int top = 0;
+ int right = left + BorderShadowWith;
+ int bottom = getHeight();
+ if (left > BorderShadowWith) {
+ getPageNextBorderDrawable().setBounds(left, top, right, bottom);
+ getPageNextBorderDrawable().draw(canvas);
+ }
+ }
+
+ @Override
+ public void drawPageNextBottomPage(Canvas canvas) {
+ //绘制执行下一页时的下面页部分
+ float startPosition = getWidth() + getMoveDistance();
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(getWidth(), 0);
+ mPath.lineTo(getWidth(), getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath,Region.Op.INTERSECT);
+ canvas.drawBitmap(getBottomPage(), startPosition, 0, null);
+
+
+ }
+
+ @Override
+ public void drawPageNextTopPage(Canvas canvas) {
+ //绘制执行下一页时的上面页部分
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(getWidth() , 0);
+ mPath.lineTo(getWidth() , getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath,Region.Op.INTERSECT);
+ canvas.drawBitmap(getTopPage(), getMoveDistance()+1, 0, null);
+ }
+
+ @Override
+ public void drawPagePrePageShadow(Canvas canvas) {
+ //绘制执行上一页的页边阴影
+ mPath.reset();
+ int left = (int) getMoveDistance() - BorderShadowWith;
+ int top = 0;
+ int right = (int) getMoveDistance();
+ int bottom = getHeight();
+ if (right < getWidth() - BorderShadowWith) {
+ getPagePreBorderDrawable().setBounds(left, top, right, bottom);
+ getPagePreBorderDrawable().draw(canvas);
+ }
+
+ }
+
+ @Override
+ public void drawPagePreBottomPage(Canvas canvas) {
+ //绘制执行上一页时的下面页部分
+ float startPosition = getMoveDistance();
+ float x = 0;
+ mPath.reset();
+ mPath.moveTo(x, 0);
+ mPath.lineTo(getWidth(), 0);
+ mPath.lineTo(getWidth(), getHeight());
+ mPath.lineTo(x, getHeight());
+ mPath.lineTo(x, 0);
+ canvas.clipPath(mPath, Region.Op.INTERSECT);
+ canvas.drawBitmap(getBottomPage(), startPosition-getWidth(), 0, null);
+
+ }
+
+ @Override
+ public void drawPagePreTopPage(Canvas canvas) {
+ //绘制执行上一页时的上面页部分
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(getWidth(), 0);
+ mPath.lineTo(getWidth(), getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath, Region.Op.INTERSECT);
+ canvas.drawBitmap(getTopPage(), getMoveDistance(), 0, null);
+ }
+
+ private Boolean onPageStateBackAnimation = false;
+
+ @Override
+ public void startPageStateBackAnimation() {
+ if (readerView.isPagePre() || readerView.isPageNext()) {
+ onPageStateBackAnimation = true;
+ scroller.startScroll((int) readerView.mTouch.x, 0, -(int) getMoveDistance(), 0, PageSwitchTime);
+ postInvalidate();
+ }
+
+ }
+
+
+ @Override
+ public void startPageNextAnimation() {
+ scroller.startScroll(getWidth() + (int) getMoveDistance(), 0, -(getWidth() + (int) getMoveDistance()), 0, PageSwitchTime);
+ readerView.mDown.x = getWidth();//从getWidth()开始
+ readerView.CurrentMode = TxtReaderBaseView.Mode.PageNextIng;
+ postInvalidate();
+ }
+
+ @Override
+ public void startPagePreAnimation() {
+ scroller.startScroll((int) getMoveDistance(), 0, getWidth() - (int) getMoveDistance(), 0, PageSwitchTime);
+ readerView.mDown.x = 0;
+ readerView.CurrentMode = TxtReaderBaseView.Mode.PagePreIng;
+ postInvalidate();
+ }
+
+ @Override
+ public void onTextSelectMoveForward(MotionEvent event) {
+
+ }
+
+ @Override
+ public void onTextSelectMoveBack(MotionEvent event) {
+
+ }
+
+ @Override
+ public void onPageMove(MotionEvent event) {
+ readerView.mTouch.x = event.getX();
+ readerView.mTouch.y = event.getY();
+ postInvalidate();
+ }
+
+ private void postInvalidate() {
+ readerView.postInvalidate();
+ }
+
+ @Override
+ public void drawNote(Canvas canvas) {
+
+ }
+
+ @Override
+ public void drawSelectedText(Canvas canvas) {
+ if (readerView.CurrentMode == TxtReaderBaseView.Mode.PressSelectText) {
+ drawPressSelectedText(canvas);
+ } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveBack) {
+ drawSelectedLinesText(canvas);
+ } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveForward) {
+ drawSelectedLinesText(canvas);
+ }
+
+ }
+
+ private void drawSelectedLinesText(Canvas canvas) {
+ getTextSelectDrawer().drawSelectedLines(readerView.getCurrentSelectTextLine(), canvas, readerContext.getPaintContext().selectTextPaint);
+ //draw slider
+ drawSlider(canvas);
+
+ }
+
+ private void drawPressSelectedText(Canvas canvas) {
+ getTextSelectDrawer().drawSelectedChar(readerView.FirstSelectedChar, canvas, readerContext.getPaintContext().selectTextPaint);
+ //draw slider
+ drawSlider(canvas);
+ }
+
+ private void drawSlider(Canvas canvas) {
+ if (readerView.getLeftSliderPath() != null && readerView.getRightSliderPath() != null) {
+ canvas.drawPath(readerView.getLeftSliderPath(), readerContext.getPaintContext().sliderPaint);
+ canvas.drawPath(readerView.getRightSliderPath(), readerContext.getPaintContext().sliderPaint);
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ if (scroller.computeScrollOffset()) {
+ readerView.mTouch.x = scroller.getCurrX();
+ readerView.invalidate();
+ checkPageData();
+ }
+ }
+
+ private synchronized void checkPageData() {
+ if (onPageStateBackAnimation) {
+ if ((getMoveDistance()>0&&getMoveDistance() <= 3) || (getMoveDistance()<0&&getMoveDistance() >= -3) ) {
+ scroller.abortAnimation();
+ readerView.releaseTouch();
+ readerView.invalidate();
+ onPageStateBackAnimation = false;
+ }
+ } else {
+ if (readerView.mTouch.x == 0)//执行下一页数据获取
+ {
+ readerView.doPageNextDone();
+ scroller.abortAnimation();
+
+ } else if (readerView.mTouch.x == getWidth()) {//执行上一页数据获取
+ readerView.doPagePreDone();
+ scroller.abortAnimation();
+
+ }
+ }
+
+ }
+
+
+ private GradientDrawable mPagePreBorderDrawable;
+ private GradientDrawable mPageNextBorderDrawable;
+
+ private GradientDrawable getPagePreBorderDrawable() {
+ if (mPagePreBorderDrawable == null) {
+ int[] color = new int[]{0x55666666, 0x55666666, 0x55666666};
+ mPagePreBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, color);
+ }
+ return mPagePreBorderDrawable;
+ }
+
+ private GradientDrawable getPageNextBorderDrawable() {
+ if (mPageNextBorderDrawable == null) {
+ int[] color = new int[]{0x55666666, 0x55666666, 0x55666666};
+ mPageNextBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, color);
+ }
+ return mPageNextBorderDrawable;
+ }
+
+
+}
+
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ShearPageDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ShearPageDrawer.java
new file mode 100644
index 0000000..cbcc08e
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ShearPageDrawer.java
@@ -0,0 +1,253 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Canvas;
+import android.graphics.Region;
+import android.graphics.drawable.GradientDrawable;
+import android.view.MotionEvent;
+import android.widget.Scroller;
+
+import com.bifan.txtreaderlib.interfaces.IReaderViewDrawer;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/2.
+ */
+
+public class ShearPageDrawer extends PageDrawerBase implements IReaderViewDrawer {
+ private String tag = "SerialPageDrawer";
+ private final static int BorderShadowWith = 5;
+
+ public ShearPageDrawer(TxtReaderView readerView, TxtReaderContext readerContext, Scroller scroller) {
+ super(readerView, readerContext, scroller);
+ }
+
+ @Override
+ public void drawPageNextPageShadow(Canvas canvas) {
+ //绘制执行下一页的页边阴影
+ mPath.reset();
+ int left = (int) getMoveDistance() + getWidth();
+ int top = 0;
+ int right = left + BorderShadowWith;
+ int bottom = getHeight();
+ if (left > BorderShadowWith) {
+ getPageNextBorderDrawable().setBounds(left, top, right, bottom);
+ getPageNextBorderDrawable().draw(canvas);
+ }
+ }
+
+ @Override
+ public void drawPageNextBottomPage(Canvas canvas) {
+ //绘制执行下一页时的下面页部分
+ float startPosition = getWidth() + getMoveDistance();
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(getWidth(), 0);
+ mPath.lineTo(getWidth(), getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath,Region.Op.INTERSECT);
+ canvas.drawBitmap(getBottomPage(), startPosition, startPosition, null);
+
+
+ }
+
+ @Override
+ public void drawPageNextTopPage(Canvas canvas) {
+ //绘制执行下一页时的上面页部分
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(getWidth() , 0);
+ mPath.lineTo(getWidth() , getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath,Region.Op.INTERSECT);
+ canvas.drawBitmap(getTopPage(), getMoveDistance()+1, 0, null);
+ }
+
+ @Override
+ public void drawPagePrePageShadow(Canvas canvas) {
+ //绘制执行上一页的页边阴影
+ mPath.reset();
+ int left = (int) getMoveDistance() - BorderShadowWith;
+ int top = 0;
+ int right = (int) getMoveDistance();
+ int bottom = getHeight();
+ if (right < getWidth() - BorderShadowWith) {
+ getPagePreBorderDrawable().setBounds(left, top, right, bottom);
+ getPagePreBorderDrawable().draw(canvas);
+ }
+
+ }
+
+ @Override
+ public void drawPagePreBottomPage(Canvas canvas) {
+ //绘制执行上一页时的下面页部分
+ float startPosition = getMoveDistance();
+ float x = 0;
+ mPath.reset();
+ mPath.moveTo(x, 0);
+ mPath.lineTo(getWidth(), 0);
+ mPath.lineTo(getWidth(), getHeight());
+ mPath.lineTo(x, getHeight());
+ mPath.lineTo(x, 0);
+ canvas.clipPath(mPath, Region.Op.INTERSECT);
+ float y = startPosition-getWidth();
+ canvas.drawBitmap(getBottomPage(), y, y, null);
+
+ }
+
+ @Override
+ public void drawPagePreTopPage(Canvas canvas) {
+ //绘制执行上一页时的上面页部分
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(getWidth(), 0);
+ mPath.lineTo(getWidth(), getHeight());
+ mPath.lineTo(0, getHeight());
+ mPath.lineTo(0, 0);
+ canvas.clipPath(mPath, Region.Op.INTERSECT);
+ canvas.drawBitmap(getTopPage(), getMoveDistance(), 0, null);
+ }
+
+ private Boolean onPageStateBackAnimation = false;
+
+ @Override
+ public void startPageStateBackAnimation() {
+ if (readerView.isPagePre() || readerView.isPageNext()) {
+ onPageStateBackAnimation = true;
+ scroller.startScroll((int) readerView.mTouch.x, 0, -(int) getMoveDistance(), 0, PageSwitchTime);
+ postInvalidate();
+ }
+
+ }
+
+
+ @Override
+ public void startPageNextAnimation() {
+ scroller.startScroll(getWidth() + (int) getMoveDistance(), 0, -(getWidth() + (int) getMoveDistance()), 0, PageSwitchTime);
+ readerView.mDown.x = getWidth();//从getWidth()开始
+ readerView.CurrentMode = TxtReaderBaseView.Mode.PageNextIng;
+ postInvalidate();
+ }
+
+ @Override
+ public void startPagePreAnimation() {
+ scroller.startScroll((int) getMoveDistance(), 0, getWidth() - (int) getMoveDistance(), 0, PageSwitchTime);
+ readerView.mDown.x = 0;
+ readerView.CurrentMode = TxtReaderBaseView.Mode.PagePreIng;
+ postInvalidate();
+ }
+
+ @Override
+ public void onTextSelectMoveForward(MotionEvent event) {
+
+ }
+
+ @Override
+ public void onTextSelectMoveBack(MotionEvent event) {
+
+ }
+
+ @Override
+ public void onPageMove(MotionEvent event) {
+ readerView.mTouch.x = event.getX();
+ readerView.mTouch.y = event.getY();
+ postInvalidate();
+ }
+
+ private void postInvalidate() {
+ readerView.postInvalidate();
+ }
+
+ @Override
+ public void drawNote(Canvas canvas) {
+
+ }
+
+ @Override
+ public void drawSelectedText(Canvas canvas) {
+ if (readerView.CurrentMode == TxtReaderBaseView.Mode.PressSelectText) {
+ drawPressSelectedText(canvas);
+ } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveBack) {
+ drawSelectedLinesText(canvas);
+ } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveForward) {
+ drawSelectedLinesText(canvas);
+ }
+
+ }
+
+ private void drawSelectedLinesText(Canvas canvas) {
+ getTextSelectDrawer().drawSelectedLines(readerView.getCurrentSelectTextLine(), canvas, readerContext.getPaintContext().selectTextPaint);
+ //draw slider
+ drawSlider(canvas);
+
+ }
+
+ private void drawPressSelectedText(Canvas canvas) {
+ getTextSelectDrawer().drawSelectedChar(readerView.FirstSelectedChar, canvas, readerContext.getPaintContext().selectTextPaint);
+ //draw slider
+ drawSlider(canvas);
+ }
+
+ private void drawSlider(Canvas canvas) {
+ if (readerView.getLeftSliderPath() != null && readerView.getRightSliderPath() != null) {
+ canvas.drawPath(readerView.getLeftSliderPath(), readerContext.getPaintContext().sliderPaint);
+ canvas.drawPath(readerView.getRightSliderPath(), readerContext.getPaintContext().sliderPaint);
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ if (scroller.computeScrollOffset()) {
+ readerView.mTouch.x = scroller.getCurrX();
+ readerView.invalidate();
+ checkPageData();
+ }
+ }
+
+ private synchronized void checkPageData() {
+ if (onPageStateBackAnimation) {
+ if ((getMoveDistance()>0&&getMoveDistance() <= 3) || (getMoveDistance()<0&&getMoveDistance() >= -3) ) {
+ scroller.abortAnimation();
+ readerView.releaseTouch();
+ readerView.invalidate();
+ onPageStateBackAnimation = false;
+ }
+ } else {
+ if (readerView.mTouch.x == 0)//执行下一页数据获取
+ {
+ readerView.doPageNextDone();
+ scroller.abortAnimation();
+
+ } else if (readerView.mTouch.x == getWidth()) {//执行上一页数据获取
+ readerView.doPagePreDone();
+ scroller.abortAnimation();
+
+ }
+ }
+
+ }
+
+
+ private GradientDrawable mPagePreBorderDrawable;
+ private GradientDrawable mPageNextBorderDrawable;
+
+ private GradientDrawable getPagePreBorderDrawable() {
+ if (mPagePreBorderDrawable == null) {
+ int[] color = new int[]{0x55666666, 0x55666666, 0x55666666};
+ mPagePreBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, color);
+ }
+ return mPagePreBorderDrawable;
+ }
+
+ private GradientDrawable getPageNextBorderDrawable() {
+ if (mPageNextBorderDrawable == null) {
+ int[] color = new int[]{0x55666666, 0x55666666, 0x55666666};
+ mPageNextBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, color);
+ }
+ return mPageNextBorderDrawable;
+ }
+
+
+}
+
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtConfig.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtConfig.java
new file mode 100644
index 0000000..42bfa32
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtConfig.java
@@ -0,0 +1,266 @@
+package com.bifan.txtreaderlib.main;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+
+/**
+ * Created by HP on 2017/11/26.
+ */
+
+public class TxtConfig {
+
+ public static final String SAVE_NAME = "TxtConfig";
+ public static final String C_TEXT_SIZE = "TEXT_SIZE ";
+ public static final String C_TEXT_COLOR = "TEXT_COLOR";
+ public static final String C_NOTE_TEXT_COLOR = "TEXT_COLOR";
+ public static final String C_SLIDER_COLOR = "SLIDER_COLOR";
+ public static final String C_SELECT_TEXT_COLOR = "SELECTED_TEXT_COLOR";
+ public static final String C_BACKGROUND_COLOR = "BACKGROUND_COLOR";
+ public static final String C_IS_SHOW_NOTE = "IS_SHOW_NOTE";
+ public static final String C_CAN_PRESS_SELECT = "CAN_PRESS_SELECT";
+ public static final String C_BOLD = "BOLD ";
+ public static final String C_SHOW_SPECIAL_CHAR = "SHOW_SPECIAL_CHAR ";
+ public static final String C_CENTER_CLICK_AREA = "CENTER_CLICK_AREA";
+ public static final String C_PAGE_SWITCH_DURATION = "PAGE_SWITCH_DURATION";
+ public static final String C_PAGE_VERTICAL_MODE = "PAGE_VERTICAL_MODE ";
+ public static final String C_PAGE_SWITCH_TYPE_MODE = "PAGE_SWITCH_SYPE_MODE ";
+
+ public static final int PAGE_SWITCH_MODE_COVER = 1;//in px
+ public static final int PAGE_SWITCH_MODE_SERIAL = 2;//in px
+ public static final int PAGE_SWITCH_MODE_SHEAR = 3;//in px
+
+ public static int Page_PaddingLeft = 20;//in px
+ public static int Page_PaddingBottom = 20;//in px
+ public static int Page_PaddingTop = 20;//in px
+ public static int Page_PaddingRight = 20;//in px
+ public static int Page_LinePadding = 30;//in px
+ public static int Page_Paragraph_margin = 20;//in px,为0,没有间距
+
+ public int Page_Switch_Mode = PAGE_SWITCH_MODE_COVER;
+ public static int MAX_TEXT_SIZE = 150;//in px
+ public static int MIN_TEXT_SIZE = 50;//in px
+ public static int DEFAULT_SELECT_TEXT_COLOR = Color.parseColor("#44f6950b");
+ public static int DEFAULT_SLIDER_COLOR = Color.parseColor("#1f4cf5");
+
+ public int textSize = MIN_TEXT_SIZE;//字体大小
+ public int textColor = Color.BLACK;//字体颜色
+ public int backgroundColor = Color.WHITE;//背景颜色
+ public int NoteColor = Color.RED;//笔记颜色
+ public int SelectTextColor = DEFAULT_SELECT_TEXT_COLOR;//选中颜色
+ public int SliderColor = DEFAULT_SLIDER_COLOR;//滑动条颜色
+
+ public static boolean DebugMode = true;//debug模式会进行打印日志,默认是开启
+
+ public Boolean showNote = true;//是否显示笔记
+ public Boolean canPressSelect = true;//是否能长按选中
+
+ public Boolean VerticalPageMode = false;
+ public Boolean Bold = false;//是否加粗
+ public Boolean ShowSpecialChar = true;//是否显示特殊符号,对于数字、英文,可以显示特定颜色
+ public float CenterClickArea = 0.35f;//0~1,中间点击区域占View宽度的百分比,区域为高为宽两倍的矩形,如果为1f,说明点击翻页将不起效果
+ public int PageSwitchDuration = 400;//页面滑动时间间隔,毫秒,建议不要低于200
+
+ public static SharedPreferences getS(Context context) {
+ SharedPreferences share = context.getSharedPreferences(SAVE_NAME, Context.MODE_PRIVATE);
+ return share;
+ }
+
+ public static int getPageSwitchMode(Context context) {
+ SharedPreferences share = getS(context);
+ int PageSwitchMode = share.getInt(C_PAGE_SWITCH_TYPE_MODE, PAGE_SWITCH_MODE_COVER);
+ if(PageSwitchMode!=PAGE_SWITCH_MODE_COVER
+ &&PageSwitchMode!= PAGE_SWITCH_MODE_SERIAL
+ &&PageSwitchMode!= PAGE_SWITCH_MODE_SHEAR){
+ return PAGE_SWITCH_MODE_COVER;
+ }
+ return PageSwitchMode;
+ }
+
+ /**
+ * @param PageSwitchMode PAGE_SWITCH_MODE_COVER、PAGE_SWITCH_MODE_SERIAL、PAGE_SWITCH__MODE_SHEAR
+ */
+ public static void savePageSwitchMode(Context context,int PageSwitchMode) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putInt(C_PAGE_SWITCH_TYPE_MODE, PageSwitchMode);
+ editor.apply();
+ }
+
+
+ /**
+ * @param context
+ * @param duration 不能低于100,建议200以上
+ */
+ public static void savePageSwitchDuration(Context context, int duration) {
+ duration = Math.max(duration, 100);
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putInt(C_PAGE_SWITCH_DURATION, duration);
+ editor.apply();
+ }
+
+ public static int getPageSwitchDuration(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getInt(C_PAGE_SWITCH_DURATION, 400);
+ }
+
+ public static void saveTextSize(Context context, int textSize) {
+ textSize = Math.max(textSize, MIN_TEXT_SIZE);
+ textSize = Math.min(textSize, MAX_TEXT_SIZE);
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putInt(C_TEXT_SIZE, textSize);
+ editor.apply();
+ }
+
+ public static int getTextSize(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getInt(C_TEXT_SIZE, MIN_TEXT_SIZE);
+ }
+
+ public static void saveTextColor(Context context, int textColor) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putInt(C_TEXT_COLOR, textColor);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static int getTextColor(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getInt(C_TEXT_COLOR, Color.BLACK);
+ }
+
+ public static void saveNoteTextColor(Context context, int textColor) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putInt(C_NOTE_TEXT_COLOR, textColor);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static int getNoteTextColor(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getInt(C_NOTE_TEXT_COLOR, Color.BLACK);
+ }
+
+ public static void saveSelectTextColor(Context context, int textColor) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putInt(C_SELECT_TEXT_COLOR, textColor);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static int getSelectTextColor(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getInt(C_SELECT_TEXT_COLOR, DEFAULT_SELECT_TEXT_COLOR);
+ }
+
+
+ public static void saveBackgroundColor(Context context, int BackgroundColor) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putInt(C_BACKGROUND_COLOR, BackgroundColor);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static int getBackgroundColor(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getInt(C_BACKGROUND_COLOR, Color.WHITE);
+ }
+
+ public static void saveCenterClickArea(Context context, float CenterClickArea) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putFloat(C_CENTER_CLICK_AREA, CenterClickArea);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static float getCenterClickArea(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getFloat(C_CENTER_CLICK_AREA, 0.3f);
+ }
+
+ public static void saveSliderColor(Context context, float CenterClickArea) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putFloat(C_SLIDER_COLOR, CenterClickArea);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static int getSliderColor(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getInt(C_SLIDER_COLOR, DEFAULT_SLIDER_COLOR);
+ }
+
+ public static void saveIsShowNote(Context context, Boolean IsShowNote) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putBoolean(C_IS_SHOW_NOTE, IsShowNote);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static Boolean getIsShowNote(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getBoolean(C_IS_SHOW_NOTE, true);
+ }
+
+ public static void saveCanPressSelect(Context context, Boolean CanPressSelect) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putBoolean(C_CAN_PRESS_SELECT, CanPressSelect);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static Boolean getCanPressSelect(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getBoolean(C_CAN_PRESS_SELECT, true);
+ }
+
+
+ public static void saveIsBold(Context context, Boolean bold) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putBoolean(C_BOLD, bold);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static Boolean isBold(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getBoolean(C_BOLD, false);
+ }
+
+ public static void saveIsShowSpecialChar(Context context, Boolean showSpecialChar) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putBoolean(C_SHOW_SPECIAL_CHAR, showSpecialChar);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static Boolean IsShowSpecialChar(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getBoolean(C_SHOW_SPECIAL_CHAR, true);
+ }
+
+ public static void saveIsOnVerticalPageMode(Context context, Boolean IsOnVerticalPageMode) {
+ SharedPreferences share = getS(context);
+ SharedPreferences.Editor editor = share.edit();
+ editor.putBoolean(C_PAGE_VERTICAL_MODE, IsOnVerticalPageMode);
+ editor.apply();
+ editor.commit();
+ }
+
+ public static Boolean IsOnVerticalPageMode(Context context) {
+ SharedPreferences share = getS(context);
+ return share.getBoolean(C_PAGE_VERTICAL_MODE, false);
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderBaseView.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderBaseView.java
new file mode 100644
index 0000000..2bc1351
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderBaseView.java
@@ -0,0 +1,1403 @@
+package com.bifan.txtreaderlib.main;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.graphics.Region;
+
+import androidx.annotation.Nullable;
+
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Scroller;
+
+import com.bifan.txtreaderlib.bean.DefaultLeftSlider;
+import com.bifan.txtreaderlib.bean.DefaultRightSlider;
+import com.bifan.txtreaderlib.bean.Slider;
+import com.bifan.txtreaderlib.bean.TxtChar;
+import com.bifan.txtreaderlib.bean.TxtLine;
+import com.bifan.txtreaderlib.bean.TxtMsg;
+import com.bifan.txtreaderlib.interfaces.ICenterAreaClickListener;
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.IPage;
+import com.bifan.txtreaderlib.interfaces.IPageChangeListener;
+import com.bifan.txtreaderlib.interfaces.IPageEdgeListener;
+import com.bifan.txtreaderlib.interfaces.ISliderListener;
+import com.bifan.txtreaderlib.interfaces.ITxtLine;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.tasks.BitmapProduceTask;
+import com.bifan.txtreaderlib.tasks.DestroyableTask;
+import com.bifan.txtreaderlib.tasks.TextLoader;
+import com.bifan.txtreaderlib.tasks.TxtFileLoader;
+import com.bifan.txtreaderlib.utils.DisPlayUtil;
+import com.bifan.txtreaderlib.utils.ELogger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by bifa-wei
+ * on 2017/11/28.
+ */
+
+public abstract class TxtReaderBaseView extends View implements GestureDetector.OnGestureListener {
+ private String tag = "TxtReaderBaseView";
+
+ protected static int SliderWidth = 40;//滑动条宽度
+ protected static int PageChangeMinMoveDistance = 40;//页面切换需要的最小滑动距离
+ protected TxtReaderContext readerContext;//阅读器上下文
+ protected Scroller mScroller;//滑动器
+ protected GestureDetector mGestureDetector;//手势检测器
+ protected PointF mTouch = new PointF();//滑动坐标
+ protected PointF mDown = new PointF();//点下的坐标
+ protected TxtChar FirstSelectedChar = null;//第一个选中的字符
+ protected TxtChar LastSelectedChar = null;//最后一个选中的字符
+ protected Slider mLeftSlider = null;//左侧滑动条
+ protected Slider mRightSlider = null;//右侧滑动条
+ protected Bitmap TopPage = null;//顶部页
+ protected Bitmap BottomPage = null;//底部页
+ protected Mode CurrentMode = Mode.Normal;//当前页面模式
+ protected boolean hasDown = false;
+ private IPageEdgeListener pageEdgeListener;
+ private IPageChangeListener pageChangeListener;
+ private ISliderListener sliderListener;
+ private ICenterAreaClickListener centerAreaClickListener;
+ private DestroyableTask mFileLoadTask, mStrLoadTask;
+ private TxtFileLoader mTxtFileLoader;
+
+ public TxtReaderBaseView(Context context) {
+ super(context);
+ init();
+ }
+
+ public TxtReaderBaseView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ protected void init() {
+ if (mLeftSlider == null) {
+ mLeftSlider = new DefaultLeftSlider();
+ }
+ if (mRightSlider == null) {
+ mRightSlider = new DefaultRightSlider();
+ }
+ SliderWidth = DisPlayUtil.dip2px(getContext(), 13);
+ mLeftSlider.SliderWidth = SliderWidth;
+ mRightSlider.SliderWidth = SliderWidth;
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ readerContext = new TxtReaderContext(getContext());
+ mScroller = new TxtReaderScroller(getContext());
+ mGestureDetector = new GestureDetector(getContext(), this);
+ PageChangeMinMoveDistance = DisPlayUtil.dip2px(getContext(), 30);
+ readerContext.setPageParam(new PageParam());
+ setClickable(true);
+ }
+
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ //把默认背景绘制出来,不然默认是白色的,导致显示有点奇怪
+ if (readerContext != null) {
+ canvas.drawColor(readerContext.getTxtConfig().backgroundColor);
+ }
+ //初始化完成才允许绘制
+ if (readerContext.InitDone()) {
+ //绘制页面行数据
+ drawLineText(canvas);
+ //是否显示笔记 该功能未完成
+ if (readerContext.getTxtConfig().showNote) {
+ //绘制笔记
+ drawNote(canvas);
+ }
+ //不是正常模式,可能需要绘制长按选择的文字
+ if (readerContext.getTxtConfig().canPressSelect && CurrentMode != Mode.Normal) {
+ drawSelectedText(canvas);
+ }
+ }
+ }
+
+
+ /**
+ * @param canvas 绘制页面数据
+ */
+ protected abstract void drawLineText(Canvas canvas);
+
+ /**
+ * @param canvas 绘制笔记
+ */
+ protected abstract void drawNote(Canvas canvas);
+
+ /**
+ * @param canvas 绘制滑动选中的文字
+ */
+ protected abstract void drawSelectedText(Canvas canvas);
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ //检查是否在滑动中
+ if (mScroller.computeScrollOffset()
+ || CurrentMode == Mode.PageNextIng
+ || CurrentMode == Mode.PagePreIng) {
+ if (hasDown) {
+ hasDown = false;
+ }
+ return true;
+ }
+
+ //手势处理完成,捕捉了的话,拦截它
+ Boolean Deal = mGestureDetector.onTouchEvent(event);
+ //判断是否已经被处理了
+ if (Deal) {
+ return true;
+ }
+
+ if (!hasDown) {
+ //如果在滑动中,拦截了ACTION_DOWN事件,在滑动结束
+ //事件不会被Deal,出现没有初始化的情况,可能出现翻下一页却是在翻上一页的情况
+ return true;
+ }
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ onActionUp(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ onActionMove(event);
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * @param event onActionUp
+ */
+ protected void onActionUp(MotionEvent event) {
+ //默认正常模式下才能响应up事件
+ if (CurrentMode == Mode.Normal) {
+ startPageUpAnimation(event);
+ }
+ }
+
+
+ /**
+ * @param event onActionMove
+ */
+ protected void onActionMove(MotionEvent event) {
+ if (CurrentMode == Mode.Normal) {
+ //正常模式,执行页面滑动
+ onPageMove(event);
+ } else {
+ //当前需要执行向后滑动选择文字
+ if (CurrentMode == Mode.SelectMoveBack) {
+ float dx = event.getX() - mDown.x;
+ float dy = event.getY() - mDown.y;
+ float x = mRightSlider.getX(dx);
+ float y = mRightSlider.getY(dy);
+
+ if (CanMoveBack(x, y)) {
+ TxtChar moveToChar = findCharByPositionWhileMove(x, y);
+ if (FirstSelectedChar != null && moveToChar != null) {
+ if (moveToChar.Top > FirstSelectedChar.Top
+ || (moveToChar.Top == FirstSelectedChar.Top
+ && moveToChar.Left >= FirstSelectedChar.Left)) {
+ LastSelectedChar = moveToChar;
+ checkSelectedText();
+ onTextSelectMoveBack(event);
+ invalidate();
+ }
+ }
+ }
+
+ } else if (CurrentMode == Mode.SelectMoveForward) { //当前需要执行向前滑动选择文字
+ float dx = event.getX() - mDown.x;
+ float dy = event.getY() - mDown.y;
+
+ float x = mLeftSlider.getX(dx);
+ float y = mLeftSlider.getY(dy);
+
+ if (CanMoveForward(x, y)) {
+ TxtChar moveToChar = findCharByPositionWhileMove(x, y);
+ if (LastSelectedChar != null && moveToChar != null) {
+ if (moveToChar.Bottom < LastSelectedChar.Bottom
+ || (moveToChar.Bottom == LastSelectedChar.Bottom
+ && moveToChar.Right <= LastSelectedChar.Right)) {
+ FirstSelectedChar = moveToChar;
+ checkSelectedText();
+ onTextSelectMoveForward(event);
+ invalidate();
+ }
+ }
+ }
+ } else if (CurrentMode == Mode.PressUnSelectText) {
+
+ }
+ }
+ }
+
+ /**
+ * 执行上一页动画
+ *
+ * @param event
+ */
+ protected void startPageUpAnimation(MotionEvent event) {
+ if (getMoveDistance() < -PageChangeMinMoveDistance || getMoveDistance() > PageChangeMinMoveDistance) {
+ if (isPagePre()) {
+ if (!isFirstPage()) {
+ startPagePreAnimation();
+ } else {
+ releaseTouch();
+ invalidate();
+ }
+ } else if (isPageNext()) {
+ if (!isLastPage()) {
+ startPageNextAnimation();
+ } else {
+ releaseTouch();
+ invalidate();
+ }
+ }
+ } else {
+ //没有超出距离,自动还原
+ if ((getMoveDistance() > 0 && isFirstPage()) || (getMoveDistance() < 0 && isLastPage())) {
+ //这种情况不执行
+ } else {
+ //如果只是移动一点点,释放即可,不需要恢复
+ if ((getMoveDistance() > 0 && getMoveDistance() < 5) || (getMoveDistance() <= 0 && getMoveDistance() > -5)) {
+ releaseTouch();
+ invalidate();
+ } else {//ss
+ startPageStateBackAnimation();
+ }
+ }
+
+ }
+
+ }
+
+
+ /**
+ * @return 当前页是否是第一页
+ */
+ protected synchronized Boolean isFirstPage() {
+ return readerContext.getPageData().FirstPage() == null || getTopPage() == null;
+ }
+
+ /**
+ * @return 当前页是否是最后一页
+ */
+ protected synchronized Boolean isLastPage() {
+ return readerContext.getPageData().LastPage() == null || getBottomPage() == null;
+ }
+
+ /**
+ * 执行恢复到原状态动画
+ */
+ protected abstract void startPageStateBackAnimation();
+
+ /**
+ * 执行滑动到下一页
+ */
+ protected abstract void startPageNextAnimation();
+
+ /**
+ * 执行滑动到上一页
+ */
+ protected abstract void startPagePreAnimation();
+
+ /**
+ * @param event 向前滑动选择文字
+ */
+ protected abstract void onTextSelectMoveForward(MotionEvent event);
+
+ /**
+ * @param event 向后滑动选择文字
+ */
+ protected abstract void onTextSelectMoveBack(MotionEvent event);
+
+ /**
+ * @param event 页面移动
+ */
+ protected abstract void onPageMove(MotionEvent event);
+
+
+ @Override
+ public boolean onDown(MotionEvent motionEvent) {
+ mDown.x = motionEvent.getX();
+ mDown.y = motionEvent.getY();
+ mTouch.x = motionEvent.getX();
+ mTouch.y = motionEvent.getY();
+ hasDown = true;
+
+ if (CurrentMode == Mode.PressSelectText
+ || CurrentMode == Mode.SelectMoveForward
+ || CurrentMode == Mode.SelectMoveBack) {
+ CurrentMode = Mode.PressSelectText;
+ Path leftSliderPath = getLeftSliderPath();
+ Path rightSliderPath = getRightSliderPath();
+
+ if (leftSliderPath != null && rightSliderPath != null) {
+
+ Boolean downOnLeftSlider = computeRegion(getLeftSliderPath()).contains((int) mDown.x, (int) mDown.y);
+ Boolean downOnRightSlider = computeRegion(getRightSliderPath()).contains((int) mDown.x, (int) mDown.y);
+
+ if (downOnLeftSlider || downOnRightSlider) {
+ if (downOnLeftSlider) {
+ CurrentMode = Mode.SelectMoveForward;
+ setLeftSlider(FirstSelectedChar);
+ } else {
+
+ CurrentMode = Mode.SelectMoveBack;
+ setRightSlider(LastSelectedChar);
+ }
+ return true;
+ }
+ }
+ } else {
+ if (CurrentMode == Mode.PagePreIng || CurrentMode == Mode.PageNextIng) {//执行动画的使用不允许刷新状态
+ } else {
+ CurrentMode = Mode.Normal;
+ invalidate();
+ }
+ return true;
+ }
+ return true;
+ }
+
+
+ /**
+ * 释放了滑动条
+ */
+ protected void onReleasedSlider() {
+ //已经释放了滑动选择
+ if (sliderListener != null) {
+ sliderListener.onReleaseSlider();
+ }
+ }
+
+ /**
+ * 显示了滑动条
+ */
+ protected void onShownSlider() {
+ //开始显示滑动选择
+ if (sliderListener != null) {
+ sliderListener.onShowSlider(FirstSelectedChar);
+ sliderListener.onShowSlider(FirstSelectedChar.getValueStr());
+ }
+ }
+
+
+ /**
+ * 轻击翻页
+ */
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ //当前是长按滑动事件的话,没有点击到滑动条,应该释放
+ if (CurrentMode == Mode.PressSelectText
+ || CurrentMode == Mode.SelectMoveForward
+ || CurrentMode == Mode.SelectMoveBack) {
+ //没有点击到滑动条,释放
+ CurrentMode = Mode.Normal;
+ onReleasedSlider();
+ invalidate();
+ return true;
+ }
+
+ Boolean dealCenterClickAndDonChangePage = dealCenterClickAndDoChangePage(e);
+ if (dealCenterClickAndDonChangePage) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 处理中间区域点击事件并且可能执行点击翻页效果
+ */
+ private boolean dealCenterClickAndDoChangePage(MotionEvent e) {
+
+ if (CurrentMode == Mode.Normal && readerContext.InitDone()) {
+ float widthPercent = readerContext.getTxtConfig().CenterClickArea;
+ //捕捉异常情况
+ widthPercent = widthPercent < 0 ? 0 : widthPercent;
+ widthPercent = widthPercent > 1 ? 1 : widthPercent;
+
+ int width = (int) (getWidth() * widthPercent);
+ int left = getWidth() / 2 - width / 2;
+ int top = getHeight() / 2 - width;
+ int bottom = top + width + width;
+ int right = left + width;
+
+ int x = (int) e.getX();
+ int y = (int) e.getY();
+
+ boolean needPagePre = x < left;
+ boolean needPageNext = x > right;
+ boolean inCenter = x > left && x < right && y > top && y < bottom;
+ boolean deal = false;
+
+ if (inCenter) {
+ //点击中间区域
+ if (centerAreaClickListener != null) {
+ deal = centerAreaClickListener.onCenterClick(widthPercent);
+ }
+ } else {
+ if (centerAreaClickListener != null) {
+ deal = centerAreaClickListener.onOutSideCenterClick(widthPercent);
+ }
+ }
+ //getMoveDistance() < -PageChangeMinMoveDistance || getMoveDistance() > PageChangeMinMoveDistance
+ // if ((getMoveDistance() > 0 && isFirstPage()) || (getMoveDistance() < 0 && isLastPage())) {} 这样的情况才不执行
+
+ //如果这个事件没有被处理,将可能会执行翻页事件
+ if (!deal) {
+ if (needPagePre && !isFirstPage()) {
+ // mTouch.x - mDown.x>10
+ //模拟滑动执行翻上一页手势
+ mDown.x = 0;
+ mTouch.x = mDown.x + 15;
+ tryDoPagePre();
+ startPagePreAnimation();
+ return true;
+ }
+
+ if (needPageNext && !isLastPage()) {
+ //mTouch.x - mDown.x<-10
+ ///模拟滑动执行翻下一页手势
+ mDown.x = getWidth();
+ mTouch.x = mDown.x - 15;
+ tryDoPageNext();
+ startPageNextAnimation();
+ return true;
+ }
+ }
+
+ }
+ return false;
+ }
+
+ /**
+ * 长按事件,执行检测长按文字
+ */
+ @Override
+ public void onLongPress(MotionEvent e) {
+ if (CurrentMode == Mode.Normal) {
+ onPressSelectText(e);
+ }
+ }
+
+ /**
+ * @param motionEvent motionEvent
+ */
+ @Override
+ public void onShowPress(MotionEvent motionEvent) {
+ ELogger.log(tag, "onShowPress ,CurrentMode:" + CurrentMode);
+ }
+
+ /**
+ * @param motionEvent motionEvent
+ * @param motionEvent1 motionEvent1
+ * @param v v
+ * @param v1 v1
+ * @return
+ */
+ @Override
+ public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
+ return false;
+ }
+
+ /**
+ * 快速滑动翻页
+ *
+ * @param e1 e1
+ * @param e2 e2
+ * @param velocityX velocityX
+ * @param velocityY velocityY
+ * @return
+ */
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ float MaxVelocityX = 1000;
+ if (CurrentMode == Mode.Normal) {//正常情况下快速滑动,执行翻页动作
+ if (isPagePre() && !isFirstPage() && velocityX > MaxVelocityX) {
+ startPagePreAnimation();
+ return true;
+ } else if (isPageNext() && !isLastPage() && velocityX < -MaxVelocityX) {
+ startPageNextAnimation();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 长按获取按着的文字,这时 FirstSelectedChar = LastSelectedChar
+ */
+ protected void onPressSelectText(MotionEvent e) {
+ TxtChar selectedChar = findCharByPosition(e.getX(), e.getY());
+ if (selectedChar != null)
+ ELogger.log("onPressSelectText", selectedChar.toString());
+ else
+ ELogger.log("onPressSelectText", "is null" + e.getX() + "," + e.getY());
+ if (selectedChar != null) {
+ FirstSelectedChar = selectedChar;
+ LastSelectedChar = selectedChar;
+ setLeftSlider(FirstSelectedChar);
+ setRightSlider(LastSelectedChar);
+ CurrentMode = Mode.PressSelectText;
+ onShownSlider();
+ } else {
+ CurrentMode = Mode.PressUnSelectText;
+ FirstSelectedChar = null;
+ LastSelectedChar = null;
+ onReleasedSlider();
+ }
+ releaseTouch();
+ postInvalidate();
+
+ }
+
+ /**
+ * @param FirstSelectedChar 设置左滑动条数据
+ */
+ private void setLeftSlider(TxtChar FirstSelectedChar) {
+ mLeftSlider.Left = FirstSelectedChar.Left - SliderWidth * 2;
+ mLeftSlider.Right = FirstSelectedChar.Left;
+ mLeftSlider.Top = FirstSelectedChar.Bottom;
+ mLeftSlider.Bottom = FirstSelectedChar.Bottom + SliderWidth * 2;
+ }
+
+ /**
+ * @param LastSelectedChar 设置右滑动条数据
+ */
+ private void setRightSlider(TxtChar LastSelectedChar) {
+ mRightSlider.Left = LastSelectedChar.Right;
+ mRightSlider.Right = LastSelectedChar.Right + SliderWidth * 2;
+ mRightSlider.Top = LastSelectedChar.Bottom;
+ mRightSlider.Bottom = LastSelectedChar.Bottom + SliderWidth * 2;
+ }
+
+
+ /**
+ * @return 找到长按选中的文字,找不到返回null
+ */
+ private TxtChar findCharByPosition(float positionX, float positionY) {
+ boolean isVerticalMode = false;
+ if (readerContext != null && readerContext.getTxtConfig() != null) {
+ isVerticalMode = readerContext.getTxtConfig().VerticalPageMode;
+ }
+ if (isVerticalMode) {
+ return findCharByPositionOfVerticalMode(positionX, positionY);
+ } else {
+ return findCharByPositionOfHorizontalMode(positionX, positionY);
+ }
+ }
+
+ private TxtChar findCharByPositionOfVerticalMode(float positionX, float positionY) {
+ IPage page = readerContext.getPageData().MidPage();
+ int offset = readerContext.getPageParam().LinePadding / 2;
+ //当前页面有数据才执行查找
+ if (page != null && page.HasData()) {
+ List lines = page.getLines();
+ for (ITxtLine line : lines) {
+ List chars = line.getTxtChars();
+ if (chars != null && chars.size() > 0) {
+ for (TxtChar c : chars) {
+ if (positionX > (c.Left - offset) && positionX < (c.Right + offset)) {
+ if (positionY > c.Top && positionY <= c.Bottom) {
+ return c;
+ }
+ } else {
+ break;//说明在下一行
+ }
+ }
+ }
+ }
+ } else {
+ ELogger.log(tag, "page not null and page hasData()");
+ }
+ return null;
+ }
+
+ private TxtChar findCharByPositionOfHorizontalMode(float positionX, float positionY) {
+ IPage page = readerContext.getPageData().MidPage();
+ int offset = readerContext.getPageParam().LinePadding / 2;
+ //当前页面有数据才执行查找
+ if (page != null && page.HasData()) {
+ List lines = page.getLines();
+ for (ITxtLine line : lines) {
+ List chars = line.getTxtChars();
+ if (chars != null && chars.size() > 0) {
+ for (TxtChar c : chars) {
+ if (positionY > (c.Top - offset) && positionY < (c.Bottom + offset)) {
+ if (positionX > c.Left && positionX <= c.Right) {
+ return c;
+ }
+ } else {
+ break;//说明在下一行
+ }
+ }
+ }
+ }
+ } else {
+ ELogger.log(tag, "page not null and page hasData()");
+ }
+ return null;
+ }
+
+ /**
+ * @return 找到长按选中的文字,找不到返回null
+ */
+ private TxtChar findCharByPositionWhileMove(float positionX, float positionY) {
+ IPage page = readerContext.getPageData().MidPage();
+ int offset = readerContext.getPageParam().LinePadding / 2;
+ //当前页面有数据才执行查找
+ if (page != null && page.HasData()) {
+ List lines = page.getLines();
+ for (ITxtLine line : lines) {
+ List chars = line.getTxtChars();
+ if (chars != null && chars.size() > 0) {
+ for (TxtChar c : chars) {
+ if (positionY > (c.Top - offset) && positionY < (c.Bottom + offset)) {
+ if (positionX > c.Left && positionX < c.Right) {
+ return c;
+ } else {//说明在行的左边或者右边啊
+ TxtChar first = chars.get(0);
+ TxtChar last = chars.get(chars.size() - 1);
+ if (positionX < first.Left) {
+ return first;
+ } else if (positionX > last.Right) {
+ return last;
+ }
+ }
+ } else {
+ break;//说明在下一行
+ }
+ }
+ }
+ }
+ } else {
+ ELogger.log(tag, "page not null and page hasData()");
+ }
+ return null;
+ }
+
+ private Path mSliderPath = new Path();
+
+ /**
+ * @return 可能返回null
+ */
+ protected Path getLeftSliderPath() {
+ return mLeftSlider.getPath(FirstSelectedChar, mSliderPath);
+
+ }
+
+ /**
+ * @return 可能返回null
+ */
+ protected Path getRightSliderPath() {
+ return mRightSlider.getPath(LastSelectedChar, mSliderPath);
+ }
+
+ //当前滑动选择的数据
+ private final List mSelectLines = new ArrayList<>();
+
+ /**
+ * @return 获取当前选中的行文字
+ */
+ protected synchronized List getCurrentSelectTextLine() {
+ return mSelectLines;
+ }
+
+
+ /**
+ * 检测滑动选中的文字
+ */
+ protected synchronized void checkSelectedText() {
+ Boolean Started = false;
+ Boolean Ended = false;
+ //清空之前选择的数据
+ mSelectLines.clear();
+ IPage currentPage = readerContext.getPageData().MidPage();
+ //当前页面没有数据或者没有选择或者已经释放了长按选择事件,不执行
+ if (currentPage == null || !currentPage.HasData() || FirstSelectedChar == null || LastSelectedChar == null) {
+ return;
+ }
+ //获取当前页面行数据
+ List lines = currentPage.getLines();
+ // 找到选择的字符数据,转化为选择的行,然后将行选择背景画出来
+ for (ITxtLine l : lines) {
+ ITxtLine selectLine = new TxtLine();
+ for (TxtChar c : l.getTxtChars()) {
+ if (!Started) {
+ if (c.ParagraphIndex == FirstSelectedChar.ParagraphIndex && c.CharIndex == FirstSelectedChar.CharIndex) {
+ Started = true;
+ selectLine.addChar(c);
+ if (c.ParagraphIndex == LastSelectedChar.ParagraphIndex && c.CharIndex == LastSelectedChar.CharIndex) {
+ Ended = true;
+ break;
+ }
+ }
+ } else {
+ if (c.ParagraphIndex == LastSelectedChar.ParagraphIndex && c.CharIndex == LastSelectedChar.CharIndex) {
+ Ended = true;
+ if (selectLine.getTxtChars() == null || !selectLine.getTxtChars().contains(c)) {
+ selectLine.addChar(c);
+ }
+ break;
+ } else {
+ selectLine.addChar(c);
+ }
+ }
+ }
+
+ if (selectLine.HasData()) {
+ mSelectLines.add(selectLine);
+ }
+
+ if (Started && Ended) {
+ return;
+ }
+ }
+ }
+
+ /**
+ * @return 获取当前选中的文字,不会返回null
+ */
+ protected String getCurrentSelectedText() {
+ String text = "";
+ for (ITxtLine l : mSelectLines) {
+ text = text + l.getLineStr();
+ }
+ return text;
+ }
+
+ /**
+ * @param path
+ * @return 计算区域
+ */
+
+ private Region computeRegion(Path path) {
+ Region region = new Region();
+ RectF f = new RectF();
+ path.computeBounds(f, true);
+ region.setPath(path, new Region((int) f.left, (int) f.top, (int) f.right, (int) f.bottom));
+ return region;
+ }
+
+ /**
+ * @param TouchX
+ * @param TouchY
+ * @return 是否可以向后滑动
+ */
+ private boolean CanMoveBack(float TouchX, float TouchY) {
+ if (FirstSelectedChar != null) {
+ Path p = new Path();
+ p.moveTo(FirstSelectedChar.Right, FirstSelectedChar.Top);
+ p.lineTo(getWidth(), FirstSelectedChar.Top);
+ p.lineTo(getWidth(), getHeight());
+ p.lineTo(0, getHeight());
+ p.lineTo(0, FirstSelectedChar.Bottom);
+ p.lineTo(FirstSelectedChar.Right, FirstSelectedChar.Bottom);
+ p.lineTo(FirstSelectedChar.Right, FirstSelectedChar.Top);
+ return computeRegion(p).contains((int) TouchX, (int) TouchY);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @param TouchX
+ * @param TouchY
+ * @return 是否可以向前滑动
+ */
+ private boolean CanMoveForward(float TouchX, float TouchY) {
+ if (LastSelectedChar != null) {
+ Path p = new Path();
+ p.moveTo(LastSelectedChar.Left, LastSelectedChar.Top);
+ p.lineTo(getWidth(), LastSelectedChar.Top);
+ p.lineTo(getWidth(), 0);
+ p.lineTo(0, 0);
+ p.lineTo(0, LastSelectedChar.Bottom);
+ p.lineTo(LastSelectedChar.Left, LastSelectedChar.Bottom);
+ p.lineTo(LastSelectedChar.Left, LastSelectedChar.Top);
+ return computeRegion(p).contains((int) TouchX, (int) TouchY);
+ } else {
+ return false;
+ }
+ }
+
+
+ protected Boolean isPageNext() {
+ //是否是执行下一页
+ return getMoveDistance() < -10;
+ }
+
+ protected Boolean isPagePre() {
+ //是否是执行上一页
+ return getMoveDistance() > 10;
+ }
+
+ /**
+ * @return 获取当前滑动距离
+ */
+ protected synchronized float getMoveDistance() {
+ int i = (int) (mTouch.x - mDown.x);
+ float m = mTouch.x - mDown.x;
+ if (i < m) {
+ return i + 1;
+ }
+ return m;
+ }
+
+ protected void tryDoPagePre() {
+ BottomPage = readerContext.getBitmapData().FirstPage();
+ }
+
+ protected void tryDoPageNext() {
+ BottomPage = readerContext.getBitmapData().LastPage();
+ }
+
+ protected void checkMoveState() {
+ //检测页面滑动状态更新显示数据
+ if (isPagePre()) {
+ tryDoPagePre();
+ } else if (isPageNext()) {
+ tryDoPageNext();
+ } else {
+ TopPage = readerContext.getBitmapData().MidPage();
+ tryDoPageNext();
+ }
+ }
+
+ protected void doPagePreDone() {
+ //执行获取上一页数据
+ IPage firstPage = readerContext.getPageData().FirstPage();
+ if (firstPage == null) {//没有上一页数据了
+ ELogger.log(tag, "没有上一页数据了");
+ CurrentMode = Mode.Normal;
+ return;
+ }
+ pagePreTask.Run(null, readerContext);
+ }
+
+ protected void doPageNextDone() {
+ //执行获取下一页数据
+ IPage lastPage = readerContext.getPageData().LastPage();
+ if (lastPage == null) {//没有下一页数据了
+ CurrentMode = Mode.Normal;
+ return;
+ }
+
+ pageNextTask.Run(null, readerContext);
+ }
+
+ protected Bitmap getTopPage() {
+ if (TopPage != null && TopPage.isRecycled()) {
+ TopPage = null;
+ }
+ return TopPage;
+ }
+
+ protected Bitmap getBottomPage() {
+ if (BottomPage != null && BottomPage.isRecycled()) {
+ BottomPage = null;
+ }
+ return BottomPage;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (readerContext != null) {
+ readerContext.Clear();
+ }
+ }
+
+ /**
+ * 释放触摸事件
+ */
+ protected void releaseTouch() {
+ mTouch.x = 0;
+ mDown.x = 0;
+ hasDown = false;
+ }
+
+ public enum Mode {
+ Normal, //正常模式
+ PagePreIng,//执行上一页数据获取等相关操作
+ PageNextIng,//执行下一页数据获取等相关操作
+ PressSelectText,//长按选中文字
+ PressUnSelectText,//长按但是未选中文字
+ SelectMoveForward, //向前滑动选中文字
+ SelectMoveBack//向后滑动选中文字
+ }
+
+
+ //---------------------------获取上一页、获取下一页数据--------------------------------------
+
+ protected final ITxtTask pageNextTask = new PageNextTask();
+ protected final ITxtTask pagePreTask = new PagePreTask();
+ protected final BitmapProduceTask bitmapProduceTask = new BitmapProduceTask();
+
+ private class PageNextTask implements ITxtTask {
+ @Override
+ public void Run(ILoadListener callBack, final TxtReaderContext readerContext) {
+ CurrentMode = Mode.PageNextIng;
+ getPageNextData();
+ bitmapProduceTask.Run(new ILoadListener() {
+ @Override
+ public void onSuccess() {
+ releaseTouch();
+ checkMoveState();
+
+ post(new Runnable() {
+ @Override
+ public void run() {
+ invalidate();
+ CurrentMode = Mode.Normal;
+ onPageProgress(readerContext.getPageData().MidPage());
+ }
+ });
+
+ }
+
+ @Override
+ public void onFail(TxtMsg txtMsg) {
+ CurrentMode = Mode.Normal;
+ ELogger.log(tag + "PageNextTask", "PageNextTask onFail" + txtMsg);
+ }
+
+ @Override
+ public void onMessage(String message) {
+ CurrentMode = Mode.Normal;
+ ELogger.log(tag + "PageNextTask", "PageNextTask onMessage" + message);
+ }
+ }, readerContext);
+ }
+
+ private void getPageNextData() {
+
+ IPage nextMidPage = readerContext.getPageData().LastPage();
+ IPage nextFirstPage = readerContext.getPageData().MidPage();
+
+ IPage firstPage = null;
+ IPage midPage;
+ IPage nextPage = null;
+
+ if (nextFirstPage != null && nextFirstPage.HasData()) {
+ if (nextFirstPage.isFullPage()) { //说明,nextFirstPage是完整的页数据,直接获取
+ firstPage = nextFirstPage;
+ }
+ }
+
+ if (nextMidPage != null && nextMidPage.isFullPage()) {//说明之前的LastPage是完整的数据,直接获取
+ midPage = nextMidPage;
+ } else {
+ midPage = nextMidPage;//说明之前的LastPage不是完整的数据,也直接获取
+ }
+
+ if (midPage != null && midPage.isFullPage()) {
+ //midPage是完整页,说明可能有下一页数据,否则没有下一页数据了
+ nextPage = readerContext.getPageDataPipeline().getPageStartFromProgress(midPage.getLastChar().ParagraphIndex, midPage.getLastChar().CharIndex + 1);
+ }
+
+
+ if (firstPage != null && nextFirstPage != null) {
+ readerContext.getBitmapData().setFirstPage(readerContext.getBitmapData().MidPage());
+ readerContext.getPageData().refreshTag[0] = 0;
+
+ }
+
+ if (midPage != null && midPage.HasData()) {
+ readerContext.getBitmapData().setMidPage(readerContext.getBitmapData().LastPage());
+ readerContext.getPageData().refreshTag[1] = 0;
+ }
+
+ readerContext.getBitmapData().setLastPage(null);
+ readerContext.getPageData().refreshTag[2] = 1;
+ readerContext.getPageData().setFirstPage(firstPage);
+ readerContext.getPageData().setMidPage(midPage);
+ readerContext.getPageData().setLastPage(nextPage);
+
+ }
+ }
+
+
+ private class PagePreTask implements ITxtTask {
+ @Override
+ public void Run(ILoadListener callBack, final TxtReaderContext readerContext) {
+ CurrentMode = Mode.PagePreIng;
+ getPagePreData();
+ bitmapProduceTask.Run(new ILoadListener() {
+ @Override
+ public void onSuccess() {
+ releaseTouch();
+ checkMoveState();
+ post(new Runnable() {
+ @Override
+ public void run() {
+ invalidate();
+ CurrentMode = Mode.Normal;
+ onPageProgress(readerContext.getPageData().MidPage());
+ }
+ });
+ }
+
+ @Override
+ public void onFail(TxtMsg txtMsg) {
+ CurrentMode = Mode.Normal;
+ ELogger.log(tag + "PagePreTask", "PageNextTask onFail" + txtMsg);
+ }
+
+ @Override
+ public void onMessage(String message) {
+ CurrentMode = Mode.Normal;
+ ELogger.log(tag + "PagePreTask", "PageNextTask onMessage" + message);
+ }
+ }, readerContext);
+ }
+
+ private void getPagePreData() {
+ IPage nextMayMidPage = readerContext.getPageData().FirstPage();
+ IPage nextMayLastPage = readerContext.getPageData().MidPage();
+
+ IPage firstPage = null;
+ IPage midPage = null;
+ IPage nextPage = null;
+
+ if (nextMayMidPage != null && nextMayMidPage.HasData()) {
+ if (nextMayMidPage.isFullPage()) {//之前的FirstPage是满页的,直接获取
+ midPage = nextMayMidPage;
+ } else {//之前的FirstPage不是是满页的,直接从头开始获取
+ midPage = readerContext.getPageDataPipeline().getPageStartFromProgress(0, 0);
+ }
+ }
+
+ if (midPage != null && midPage.isFullPage()) {//现在说明midPage是满页的,可能有上一页数据与下一页数据
+ if (midPage.getFirstChar().ParagraphIndex == 0 && midPage.getFirstChar().CharIndex == 0) {
+ firstPage = null;//之前的FirstPage不是是满页的,直接从头开始获取,没有firstPage
+ } else {
+
+ firstPage = readerContext.getPageDataPipeline().getPageEndToProgress(midPage.getFirstChar().ParagraphIndex, midPage.getFirstChar().CharIndex);
+ }
+ nextPage = readerContext.getPageDataPipeline().getPageStartFromProgress(midPage.getLastChar().ParagraphIndex, midPage.getLastChar().CharIndex + 1);
+ }
+
+
+ //判断是否是相同数据然后进行判断是否需要进行刷新
+ int needRefresh = 1;
+ if (isSamePageData(nextPage, nextMayLastPage)) {
+ needRefresh = 0;
+ readerContext.getBitmapData().setLastPage(readerContext.getBitmapData().MidPage());
+ nextPage = nextMayLastPage;
+ }
+ readerContext.getPageData().refreshTag[2] = needRefresh;
+
+ //判断是否是相同数据然后进行判断是否需要进行刷新
+ needRefresh = 1;
+ if (isSamePageData(midPage, nextMayMidPage)) {
+ needRefresh = 0;
+ readerContext.getBitmapData().setMidPage(readerContext.getBitmapData().FirstPage());
+ midPage = nextMayMidPage;
+ }
+ readerContext.getPageData().refreshTag[1] = needRefresh;
+ readerContext.getBitmapData().setFirstPage(null);
+ readerContext.getPageData().refreshTag[0] = 1;
+ readerContext.getPageData().setFirstPage(firstPage);
+ readerContext.getPageData().setMidPage(midPage);
+ readerContext.getPageData().setLastPage(nextPage);
+ }
+ }
+
+
+ private boolean isSamePageData(IPage f, IPage to) {
+ if (f != null && to != null && f.HasData() && to.HasData()) {
+ TxtChar fF = f.getFirstChar();
+ TxtChar fL = f.getLastChar();
+ TxtChar toF = to.getFirstChar();
+ TxtChar toL = to.getLastChar();
+ return fF.equals(toF) && fL.equals(toL);
+ }
+ return false;
+ }
+
+ protected void onPageProgress(IPage page) {
+ if (page != null && page.HasData()) {
+ TxtChar lastChar = page.getLastChar();
+ onProgressCallBack(getProgress(lastChar.ParagraphIndex, lastChar.CharIndex));
+ } else {
+ ELogger.log(tag, "onPageProgress ,page data may be empty");
+ }
+
+ }
+
+ protected float getProgress(int ParagraphIndex, int chartIndex) {
+ float progress = 0;
+ int pN = readerContext.getParagraphData().getParagraphNum();
+ if (pN > 0 && pN > ParagraphIndex) {
+ int index = readerContext.getParagraphData().getParaStartCharIndex(ParagraphIndex) + chartIndex;
+ int num = readerContext.getParagraphData().getCharNum();
+ if (num > 0) {
+ if (index > num) {
+ progress = 1;
+ } else {
+ progress = (float) index / (float) num;
+ }
+ }
+ }
+ return progress;
+ }
+
+ protected void onProgressCallBack(float progress) {
+ if (pageChangeListener != null) {
+ pageChangeListener.onCurrentPage(progress);
+ }
+ if (pageEdgeListener != null) {
+ if (isFirstPage()) {
+ pageEdgeListener.onCurrentFirstPage();
+ }
+ if (isLastPage()) {
+ pageEdgeListener.onCurrentLastPage();
+ }
+ }
+ }
+
+
+ private class TxtReaderScroller extends Scroller {
+ public TxtReaderScroller(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void abortAnimation() {
+ super.abortAnimation();
+ releaseTouch();
+ }
+ }
+
+ /**
+ * 刷新数据标签,int r1, int r2, int r3决定了页面数据是否出现刷新
+ * ,不需要的话直接使用缓存bitmap
+ *
+ * @param r1
+ * @param r2
+ * @param r3
+ */
+ protected void refreshTag(int r1, int r2, int r3) {
+ readerContext.getPageData().refreshTag[0] = r1;
+ readerContext.getPageData().refreshTag[1] = r2;
+ readerContext.getPageData().refreshTag[2] = r3;
+ }
+
+
+ /**
+ * @param filePath
+ * @param listener
+ */
+ private void loadFile(final String filePath, final ILoadListener listener) {
+ destroyFileLoadTask();
+ final TxtFileLoader loader = new TxtFileLoader();
+ DestroyableTask task = new DestroyableTask(loader::onStop);
+ task.excuse(() -> loader.load(filePath, readerContext, new DataLoadListener(listener)));
+ mFileLoadTask = task;
+ }
+
+ /**
+ * @param text
+ * @param listener
+ */
+ private void loadTextStr(final String text, final ILoadListener listener) {
+ destroyStrLoadTask();
+ final TextLoader loader = new TextLoader();
+ DestroyableTask task = new DestroyableTask(null);
+ task.excuse(() -> loader.load(text, readerContext, new DataLoadListener(listener)));
+ mStrLoadTask = task;
+ }
+
+ private void destroyFileLoadTask() {
+ if (mFileLoadTask != null) {
+ mFileLoadTask.destroy();
+ }
+ }
+
+ private void destroyStrLoadTask() {
+ if (mStrLoadTask != null) {
+ mStrLoadTask.destroy();
+ }
+ }
+
+ /**
+ * 数据加载监听
+ */
+ private class DataLoadListener implements ILoadListener {
+ ILoadListener listener;
+
+ public DataLoadListener(ILoadListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public void onSuccess() {
+ checkMoveState();
+ postInvalidate();
+ post(new Runnable() {
+ @Override
+ public void run() {
+ onPageProgress(readerContext.getPageData().MidPage());
+ if (listener != null) {
+ listener.onSuccess();
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public void onFail(TxtMsg txtMsg) {
+ if (listener != null) {
+ listener.onFail(txtMsg);
+ }
+ }
+
+ @Override
+ public void onMessage(String message) {
+ if (listener != null) {
+ listener.onMessage(message);
+ }
+ }
+ }
+
+ private void initReaderContext() {
+ PageChangeMinMoveDistance = getWidth() / 5;
+ PageParam param = readerContext.getPageParam();
+ param.PageWidth = getWidth();
+ param.PageHeight = getHeight();
+ }
+
+
+ //-------------------------------------------------------------
+
+
+ /**
+ * @param pageEdgeListener 当前页是首页获取尾页监听,注意如果文本只有一页,那边首页与尾页都会回调
+ */
+ public void setOnPageEdgeListener(IPageEdgeListener pageEdgeListener) {
+ this.pageEdgeListener = pageEdgeListener;
+ }
+
+ /**
+ * @param pageChangeListener 页面进度改变监听
+ */
+ public void setPageChangeListener(IPageChangeListener pageChangeListener) {
+ this.pageChangeListener = pageChangeListener;
+ }
+
+ /**
+ * @param sliderListener 长按选择出现与消失监听
+ */
+ public void setOnSliderListener(ISliderListener sliderListener) {
+ this.sliderListener = sliderListener;
+ }
+
+ /**
+ * @param centerAreaClickListener 中间区域点击监听
+ */
+ public void setOnCenterAreaClickListener(ICenterAreaClickListener centerAreaClickListener) {
+ this.centerAreaClickListener = centerAreaClickListener;
+ }
+
+ /**
+ * 通过文件路径加载文件
+ *
+ * @param filePath 文件路径
+ * @param listener 监听
+ */
+ public void loadTxtFile(final String filePath, final ILoadListener listener) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ initReaderContext();
+ loadFile(filePath, listener);
+ }
+
+ });
+ }
+
+ /**
+ * 通过字符串加载
+ *
+ * @param text 文本字符串数据
+ * @param listener 监听
+ */
+ public void loadText(final String text, final ILoadListener listener) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ initReaderContext();
+ loadTextStr(text, listener);
+ }
+ });
+ }
+
+ /**
+ * @return 获取当前页的第一个字符, 当前页每页数据,返回null
+ */
+ public TxtChar getCurrentFirstChar() {
+ IPage page = readerContext.getPageData().MidPage();
+ //当前页面有数据才执行查找
+ if (page != null && page.HasData()) {
+ return page.getFirstChar();
+ }
+ return null;
+ }
+
+
+ /**
+ * @return 获取当前页的第一行, 当前页每页数据,返回null
+ */
+ public ITxtLine getCurrentFirstLines() {
+ IPage page = readerContext.getPageData().MidPage();
+ //当前页面有数据才执行查找
+ if (page != null && page.HasData()) {
+ return page.getFirstLine();
+ }
+ return null;
+ }
+
+ /**
+ * @param leftSlider 设置左侧滑动条
+ */
+ public void setLeftSlider(Slider leftSlider) {
+ this.mLeftSlider = leftSlider;
+ this.mLeftSlider.SliderWidth = SliderWidth;
+ }
+
+ /**
+ * @param rightSlider 设置右侧滑动条
+ */
+ public void setRightSlider(Slider rightSlider) {
+ this.mRightSlider = rightSlider;
+ this.mRightSlider.SliderWidth = SliderWidth;
+
+ }
+
+ /**
+ * 调用该方法释放长按选择模式
+ */
+ public void releaseSelectedState() {
+ CurrentMode = Mode.Normal;
+ postInvalidate();
+ }
+
+ public void onDestroy() {
+ destroyFileLoadTask();
+ destroyStrLoadTask();
+ }
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderContext.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderContext.java
new file mode 100644
index 0000000..3c916c0
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderContext.java
@@ -0,0 +1,156 @@
+package com.bifan.txtreaderlib.main;
+
+import android.content.Context;
+
+import com.bifan.txtreaderlib.bean.TxtFileMsg;
+import com.bifan.txtreaderlib.interfaces.IChapter;
+import com.bifan.txtreaderlib.interfaces.IChapterMatcher;
+import com.bifan.txtreaderlib.interfaces.IPageDataPipeline;
+import com.bifan.txtreaderlib.interfaces.IParagraphData;
+
+import java.util.List;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class TxtReaderContext {
+ public Context context;
+ private IParagraphData paragraphData;
+ private PageParam pageParam;
+ private TxtFileMsg fileMsg;
+ private List chapters;
+ private IPageDataPipeline pageDataPipeline;
+ private PaintContext paintContext;
+ private TxtConfig txtConfig;
+ private Boolean InitDone = false;
+ private IChapterMatcher chapterMatcher;
+
+ private final BitmapData bitmapData = new BitmapData();
+ private final PageData pageData = new PageData();
+
+ public TxtReaderContext(Context context) {
+ this.context = context;
+ }
+
+ public TxtFileMsg getFileMsg() {
+ return fileMsg;
+ }
+
+ public void setFileMsg(TxtFileMsg fileMsg) {
+ this.fileMsg = fileMsg;
+ }
+
+ public void setParagraphData(IParagraphData paragraphData) {
+ this.paragraphData = paragraphData;
+ }
+
+ public IParagraphData getParagraphData() {
+ return paragraphData;
+ }
+
+ public PageParam getPageParam() {
+ return pageParam;
+ }
+
+ public void setPageParam(PageParam pageParam) {
+ this.pageParam = pageParam;
+ }
+
+ public Context getContext() {
+ return context;
+ }
+
+ public List getChapters() {
+ return chapters;
+ }
+
+ public void setContext(Context context) {
+ this.context = context;
+ }
+
+ public void setChapters(List chapters) {
+ this.chapters = chapters;
+ }
+
+ public IChapterMatcher getChapterMatcher() {
+ return chapterMatcher;
+ }
+
+ public void setChapterMatcher(IChapterMatcher chapterMatcher) {
+ this.chapterMatcher = chapterMatcher;
+ }
+
+ public PageData getPageData() {
+ return pageData;
+ }
+
+ public IPageDataPipeline getPageDataPipeline() {
+ if (pageDataPipeline == null) {
+ if (getTxtConfig().VerticalPageMode) {
+ pageDataPipeline = new VerticalPageDataPipeline(this);
+ }else{
+ pageDataPipeline = new PageDataPipeline(this);
+ }
+ }
+ return pageDataPipeline;
+ }
+
+ public PaintContext getPaintContext() {
+ if (paintContext == null) {
+ paintContext = new PaintContext();
+ }
+ return paintContext;
+ }
+
+ public void setPaintContext(PaintContext paintContext) {
+ this.paintContext = paintContext;
+ }
+
+ public void setPageDataPipeline(IPageDataPipeline pageDataPipeline) {
+ this.pageDataPipeline = pageDataPipeline;
+ }
+
+ public TxtConfig getTxtConfig() {
+ if (txtConfig == null) {
+ txtConfig = new TxtConfig();
+ }
+ return txtConfig;
+ }
+
+ public void setTxtConfig(TxtConfig txtConfig) {
+ this.txtConfig = txtConfig;
+ }
+
+ public BitmapData getBitmapData() {
+ return bitmapData;
+ }
+
+ public Boolean InitDone() {
+ return InitDone;
+ }
+
+ public void setInitDone(Boolean initDone) {
+ InitDone = initDone;
+ }
+
+ public void Clear() {
+ if (paragraphData != null) {
+ paragraphData.Clear();
+ paragraphData = null;
+ }
+ if (paintContext != null) {
+ paintContext.onDestroy();
+ paintContext = null;
+ }
+ if (chapters != null) {
+ chapters.clear();
+ chapters = null;
+ }
+ bitmapData.onDestroy();
+ pageData.onDestroy();
+ chapterMatcher = null;
+
+ }
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderView.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderView.java
new file mode 100644
index 0000000..0d8fbfa
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderView.java
@@ -0,0 +1,616 @@
+package com.bifan.txtreaderlib.main;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.bifan.txtreaderlib.bean.FileReadRecordBean;
+import com.bifan.txtreaderlib.bean.TxtChar;
+import com.bifan.txtreaderlib.bean.TxtFileMsg;
+import com.bifan.txtreaderlib.interfaces.IChapter;
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.IPage;
+import com.bifan.txtreaderlib.interfaces.IReaderViewDrawer;
+import com.bifan.txtreaderlib.interfaces.ITextSelectListener;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.tasks.DrawPrepareTask;
+import com.bifan.txtreaderlib.tasks.TxtConfigInitTask;
+import com.bifan.txtreaderlib.tasks.TxtPageLoadTask;
+import com.bifan.txtreaderlib.utils.ELogger;
+import com.bifan.txtreaderlib.utils.FileUtil;
+import com.bifan.txtreaderlib.utils.TxtBitmapUtil;
+
+import java.io.File;
+import java.util.List;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Created by bifa-wei
+ * on 2017/11/28.
+ */
+
+public class TxtReaderView extends TxtReaderBaseView {
+ private String tag = "TxtReaderView";
+
+ public TxtReaderView(Context context) {
+ super(context);
+ }
+
+ public TxtReaderView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private IReaderViewDrawer drawer = null;
+
+ @Override
+ protected void init() {
+ super.init();
+ }
+
+ @Override
+ protected void drawLineText(Canvas canvas) {
+ if (isPagePre() || isPageNext()) {
+ if (isPagePre()) {
+ //当前是第一页,只显示第一页
+ if (isFirstPage()) {
+ if (getTopPage() != null) {
+ canvas.drawBitmap(getTopPage(), 0, 0, null);
+ }
+ } else {
+ //绘制上一页
+ if (getTopPage() != null) {
+ drawPagePreTopPage(canvas);
+ }
+ //绘制下一页
+ if (getBottomPage() != null) {
+ drawPagePreBottomPage(canvas);
+ }
+ //绘制阴影线
+ drawPagePrePageShadow(canvas);
+ }
+ } else {
+ if (getTopPage() != null) {
+ drawPageNextTopPage(canvas);
+ }
+ if (getBottomPage() != null) {
+ drawPageNextBottomPage(canvas);
+ }
+ drawPageNextPageShadow(canvas);
+
+ }
+ } else {
+ //说明没有触碰移动屏幕
+ if (getTopPage() != null) {
+ canvas.drawBitmap(getTopPage(), 0, 0, null);
+ }
+ }
+
+ }
+
+ private void drawPageNextPageShadow(Canvas canvas) {
+ getDrawer().drawPageNextPageShadow(canvas);
+
+ }
+
+ private void drawPageNextBottomPage(Canvas canvas) {
+ //绘制执行下一页时的下面页部分
+ getDrawer().drawPageNextBottomPage(canvas);
+
+
+ }
+
+ private void drawPageNextTopPage(Canvas canvas) {
+ //绘制执行下一页时的上面页部分
+ getDrawer().drawPageNextTopPage(canvas);
+
+ }
+
+ private void drawPagePrePageShadow(Canvas canvas) {
+ //绘制执行上一页的页边阴影
+ getDrawer().drawPagePrePageShadow(canvas);
+
+ }
+
+ private void drawPagePreBottomPage(Canvas canvas) {
+ //绘制执行上一页时的下面页部分
+ getDrawer().drawPagePreBottomPage(canvas);
+
+ }
+
+ private void drawPagePreTopPage(Canvas canvas) {
+ //绘制执行上一页时的上面页部分
+ getDrawer().drawPagePreTopPage(canvas);
+ }
+
+ @Override
+ protected void startPageStateBackAnimation() {
+ getDrawer().startPageStateBackAnimation();
+ }
+
+ @Override
+ protected void startPageNextAnimation() {
+ getDrawer().startPageNextAnimation();
+ }
+
+ @Override
+ protected void startPagePreAnimation() {
+ getDrawer().startPagePreAnimation();
+ }
+
+
+ @Override
+ protected void onPageMove(MotionEvent event) {
+
+ mTouch.x = event.getX();
+ mTouch.y = event.getY();
+
+
+ checkMoveState();
+
+ if (getMoveDistance() > 0 && isFirstPage()) {
+ ELogger.log(tag, "是第一页了");
+ return;
+ }
+
+ if (getMoveDistance() < 0 && isLastPage()) {
+ ELogger.log(tag, "是最后一页了");
+ return;
+ }
+ invalidate();
+ }
+
+ protected void onActionUp(MotionEvent event) {
+ super.onActionUp(event);
+ if (CurrentMode == Mode.SelectMoveBack) {
+ if (textSelectListener != null) {
+ textSelectListener.onTextSelected(getCurrentSelectedText());
+ }
+ } else if (CurrentMode == Mode.SelectMoveForward) {
+ if (textSelectListener != null) {
+ textSelectListener.onTextSelected(getCurrentSelectedText());
+ }
+ }
+
+ }
+
+ @Override
+ protected void drawNote(Canvas canvas) {
+ getDrawer().drawNote(canvas);
+ }
+
+ @Override
+ protected void drawSelectedText(Canvas canvas) {
+ getDrawer().drawSelectedText(canvas);
+ }
+
+ @Override
+ protected void onTextSelectMoveForward(MotionEvent event) {
+ getDrawer().onTextSelectMoveForward(event);
+ if (textSelectListener != null) {
+ textSelectListener.onTextChanging(FirstSelectedChar, LastSelectedChar);
+ textSelectListener.onTextChanging(getCurrentSelectedText());
+ }
+ }
+
+ @Override
+ protected void onTextSelectMoveBack(MotionEvent event) {
+ getDrawer().onTextSelectMoveBack(event);
+ if (textSelectListener != null) {
+ textSelectListener.onTextChanging(FirstSelectedChar, LastSelectedChar);
+ textSelectListener.onTextChanging(getCurrentSelectedText());
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ super.computeScroll();
+ getDrawer().computeScroll();
+ }
+
+
+ private IReaderViewDrawer getDrawer() {
+ if (drawer == null) {
+ int pageSwitchMode = readerContext.getTxtConfig().Page_Switch_Mode;
+ switch (pageSwitchMode) {
+ case TxtConfig.PAGE_SWITCH_MODE_SERIAL:
+ drawer = new SerialPageDrawer(this, readerContext, mScroller);
+ break;
+ case TxtConfig.PAGE_SWITCH_MODE_SHEAR:
+ drawer = new ShearPageDrawer(this, readerContext, mScroller);
+ break;
+ default:
+ drawer = new NormalPageDrawer(this, readerContext, mScroller);
+ }
+
+ }
+ return drawer;
+ }
+
+ private ITextSelectListener textSelectListener;
+
+ public void setOnTextSelectListener(ITextSelectListener textSelectListener) {
+ this.textSelectListener = textSelectListener;
+ }
+
+ public TxtReaderContext getTxtReaderContext() {
+ return readerContext;
+ }
+
+ private void Reload() {
+ saveProgress();
+ readerContext.getPageData().refreshTag[0] = 1;
+ readerContext.getPageData().refreshTag[1] = 1;
+ readerContext.getPageData().refreshTag[2] = 1;
+ TxtConfigInitTask configInitTask = new TxtConfigInitTask();
+ configInitTask.Run(actionLoadListener, readerContext);
+ }
+
+ /**
+ * 设置字体大小
+ *
+ * @param textSize min 25 max 70 in px
+ */
+ public void setTextSize(int textSize) {
+ readerContext.getTxtConfig().saveTextSize(getContext(), textSize);
+ if (getWidth() > 0) {
+ Reload();
+ }
+ }
+
+
+ /**
+ * 获取字体大小 in px
+ *
+ * @return
+ */
+ public int getTextSize() {
+ return readerContext.getTxtConfig().getTextSize(getContext());
+ }
+
+ /**
+ * 获取背景颜色
+ *
+ * @return
+ */
+ public int getBackgroundColor() {
+ return readerContext.getTxtConfig().getBackgroundColor(getContext());
+ }
+
+ /**
+ * 设置样式
+ *
+ * @param backgroundColor
+ * @param textColor
+ */
+ public void setStyle(int backgroundColor, int textColor) {
+ saveProgress();
+ TxtConfig.saveTextColor(getContext(), textColor);
+ TxtConfig.saveBackgroundColor(getContext(), backgroundColor);
+ if (getWidth() > 0) {
+ readerContext.getTxtConfig().textColor = textColor;
+ readerContext.getTxtConfig().backgroundColor = backgroundColor;
+ if (readerContext.getBitmapData().getBgBitmap() != null) {
+ readerContext.getBitmapData().getBgBitmap().recycle();
+ }
+ int width = readerContext.getPageParam().PageWidth;
+ int height = readerContext.getPageParam().PageHeight;
+ readerContext.getBitmapData().setBgBitmap(TxtBitmapUtil.createBitmap(backgroundColor, width, height));
+ refreshCurrentView();
+ }
+ }
+
+
+ /**
+ * 从指定进度加载
+ *
+ * @param progress 0~100
+ */
+ public void loadFromProgress(float progress) {
+ if (readerContext == null || readerContext.getParagraphData() == null) return;
+ if (progress < 0) progress = 0;
+ if (progress > 100) progress = 100;
+ float p = progress / 100;
+ int charNum = readerContext.getParagraphData().getCharNum();
+ int charIndex = (int) (p * charNum);
+ int paragraphNum = readerContext.getParagraphData().getParagraphNum();
+ int paragraphIndex = readerContext.getParagraphData().findParagraphIndexByCharIndex(charIndex);
+
+ if (progress == 100 || paragraphIndex >= paragraphNum) {
+ paragraphIndex = paragraphNum - 1;
+ }
+
+ if (paragraphIndex < 0) {
+ paragraphIndex = 0;
+ }
+
+ ELogger.log(tag, "loadFromProgress ,progress:" + progress + "/paragraphIndex:" + paragraphIndex + "/paragraphNum:" + paragraphNum);
+ loadFromProgress(paragraphIndex, 0);
+ }
+
+ /**
+ * 根据字符的准确位置跳转进度
+ *
+ * @param charIndex
+ * @param paragraphIndex
+ */
+ public void loadFromProgress(final int paragraphIndex, final int charIndex) {
+ refreshTag(1, 1, 1);
+ TxtPageLoadTask txtPageLoadTask = new TxtPageLoadTask(paragraphIndex, charIndex);
+ txtPageLoadTask.Run(new LoadListenerAdapter() {
+ @Override
+ public void onSuccess() {
+ if (readerContext != null) {
+ checkMoveState();
+ postInvalidate();
+ post(new Runnable() {
+ @Override
+ public void run() {
+ onProgressCallBack(getProgress(paragraphIndex, charIndex));
+ tryFetchFirstPage();
+ }
+ });
+ }
+ }
+ }, readerContext);
+ }
+
+
+ private void tryFetchFirstPage() {
+ IPage midPage = readerContext.getPageData().MidPage();
+ IPage firstPage = null;
+ onPageProgress(midPage);
+ if (midPage != null && midPage.HasData()) {
+ if (midPage.getFirstChar().ParagraphIndex == 0 && midPage.getFirstChar().CharIndex == 0) {
+ } else {
+ firstPage = readerContext.getPageDataPipeline().getPageEndToProgress(midPage.getFirstChar().ParagraphIndex, midPage.getFirstChar().CharIndex);
+ }
+ }
+ if (firstPage != null && firstPage.HasData()) {
+ if (!firstPage.isFullPage()) {
+ //说明是开始数据,重新刷新界面
+ refreshTag(1, 1, 1);
+ loadFromProgress(0, 0);
+ } else {
+ refreshTag(1, 0, 0);
+ readerContext.getPageData().setFirstPage(firstPage);
+ ITxtTask task = new DrawPrepareTask();
+ task.Run(actionLoadListener, readerContext);//获取上一页数据
+ }
+ }
+ }
+
+ /**
+ * 获取当前页章节
+ *
+ * @return 没有章节返回null, 当前页没有数据返回null
+ */
+ public IChapter getCurrentChapter() {
+ List chapters = readerContext.getChapters();
+ IPage midPage = readerContext.getPageData().MidPage();
+ if (chapters == null || chapters.size() == 0 || midPage == null || !midPage.HasData()) {
+ return null;
+ }
+ IChapter lastChapter = readerContext.getChapters().get(readerContext.getChapters().size() - 1);
+ int currentP = midPage.getFirstChar().ParagraphIndex;
+ int lastP = lastChapter.getStartParagraphIndex();
+ int pre = 0;
+ int next = 0;
+ int index = 1;
+
+ for (int i = 0; i < chapters.size(); i++) {
+ int startP = chapters.get(i).getStartParagraphIndex();
+ if (i == 0) {
+ pre = startP;
+ } else {
+ next = startP;
+ if (currentP >= pre && currentP < next) {
+ index = i;
+ break;
+ } else {
+ pre = next;
+ }
+ }
+ }
+ if (currentP >= lastP) {
+ return lastChapter;
+ } else {
+ return chapters.get(index - 1);
+ }
+ }
+
+ /**
+ * 跳转到上一章
+ *
+ * @return 是否成功
+ */
+ public Boolean jumpToPreChapter() {
+ IChapter currentChapter = getCurrentChapter();
+ List chapters = getChapters();
+ if (chapters == null || currentChapter == null) {
+ ELogger.log(tag, "jumpToPreChapter false chapters == null or currentChapter == null");
+ return false;
+ }
+
+ int index = currentChapter.getIndex();
+
+ if (index == 0 || chapters.size() == 0) {
+ ELogger.log(tag, "jumpToPreChapter false index == 0 or chapters.size() == 0");
+ return false;
+ }
+ refreshTag(1, 1, 1);
+
+ IChapter preChapter = chapters.get(index - 1);
+ loadFromProgress(preChapter.getStartParagraphIndex(), 0);
+ return true;
+ }
+
+ /**
+ * 跳转到下一章
+ *
+ * @return 是否成功
+ */
+ public Boolean jumpToNextChapter() {
+ IChapter currentChapter = getCurrentChapter();
+ List chapters = getChapters();
+ if (chapters == null || currentChapter == null) {
+ ELogger.log(tag, "jumpToNextChapter false chapters == null or currentChapter == null");
+ return false;
+ }
+ int index = currentChapter.getIndex();
+ if (index >= chapters.size() - 1 || chapters.size() == 0) {
+ ELogger.log(tag, "jumpToNextChapter false < chapters.size() - 1 or chapters.size() == 0");
+ return false;
+ }
+
+ refreshTag(1, 1, 1);
+
+ IChapter nextChapter = chapters.get(index + 1);
+ loadFromProgress(nextChapter.getStartParagraphIndex(), 0);
+ return true;
+ }
+
+ /**
+ * 保存当前进度
+ */
+ public void saveProgress() {
+ IPage currentPage = readerContext.getPageData().MidPage();
+ if (currentPage != null && currentPage.HasData() && readerContext.getFileMsg() != null) {
+ TxtChar firstChar = currentPage.getFirstChar();
+ readerContext.getFileMsg().PreParagraphIndex = firstChar.ParagraphIndex;
+ readerContext.getFileMsg().PreCharIndex = firstChar.CharIndex;
+ readerContext.getFileMsg().CurrentParagraphIndex = firstChar.ParagraphIndex;
+ readerContext.getFileMsg().CurrentCharIndex = firstChar.CharIndex;
+ }
+ }
+
+
+ /**
+ * @param isBold 字体否加粗
+ */
+ public void setTextBold(boolean isBold) {
+ TxtConfig.saveIsBold(getContext(), isBold);
+ getTxtReaderContext().getTxtConfig().Bold = isBold;
+ refreshCurrentView();
+ }
+
+ /**
+ * 平移切换页面
+ */
+ public void setPageSwitchByTranslate() {
+ TxtConfig.savePageSwitchMode(getContext(), TxtConfig.PAGE_SWITCH_MODE_SERIAL);
+ getTxtReaderContext().getTxtConfig().Page_Switch_Mode = TxtConfig.PAGE_SWITCH_MODE_SERIAL;
+ drawer = new SerialPageDrawer(this, readerContext, mScroller);
+ }
+
+ /**
+ * 剪切切换页面
+ */
+ public void setPageSwitchByShear() {
+ TxtConfig.savePageSwitchMode(getContext(), TxtConfig.PAGE_SWITCH_MODE_SHEAR);
+ getTxtReaderContext().getTxtConfig().Page_Switch_Mode = TxtConfig.PAGE_SWITCH_MODE_SHEAR;
+ drawer = new ShearPageDrawer(this, readerContext, mScroller);
+ }
+
+ /**
+ * 滑盖切换页面
+ */
+ public void setPageSwitchByCover() {
+ TxtConfig.savePageSwitchMode(getContext(), TxtConfig.PAGE_SWITCH_MODE_COVER);
+ getTxtReaderContext().getTxtConfig().Page_Switch_Mode = TxtConfig.PAGE_SWITCH_MODE_COVER;
+ drawer = new NormalPageDrawer(this, readerContext, mScroller);
+ }
+
+ /**
+ * 保存当前进度到数据库,建议退出时调用
+ */
+ public void saveCurrentProgress() {
+ TxtFileMsg fileMsg = getTxtReaderContext().getFileMsg();
+ if (getTxtReaderContext().InitDone() && fileMsg != null) {
+ String path = fileMsg.FilePath;
+ if (path != null && new File(path).exists()) {
+ //说明当前是有数据的
+ IPage midPage = getTxtReaderContext().getPageData().MidPage();
+ if (midPage != null && midPage.HasData()) {
+ FileReadRecordDB readRecordDB = new FileReadRecordDB(readerContext.getContext());
+ readRecordDB.createTable();
+ FileReadRecordBean r = new FileReadRecordBean();
+ r.fileName = fileMsg.FileName;
+ r.filePath = fileMsg.FilePath;
+ try {
+ r.fileHashName = FileUtil.getMD5Checksum(path);
+ } catch (Exception e) {
+ ELogger.log(tag, "saveCurrentProgress Exception:" + e.toString());
+ readRecordDB.closeTable();
+ return;
+ }
+
+ r.paragraphIndex = midPage.getFirstChar().ParagraphIndex;
+ r.chartIndex = midPage.getFirstChar().CharIndex;
+ readRecordDB.insertData(r);
+ readRecordDB.closeTable();
+ } else {
+ ELogger.log(tag, "saveCurrentProgress midPage is false empty");
+ }
+ }
+
+ }
+ }
+
+ private void refreshCurrentView() {
+ if (getWidth() > 0) {
+ refreshTag(1, 1, 1);
+ ITxtTask task = new DrawPrepareTask();
+ task.Run(actionLoadListener, readerContext);
+ }
+ }
+
+ /**
+ * @return 获取章节数据
+ */
+ public List getChapters() {
+ return readerContext.getChapters();
+ }
+
+
+ /**
+ * @param progress 获取指定进度的章节信息
+ * @return
+ */
+ public IChapter getChapterFromProgress(int progress) {
+ List chapters = getChapters();
+ if (chapters != null && chapters.size() > 0) {
+ int paragraphNum = getTxtReaderContext().getParagraphData().getParagraphNum();
+ int terminalParagraphIndex = progress * paragraphNum / 100;
+
+ if (terminalParagraphIndex == 0) {
+ return chapters.get(0);
+ }
+ for (IChapter chapter : chapters) {
+ int startIndex = chapter.getStartParagraphIndex();
+ int endIndex = chapter.getEndParagraphIndex();
+ ELogger.log("getChapterFromProgress", startIndex + "," + endIndex);
+ if (terminalParagraphIndex >= startIndex && terminalParagraphIndex < endIndex) {
+ return chapter;
+ }
+ }
+ }
+ return null;
+ }
+
+ private ILoadListener actionLoadListener = new LoadListenerAdapter() {
+ @Override
+ public void onSuccess() {
+ checkMoveState();
+ postInvalidate();
+ post(new Runnable() {
+ @Override
+ public void run() {
+ onPageProgress(readerContext.getPageData().MidPage());
+ }
+ });
+
+ }
+ };
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/VerticalPageDataPipeline.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/VerticalPageDataPipeline.java
new file mode 100644
index 0000000..457422f
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/VerticalPageDataPipeline.java
@@ -0,0 +1,298 @@
+package com.bifan.txtreaderlib.main;
+
+import android.graphics.Paint;
+
+import com.bifan.txtreaderlib.bean.EnChar;
+import com.bifan.txtreaderlib.bean.NumChar;
+import com.bifan.txtreaderlib.bean.Page;
+import com.bifan.txtreaderlib.bean.TxtChar;
+import com.bifan.txtreaderlib.bean.TxtLine;
+import com.bifan.txtreaderlib.interfaces.IPage;
+import com.bifan.txtreaderlib.interfaces.IPageDataPipeline;
+import com.bifan.txtreaderlib.interfaces.IParagraphData;
+import com.bifan.txtreaderlib.interfaces.ITxtLine;
+import com.bifan.txtreaderlib.utils.FormatUtil;
+import com.bifan.txtreaderlib.utils.TextBreakUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * created by : bifan-wei
+ */
+public class VerticalPageDataPipeline implements IPageDataPipeline{
+ private TxtReaderContext readerContext;
+
+ public VerticalPageDataPipeline(TxtReaderContext readerContext) {
+ this.readerContext = readerContext;
+ }
+ @Override
+ public IPage getPageStartFromProgress(int paragraphIndex, int charIndex) {
+ IParagraphData data = readerContext.getParagraphData();
+ PageParam param = readerContext.getPageParam();
+
+ if (data == null) return null;
+
+ int PageLineNum = param.PageLineNum;
+ int PageWidth = param.PageWidth;
+ int LineWidth = (int) param.LineWidth;
+ float lineHeight = readerContext.getPageParam().LineHeight;
+ float textPadding = readerContext.getPageParam().TextPadding;
+
+ int CurrentPaIndex = paragraphIndex;
+ int startIndex = charIndex;
+ int ParagraphNum = data.getParagraphNum();
+
+ int paragraphMargin = param.ParagraphMargin;
+
+ if (CurrentPaIndex >= ParagraphNum || CurrentPaIndex < 0) return null;//超过返回null
+
+ IPage page = new Page();
+
+ //获取页面数据
+ while (page.getLineNum() < PageLineNum && CurrentPaIndex < ParagraphNum) {
+ String paragraphStr = data.getParagraphStr(CurrentPaIndex);
+ if (paragraphStr != null && paragraphStr.length() > 0) {
+ List lines = getLineFromParagraphStartOnBeginning(paragraphStr, CurrentPaIndex, startIndex, lineHeight, textPadding, readerContext.getPaintContext().textPaint);
+ for (ITxtLine line : lines) {
+ page.addLine(line);
+ if (page.getLineNum() >= PageLineNum) {
+ page.setFullPage(true);
+ break;
+ }
+ }
+
+ startIndex = 0;
+ }
+ CurrentPaIndex++;
+ }
+
+ //尝试识别是否需要加段落间距数据
+ int textWidth = readerContext.getTxtConfig().textSize;
+ if (paragraphMargin > 0 && LineWidth > 0 && PageWidth > LineWidth && page.getLineNum() > 0) {
+ int width = param.PaddingLeft;
+ List lines = new ArrayList<>();
+ for (int i = 0; i < page.getLineNum(); i++) {
+ ITxtLine line = page.getLine(i);
+ width = width + LineWidth;
+ if (width > PageWidth) {
+ page.setFullPage(true);
+ if (width - LineWidth + textWidth <= PageWidth) {
+ lines.add(line);
+ }
+ break;
+ } else {
+ lines.add(line);
+ }
+ if (line.isParagraphEndLine()) {
+ width = width + paragraphMargin;//说明还有行数据
+ }
+ }
+ if (width >= PageWidth) {
+ page.setFullPage(true);
+ }
+ page.setLines(lines);
+ }
+
+ return page;
+ }
+
+ @Override
+ public IPage getPageEndToProgress(int paragraphIndex, int charIndex) {
+
+ IParagraphData data = readerContext.getParagraphData();
+ PageParam param = readerContext.getPageParam();
+
+ if(data==null) return null;
+
+ int PageLineNum = param.PageLineNum;
+ int PageWidth = param.PageWidth;
+ int LineHeight = (int) param.LineHeight;
+ int LineWidth = (int) param.LineWidth;
+ int CurrentPaIndex = paragraphIndex;
+ int startIndex = charIndex;
+ int ParagraphNum = data.getParagraphNum();
+ float textPadding = readerContext.getPageParam().TextPadding;
+
+ if (charIndex == 0) {//说明上页开始是段落开始位置,段落左移
+ CurrentPaIndex--;//
+ startIndex = 0;
+ }
+ if (CurrentPaIndex >= ParagraphNum || CurrentPaIndex < 0) return null;//超过返回null
+
+ IPage page = new Page();
+ int paragraphMargin = param.ParagraphMargin;
+ while (page.getLineNum() < PageLineNum && CurrentPaIndex >= 0) {
+ String paragraphStr = data.getParagraphStr(CurrentPaIndex);
+
+ if (paragraphStr != null && paragraphStr.length() > 0) {
+ if (startIndex == 0) {//说明上页开始是段落开始位置
+ startIndex = paragraphStr.length();
+ }
+ List lines = getLineFromParagraphOnEnd(paragraphStr, CurrentPaIndex, startIndex, LineHeight, textPadding, readerContext.getPaintContext().textPaint);
+ if (lines.size() > 0) {
+ for (int i = lines.size() - 1; i >= 0; i--) {
+ ITxtLine line = lines.get(i);
+ page.addLine(line);
+ if (page.getLineNum() >= PageLineNum) {
+ page.setFullPage(true);
+ break;
+ }
+ }
+ }
+ }
+ CurrentPaIndex--;
+ startIndex = 0;
+ }
+
+
+ if (page.HasData()) {
+ Collections.reverse(page.getLines());
+ }
+
+ if (paragraphMargin > 0 && LineHeight > 0 && PageWidth > LineHeight && page.getLineNum() > 0) {
+ int width = param.PaddingLeft;
+ int textWidth = readerContext.getTxtConfig().textSize;
+ List lines = new ArrayList<>();
+ for (int i = page.getLineNum() - 1; i >= 0; i--) {
+ ITxtLine line = page.getLine(i);
+ if (i == page.getLineNum() - 1) {//底部那个不添加偏移量
+ lines.add(line);
+ width = width + textWidth;
+ } else {
+ if (width + LineWidth > PageWidth) {
+ page.setFullPage(true);
+ if (width + textWidth <= PageWidth) {
+ lines.add(line);
+ }
+ break;
+ } else {
+ lines.add(line);
+ width = width + LineWidth;
+ if (line.isParagraphEndLine()) {
+ width = width + paragraphMargin;//说明还有行数据
+ }
+ }
+ }
+ }
+ if (width >= PageWidth) {
+ page.setFullPage(true);
+ }
+ page.setLines(lines);
+ if (page.HasData()) {
+ Collections.reverse(page.getLines());
+ }
+ }
+ return page;
+ }
+
+ private List getLineFromParagraphOnEnd(String paragraphData, int paragraphIndex, int endCharIndex,
+ float lineHeight, float textPadding, Paint paint) {
+ List lines = new ArrayList<>();
+ int startIndex = 0;
+ int paragraphLength = paragraphData.length();
+ if (paragraphLength == 0 || endCharIndex <= 0) {
+ return lines;
+ }
+ endCharIndex = endCharIndex >= paragraphData.length() ? paragraphData.length() : endCharIndex;
+
+ if (endCharIndex > 0) {
+ String s = paragraphData.substring(startIndex, endCharIndex);//截取要的数据
+ while (s.length() > 0) {// is[0] 为个数 is[1] 为是否满一行
+ float[] is = TextBreakUtil.BreakTextVertical(s, lineHeight, textPadding, paint);
+ ITxtLine line;
+ int num = (int) is[0];
+ if (is[1] != 1) {//不满一行
+ line = getLineFromString(s, paragraphIndex, startIndex, is);
+ if (endCharIndex == paragraphLength && s.length() + startIndex >= paragraphLength) {
+ line.setParagraphEndLine(true);
+ }
+ lines.add(line);
+ return lines;
+ }
+
+ String lineStr = s.substring(0, num);
+ line = getLineFromString(lineStr, paragraphIndex, startIndex, is);
+ startIndex = startIndex + num;
+ if (line != null) {
+ lines.add(line);
+ }
+ s = s.substring(lineStr.length());
+ }
+ }
+ return lines;
+ }
+
+
+ /**
+ * @param paragraphData
+ * @param paragraphIndex
+ * @param startCharIndex
+ * @param lineHeight
+ * @param textPadding
+ * @param paint
+ * @return
+ */
+ private List getLineFromParagraphStartOnBeginning(String paragraphData, int paragraphIndex, int startCharIndex,
+ float lineHeight, float textPadding, Paint paint) {
+ List lines = new ArrayList<>();
+ int startIndex = startCharIndex;
+ startIndex = startIndex < 0 ? 0 : startIndex;
+ int paragraphLength = paragraphData.length();
+
+ if ( startIndex >= paragraphLength) {
+ return lines;
+ }
+ if ( paragraphData.length() > 0) {
+ String s = paragraphData.substring(startIndex);//截取要的数据
+ while (s.length() > 0) {// is[0] 为个数 is[1] 为是否满一行
+ float[] is = TextBreakUtil.BreakTextVertical(s, lineHeight, textPadding, paint);
+ ITxtLine line;
+ if (is[1] != 1) {//不满一行
+ line = getLineFromString(s, paragraphIndex, startIndex, is);
+ if (s.length() + startIndex >= paragraphLength) {
+ line.setParagraphEndLine(true);
+ }
+ lines.add(line);
+ break;
+ }
+ int num = (int) is[0];
+ String lineStr = s.substring(0, num);
+ line = getLineFromString(lineStr, paragraphIndex, startIndex, is);
+ startIndex = startIndex + num;
+ if (line != null) {
+ lines.add(line);
+ }
+ s = s.substring(num);
+ }
+ }
+ return lines;
+ }
+ private ITxtLine getLineFromString(String str, int ParagraphIndex, int charIndex, float[] is) {
+ ITxtLine line = null;
+ int index = charIndex;
+ int cIndex = 2;//找到字符宽度
+ if (str != null && str.length() > 0) {
+ line = new TxtLine();
+
+ char[] cs = str.toCharArray();
+ for (char c : cs) {
+ TxtChar Char;
+ if (FormatUtil.isDigital(c)) {
+ Char = new NumChar(c);
+ } else if (FormatUtil.isLetter(c)) {
+ Char = new EnChar(c);
+ } else {
+ Char = new TxtChar(c);
+ }
+ Char.ParagraphIndex = ParagraphIndex;
+ Char.CharIndex = index++;
+ Char.CharWidth = is[cIndex++];
+ line.addChar(Char);
+ }
+
+ }
+ return line;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/BitmapProduceTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/BitmapProduceTask.java
new file mode 100644
index 0000000..7cfa0dd
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/BitmapProduceTask.java
@@ -0,0 +1,62 @@
+package com.bifan.txtreaderlib.tasks;
+
+import android.graphics.Bitmap;
+
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.IPage;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.main.TxtReaderContext;
+import com.bifan.txtreaderlib.utils.ELogger;
+import com.bifan.txtreaderlib.utils.TxtBitmapUtil;
+
+/**
+ * Created by bifan-wei
+ * on 2017/11/27.
+ */
+
+public class BitmapProduceTask implements ITxtTask {
+ private String tag = "BitmapProduceTask";
+
+ @Override
+ public void Run(ILoadListener callBack, TxtReaderContext readerContext) {
+ ELogger.log(tag, "produce bitmap");
+ callBack.onMessage("start to produce bitmap");
+
+ int[] rs = readerContext.getPageData().refreshTag;
+ IPage[] pages = readerContext.getPageData().getPages();
+ Bitmap[] bitmaps = readerContext.getBitmapData().getPages();
+
+ int index = 0;
+ for (int neeRefresh : rs) {
+ IPage page = pages[index];
+ if (neeRefresh == 1) {
+ ELogger.log(tag, "page " + index + " neeRefresh");
+ Bitmap bitmap ;
+ if(readerContext.getTxtConfig().VerticalPageMode){
+ bitmap = TxtBitmapUtil.createVerticalPage(
+ readerContext.getBitmapData().getBgBitmap(),
+ readerContext.getPaintContext(),
+ readerContext.getPageParam(),
+ readerContext.getTxtConfig(), page);
+ }else {
+ bitmap = TxtBitmapUtil.createHorizontalPage(
+ readerContext.getBitmapData().getBgBitmap(),
+ readerContext.getPaintContext(),
+ readerContext.getPageParam(),
+ readerContext.getTxtConfig(), page);
+ }
+
+ bitmaps[index] = bitmap;
+ } else {
+ ELogger.log(tag, "page " + index + " no neeRefresh");
+ //no neeRefresh ,do not change
+ }
+ index++;
+ }
+ ELogger.log(tag, "already done ,call back success");
+ callBack.onMessage("already done ,call back success");
+ readerContext.setInitDone(true);
+ //already done ,call back success
+ callBack.onSuccess();
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DestroyableTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DestroyableTask.java
new file mode 100644
index 0000000..5da42bc
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DestroyableTask.java
@@ -0,0 +1,32 @@
+package com.bifan.txtreaderlib.tasks;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * author: bifan-wei
+ * 2021/11/13.
+ */
+public class DestroyableTask {
+ private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+ private final IShutdownCall mShutdownCall;
+
+ public DestroyableTask(IShutdownCall shutdownCall) {
+ this.mShutdownCall = shutdownCall;
+ }
+
+ public void excuse(Runnable runnable) {
+ mExecutorService.submit(runnable);
+ }
+
+ public void destroy() {
+ if (mShutdownCall != null) {
+ mShutdownCall.clearIfNeedBeforeShutdown();
+ }
+ mExecutorService.shutdownNow();
+ }
+
+ public interface IShutdownCall {
+ void clearIfNeedBeforeShutdown();
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DrawPrepareTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DrawPrepareTask.java
new file mode 100644
index 0000000..391784d
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DrawPrepareTask.java
@@ -0,0 +1,34 @@
+package com.bifan.txtreaderlib.tasks;
+
+import android.content.Context;
+import android.graphics.Color;
+
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.main.PaintContext;
+import com.bifan.txtreaderlib.main.TxtConfig;
+import com.bifan.txtreaderlib.main.TxtReaderContext;
+import com.bifan.txtreaderlib.utils.ELogger;
+
+/**
+ * Created by bifan-wei
+ * on 2017/11/27.
+ */
+
+public class DrawPrepareTask implements ITxtTask {
+ private String tag = "DrawPrepareTask";
+
+ @Override
+ public void Run(ILoadListener callBack, TxtReaderContext readerContext) {
+ callBack.onMessage("start do DrawPrepare");
+ ELogger.log(tag, "do DrawPrepare");
+ initPainContext(readerContext.context,readerContext.getPaintContext(), readerContext.getTxtConfig());
+ readerContext.getPaintContext().textPaint.setColor(Color.WHITE);
+ ITxtTask txtTask = new BitmapProduceTask();
+ txtTask.Run(callBack, readerContext);
+ }
+
+ private void initPainContext(Context context,PaintContext paintContext, TxtConfig txtConfig) {
+ TxtConfigInitTask.initPainContext(context,paintContext, txtConfig);
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/FileDataLoadTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/FileDataLoadTask.java
new file mode 100644
index 0000000..ae81131
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/FileDataLoadTask.java
@@ -0,0 +1,156 @@
+package com.bifan.txtreaderlib.tasks;
+
+import com.bifan.txtreaderlib.bean.Chapter;
+import com.bifan.txtreaderlib.bean.TxtMsg;
+import com.bifan.txtreaderlib.interfaces.IChapter;
+import com.bifan.txtreaderlib.interfaces.IChapterMatcher;
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.IParagraphData;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.main.ParagraphData;
+import com.bifan.txtreaderlib.main.TxtReaderContext;
+import com.bifan.txtreaderlib.utils.ELogger;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author bifan-wei
+ * @description
+ * @time 2021/11/13 16:52
+ */
+
+public class FileDataLoadTask implements ITxtTask {
+ private static final String ChapterPatternStr = "(^.{0,3}\\s*第)(.{1,9})[章节卷集部篇回](\\s*)";
+ private String tag = "FileDataLoadTask";
+ private IChapterMatcher chapterMatcher;
+ private boolean stop = false;
+
+ public void onStop() {
+ stop = true;
+ }
+
+ @Override
+ public void Run(ILoadListener callBack, TxtReaderContext readerContext) {
+ stop = false;
+ IParagraphData paragraphData = new ParagraphData();
+ chapterMatcher = readerContext.getChapterMatcher();
+ List chapter = new ArrayList<>();
+ callBack.onMessage("start read file data");
+ Boolean readSuccess = ReadData(readerContext.getFileMsg().FilePath, readerContext.getFileMsg().FileCode, paragraphData, chapter);
+ if (readSuccess) {
+ ELogger.log(tag, "ReadData readSuccess");
+ callBack.onMessage(" read file data success");
+ readerContext.setParagraphData(paragraphData);
+ readerContext.setChapters(chapter);
+ ITxtTask txtTask = new TxtConfigInitTask();
+ txtTask.Run(callBack, readerContext);
+
+ } else {
+ callBack.onFail(TxtMsg.InitError);
+ callBack.onMessage("ReadData fail on FileDataLoadTask");
+ }
+ stop = true;
+ }
+
+ private Boolean ReadData(String filePath, String Charset, IParagraphData paragraphData, List chapters) {
+ File file = new File(filePath);
+ BufferedReader bufferedReader = null;
+ ELogger.log(tag, "start to ReadData");
+ ELogger.log(tag, "--file Charset:" + Charset);
+ try {
+ bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset));
+ try {
+ String data;
+ int index = 0;
+ int chapterIndex = 0;
+ while (!stop && (data = bufferedReader.readLine()) != null) {
+ if (data.length() > 0) {
+ IChapter chapter = compileChapter(data, paragraphData.getCharNum(), index, chapterIndex);
+ paragraphData.addParagraph(data);
+ if (chapter != null) {
+ chapterIndex++;
+ chapters.add(chapter);
+ }
+ index++;
+ }
+ }
+ initChapterEndIndex(chapters, paragraphData.getParagraphNum());
+ return !stop;
+ } catch (IOException e) {
+ ELogger.log(tag, "IOException:" + e.toString());
+ }
+ } catch (UnsupportedEncodingException e) {
+ ELogger.log(tag, "UnsupportedEncodingException:" + e.toString());
+ } catch (FileNotFoundException e) {
+ ELogger.log(tag, "FileNotFoundException:" + e.toString());
+ } finally {
+ if (bufferedReader != null) {
+ try {
+ bufferedReader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return false;
+ }
+
+
+ private void initChapterEndIndex(List chapters, int paragraphNum) {
+ if (chapters != null && chapters.size() > 0) {
+ for (int i = 0, sum = chapters.size(); i < sum; i++) {
+ int nextIndex = i + 1;
+ IChapter chapter = chapters.get(i);
+ if (nextIndex < sum) {
+ int startIndex = chapter.getStartParagraphIndex();
+ int endIndex = chapters.get(nextIndex).getEndParagraphIndex() - 1;
+ if (endIndex < startIndex) {
+ endIndex = startIndex;
+ }
+ chapter.setEndParagraphIndex(endIndex);
+ } else {
+ int endIndex = paragraphNum - 1;
+ endIndex = Math.max(endIndex, 0);
+ chapter.setEndParagraphIndex(endIndex);
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * @param data 文本数据
+ * @param chapterStartIndex 开始字符在全文中的位置
+ * @param ParagraphIndex 段落位置
+ * @param chapterIndex 章节位置
+ * @return 没有识别到章节数据返回null
+ */
+ private IChapter compileChapter(String data, int chapterStartIndex, int ParagraphIndex, int chapterIndex) {
+ if (chapterMatcher == null) {
+ if (data.trim().startsWith("第") || data.contains("第")) {
+ Pattern p = Pattern.compile(ChapterPatternStr);
+ Matcher matcher = p.matcher(data);
+ if (matcher.find()) {
+ int startIndex = 0;
+ int endIndex = data.length();
+ return new Chapter(chapterStartIndex, chapterIndex, data, ParagraphIndex, ParagraphIndex, startIndex, endIndex);
+ }
+ }
+ return null;
+ } else {
+ return chapterMatcher.match(data, ParagraphIndex);
+ }
+
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TextLoader.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TextLoader.java
new file mode 100644
index 0000000..883ce6e
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TextLoader.java
@@ -0,0 +1,33 @@
+package com.bifan.txtreaderlib.tasks;
+
+import com.bifan.txtreaderlib.interfaces.IChapter;
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.IParagraphData;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.main.ParagraphData;
+import com.bifan.txtreaderlib.main.TxtReaderContext;
+import com.bifan.txtreaderlib.utils.ELogger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by bifan-wei
+ * on 2018/1/28.
+ */
+
+public class TextLoader {
+ private final String tag = "FileDataLoadTask";
+
+ public void load(String text, TxtReaderContext readerContext, ILoadListener callBack) {
+ IParagraphData paragraphData = new ParagraphData();
+ List chapter = new ArrayList<>();
+ callBack.onMessage("start read text");
+ ELogger.log(tag, "start read text");
+ paragraphData.addParagraph(text + "");
+ readerContext.setParagraphData(paragraphData);
+ readerContext.setChapters(chapter);
+ ITxtTask txtTask = new TxtConfigInitTask();
+ txtTask.Run(callBack, readerContext);
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtConfigInitTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtConfigInitTask.java
new file mode 100644
index 0000000..468d4ba
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtConfigInitTask.java
@@ -0,0 +1,134 @@
+package com.bifan.txtreaderlib.tasks;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.main.PageParam;
+import com.bifan.txtreaderlib.main.PaintContext;
+import com.bifan.txtreaderlib.main.TxtConfig;
+import com.bifan.txtreaderlib.main.TxtReaderContext;
+import com.bifan.txtreaderlib.utils.ELogger;
+import com.bifan.txtreaderlib.utils.TxtBitmapUtil;
+
+/**
+ * Created by HP on 2017/11/26.
+ */
+
+public class TxtConfigInitTask implements ITxtTask {
+ private String tag = "TxtConfigInitTask";
+
+ @Override
+ public void Run(ILoadListener callBack, TxtReaderContext readerContext) {
+ ELogger.log(tag, "do TxtConfigInit");
+ callBack.onMessage("start init settings in TxtConfigInitTask");
+
+ TxtConfig config = readerContext.getTxtConfig();
+ initTxtConfig(readerContext, config);
+
+ //if not null ,do recycle
+ if (readerContext.getBitmapData().getBgBitmap() != null) {
+ readerContext.getBitmapData().getBgBitmap().recycle();
+ }
+
+ int width = readerContext.getPageParam().PageWidth;
+ int height = readerContext.getPageParam().PageHeight;
+
+ //init the bg bitmap
+ readerContext.getBitmapData().setBgBitmap(TxtBitmapUtil.createBitmap(config.backgroundColor, width, height));
+ //initPageParam
+ initPageParam(readerContext);
+ //start draw prepare
+
+ //get preRead Progress
+ int startParagraphIndex = 0;
+ int startCharIndex = 0;
+
+ if (readerContext.getFileMsg() != null) {
+ startParagraphIndex = readerContext.getFileMsg().PreParagraphIndex;
+ startCharIndex = readerContext.getFileMsg().PreCharIndex;
+ }
+ //init Context
+ initPainContext(readerContext.context, readerContext.getPaintContext(), readerContext.getTxtConfig());
+
+ ITxtTask txtTask = new TxtPageLoadTask(startParagraphIndex, startCharIndex);
+ txtTask.Run(callBack, readerContext);
+ }
+
+ /**
+ * get pre or default config
+ *
+ * @param readerContext
+ * @param config
+ */
+ private void initTxtConfig(TxtReaderContext readerContext, TxtConfig config) {
+ config.showNote = TxtConfig.getIsShowNote(readerContext.context);
+ config.canPressSelect = TxtConfig.getCanPressSelect(readerContext.context);
+ config.textColor = TxtConfig.getTextColor(readerContext.context);
+ config.textSize = TxtConfig.getTextSize(readerContext.context);
+ config.backgroundColor = TxtConfig.getBackgroundColor(readerContext.context);
+ config.NoteColor = TxtConfig.getNoteTextColor(readerContext.context);
+ config.SelectTextColor = TxtConfig.getSelectTextColor(readerContext.context);
+ config.SliderColor = TxtConfig.getSliderColor(readerContext.context);
+ config.Bold = TxtConfig.isBold(readerContext.context);
+ config.Page_Switch_Mode = TxtConfig.getPageSwitchMode(readerContext.context);
+ config.ShowSpecialChar = TxtConfig.IsShowSpecialChar(readerContext.context);
+ config.VerticalPageMode = TxtConfig.IsOnVerticalPageMode(readerContext.context);
+ config.PageSwitchDuration = TxtConfig.getPageSwitchDuration(readerContext.context);
+ }
+
+ /**
+ * @param paintContext
+ * @param txtConfig
+ */
+ public static void initPainContext(Context context, PaintContext paintContext, TxtConfig txtConfig) {
+ paintContext.textPaint.setTextSize(txtConfig.textSize);
+ paintContext.textPaint.setFakeBoldText(txtConfig.Bold);
+ paintContext.textPaint.setTextAlign(Paint.Align.LEFT);
+ paintContext.textPaint.setColor(txtConfig.textColor);
+ paintContext.textPaint.setAntiAlias(true);
+ paintContext.notePaint.setTextSize(txtConfig.textSize);
+ paintContext.notePaint.setColor(txtConfig.NoteColor);
+ paintContext.notePaint.setTextAlign(Paint.Align.LEFT);
+ paintContext.notePaint.setAntiAlias(true);
+ paintContext.selectTextPaint.setTextSize(txtConfig.textSize);
+ paintContext.selectTextPaint.setColor(txtConfig.SelectTextColor);
+ paintContext.selectTextPaint.setTextAlign(Paint.Align.LEFT);
+ paintContext.selectTextPaint.setAntiAlias(true);
+ paintContext.sliderPaint.setColor(txtConfig.SliderColor);
+ paintContext.sliderPaint.setAntiAlias(true);
+ paintContext.textPaint.setFakeBoldText(txtConfig.Bold);
+ paintContext.textPaint.setTypeface(null);
+ if (txtConfig.VerticalPageMode) {
+ AssetManager mgr = context.getAssets();
+ Typeface tf = Typeface.createFromAsset(mgr, "fonts/text_style.TTF");
+ paintContext.textPaint.setTypeface(tf);
+ }
+ }
+
+ private void initPageParam(TxtReaderContext readerContext) {
+ PageParam param = readerContext.getPageParam();
+ int lineHeight = readerContext.getTxtConfig().textSize + param.LinePadding;
+ param.LineHeight = lineHeight;
+ if (!readerContext.getTxtConfig().VerticalPageMode) {
+ param.LineWidth = param.PageWidth - param.PaddingLeft - param.PaddingRight;
+ param.LineHeight = lineHeight;
+ param.PageLineNum = (param.PageHeight - param.PaddingTop - param.PaddingBottom - readerContext.getTxtConfig().textSize - 2) / lineHeight + 1;
+ } else {
+ param.LineWidth = lineHeight;
+ param.LineHeight = param.PageHeight - param.PaddingTop - param.PaddingBottom;
+ param.PageLineNum = (param.PageWidth - param.PaddingLeft - param.PaddingRight - readerContext.getTxtConfig().textSize - 2) / lineHeight + 1;
+ }
+ param.PaddingLeft = TxtConfig.Page_PaddingLeft;
+ param.LinePadding = TxtConfig.Page_LinePadding;
+ param.PaddingRight = TxtConfig.Page_PaddingRight;
+ param.PaddingTop = TxtConfig.Page_PaddingTop;
+ param.PaddingBottom = TxtConfig.Page_PaddingBottom;
+ param.ParagraphMargin = TxtConfig.Page_Paragraph_margin;
+ }
+
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtFileLoader.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtFileLoader.java
new file mode 100644
index 0000000..1668695
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtFileLoader.java
@@ -0,0 +1,82 @@
+package com.bifan.txtreaderlib.tasks;
+
+import com.bifan.txtreaderlib.bean.FileReadRecordBean;
+import com.bifan.txtreaderlib.bean.TxtFileMsg;
+import com.bifan.txtreaderlib.bean.TxtMsg;
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.main.FileReadRecordDB;
+import com.bifan.txtreaderlib.main.TxtReaderContext;
+import com.bifan.txtreaderlib.utils.ELogger;
+import com.bifan.txtreaderlib.utils.FileCharsetDetector;
+import com.bifan.txtreaderlib.utils.FileUtil;
+
+import java.io.File;
+
+
+/**
+ * @description
+ * @author bifan-wei
+ * @time 2021/11/13 16:53
+ */
+
+public class TxtFileLoader {
+ private String tag = "TxtFileLoader";
+ private FileDataLoadTask mFileDataLoadTask;
+
+ public void load(String filePath, TxtReaderContext readerContext, ILoadListener loadListener) {
+ load(filePath, null, readerContext, loadListener);
+ }
+
+ public void load(String filePath, String fileName, TxtReaderContext readerContext, ILoadListener loadListener) {
+ onStop();
+ if (!FileUtil.FileExist(filePath)) {
+ loadListener.onFail(TxtMsg.FileNoExist);
+ return;
+ }
+ loadListener.onMessage("initFile start");
+ initFile(filePath, fileName, readerContext);
+ ELogger.log(tag, "initFile done");
+ loadListener.onMessage("initFile done");
+ mFileDataLoadTask = new FileDataLoadTask();
+ mFileDataLoadTask.Run(loadListener, readerContext);
+
+ }
+
+ private void initFile(String filePath, String fileName, TxtReaderContext readerContext) {
+ File file = new File(filePath);
+ TxtFileMsg fileMsg = new TxtFileMsg();
+ fileMsg.FileSize = file.getTotalSpace();
+ fileMsg.FilePath = filePath;
+ fileMsg.FileCode = new FileCharsetDetector().getCharset(new File(filePath));
+
+ fileMsg.CurrentParagraphIndex = 0;
+ fileMsg.PreParagraphIndex = 0;
+ fileMsg.PreCharIndex = 0;
+ if (fileName == null || fileName.trim().length() == 0) {
+ fileName = file.getName();
+ }
+ fileMsg.FileName = fileName;
+
+ //获取之前的播放进度记录
+ FileReadRecordDB readRecordDB = new FileReadRecordDB(readerContext.getContext());
+ readRecordDB.createTable();
+ try {
+ FileReadRecordBean r = readRecordDB.getRecordByHashName(FileUtil.getMD5Checksum(filePath));
+ if (r != null) {
+ fileMsg.PreParagraphIndex = r.paragraphIndex;
+ fileMsg.PreCharIndex = r.chartIndex;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ readRecordDB.createTable();
+ readerContext.setFileMsg(fileMsg);
+ }
+
+ public void onStop() {
+ if (mFileDataLoadTask != null) {
+ mFileDataLoadTask.onStop();
+ }
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtPageLoadTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtPageLoadTask.java
new file mode 100644
index 0000000..84d7dc6
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtPageLoadTask.java
@@ -0,0 +1,82 @@
+package com.bifan.txtreaderlib.tasks;
+
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.IPage;
+import com.bifan.txtreaderlib.interfaces.ITxtTask;
+import com.bifan.txtreaderlib.main.TxtReaderContext;
+import com.bifan.txtreaderlib.utils.ELogger;
+
+/**
+ * Created by bifan-wei
+ * on 2017/11/27.
+ */
+
+public class TxtPageLoadTask implements ITxtTask {
+ private String tag = "TxtPageLoadTask";
+ private final int startParagraphIndex;
+ private final int startCharIndex;
+
+ public TxtPageLoadTask(int startParagraphIndex, int startCharIndex) {
+ this.startParagraphIndex = startParagraphIndex;
+ this.startCharIndex = startCharIndex;
+ }
+
+ @Override
+ public void Run(ILoadListener callBack, TxtReaderContext readerContext) {
+ callBack.onMessage("start load pageData");
+
+ IPage firstPage = null;
+ IPage midPage;
+ IPage nextPage = null;
+
+ midPage = readerContext.getPageDataPipeline().getPageStartFromProgress(startParagraphIndex, startCharIndex);
+
+ if (midPage != null && midPage.HasData()) {
+ firstPage = readerContext.getPageDataPipeline().getPageEndToProgress(midPage.getFirstChar().ParagraphIndex, midPage.getFirstChar().CharIndex - 1);
+ }
+
+ if (midPage != null && midPage.isFullPage()) {
+ nextPage = readerContext.getPageDataPipeline().getPageStartFromProgress(midPage.getLastChar().ParagraphIndex, midPage.getLastChar().CharIndex + 1);
+ }
+
+ ELogger.log(tag, "获取进度数据完成");
+ ELogger.log(tag, "startParagraphIndex/ startCharIndex+" + startParagraphIndex + "/" + startCharIndex);
+
+ //show the data
+ if (firstPage != null) {
+ ELogger.log(tag, "firstPage:" + firstPage.toString());
+ if (!firstPage.HasData()) {
+ firstPage = null;
+ }
+ } else {
+ ELogger.log(tag, "firstPage is null");
+ }
+
+ if (midPage != null) {
+ ELogger.log(tag, "midPage:" + midPage.toString());
+ if (!midPage.HasData()) {
+ midPage = null;
+ }
+ } else {
+ ELogger.log(tag, "midPage is null");
+ }
+
+ if (nextPage != null) {
+ ELogger.log(tag, "nextPage:" + nextPage.toString());
+ if (!nextPage.HasData()) {
+ nextPage = null;
+ }
+ } else {
+ ELogger.log(tag, "nextPage is null");
+ }
+
+ readerContext.getPageData().setFirstPage(firstPage);
+ readerContext.getPageData().setMidPage(midPage);
+ readerContext.getPageData().setLastPage(nextPage);
+
+ ITxtTask txtTask = new DrawPrepareTask();
+ txtTask.Run(callBack, readerContext);
+
+ }
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChapterList.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChapterList.java
new file mode 100644
index 0000000..c0aaddc
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChapterList.java
@@ -0,0 +1,157 @@
+package com.bifan.txtreaderlib.ui;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.bifan.txtreaderlib.R;
+import com.bifan.txtreaderlib.interfaces.IChapter;
+
+import java.util.List;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/12.
+ */
+
+public class ChapterList extends PopupWindow {
+ private Context mContext;
+ private ListView mRootView;
+ private MyAdapter mAdapter;
+ private List mChapters;
+ private int CurrentIndex = -1;
+ private int AllCharNum ;
+ private int ViewHeight;
+
+ public ChapterList(Context mContext, int ViewHeight, List mChapters, int allCharNum) {
+ super(mContext);
+ this.mContext = mContext;
+ this.ViewHeight = ViewHeight;
+ this.mChapters = mChapters;
+ this.AllCharNum = allCharNum;
+ initRootView();
+ }
+
+ public int getAllCharNum() {
+ return AllCharNum;
+ }
+
+ public void setCurrentIndex(int currentIndex) {
+ CurrentIndex = currentIndex;
+ }
+
+ public void notifyDataSetChanged() {
+ mAdapter.notifyDataSetChanged();
+ }
+
+ public ListView getListView() {
+ return mRootView;
+ }
+
+ public BaseAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ protected void initRootView() {
+ WindowManager m = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metrics = new DisplayMetrics();
+ m.getDefaultDisplay().getMetrics(metrics);
+
+ int ViewHeight = this.ViewHeight;
+ int ViewWidth = metrics.widthPixels;
+ mRootView = new ListView(mContext);
+ ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ mRootView.setLayoutParams(params);
+ this.setContentView(mRootView);
+ this.setWidth(ViewWidth);
+ this.setHeight(ViewHeight);
+ this.setFocusable(true);
+ this.setOutsideTouchable(true);
+ this.setAnimationStyle(R.style.HwTxtChapterMenuAnimation);
+ this.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#1c1c1a")));
+ mAdapter = new MyAdapter();
+ mRootView.setAdapter(mAdapter);
+ }
+
+
+ private class MyAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mChapters == null ? 0 : mChapters.size();
+ }
+
+ @Override
+ public Object getItem(int i) {
+ return mChapters.get(i);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return 0;
+ }
+
+ private final int ColorNegative = Color.parseColor("#aeaca2");
+ private final int ColorSelected = Color.parseColor("#fa4613");
+ @Override
+ public View getView(int i, View view, ViewGroup viewGroup) {
+ Holder holder;
+ if (view == null) {
+ holder = new Holder();
+ view = LayoutInflater.from(mContext).inflate(R.layout.adapter_chapterlist, null);
+ holder.index = view.findViewById(R.id.adapter_chapterList_index);
+ holder.title = view.findViewById(R.id.adapter_chapterList_title);
+ holder.progress = view.findViewById(R.id.adapter_chapterList_progress);
+ view.setTag(holder);
+ } else {
+ holder = (Holder) view.getTag();
+ }
+ IChapter chapter = mChapters.get(i);
+ if (CurrentIndex == i) {
+ holder.title.setTextColor(ColorSelected);
+ holder.progress.setTextColor(Color.WHITE);
+ holder.progress.setText("当前");
+ } else {
+ holder.title.setTextColor(Color.WHITE);
+ holder.progress.setTextColor(ColorNegative);
+ float p = 0;
+ if (AllCharNum > 0) {
+ p = (float) chapter.getStartIndex() / (float) AllCharNum;
+ if (p > 1) {
+ p = 1;
+ }
+ }
+ holder.progress.setText((int) (p * 100) + "%");
+ }
+
+ holder.index.setText(i + 1 + "");
+ holder.title.setText((chapter.getTitle() + "").trim());
+ return view;
+ }
+
+ private class Holder {
+ TextView index;
+ TextView title;
+ TextView progress;
+ }
+ }
+
+ public void onDestroy() {
+ mContext = null;
+ mRootView = null;
+ mAdapter = null;
+ if (mChapters != null) {
+ mChapters.clear();
+ mChapters = null;
+ }
+ }
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChatWithGptActivity.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChatWithGptActivity.java
new file mode 100644
index 0000000..2da2636
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChatWithGptActivity.java
@@ -0,0 +1,237 @@
+package com.bifan.txtreaderlib.ui;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bifan.txtreaderlib.R;
+import com.bifan.txtreaderlib.adapter.MessageAdapter;
+import com.bifan.txtreaderlib.bean.ChatMessage_bmob;
+import com.bifan.txtreaderlib.bean.ChatRequest;
+import com.bifan.txtreaderlib.bean.ChatResponse;
+import com.bifan.txtreaderlib.bean.Message;
+import com.bifan.txtreaderlib.interfaces.ChatGPTService;
+
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import cn.bmob.v3.Bmob;
+import cn.bmob.v3.BmobQuery;
+import cn.bmob.v3.exception.BmobException;
+import cn.bmob.v3.listener.FindListener;
+import cn.bmob.v3.listener.SaveListener;
+import okhttp3.OkHttpClient;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class ChatWithGptActivity extends AppCompatActivity {
+ private RecyclerView recyclerView;
+ private MessageAdapter messageAdapter;
+ private EditText editTextMessage;
+ private TextView chatbot_title;
+ private Button buttonSend;
+ private String bookName = "";
+ private String userid="";
+ private ArrayList messageList = new ArrayList<>();
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_chatwithgpt);
+ Bmob.initialize(this, "8f9f1d1ea520b0ce4f84a6fa83a5f754");
+ if (getIntent() != null && getIntent().hasExtra("book_name")) {
+ bookName = getIntent().getStringExtra("book_name");
+ }
+ SharedPreferences sharedPreferences = getSharedPreferences("userinf", Context.MODE_PRIVATE);
+ String userIdFromPrefs = sharedPreferences.getString("user_id", "");
+ userid = userIdFromPrefs != null ? userIdFromPrefs : "";
+
+ recyclerView = findViewById(R.id.chatRecyclerView);
+ editTextMessage = findViewById(R.id.inputEditText);
+ buttonSend = findViewById(R.id.sendButton);
+ chatbot_title=findViewById(R.id.chatbot_title);
+ messageAdapter = new MessageAdapter(messageList);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ recyclerView.setAdapter(messageAdapter);
+ init();
+ buttonSend.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String text = editTextMessage.getText().toString();
+ if (!text.isEmpty()) {
+ messageList.add(new Message(text, 1)); // User message
+ messageAdapter.notifyItemInserted(messageList.size() - 1);
+ recyclerView.scrollToPosition(messageList.size() - 1);
+ editTextMessage.setText("");
+ sendMessageToChatGPT(text);
+ ChatMessage_bmob chatMessage_bmob=new ChatMessage_bmob(text,1,bookName,userid);
+ chatMessage_bmob.save(new SaveListener() {
+ @Override
+ public void done(String objectId,BmobException e) {
+ if(e==null){
+// toast("添加数据成功,返回objectId为:"+objectId);
+ }else{
+ //toast("创建数据失败:" + e.getMessage());
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ private void init(){
+ chatbot_title.setText(bookName);
+ BmobQuery bmobQuery = new BmobQuery<>();
+ bmobQuery.addWhereEqualTo("relatedBook", bookName);
+ bmobQuery.addWhereEqualTo("userid", userid);
+ bmobQuery.findObjects(new FindListener() {
+ @Override
+ public void done(List messages, BmobException e) {
+ if (e == null && messages != null) {
+ if (!messages.isEmpty()) {
+ for (ChatMessage_bmob item : messages) {
+ if (item.getContent() == null) {
+ String content = "出现未知错误!";
+ messageList.add(new Message(content, item.getIsuser()));
+ } else {
+ messageList.add(new Message(item.getContent(), item.getIsuser()));
+ }
+ }
+ } else {
+ // 如果没有消息,添加一条 bot 的欢迎消息
+ String welcomeMessage = "你好!我是你的阅读助手。我可以帮助你解答关于书籍的各种问题,比如内容概述、作者信息、主要主题和角色分析等。请随时向我提问,现在,你有什么关于《" + bookName + "》的问题吗?";
+ messageList.add(new Message(welcomeMessage, 0)); // 假设 0 表示系统或助手消息
+ ChatMessage_bmob chatMessage_bmob=new ChatMessage_bmob(welcomeMessage,0,bookName,userid);
+ chatMessage_bmob.save(new SaveListener() {
+ @Override
+ public void done(String objectId,BmobException e) {
+ if(e==null){
+// toast("添加数据成功,返回objectId为:"+objectId);
+ }else{
+ //toast("创建数据失败:" + e.getMessage());
+ }
+ }
+ });
+
+ }
+ messageAdapter.notifyDataSetChanged();
+ } else {
+ Log.e("BookInfo", "Error loading book info: " + (e != null ? e.getMessage() : ""));
+ }
+ }
+ });
+ }
+
+ /**
+ * 从数据库加载历史对话并构建消息列表
+ */
+ private List loadMessagesFromDatabase(String bookName, String userId) {
+ List messages = new ArrayList<>();
+ BmobQuery bmobQuery = new BmobQuery<>();
+ bmobQuery.addWhereEqualTo("relatedBook", bookName);
+ bmobQuery.addWhereEqualTo("userid", userId);
+ bmobQuery.order("createdAt");
+ bmobQuery.setLimit(10); //设置上下文最多10条历史对话
+ bmobQuery.findObjects(new FindListener() {
+ @Override
+ public void done(List chatMessages, BmobException e) {
+ if (e == null && chatMessages != null && !chatMessages.isEmpty()) {
+ for (ChatMessage_bmob item : chatMessages) {
+ // 根据消息来源判断角色,假设 0 表示用户,1 表示助手
+ String role = item.getIsuser() == 0 ? "user" : "assistant";
+ messages.add(new ChatRequest.Message(role, item.getContent()));
+ Log.d("test",item.getContent());
+ }
+ } else {
+ Log.e("BookInfo", "Error loading book info: " + (e != null ? e.getMessage() : "No error details."));
+ }
+ }
+ });
+ return messages;
+ }
+
+ private void sendMessageToChatGPT(String messageText) {
+ OkHttpClient client = new OkHttpClient.Builder()
+ .connectTimeout(60, TimeUnit.SECONDS)
+ .readTimeout(60, TimeUnit.SECONDS)
+ .writeTimeout(60, TimeUnit.SECONDS)
+ .build();
+ Retrofit retrofit = new Retrofit.Builder()
+ .client(client)
+ .baseUrl("https://api.openai-ch.top/")
+ .addConverterFactory(GsonConverterFactory.create())
+ .build();
+ ChatGPTService service = retrofit.create(ChatGPTService.class);
+ List messages = new ArrayList<>();
+ String prompt = "你好!我是你的阅读助手。我可以帮助你解答关于书籍的各种问题,比如内容概述、作者信息、主要主题和角色分析等。请随时向我提问,我会用中文简体来回答你。现在,你有什么关于《" + bookName + "》的问题吗?";
+ messages.add(new ChatRequest.Message("system", prompt));
+
+// messages.add(new ChatRequest.Message("system", "You are a reading assistant. The user will ask you questions about the book '" + bookName + "'."));
+ messages.addAll(loadMessagesFromDatabase(bookName, userid));
+ messages.add(new ChatRequest.Message("user", messageText)); // 添加用户消息
+
+ ChatRequest request = new ChatRequest(messages); // 使用消息列表创建请求
+ request.setModel("gpt-4-turbo"); // 根据需要选择不同的模型
+ request.setMaxTokens(200); // 设置最大 token 数,根据需要调整
+ request.setTemperature(0.5); // 设置适当的温度
+ service.postMessage(request).enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ if (response.isSuccessful() && response.body() != null) {
+ ChatResponse chatResponse = response.body();
+ if (chatResponse.choices != null && !chatResponse.choices.isEmpty()) {
+ ChatResponse.Message message = chatResponse.choices.get(0).message;
+ String reply = message.content; // 这是从 GPT-3.5 响应中提取的内容
+ runOnUiThread(() -> {
+ messageList.add(new Message(reply, 0));
+ messageAdapter.notifyItemInserted(messageList.size() - 1);
+ recyclerView.scrollToPosition(messageList.size() - 1);
+ ChatMessage_bmob chatMessage_bmob = new ChatMessage_bmob(reply, 0, bookName, userid);
+ chatMessage_bmob.save(new SaveListener() {
+ @Override
+ public void done(String objectId, BmobException e) {
+ if (e == null) {
+ // toast("添加数据成功,返回objectId为:" + objectId);
+ } else {
+ // toast("创建数据失败:" + e.getMessage());
+ }
+ }
+ });
+ });
+ }
+ } else {
+ try {
+ String errorBody = response.errorBody() != null ? response.errorBody().string() : "Unknown error";
+ Log.e("ChatGPT", "Error: " + errorBody);
+ } catch (IOException e) {
+ Log.e("ChatGPT", "Error reading error body", e);
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ Log.e("ChatGPT", "Failure: " + t.getMessage());
+ }
+ });
+ }
+
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/CircleView.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/CircleView.java
new file mode 100644
index 0000000..fa93143
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/CircleView.java
@@ -0,0 +1,74 @@
+package com.bifan.txtreaderlib.ui;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.bifan.txtreaderlib.R;
+
+
+
+public class CircleView extends View {
+ private Paint mPaint;
+ private float Radius = 0.0F;
+ private int CoverColor = Color.parseColor("#66ffffff");
+
+ public CircleView(Context context) {
+ super(context);
+ this.init();
+ }
+
+ public CircleView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ getAttrs(attrs);
+ this.init();
+ }
+
+ public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ getAttrs(attrs);
+ this.init();
+ }
+
+ private void getAttrs(AttributeSet attrs) {
+ TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CircleView);
+ Radius = ta.getDimension(R.styleable.CircleView_CircleRadius, Radius);
+ CoverColor = ta.getColor(R.styleable.CircleView_BgColor, CoverColor);
+ ta.recycle();
+ }
+
+ private void init() {
+ this.mPaint = new Paint();
+ this.mPaint.setAntiAlias(true);
+ this.mPaint.setColor(this.CoverColor);
+
+ }
+
+ public void setCoverColor(int CoverColor) {
+ this.CoverColor = CoverColor;
+ this.mPaint.setColor(CoverColor);
+ this.postInvalidate();
+ }
+
+ @SuppressLint({"DrawAllocation"})
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ int vWidth = this.getWidth();
+ int vHeight = this.getHeight();
+ int radius = Math.max(vHeight, vWidth) / 2;
+ if (Radius > 0) {
+ radius = (int) Radius;
+ }
+ float Cx = (float) (vWidth / 2);
+ float Cy = (float) (vHeight / 2);
+ this.mPaint.setColor(Color.WHITE);
+ canvas.drawCircle(Cx, Cy, (float) radius, this.mPaint);
+ this.mPaint.setColor(CoverColor);
+ canvas.drawCircle(Cx, Cy, (float) radius-3, this.mPaint);
+ }
+}
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/HwTxtPlayActivity.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/HwTxtPlayActivity.java
new file mode 100644
index 0000000..d8c6efa
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/HwTxtPlayActivity.java
@@ -0,0 +1,808 @@
+package com.bifan.txtreaderlib.ui;
+
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.res.ResourcesCompat;
+
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.bifan.txtreaderlib.R;
+import com.bifan.txtreaderlib.bean.TxtChar;
+import com.bifan.txtreaderlib.bean.TxtMsg;
+import com.bifan.txtreaderlib.interfaces.ICenterAreaClickListener;
+import com.bifan.txtreaderlib.interfaces.IChapter;
+import com.bifan.txtreaderlib.interfaces.ILoadListener;
+import com.bifan.txtreaderlib.interfaces.IPageChangeListener;
+import com.bifan.txtreaderlib.interfaces.ISliderListener;
+import com.bifan.txtreaderlib.interfaces.ITextSelectListener;
+import com.bifan.txtreaderlib.main.TxtConfig;
+import com.bifan.txtreaderlib.main.TxtReaderView;
+import com.bifan.txtreaderlib.utils.ELogger;
+import com.bifan.txtreaderlib.utils.FileProvider;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.io.File;
+
+/**
+ * Created by bifan-wei
+ * on 2017/12/8.
+ */
+
+public class HwTxtPlayActivity extends AppCompatActivity {
+ protected Handler mHandler;
+ protected boolean FileExist = false;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(getContentViewLayout());
+ FileExist = getIntentData();
+ init();
+ loadFile();
+ registerListener();
+ }
+
+ protected int getContentViewLayout() {
+ return R.layout.activity_hwtxtpaly;
+ }
+
+ protected boolean getIntentData() {
+ // Get the intent that started this activity
+ Uri uri = getIntent().getData();
+ if (uri != null) {
+ ELogger.log("getIntentData", "" + uri);
+ } else {
+ ELogger.log("getIntentData", "uri is null");
+ }
+ if (uri != null) {
+ try {
+ String path = FileProvider.getFileAbsolutePath(this, uri);
+ if (!TextUtils.isEmpty(path)) {
+ if (path.contains("/storage/")) {
+ path = path.substring(path.indexOf("/storage/"));
+ }
+ ELogger.log("getIntentData", "path:" + path);
+ File file = new File(path);
+ if (file.exists()) {
+ FilePath = path;
+ FileName = file.getName();
+ return true;
+ } else {
+ toast("文件不存在");
+ return false;
+ }
+ }
+ return false;
+ } catch (Exception e) {
+ toast("文件出错了");
+ }
+ }
+
+ FilePath = getIntent().getStringExtra("FilePath");
+ FileName = getIntent().getStringExtra("FileName");
+ ContentStr = getIntent().getStringExtra("ContentStr");
+ if (ContentStr == null) {
+ return FilePath != null && new File(FilePath).exists();
+ } else {
+ return true;
+ }
+
+ }
+
+
+ /**
+ * @param context 上下文
+ * @param FilePath 文本文件路径
+ */
+ public static void loadTxtFile(Context context, String FilePath) {
+ loadTxtFile(context, FilePath, null);
+ }
+
+ /**
+ * @param context 上下文
+ * @param str 文本文内容
+ */
+ public static void loadStr(Context context, String str) {
+ loadTxtStr(context, str, null);
+ }
+
+ /**
+ * @param context 上下文
+ * @param str 文本显示内容
+ * @param FileName 显示的书籍或者文件名称
+ */
+ public static void loadTxtStr(Context context, String str, String FileName) {
+ Intent intent = new Intent();
+ intent.putExtra("ContentStr", str);
+ intent.putExtra("FileName", FileName);
+ intent.setClass(context, HwTxtPlayActivity.class);
+ context.startActivity(intent);
+ }
+
+ /**
+ * @param context 上下文
+ * @param FilePath 文本文件路径
+ * @param FileName 显示的书籍或者文件名称
+ */
+ public static void loadTxtFile(Context context, String FilePath, String FileName) {
+ Intent intent = new Intent();
+ intent.putExtra("FilePath", FilePath);
+ intent.putExtra("FileName", FileName);
+ intent.setClass(context, HwTxtPlayActivity.class);
+ context.startActivity(intent);
+ }
+
+ protected View mTopDecoration, mBottomDecoration;
+ protected View mChapterMsgView;
+ protected TextView mChapterMsgName;
+ protected TextView mChapterMsgProgress;
+ protected TextView mChapterNameText;
+ protected TextView mChapterMenuText;
+ protected TextView mProgressText;
+ protected TextView mSettingText;
+ protected TextView mSelectedText;
+ protected TxtReaderView mTxtReaderView;
+ protected View mTopMenu;
+ protected View mBottomMenu;
+ protected View mCoverView;
+ protected View ClipboardView;
+ protected String CurrentSelectedText;
+
+ protected ChapterList mChapterListPop;
+ protected MenuHolder mMenuHolder = new MenuHolder();
+
+ protected FloatingActionButton fab;
+
+ protected void init() {
+ mHandler = new Handler();
+ mChapterMsgView = findViewById(R.id.activity_hwTxtPlay_chapter_msg);
+ mChapterMsgName = findViewById(R.id.chapter_name);
+ mChapterMsgProgress = findViewById(R.id.chapter_progress);
+ mTopDecoration = findViewById(R.id.activity_hwTxtPlay_top);
+ mBottomDecoration = findViewById(R.id.activity_hwTxtPlay_bottom);
+ mTxtReaderView = findViewById(R.id.activity_hwTxtPlay_readerView);
+ mChapterNameText = findViewById(R.id.activity_hwTxtPlay_chapterName);
+ mChapterMenuText = findViewById(R.id.activity_hwTxtPlay_chapter_menuText);
+ mProgressText = findViewById(R.id.activity_hwTxtPlay_progress_text);
+ mSettingText = findViewById(R.id.activity_hwTxtPlay_setting_text); //设置
+ mTopMenu = findViewById(R.id.activity_hwTxtPlay_menu_top);
+ mBottomMenu = findViewById(R.id.activity_hwTxtPlay_menu_bottom);
+ mCoverView = findViewById(R.id.activity_hwTxtPlay_cover);
+ ClipboardView = findViewById(R.id.activity_hwTxtPlay_ClipBoar);
+ mSelectedText = findViewById(R.id.activity_hwTxtPlay_selected_text);
+
+ mMenuHolder.mTitle = findViewById(R.id.txtReader_menu_title);
+ mMenuHolder.mPreChapter = findViewById(R.id.txtReadr_menu_chapter_pre);
+ mMenuHolder.mNextChapter = findViewById(R.id.txtReadr_menu_chapter_next);
+ mMenuHolder.mSeekBar = findViewById(R.id.txtReadr_menu_seekbar);
+ mMenuHolder.mTextSizeDel = findViewById(R.id.txtRead_menu_textsize_del);
+ mMenuHolder.mTextSize = findViewById(R.id.txtRead_menu_textSize);
+ mMenuHolder.mTextSizeAdd = findViewById(R.id.txtRead_menu_textSize_add);
+ mMenuHolder.mBoldSelectedLayout = findViewById(R.id.txtRead_menu_textSetting1_bold);
+ mMenuHolder.mCoverSelectedLayout = findViewById(R.id.txtRead_menu_textSetting2_cover);
+ mMenuHolder.mShearSelectedLayout = findViewById(R.id.txtRead_menu_textSetting2_shear);
+ mMenuHolder.mTranslateSelectedLayout = findViewById(R.id.txtRead_menu_textSetting2_translate);
+ mMenuHolder.mStyle1 = findViewById(R.id.txtReader_menu_style1);
+ mMenuHolder.mStyle2 = findViewById(R.id.txtReader_menu_style2);
+ mMenuHolder.mStyle3 = findViewById(R.id.txtRead_menu_style3);
+ mMenuHolder.mStyle4 = findViewById(R.id.txtReader_menu_style4);
+ mMenuHolder.mStyle5 = findViewById(R.id.txtReader_menu_style5);
+ fab = findViewById(R.id.chatbot);
+ }
+
+ private final int[] StyleTextColors = new int[]{
+ Color.parseColor("#4a453a"),
+ Color.parseColor("#505550"),
+ Color.parseColor("#453e33"),
+ Color.parseColor("#8f8e88"),
+ Color.parseColor("#27576c")
+ };
+
+ protected String ContentStr = null;
+ protected String FilePath = null;
+ protected String FileName = null;
+
+ protected void loadFile() {
+ TxtConfig.savePageSwitchDuration(this, 400);
+ if (ContentStr == null) {
+ if (TextUtils.isEmpty(FilePath) || !(new File(FilePath).exists())) {
+ toast("文件不存在");
+ return;
+ }
+ }
+ mHandler.postDelayed(() -> {
+ //延迟加载避免闪一下的情况出现
+ if (ContentStr == null) {
+ loadOurFile();
+ } else {
+ loadStr();
+ }
+ }, 300);
+
+
+ }
+
+ /**
+ *
+ */
+ protected void loadOurFile() {
+ mTxtReaderView.loadTxtFile(FilePath, new ILoadListener() {
+ @Override
+ public void onSuccess() {
+ if (!hasExisted) {
+ onLoadDataSuccess();
+ }
+ }
+
+ @Override
+ public void onFail(final TxtMsg txtMsg) {
+ if (!hasExisted) {
+ runOnUiThread(() -> onLoadDataFail(txtMsg));
+ }
+
+ }
+
+ @Override
+ public void onMessage(String message) {
+ //加载过程信息
+ }
+ });
+ }
+
+ /**
+ * @param txtMsg txtMsg
+ */
+ protected void onLoadDataFail(TxtMsg txtMsg) {
+ //加载失败信息
+ toast(String.valueOf(txtMsg));
+ }
+
+ /**
+ *
+ */
+ protected void onLoadDataSuccess() {
+ if (TextUtils.isEmpty(FileName)) {//没有显示的名称,获取文件名显示
+ FileName = mTxtReaderView.getTxtReaderContext().getFileMsg().FileName;
+ }
+ setBookName(FileName);
+ initWhenLoadDone();
+ }
+
+ private void loadStr() {
+ String testText = ContentStr;
+ mTxtReaderView.loadText(testText, new ILoadListener() {
+ @Override
+ public void onSuccess() {
+ setBookName("test with str");
+ initWhenLoadDone();
+ }
+
+ @Override
+ public void onFail(TxtMsg txtMsg) {
+ //加载失败信息
+ toast(txtMsg + "");
+ }
+
+ @Override
+ public void onMessage(String message) {
+ //加载过程信息
+ }
+ });
+ }
+
+ protected void initWhenLoadDone() {
+ if (mTxtReaderView.getTxtReaderContext().getFileMsg() != null) {
+ FileName = mTxtReaderView.getTxtReaderContext().getFileMsg().FileName;
+ }
+ mMenuHolder.mTextSize.setText(String.valueOf(mTxtReaderView.getTextSize()));
+ mTopDecoration.setBackgroundColor(mTxtReaderView.getBackgroundColor());
+ mBottomDecoration.setBackgroundColor(mTxtReaderView.getBackgroundColor());
+ //mTxtReaderView.setLeftSlider(new MuiLeftSlider());//修改左滑动条
+ //mTxtReaderView.setRightSlider(new MuiRightSlider());//修改右滑动条
+ //字体初始化
+ onTextSettingUi(mTxtReaderView.getTxtReaderContext().getTxtConfig().Bold);
+ //翻页初始化
+ onPageSwitchSettingUi(mTxtReaderView.getTxtReaderContext().getTxtConfig().Page_Switch_Mode);
+ //保存的翻页模式
+ int pageSwitchMode = mTxtReaderView.getTxtReaderContext().getTxtConfig().Page_Switch_Mode;
+ if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SERIAL) {
+ mTxtReaderView.setPageSwitchByTranslate();
+ } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_COVER) {
+ mTxtReaderView.setPageSwitchByCover();
+ } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SHEAR) {
+ mTxtReaderView.setPageSwitchByShear();
+ }
+ //章节初始化
+ if (mTxtReaderView.getChapters() != null && mTxtReaderView.getChapters().size() > 0) {
+ WindowManager m = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metrics = new DisplayMetrics();
+ m.getDefaultDisplay().getMetrics(metrics);
+ int ViewHeight = metrics.heightPixels - mTopDecoration.getHeight();
+ mChapterListPop = new ChapterList(this, ViewHeight, mTxtReaderView.getChapters(), mTxtReaderView.getTxtReaderContext().getParagraphData().getCharNum());
+ mChapterListPop.setOnDismissListener(this::hideNavigationBarStatusBar);
+ mChapterListPop.getListView().setOnItemClickListener((adapterView, view, i, l) -> {
+ IChapter chapter = (IChapter) mChapterListPop.getAdapter().getItem(i);
+ mChapterListPop.dismiss();
+ mTxtReaderView.loadFromProgress(chapter.getStartParagraphIndex(), 0);
+ });
+ } else {
+ Gone(mChapterMenuText);
+ }
+ }
+
+ protected void registerListener() {
+ mSettingText.setOnClickListener(view -> Show(mTopMenu, mBottomMenu, mCoverView));
+ setMenuListener();
+ setSeekBarListener();
+ setCenterClickListener();
+ setPageChangeListener();
+ setOnTextSelectListener();
+ setStyleChangeListener();
+ setExtraListener();
+ setChatbotListener();
+ }
+
+ private void setChatbotListener() {
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Define what happens when the fab is clicked
+// Toast.makeText(HwTxtPlayActivity.this, "Floating Action Button Clicked!", Toast.LENGTH_SHORT).show();
+ String bookName = FileName.substring(0, FileName.lastIndexOf("."));
+ Intent intent = new Intent(HwTxtPlayActivity.this,ChatWithGptActivity.class);
+ intent.putExtra("book_name", bookName);
+ startActivity(intent);
+ }
+ });
+ }
+
+ //底部菜单中的按钮监听:上一章,下一章,字体减小、放大,粗体,翻页的滑盖、平移、剪切
+ private void setExtraListener() {
+ mMenuHolder.mPreChapter.setOnClickListener(new ChapterChangeClickListener(true));
+ mMenuHolder.mNextChapter.setOnClickListener(new ChapterChangeClickListener(false));
+ mMenuHolder.mTextSizeAdd.setOnClickListener(new TextChangeClickListener(true));
+ mMenuHolder.mTextSizeDel.setOnClickListener(new TextChangeClickListener(false));
+ mMenuHolder.mBoldSelectedLayout.setOnClickListener(new TextSettingClickListener());
+ mMenuHolder.mCoverSelectedLayout.setOnClickListener(new SwitchSettingClickListener(TxtConfig.PAGE_SWITCH_MODE_COVER));
+ mMenuHolder.mTranslateSelectedLayout.setOnClickListener(new SwitchSettingClickListener(TxtConfig.PAGE_SWITCH_MODE_SERIAL));
+ mMenuHolder.mShearSelectedLayout.setOnClickListener(new SwitchSettingClickListener(TxtConfig.PAGE_SWITCH_MODE_SHEAR));
+ }
+
+ //背景色修改
+ protected void setStyleChangeListener() {
+ mMenuHolder.mStyle1.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor1), StyleTextColors[0]));
+ mMenuHolder.mStyle2.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor2), StyleTextColors[1]));
+ mMenuHolder.mStyle3.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor3), StyleTextColors[2]));
+ mMenuHolder.mStyle4.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor4), StyleTextColors[3]));
+ mMenuHolder.mStyle5.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor5), StyleTextColors[4]));
+ }
+
+ protected void setOnTextSelectListener() {
+ mTxtReaderView.setOnTextSelectListener(new ITextSelectListener() {
+ @Override
+ public void onTextChanging(TxtChar firstSelectedChar, TxtChar lastSelectedChar) {
+ //firstSelectedChar.Top
+ // firstSelectedChar.Bottom
+ // 这里可以根据 firstSelectedChar与lastSelectedChar的top与bottom的位置
+ //计算显示你要显示的弹窗位置,如果需要的话
+ }
+
+ @Override
+ public void onTextChanging(String selectText) {
+ onCurrentSelectedText(selectText);
+ }
+
+ @Override
+ public void onTextSelected(String selectText) {
+ onCurrentSelectedText(selectText);
+ }
+ });
+
+ mTxtReaderView.setOnSliderListener(new ISliderListener() {
+ @Override
+ public void onShowSlider(TxtChar txtChar) {
+ //TxtChar 为当前长按选中的字符
+ // 这里可以根据 txtChar的top与bottom的位置
+ //计算显示你要显示的弹窗位置,如果需要的话
+ }
+
+ @Override
+ public void onShowSlider(String currentSelectedText) {
+ onCurrentSelectedText(currentSelectedText);
+ Show(ClipboardView);
+ }
+
+ @Override
+ public void onReleaseSlider() {
+ Gone(ClipboardView);
+ }
+ });
+
+ }
+
+ protected void setPageChangeListener() {
+ mTxtReaderView.setPageChangeListener(progress -> {
+ int p = (int) (progress * 1000);
+ mProgressText.setText(((float) p / 10) + "%");
+ mMenuHolder.mSeekBar.setProgress((int) (progress * 100));
+ IChapter currentChapter = mTxtReaderView.getCurrentChapter();
+ if (currentChapter != null) {
+ mChapterNameText.setText((currentChapter.getTitle() + "").trim());
+ } else {
+ mChapterNameText.setText("无章节");
+ }
+ });
+ }
+
+ protected void setCenterClickListener() {
+ mTxtReaderView.setOnCenterAreaClickListener(new ICenterAreaClickListener() {
+ @Override
+ public boolean onCenterClick(float widthPercentInView) { //点击中心区域才会弹出
+ mSettingText.performClick();
+ fab.setVisibility(View.VISIBLE); // Make FloatingActionButton visible
+ return true;
+ }
+
+ @Override
+ public boolean onOutSideCenterClick(float widthPercentInView) {
+ if (mBottomMenu.getVisibility() == View.VISIBLE) {
+ mSettingText.performClick();
+ fab.setVisibility(View.GONE); // 隐藏 FloatingActionButton
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+// 设置菜单监听器
+ protected void setMenuListener() {
+ mTopMenu.setOnTouchListener((view, motionEvent) -> true);
+ mBottomMenu.setOnTouchListener((view, motionEvent) -> true);
+ mCoverView.setOnTouchListener((view, motionEvent) -> {
+ Gone(mTopMenu, mBottomMenu, mCoverView, mChapterMsgView);
+ fab.setVisibility(View.GONE); // 同时隐藏 FloatingActionButton
+ return true;
+ });
+ //为章节信息文本设置点击监听器,当点击时会显示或隐藏章节列表弹窗
+ View.OnClickListener chapterMsgShowClick = v -> {
+ if (mChapterListPop != null) {
+ if (!mChapterListPop.isShowing()) {
+ mChapterListPop.showAsDropDown(mTopDecoration);
+ mHandler.postDelayed(() -> {
+ IChapter currentChapter = mTxtReaderView.getCurrentChapter();
+ if (currentChapter != null) {
+ mChapterListPop.setCurrentIndex(currentChapter.getIndex());
+ mChapterListPop.notifyDataSetChanged();
+ }
+ }, 300);
+ } else {
+ mChapterListPop.dismiss();
+
+ }
+ }
+ };
+ mChapterNameText.setOnClickListener(chapterMsgShowClick);
+ mChapterMenuText.setOnClickListener(chapterMsgShowClick);
+ mTopMenu.setOnClickListener(view -> {
+ if (mChapterListPop.isShowing()) {
+ mChapterListPop.dismiss();
+ }
+ });
+ }
+// 设置进度条监听器
+ protected void setSeekBarListener() {
+ mMenuHolder.mSeekBar.setOnTouchListener((view, motionEvent) -> {
+ if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
+ mTxtReaderView.loadFromProgress(mMenuHolder.mSeekBar.getProgress());
+ Gone(mChapterMsgView);
+ }
+ return false;
+ });
+ mMenuHolder.mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, final int progress, boolean fromUser) {
+ if (fromUser) {
+ mHandler.post(() -> onShowChapterMsg(progress));
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ Gone(mChapterMsgView);
+ }
+ });
+
+ }
+
+
+ private void onShowChapterMsg(int progress) {
+ if (mTxtReaderView != null && mChapterListPop != null) {
+ IChapter chapter = mTxtReaderView.getChapterFromProgress(progress);
+ if (chapter != null) {
+ float p = (float) chapter.getStartIndex() / (float) mChapterListPop.getAllCharNum();
+ if (p > 1) {
+ p = 1;
+ }
+ Show(mChapterMsgView);
+ mChapterMsgName.setText(chapter.getTitle());
+ mChapterMsgProgress.setText((int) (p * 100) + "%");
+ }
+ }
+ }
+
+ private void onCurrentSelectedText(String SelectedText) {
+ String selectTextShow = String.format(getString(R.string.select_char_num), (SelectedText + "").length());
+ mSelectedText.setText(selectTextShow);
+ CurrentSelectedText = SelectedText;
+ }
+
+ private void onTextSettingUi(Boolean isBold) {
+ int rs = isBold ? R.drawable.ic_bold_selected : R.drawable.ic_bold_normal;
+ mMenuHolder.mBoldSelectedLayout.setBackgroundDrawable(ResourcesCompat.getDrawable(getResources(), rs, null));
+ }
+
+ private void onPageSwitchSettingUi(int pageSwitchMode) {
+ if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SERIAL) {
+ mMenuHolder.mTranslateSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_selected);
+ mMenuHolder.mCoverSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected);
+ mMenuHolder.mShearSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected);
+ } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_COVER) {
+ mMenuHolder.mTranslateSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected);
+ mMenuHolder.mCoverSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_selected);
+ mMenuHolder.mShearSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected);
+ } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SHEAR) {
+ mMenuHolder.mTranslateSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected);
+ mMenuHolder.mCoverSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected);
+ mMenuHolder.mShearSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_selected);
+ }
+ }
+
+ private class TextSettingClickListener implements View.OnClickListener {
+ public TextSettingClickListener() {
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (FileExist) {
+ Boolean Bold = mTxtReaderView.getTxtReaderContext().getTxtConfig().Bold;
+ mTxtReaderView.setTextBold(!Bold);
+ onTextSettingUi(!Bold);
+ }
+ }
+ }
+ //设置滑盖 平移 剪切的翻页模式
+ private class SwitchSettingClickListener implements View.OnClickListener {
+ private final int pageSwitchMode;
+
+ public SwitchSettingClickListener(int pageSwitchMode) {
+ this.pageSwitchMode = pageSwitchMode;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (FileExist) {
+ if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_COVER) {
+ mTxtReaderView.setPageSwitchByCover();
+ } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SERIAL) {
+ mTxtReaderView.setPageSwitchByTranslate();
+ }
+ if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SHEAR) {
+ mTxtReaderView.setPageSwitchByShear();
+ }
+ onPageSwitchSettingUi(pageSwitchMode);
+ }
+ }
+ }
+
+
+ private class ChapterChangeClickListener implements View.OnClickListener {
+ private final Boolean Pre;
+
+ public ChapterChangeClickListener(Boolean pre) {
+ Pre = pre;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (Pre) {
+ mTxtReaderView.jumpToPreChapter();
+ } else {
+ mTxtReaderView.jumpToNextChapter();
+ }
+ }
+ }
+
+ private class TextChangeClickListener implements View.OnClickListener {
+ private final Boolean Add;
+
+ public TextChangeClickListener(Boolean pre) {
+ Add = pre;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (FileExist) {
+ int textSize = mTxtReaderView.getTextSize();
+ if (Add) {
+ if (textSize + 2 <= TxtConfig.MAX_TEXT_SIZE) {
+ mTxtReaderView.setTextSize(textSize + 2);
+ mMenuHolder.mTextSize.setText(textSize + 2 + "");
+ }
+ } else {
+ if (textSize - 2 >= TxtConfig.MIN_TEXT_SIZE) {
+ mTxtReaderView.setTextSize(textSize - 2);
+ mMenuHolder.mTextSize.setText(textSize - 2 + "");
+ }
+ }
+ }
+ }
+ }
+
+ private class StyleChangeClickListener implements View.OnClickListener {
+ private final int BgColor;
+ private final int TextColor;
+
+ public StyleChangeClickListener(int bgColor, int textColor) {
+ BgColor = bgColor;
+ TextColor = textColor;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (FileExist) {
+ mTxtReaderView.setStyle(BgColor, TextColor);
+ mTopDecoration.setBackgroundColor(BgColor);
+ mBottomDecoration.setBackgroundColor(BgColor);
+ }
+ }
+ }
+
+ protected void setBookName(String name) {
+ mMenuHolder.mTitle.setText(name + "");
+ }
+
+ protected void Show(View... views) {
+ for (View v : views) {
+ v.setVisibility(View.VISIBLE);
+ }
+ }
+
+ protected void Gone(View... views) {
+ for (View v : views) {
+ v.setVisibility(View.GONE);
+ }
+ }
+
+
+ private Toast t;
+
+ protected void toast(final String msg) {
+ if (t != null) {
+ t.cancel();
+ }
+ t = Toast.makeText(HwTxtPlayActivity.this, msg, Toast.LENGTH_SHORT);
+ t.show();
+ }
+
+ protected static class MenuHolder {
+ public TextView mTitle;
+ public TextView mPreChapter;
+ public TextView mNextChapter;
+ public SeekBar mSeekBar;
+ public View mTextSizeDel;
+ public View mTextSizeAdd;
+ public TextView mTextSize;
+ public View mBoldSelectedLayout;
+ public View mCoverSelectedLayout;
+ public View mShearSelectedLayout;
+ public View mTranslateSelectedLayout;
+ public View mStyle1;
+ public View mStyle2;
+ public View mStyle3;
+ public View mStyle4;
+ public View mStyle5;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ hideNavigationBarStatusBar();
+ }
+
+ private void hideNavigationBarStatusBar() {
+ if (Build.VERSION.SDK_INT >= 19) {
+ View decorView = getWindow().getDecorView();
+ decorView.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ exist();
+ }
+
+ public void BackClick(View view) {
+ finish();
+ }
+
+ public void onCopyText(View view) {
+ if (!TextUtils.isEmpty(CurrentSelectedText)) {
+ toast("已经复制到粘贴板");
+ ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setText(CurrentSelectedText + "");
+ }
+ onCurrentSelectedText("");
+ mTxtReaderView.releaseSelectedState();
+ Gone(ClipboardView);
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ finish();
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ exist();
+ }
+
+ protected boolean hasExisted = false;
+
+ protected void exist() {
+ if (!hasExisted) {
+ ContentStr = null;
+ hasExisted = true;
+ if (mHandler != null) {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler = null;
+ }
+ if (mTxtReaderView != null) {
+ mTxtReaderView.saveCurrentProgress();
+ mTxtReaderView.onDestroy();
+ }
+ if (mTxtReaderView != null) {
+ mTxtReaderView.getTxtReaderContext().Clear();
+ mTxtReaderView = null;
+ }
+ if (mChapterListPop != null) {
+ if (mChapterListPop.isShowing()) {
+ mChapterListPop.dismiss();
+ }
+ mChapterListPop.onDestroy();
+ mChapterListPop = null;
+ }
+ mMenuHolder = null;
+ }
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ConfigUtils.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ConfigUtils.java
new file mode 100644
index 0000000..b0720a7
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ConfigUtils.java
@@ -0,0 +1,21 @@
+package com.bifan.txtreaderlib.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class ConfigUtils {
+ private static final String CONFIG_FILE = "config.properties";
+
+ public static String getAuthKey() {
+ try {
+ Properties properties = new Properties();
+ InputStream inputStream = ConfigUtils.class.getClassLoader().getResourceAsStream(CONFIG_FILE);
+ properties.load(inputStream);
+ return properties.getProperty("auth_key");
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/DisPlayUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/DisPlayUtil.java
new file mode 100644
index 0000000..fd19c5b
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/DisPlayUtil.java
@@ -0,0 +1,29 @@
+package com.bifan.txtreaderlib.utils;
+
+import android.content.Context;
+
+public class DisPlayUtil {
+
+ public static int px2dip(Context context, float pxValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (pxValue / scale + 0.5f);
+ }
+
+
+ public static int dip2px(Context context, float dipValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dipValue * scale + 0.5f);
+ }
+
+
+ public static int px2sp(Context context, float pxValue) {
+ final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return (int) (pxValue / fontScale + 0.5f);
+ }
+
+
+ public static int sp2px(Context context, float spValue) {
+ final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return (int) (spValue * fontScale + 0.5f);
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ELogger.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ELogger.java
new file mode 100644
index 0000000..19434e0
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ELogger.java
@@ -0,0 +1,26 @@
+package com.bifan.txtreaderlib.utils;
+
+import android.util.Log;
+
+import com.bifan.txtreaderlib.interfaces.ITxtReaderLoggerListener;
+import com.bifan.txtreaderlib.main.TxtConfig;
+
+/**
+ * Created by HP on 2017/11/15.
+ */
+
+public class ELogger {
+ private static ITxtReaderLoggerListener l;
+ public static void setLoggerListener(ITxtReaderLoggerListener l) {
+ ELogger.l = l;
+ }
+ public static void log(String tag, String msg) {
+ if(TxtConfig.DebugMode) {
+ Log.e(tag, msg + "");
+ if (l != null) {
+ l.onLog(tag, msg + "");
+ }
+ }
+ }
+
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileCharsetDetector.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileCharsetDetector.java
new file mode 100644
index 0000000..04f0bf8
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileCharsetDetector.java
@@ -0,0 +1,121 @@
+package com.bifan.txtreaderlib.utils;
+
+import android.text.TextUtils;
+
+import org.mozilla.intl.chardet.nsDetector;
+import org.mozilla.intl.chardet.nsICharsetDetectionObserver;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class FileCharsetDetector {
+ private boolean found = false;
+ private String encoding = null;
+
+
+ public String getCharset(File file) {
+ String charset = "GBK";
+ byte[] first3Bytes = new byte[3];
+ try {
+ boolean checked = false;
+ BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+ bis.mark(0);
+ int read = bis.read(first3Bytes, 0, 3);
+ if (read == -1)
+ return charset;
+ if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
+ charset = "UTF-16LE";
+ checked = true;
+ } else if (first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF) {
+ charset = "UTF-16BE";
+ checked = true;
+ } else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB
+ && first3Bytes[2] == (byte) 0xBF) {
+ charset = "UTF-8";
+ checked = true;
+ }
+ if (!checked) {
+ charset = guessFileEncoding(file, new nsDetector());
+ if (!TextUtils.isEmpty(charset)) {
+ if (charset.equals("Big5")) {
+ charset = "GBK";
+ }
+ }
+ }
+ bis.reset();
+ bis.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return charset;
+ }
+
+
+ /**
+ * 获取文件的编码
+ * @param file
+ * @param det
+ * @return
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ private String guessFileEncoding(File file, nsDetector det) throws IOException {
+ // Set an observer...
+ // The Notify() will be called when a matching charset is found.
+ det.Init(new nsICharsetDetectionObserver() {
+ @Override
+ public void Notify(String charset) {
+ encoding = charset;
+ found = true;
+ }
+ });
+
+ BufferedInputStream imp = new BufferedInputStream(new FileInputStream(file));
+ byte[] buf = new byte[1024];
+ int len;
+ boolean done;
+ boolean isAscii = false;
+
+ while ((len = imp.read(buf, 0, buf.length)) != -1) {
+ // Check if the stream is only ascii.
+ isAscii = det.isAscii(buf, len);
+ if (isAscii) {
+ break;
+ }
+ // DoIt if non-ascii and not done yet.
+ done = det.DoIt(buf, len, false);
+ if (done) {
+ break;
+ }
+ }
+ imp.close();
+ det.DataEnd();
+
+ if (isAscii) {
+ encoding = "ASCII";
+ found = true;
+ }
+
+ if (!found) {
+ String[] prob = det.getProbableCharsets();
+ // 这里将可能的字符集组合起来返回
+ for (int i = 0; i < prob.length; i++) {
+ if (i == 0) {
+ encoding = prob[i];
+ } else {
+ encoding += "," + prob[i];
+ }
+ }
+ if (prob.length > 0) {
+ // 在没有发现情况下,也可以只取第一个可能的编码,这里返回的是一个可能的序列
+ return encoding;
+ } else {
+ return "GBK";
+ }
+ }
+ return encoding;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileProvider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileProvider.java
new file mode 100644
index 0000000..3169980
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileProvider.java
@@ -0,0 +1,233 @@
+package com.bifan.txtreaderlib.utils;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * Created by bifan-wei
+ * on 2021/11/6.
+ */
+
+public class FileProvider {
+
+
+ /**
+ * 根据Uri获取文件绝对路径,解决Android4.4以上版本Uri转换 兼容Android 10
+ *
+ * @param context
+ * @param uri
+ */
+ public static String getFileAbsolutePath(Context context, Uri uri) {
+ if (context == null || uri == null) {
+ return null;
+ }
+
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
+ return getRealFilePath(context, uri);
+ }
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && DocumentsContract.isDocumentUri(context, uri)) {
+ if (isExternalStorageDocument(uri)) {
+ String docId = DocumentsContract.getDocumentId(uri);
+ String[] split = docId.split(":");
+ String type = split[0];
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+ } else if (isDownloadsDocument(uri)) {
+ String id = DocumentsContract.getDocumentId(uri);
+ Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+ return getDataColumn(context, contentUri, null, null);
+ } else if (isMediaDocument(uri)) {
+ String docId = DocumentsContract.getDocumentId(uri);
+ String[] split = docId.split(":");
+ String type = split[0];
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+ String selection = MediaStore.Images.Media._ID + "=?";
+ String[] selectionArgs = new String[]{split[1]};
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ }
+ } // MediaStore (and general)
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
+ return uriToFileApiQ(context,uri);
+ }
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ // Return the remote address
+ if (isGooglePhotosUri(uri)) {
+ return uri.getLastPathSegment();
+ }
+ return getDataColumn(context, uri, null, null);
+ }
+ // File
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+ return null;
+ }
+
+ //此方法 只能用于4.4以下的版本
+ private static String getRealFilePath(final Context context, final Uri uri) {
+ if (null == uri) {
+ return null;
+ }
+ final String scheme = uri.getScheme();
+ String data = null;
+ if (scheme == null) {
+ data = uri.getPath();
+ } else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+ data = uri.getPath();
+ } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
+ String[] projection = {MediaStore.Images.ImageColumns.DATA};
+ Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
+
+// Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
+ if (null != cursor) {
+ if (cursor.moveToFirst()) {
+ int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
+ if (index > -1) {
+ data = cursor.getString(index);
+ }
+ }
+ cursor.close();
+ }
+ }
+ return data;
+ }
+
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is ExternalStorageProvider.
+ */
+ private static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is DownloadsProvider.
+ */
+ private static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
+ Cursor cursor = null;
+ String column = MediaStore.Images.Media.DATA;
+ String[] projection = {column};
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ int index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(index);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is MediaProvider.
+ */
+ private static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is Google Photos.
+ */
+ private static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+
+
+ /**
+ * Android 10 以上适配 另一种写法
+ * @param context
+ * @param uri
+ * @return
+ */
+ private static String getFileFromContentUri(Context context, Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ String filePath;
+ String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME};
+ ContentResolver contentResolver = context.getContentResolver();
+ Cursor cursor = contentResolver.query(uri, filePathColumn, null,
+ null, null);
+ if (cursor != null) {
+ cursor.moveToFirst();
+ try {
+ filePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]));
+ return filePath;
+ } catch (Exception e) {
+ } finally {
+ cursor.close();
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Android 10 以上适配
+ * @param context
+ * @param uri
+ * @return
+ */
+ @RequiresApi(api = Build.VERSION_CODES.Q)
+ private static String uriToFileApiQ(Context context, Uri uri) {
+ File file = null;
+ //android10以上转换
+ if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
+ file = new File(uri.getPath());
+ } else if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ //把文件复制到沙盒目录
+ ContentResolver contentResolver = context.getContentResolver();
+ Cursor cursor = contentResolver.query(uri, null, null, null, null);
+ if (cursor.moveToFirst()) {
+ String displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+ try {
+ InputStream is = contentResolver.openInputStream(uri);
+ File cache = new File(context.getExternalCacheDir().getAbsolutePath(), Math.round((Math.random() + 1) * 1000) + displayName);
+ FileOutputStream fos = new FileOutputStream(cache);
+ FileUtils.copy(is, fos);
+ file = cache;
+ fos.close();
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return file.getAbsolutePath();
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileUtil.java
new file mode 100644
index 0000000..01214e5
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileUtil.java
@@ -0,0 +1,54 @@
+package com.bifan.txtreaderlib.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.MessageDigest;
+
+
+public class FileUtil {
+ public static String getMD5Checksum(String filePath) throws Exception {
+ byte[] b = createChecksum(filePath);
+ String result = "";
+ for (int i = 0; i < b.length; i++) {
+ result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
+ }
+ return result;
+ }
+
+
+ public static Boolean FileExist(String path) {
+ if (path != null && path.length() > 0) {
+ File file = new File(path);
+ return file.exists();
+ }
+ return false;
+ }
+
+ public static String getDefaultNameFromFilePath(String filePath) {
+ if (filePath == null || filePath.length() == 0) {
+ return "";
+ }
+ File file = new File(filePath);
+ if (file.exists()) {
+ return file.getName();
+ }
+ return filePath;
+ }
+
+ private static byte[] createChecksum(String filename) throws Exception {
+ InputStream fis = new FileInputStream(filename);
+ byte[] buffer = new byte[1024];
+ MessageDigest complete = MessageDigest.getInstance("MD5");
+ int numRead;
+ do {
+ numRead = fis.read(buffer);
+ if (numRead > 0) {
+ complete.update(buffer, 0, numRead);
+ }
+ } while (numRead != -1);
+
+ fis.close();
+ return complete.digest();
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FormatUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FormatUtil.java
new file mode 100644
index 0000000..a84efa6
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FormatUtil.java
@@ -0,0 +1,15 @@
+package com.bifan.txtreaderlib.utils;
+
+/*
+* create by bifan-wei
+* 2017-11-13
+*/
+public class FormatUtil {
+ public static Boolean isDigital(char Char) {
+ return Character.isDigit(Char);
+ }
+
+ public static Boolean isLetter(char Char) {
+ return (Char >= 65 && Char <= 90) || (Char >= 97 && Char <= 122);
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TextBreakUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TextBreakUtil.java
new file mode 100644
index 0000000..e9be714
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TextBreakUtil.java
@@ -0,0 +1,120 @@
+package com.bifan.txtreaderlib.utils;
+
+
+import android.graphics.Paint;
+
+public class TextBreakUtil {
+
+ /**
+ * @param cs
+ * @param measureWidth
+ * @param textPadding
+ * @param paint
+ * @return is[0] 为个数 is[1] 为是否满一行,其余为字符宽度
+ */
+ private static float[] BreakText(char[] cs, float measureWidth, float textPadding, Paint paint) {
+ float width = 0;
+ int index = 2;
+ float[] is = new float[index + cs.length];
+
+ for (int i = 0, size = cs.length; i < size; i++) {
+ String measureStr = String.valueOf(cs[i]);
+ float charWidth = paint.measureText(measureStr);
+ if ((width + textPadding + charWidth) >= measureWidth) {
+ is[0] = i;
+ is[1] = 1;
+ return is;
+ } else {
+ is[index++] = charWidth;
+ }
+
+ width += charWidth + textPadding;
+ }
+ is[0] = cs.length;
+ is[1] = 0;
+ return is;
+ }
+
+ /**
+ * @param cs
+ * @param measureHeight
+ * @param textPadding
+ * @param paint
+ * @return
+ */
+ private static float[] BreakTextVertical(char[] cs, float measureHeight, float textPadding, Paint paint) {
+ float height = 0;
+ int index = 2;
+ float[] is = new float[index + cs.length];
+ for (int i = 0, size = cs.length; i < size; i++) {
+ float charHeight = paint.getTextSize();
+ if ((height + textPadding + charHeight) >= measureHeight) {
+ is[0] = i;
+ is[1] = 1;
+ return is;
+ } else {
+ is[index++] = charHeight;
+ }
+
+ height += charHeight + textPadding;
+ }
+ is[0] = cs.length;
+ is[1] = 0;
+ return is;
+ }
+
+
+ /**
+ * @param text
+ * @param measureHeight
+ * @param textPadding
+ * @param paint
+ * @return
+ */
+ public static float[] BreakTextVertical(String text, float measureHeight, float textPadding, Paint paint) {
+ if (text == null || text.length() == 0) {
+ float[] is = new float[2];
+ is[0] = 0;
+ is[1] = 0;
+ return is;
+ }
+ return BreakTextVertical(text.toCharArray(), measureHeight, textPadding, paint);
+
+ }
+
+ /**
+ * @param text
+ * @param measureWidth
+ * @param textPadding
+ * @param paint
+ * @return
+ */
+ public static float[] BreakText(String text, float measureWidth, float textPadding, Paint paint) {
+ if (text == null || text.length() == 0) {
+ float[] is = new float[2];
+ is[0] = 0;
+ is[1] = 0;
+ return is;
+ }
+ return BreakText(text.toCharArray(), measureWidth, textPadding, paint);
+
+ }
+
+ /**
+ * @param text
+ * @param textPadding
+ * @param paint
+ * @return
+ */
+ public static float measureText(String text, float textPadding, Paint paint) {
+ if (text == null || text.length() == 0) return 0;
+ char[] cs = text.toCharArray();
+ float width = 0;
+ for (int i = 0, size = cs.length; i < size; i++) {
+ String measureStr = String.valueOf(cs[i]);
+ float charWidth = paint.measureText(measureStr);
+ width += charWidth + textPadding;
+ }
+ return width;
+ }
+}
diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TxtBitmapUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TxtBitmapUtil.java
new file mode 100644
index 0000000..27ea266
--- /dev/null
+++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TxtBitmapUtil.java
@@ -0,0 +1,148 @@
+package com.bifan.txtreaderlib.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.bifan.txtreaderlib.bean.EnChar;
+import com.bifan.txtreaderlib.bean.NumChar;
+import com.bifan.txtreaderlib.bean.TxtChar;
+import com.bifan.txtreaderlib.interfaces.IPage;
+import com.bifan.txtreaderlib.interfaces.ITxtLine;
+import com.bifan.txtreaderlib.main.PageParam;
+import com.bifan.txtreaderlib.main.PaintContext;
+import com.bifan.txtreaderlib.main.TxtConfig;
+
+import java.util.List;
+
+/**
+ * Created by bifan-wei
+ * on 2017/11/27.
+ */
+
+public class TxtBitmapUtil {
+ public static final Bitmap createHorizontalPage(Bitmap bg, PaintContext paintContext, PageParam pageParam, TxtConfig txtConfig, IPage page) {
+ if (page == null || !page.HasData() || bg == null || bg.isRecycled()) {
+ return null;
+ }
+ Bitmap bitmap = bg.copy(Bitmap.Config.RGB_565, true);
+ Canvas canvas = new Canvas(bitmap);
+ List lines = page.getLines();
+ int textHeight = txtConfig.textSize;
+ int lineHeight = textHeight + pageParam.LinePadding;
+ int topL = (int) (pageParam.PaddingLeft + pageParam.TextPadding) + 3;
+ int bottom = pageParam.PaddingTop + textHeight;
+ int bomL = bottom;
+ int paraMargin = pageParam.ParagraphMargin;
+ float CharPadding = pageParam.TextPadding;
+ Paint paint = paintContext.textPaint;
+ int defaultColor = txtConfig.textColor;
+
+ float x = topL;
+ float y = bottom;
+
+ if (!txtConfig.ShowSpecialChar) {
+ paint.setColor(defaultColor);
+ }
+ for (ITxtLine line : lines) {
+ if (line.HasData()) {
+ for (TxtChar txtChar : line.getTxtChars()) {
+ if (txtConfig.ShowSpecialChar) {
+ if (txtChar instanceof NumChar || txtChar instanceof EnChar) {
+ paint.setColor(txtChar.getTextColor());
+ } else {
+ paint.setColor(defaultColor);
+ }
+ }
+ canvas.drawText(txtChar.getValueStr(), x, y, paint);
+ txtChar.Left = (int) x;
+ txtChar.Right = (int) (x + txtChar.CharWidth);
+ txtChar.Bottom = (int) y + 5;
+ txtChar.Top = txtChar.Bottom - textHeight;
+ x = txtChar.Right + CharPadding;
+ }
+
+ x = topL;
+ y = y + lineHeight;
+
+ if (line.isParagraphEndLine()) {
+ y = y + paraMargin;
+
+ }
+ }
+ }
+
+ return bitmap;
+ }
+
+ public static final Bitmap createVerticalPage(Bitmap bg, PaintContext paintContext, PageParam pageParam, TxtConfig txtConfig, IPage page) {
+ if (page == null || !page.HasData() || bg == null || bg.isRecycled()) {
+ return null;
+ }
+
+ Bitmap bitmap = bg.copy(Bitmap.Config.RGB_565, true);
+ Canvas canvas = new Canvas(bitmap);
+ List lines = page.getLines();
+ int textHeight = txtConfig.textSize;
+ int lineWidth = (int) pageParam.LineWidth;
+ int bottom = pageParam.PaddingTop + textHeight;
+ float CharPadding = pageParam.TextPadding;
+ Paint paint = paintContext.textPaint;
+ int defaultColor = txtConfig.textColor;
+
+ float x = calculateX(pageParam, txtConfig, page);
+ float y = bottom;
+
+ if (!txtConfig.ShowSpecialChar) {
+ paint.setColor(defaultColor);
+ }
+ for (ITxtLine line : lines) {
+ if (line.HasData()) {
+ for (TxtChar txtChar : line.getTxtChars()) {
+ if (txtConfig.ShowSpecialChar) {
+ if (txtChar instanceof NumChar || txtChar instanceof EnChar) {
+ paint.setColor(txtChar.getTextColor());
+ } else {
+ paint.setColor(defaultColor);
+ }
+ }
+ canvas.drawText(txtChar.getValueStr(), x, y, paint);
+ txtChar.Left = (int) x;
+ txtChar.Right = (int) (x + textHeight + 5);
+ txtChar.Bottom = (int) (y + 5);
+ txtChar.Top = (int) (txtChar.Bottom - txtChar.CharWidth);
+ y = y + CharPadding + textHeight;
+ }
+ x = x - lineWidth;
+ y = bottom;
+ }
+ }
+
+ return bitmap;
+ }
+
+ private static float calculateX(PageParam pageParam, TxtConfig txtConfig, IPage page) {
+ int lineNum = page.getLineNum();
+ int margin = (pageParam.PageWidth - txtConfig.textSize * lineNum - pageParam.LinePadding * (lineNum - 1)) / 2;
+ return pageParam.PageWidth - margin - txtConfig.textSize;
+ }
+
+
+ public static Bitmap createBitmap(int bitmapStyleColor, int bitmapWidth, int bitmapHeight) {
+ int[] BitmapColor = getBitmapColor(bitmapStyleColor, bitmapWidth, bitmapHeight);
+ return Bitmap.createBitmap(BitmapColor, bitmapWidth, bitmapHeight, Bitmap.Config.RGB_565);
+ }
+
+ private static int[] getBitmapColor(int color, int with, int height) {
+ int[] colors = new int[with * height];
+ int STRIDE = height;
+ int c = color;
+ for (int y = 0; y < with; y++) {// use of x,y is legible then // the //
+ for (int x = 0; x < height; x++) {
+ colors[y * STRIDE + x] = c;// the shift operation generates
+ }
+ }
+ return colors;
+ }
+
+}
diff --git a/hwtxtreaderlib/src/main/res/drawable/bg_message_bot.xml b/hwtxtreaderlib/src/main/res/drawable/bg_message_bot.xml
new file mode 100644
index 0000000..685a315
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/drawable/bg_message_bot.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/hwtxtreaderlib/src/main/res/drawable/bg_message_user.xml b/hwtxtreaderlib/src/main/res/drawable/bg_message_user.xml
new file mode 100644
index 0000000..45298d1
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/drawable/bg_message_user.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/hwtxtreaderlib/src/main/res/drawable/chatbot.png b/hwtxtreaderlib/src/main/res/drawable/chatbot.png
new file mode 100644
index 0000000..92f77f6
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/chatbot.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_bold_normal.png b/hwtxtreaderlib/src/main/res/drawable/ic_bold_normal.png
new file mode 100644
index 0000000..1e2d7fa
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_bold_normal.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_bold_selected.png b/hwtxtreaderlib/src/main/res/drawable/ic_bold_selected.png
new file mode 100644
index 0000000..f9cd235
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_bold_selected.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_chapter.png b/hwtxtreaderlib/src/main/res/drawable/ic_chapter.png
new file mode 100644
index 0000000..880260d
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_chapter.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_copy.png b/hwtxtreaderlib/src/main/res/drawable/ic_copy.png
new file mode 100644
index 0000000..bbe1f5d
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_copy.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_key_add.png b/hwtxtreaderlib/src/main/res/drawable/ic_key_add.png
new file mode 100644
index 0000000..ea27b34
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_key_add.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_key_back.png b/hwtxtreaderlib/src/main/res/drawable/ic_key_back.png
new file mode 100644
index 0000000..1be2480
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_key_back.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_key_del.png b/hwtxtreaderlib/src/main/res/drawable/ic_key_del.png
new file mode 100644
index 0000000..045c4be
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_key_del.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_regenerate.png b/hwtxtreaderlib/src/main/res/drawable/ic_regenerate.png
new file mode 100644
index 0000000..bb76d4f
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_regenerate.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_setting.png b/hwtxtreaderlib/src/main/res/drawable/ic_setting.png
new file mode 100644
index 0000000..76cb1b4
Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_setting.png differ
diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_chapter_msg.xml b/hwtxtreaderlib/src/main/res/drawable/shape_chapter_msg.xml
new file mode 100644
index 0000000..f69c998
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/drawable/shape_chapter_msg.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_little_ball.xml b/hwtxtreaderlib/src/main/res/drawable/shape_little_ball.xml
new file mode 100644
index 0000000..7b7f893
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/drawable/shape_little_ball.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_selected.xml b/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_selected.xml
new file mode 100644
index 0000000..cad31ac
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_selected.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_unselected.xml b/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_unselected.xml
new file mode 100644
index 0000000..4d8cd80
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_unselected.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_seekbar_thumb.xml b/hwtxtreaderlib/src/main/res/drawable/shape_seekbar_thumb.xml
new file mode 100644
index 0000000..1e064c8
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/drawable/shape_seekbar_thumb.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/drawable/txtview_po_seekbar.xml b/hwtxtreaderlib/src/main/res/drawable/txtview_po_seekbar.xml
new file mode 100644
index 0000000..3576730
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/drawable/txtview_po_seekbar.xml
@@ -0,0 +1,26 @@
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/layout/activity_chatwithgpt.xml b/hwtxtreaderlib/src/main/res/layout/activity_chatwithgpt.xml
new file mode 100644
index 0000000..4ff46d3
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/layout/activity_chatwithgpt.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hwtxtreaderlib/src/main/res/layout/activity_hwtxtpaly.xml b/hwtxtreaderlib/src/main/res/layout/activity_hwtxtpaly.xml
new file mode 100644
index 0000000..4793e19
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/layout/activity_hwtxtpaly.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/layout/adapter_chapterlist.xml b/hwtxtreaderlib/src/main/res/layout/adapter_chapterlist.xml
new file mode 100644
index 0000000..101cb37
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/layout/adapter_chapterlist.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/layout/item_message_bot.xml b/hwtxtreaderlib/src/main/res/layout/item_message_bot.xml
new file mode 100644
index 0000000..ea6377d
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/layout/item_message_bot.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/layout/item_message_user.xml b/hwtxtreaderlib/src/main/res/layout/item_message_user.xml
new file mode 100644
index 0000000..af3631a
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/layout/item_message_user.xml
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/hwtxtreaderlib/src/main/res/layout/view_chapter_msg.xml b/hwtxtreaderlib/src/main/res/layout/view_chapter_msg.xml
new file mode 100644
index 0000000..85f4ff6
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/layout/view_chapter_msg.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/layout/view_menu_bottom.xml b/hwtxtreaderlib/src/main/res/layout/view_menu_bottom.xml
new file mode 100644
index 0000000..a16eacb
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/layout/view_menu_bottom.xml
@@ -0,0 +1,346 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/layout/view_menu_top.xml b/hwtxtreaderlib/src/main/res/layout/view_menu_top.xml
new file mode 100644
index 0000000..d622c4e
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/layout/view_menu_top.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/values/attrs.xml b/hwtxtreaderlib/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..a7de66c
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/values/attrs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/values/colors.xml b/hwtxtreaderlib/src/main/res/values/colors.xml
new file mode 100644
index 0000000..ee9e1b4
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/values/colors.xml
@@ -0,0 +1,21 @@
+
+
+ #ffffff
+ #ffffff
+ #ccebcc
+ #d4c7a5
+ #393330
+ #00141f
+ #767678
+
+
+ #ffffff
+ #aaffffff
+ #22ffffff
+ #000000
+ #858582
+ #858582
+ #00000000
+ #7e7e7e
+ #0a0a12
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/values/dimen.xml b/hwtxtreaderlib/src/main/res/values/dimen.xml
new file mode 100644
index 0000000..1367409
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/values/dimen.xml
@@ -0,0 +1,31 @@
+
+
+ 13sp
+ 14sp
+ 16sp
+ 10dp
+ 5dp
+ 20dp
+ 13sp
+ 21sp
+ 1px
+ 3dp
+ 5dp
+ 10dp
+ 12dp
+ 13dp
+ 17dp
+ 20dp
+ 21dp
+ 15dp
+ 16dp
+ 18dp
+ 25dp
+ 30dp
+ 35dp
+ 50dp
+ 55dp
+ 80dp
+ 14dp
+
+
\ No newline at end of file
diff --git a/hwtxtreaderlib/src/main/res/values/strings.xml b/hwtxtreaderlib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e7ff11d
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/values/strings.xml
@@ -0,0 +1,16 @@
+
+ HwTxtReaderLib
+ 0%
+ 第一章
+ 选中0个字
+ 复制
+ 上一章
+ 下一章
+ 字体
+ 滑盖
+ 平移
+ 剪切
+ 翻页
+ 风格
+ 选中%d个文字
+
diff --git a/hwtxtreaderlib/src/main/res/values/styles.xml b/hwtxtreaderlib/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7f6e909
--- /dev/null
+++ b/hwtxtreaderlib/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index 3e43307..9d4d28c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,3 +8,4 @@ dependencyResolutionManagement {
}
rootProject.name = "Jianshu"
include ':app'
+include ':hwtxtreaderlib'