实现了AI功能,待完善
This commit is contained in:
parent
5e32755d48
commit
54a8a8304d
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@ -11,6 +11,7 @@
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/hwtxtreaderlib" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
|
@ -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'
|
||||
}
|
@ -12,13 +12,9 @@
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- 设备休眠 -->
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- sd卡存储 -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" /> <!-- 摄像头 -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- 录音 -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@ -28,18 +24,18 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Jianshu"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity android:name=".GoodBookActivity"/>
|
||||
<activity android:name=".HotBookActivity"/>
|
||||
<activity android:name=".GoodBookActivity" />
|
||||
<activity android:name=".HotBookActivity" />
|
||||
<activity android:name=".BookInformationActivity" />
|
||||
<activity android:name=".PerceptionActivity" />
|
||||
<activity android:name=".FindpwdActivity"/>
|
||||
<activity android:name=".LiteratureActivity"/>
|
||||
<activity android:name=".PsychologyActivity"/>
|
||||
<activity android:name=".ManagementActivity"/>
|
||||
<activity android:name=".PhilosophyActivity"/>
|
||||
<activity android:name=".FindpwdActivity" />
|
||||
<activity android:name=".LiteratureActivity" />
|
||||
<activity android:name=".PsychologyActivity" />
|
||||
<activity android:name=".ManagementActivity" />
|
||||
<activity android:name=".PhilosophyActivity" />
|
||||
<!-- <activity android:name=".BookShelfActivity"/>-->
|
||||
<activity android:name=".ScienceActivity"/>
|
||||
<activity android:name=".Author_introductionActivity"/>
|
||||
<activity android:name=".ScienceActivity" />
|
||||
<activity android:name=".Author_introductionActivity" />
|
||||
<!-- <activity android:name=".MainActivity"-->
|
||||
<!-- android:exported="true"-->
|
||||
<!-- android:label="@string/app_name"-->
|
||||
@ -50,23 +46,23 @@
|
||||
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </activity>-->
|
||||
<activity android:name=".SendSpecificActivity"></activity>
|
||||
<activity android:name=".BookIntroActivity"/>
|
||||
<activity android:name=".SendSpecificActivity" />
|
||||
<activity android:name=".BookIntroActivity" />
|
||||
<activity android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboard|keyboardHidden|navigation" />
|
||||
<activity android:name=".RegisterActivity" />
|
||||
<activity android:name=".BookShelfActivity"/>
|
||||
<activity android:name=".Activity.Perception_informationActivity"/>
|
||||
<activity android:name=".Activity.sendPerceptionActivity"/>
|
||||
<activity android:name=".MyActivity"/>
|
||||
<activity android:name=".Activity.MyPostActivity"/>
|
||||
<activity android:name=".ReEmailActivity"/>
|
||||
<activity android:name=".RePhoneActivity"/>
|
||||
<activity android:name=".ReMyinfoActivity"/>
|
||||
<activity android:name=".ReNameActivity"/>
|
||||
<activity android:name=".ReSexActivity"/>
|
||||
<activity android:name=".ReSignatureActivity"/>
|
||||
<activity android:name=".Activity.AccountManagerActivity"/>
|
||||
<activity android:name=".BookShelfActivity" />
|
||||
<activity android:name=".Activity.Perception_informationActivity" />
|
||||
<activity android:name=".Activity.sendPerceptionActivity" />
|
||||
<activity android:name=".MyActivity" />
|
||||
<activity android:name=".Activity.MyPostActivity" />
|
||||
<activity android:name=".ReEmailActivity" />
|
||||
<activity android:name=".RePhoneActivity" />
|
||||
<activity android:name=".ReMyinfoActivity" />
|
||||
<activity android:name=".ReNameActivity" />
|
||||
<activity android:name=".ReSexActivity" />
|
||||
<activity android:name=".ReSignatureActivity" />
|
||||
<activity android:name=".Activity.AccountManagerActivity" />
|
||||
<activity android:name=".ReAddressActivity"/>
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
@ -77,6 +73,16 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.bifan.txtreaderlib.ui.HwTxtPlayActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/HwTxtFullScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider
|
||||
android:name="cn.bmob.v3.util.BmobContentProvider"
|
||||
android:authorities="com.zjgsu.jianshu.BmobContentProvider">
|
||||
|
@ -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<Perception_bmob>()
|
||||
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<Bookcontent_bmob>()
|
||||
querybook.addWhereEqualTo("bookname", bookName)
|
||||
querybook.findObjects(object : FindListener<Bookcontent_bmob>() {
|
||||
override fun done(books: MutableList<Bookcontent_bmob>?, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -107,9 +107,6 @@ class MyActivity : AppCompatActivity() {
|
||||
reading_comprehension.setOnClickListener {
|
||||
NavigationHelper.navigateTo(this, PerceptionActivity::class.java)
|
||||
}
|
||||
homepage.setOnClickListener {
|
||||
NavigationHelper.navigateTo(this, MyActivity::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ class BillboardAdapter(var listOfbookRankList: List<List<Book_rank>>) : 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()
|
||||
|
@ -71,8 +71,6 @@ class PerceptionAdapter(val perceptionList: List<Perception>) :
|
||||
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<Perception>) :
|
||||
}
|
||||
}
|
||||
})
|
||||
//加载书籍封面
|
||||
// query.findObjects(object : FindListener<Perception_bmob>() {
|
||||
// override fun done(p0: MutableList<Perception_bmob>?, 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<Book_info_bmob>()
|
||||
// queryBook.findObjects(object : FindListener<Book_info_bmob>() {
|
||||
// override fun done(list: List<Book_info_bmob>, 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)
|
||||
|
@ -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=""
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
BIN
app/src/main/res/drawable/chatbot.png
Normal file
BIN
app/src/main/res/drawable/chatbot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/drawable/ic_copy.png
Normal file
BIN
app/src/main/res/drawable/ic_copy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 311 B |
BIN
app/src/main/res/drawable/ic_regenerate.png
Normal file
BIN
app/src/main/res/drawable/ic_regenerate.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 585 B |
@ -29,8 +29,10 @@
|
||||
|
||||
<!-- SwipeRefreshLayout 包裹 NestedScrollView -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefresh"
|
||||
android:layout_width="match_parent"
|
||||
@ -58,7 +60,7 @@
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/horizontal_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
@ -83,14 +85,15 @@
|
||||
|
||||
<!-- 底部导航栏布局 -->
|
||||
<include
|
||||
android:id="@+id/layout_bottom_navigation"
|
||||
layout="@layout/layout_bottom_navigation"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/layout_bottom_navigation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<!-- 左侧导航栏 -->
|
||||
|
17
app/src/main/res/layout/fragment_content.xml
Normal file
17
app/src/main/res/layout/fragment_content.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:id="@+id/textViewContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="16sp"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:padding="16dp"
|
||||
android:scrollbars="vertical"
|
||||
android:textIsSelectable="true" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
2
app/src/main/res/values/attrs.xml
Normal file
2
app/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
@ -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
|
||||
}
|
1
gradle/wrapper/gradle-wrapper.properties
vendored
1
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
||||
|
1
hwtxtreaderlib/.gitignore
vendored
Normal file
1
hwtxtreaderlib/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
33
hwtxtreaderlib/build.gradle
Normal file
33
hwtxtreaderlib/build.gradle
Normal file
@ -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'
|
||||
|
||||
}
|
BIN
hwtxtreaderlib/libs/chardet.jar
Normal file
BIN
hwtxtreaderlib/libs/chardet.jar
Normal file
Binary file not shown.
25
hwtxtreaderlib/proguard-rules.pro
vendored
Normal file
25
hwtxtreaderlib/proguard-rules.pro
vendored
Normal file
@ -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
|
24
hwtxtreaderlib/src/main/AndroidManifest.xml
Normal file
24
hwtxtreaderlib/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.bifan.txtreaderlib">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true">
|
||||
<activity android:name=".ui.ChatWithGptActivity"></activity>
|
||||
<activity
|
||||
android:name="com.bifan.txtreaderlib.ui.HwTxtPlayActivity"
|
||||
android:theme="@style/HwTxtFullScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
||||
</manifest>
|
1
hwtxtreaderlib/src/main/assets/config.properties
Normal file
1
hwtxtreaderlib/src/main/assets/config.properties
Normal file
@ -0,0 +1 @@
|
||||
auth_key=sk-No44eOPA2V17ZQmuD897C61b905743B9Aa2d83B6EeF2C38a
|
BIN
hwtxtreaderlib/src/main/assets/fonts/text_style.TTF
Normal file
BIN
hwtxtreaderlib/src/main/assets/fonts/text_style.TTF
Normal file
Binary file not shown.
@ -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<MessageAdapter.MessageViewHolder> {
|
||||
private List<Message> messages;
|
||||
|
||||
public MessageAdapter(List<Message> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<Message> messages;
|
||||
private Integer maxTokens;
|
||||
private Double temperature;
|
||||
|
||||
public ChatRequest(List<Message> 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<Message> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Choice> choices;
|
||||
|
||||
public static class Choice {
|
||||
int index;
|
||||
public Message message;
|
||||
// 你可以根据需要继续添加其他字段如 logprobs, finish_reason 等
|
||||
}
|
||||
|
||||
public static class Message {
|
||||
String role;
|
||||
public String content;
|
||||
}
|
||||
|
||||
// 假设你已经有 getter 方法或者你可以自行添加
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<ITxtLine> {
|
||||
private int CurrentIndex;
|
||||
private List<ITxtLine> 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<ITxtLine> 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<ITxtLine> 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<ITxtLine> getLines() {
|
||||
return lines;
|
||||
}
|
||||
}
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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<TxtChar> {
|
||||
private int CurrentIndex;
|
||||
private List<TxtChar> chars = null;
|
||||
private boolean isParagraphEndLine = false;
|
||||
|
||||
public TxtLine() {
|
||||
}
|
||||
@Override
|
||||
public int CurrentIndex() {
|
||||
return CurrentIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean HasData() {
|
||||
return getCount() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TxtChar> 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<TxtChar> 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.bifan.txtreaderlib.bean;
|
||||
|
||||
/*
|
||||
* create by bifan-wei
|
||||
* 2017-11-13
|
||||
*/
|
||||
public enum TxtMsg {
|
||||
InitError,
|
||||
FileNoExist,
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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<ChatResponse> postMessage(@Body ChatRequest request);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.bifan.txtreaderlib.interfaces;
|
||||
|
||||
/*
|
||||
* create by bifan-wei
|
||||
* 2017-11-13
|
||||
*/
|
||||
public interface ICursor<T> {
|
||||
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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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<ITxtLine> lines);
|
||||
TxtChar getFirstChar();
|
||||
TxtChar getLastChar();
|
||||
ITxtLine getFirstLine();
|
||||
ITxtLine getLastLine();
|
||||
List<ITxtLine> getLines();
|
||||
ICursor<ITxtLine> getLineCursor();
|
||||
int getLineNum();
|
||||
boolean isFullPage();//是否满页了
|
||||
void setFullPage(boolean fullPage);
|
||||
int CurrentIndex();
|
||||
Boolean HasData();
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.bifan.txtreaderlib.interfaces;
|
||||
|
||||
/**
|
||||
* Created by bifan-wei
|
||||
* on 2017/12/8.
|
||||
*/
|
||||
|
||||
public interface IPageChangeListener {
|
||||
void onCurrentPage(float progress);
|
||||
}
|
@ -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);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.bifan.txtreaderlib.interfaces;
|
||||
|
||||
/**
|
||||
* created by : bifan-wei
|
||||
*/
|
||||
|
||||
public interface IPageEdgeListener {
|
||||
void onCurrentFirstPage();
|
||||
void onCurrentLastPage();
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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();
|
||||
//完成移动
|
||||
}
|
@ -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();
|
||||
}
|
@ -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<ITxtLine> selectedLines, Canvas canvas, Paint paint);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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<TxtChar> getTxtChars();
|
||||
int getCharNum();
|
||||
TxtChar getFirstChar();
|
||||
TxtChar getLastChar();
|
||||
TxtChar getChar(int index);
|
||||
ICursor<TxtChar> getCharCursor();
|
||||
String getLineStr();
|
||||
char[] getLineChar();
|
||||
Boolean HasData();
|
||||
void addChar(TxtChar txtChar);
|
||||
void Clear();
|
||||
int CurrentIndex();
|
||||
boolean isParagraphEndLine();//判断是否是段落最后一句
|
||||
void setParagraphEndLine(boolean isParagraphEndLine);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<FileReadRecordBean> getRecord(String userId) {
|
||||
List<FileReadRecordBean> 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<FileReadRecordBean> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<ITxtLine> 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<ITxtLine> 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<ITxtLine> 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<ITxtLine> 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<ITxtLine> 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<ITxtLine> getLineFromParagraphStartOnBeginning(String paragraphData, int paragraphIndex, int startCharIndex,
|
||||
float lineWidth, float textPadding, Paint paint) {
|
||||
List<ITxtLine> 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<ITxtLine> getLineFromParagraphOnEnd(String paragraphData, int paragraphIndex, int endCharIndex,
|
||||
float lineWidth, float textPadding, Paint paint) {
|
||||
List<ITxtLine> 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<Integer, String> paragraph = new HashMap<>();
|
||||
private List<Integer> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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<IChapter> 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<IChapter> getChapters() {
|
||||
return chapters;
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setChapters(List<IChapter> 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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<IChapter> 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<IChapter> 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<IChapter> 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<IChapter> getChapters() {
|
||||
return readerContext.getChapters();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param progress 获取指定进度的章节信息
|
||||
* @return
|
||||
*/
|
||||
public IChapter getChapterFromProgress(int progress) {
|
||||
List<IChapter> 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());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
@ -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<ITxtLine> 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<ITxtLine> 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<ITxtLine> 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<ITxtLine> 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<ITxtLine> getLineFromParagraphOnEnd(String paragraphData, int paragraphIndex, int endCharIndex,
|
||||
float lineHeight, float textPadding, Paint paint) {
|
||||
List<ITxtLine> 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<ITxtLine> getLineFromParagraphStartOnBeginning(String paragraphData, int paragraphIndex, int startCharIndex,
|
||||
float lineHeight, float textPadding, Paint paint) {
|
||||
List<ITxtLine> 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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<IChapter> 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<IChapter> 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<IChapter> 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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<IChapter> 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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<IChapter> mChapters;
|
||||
private int CurrentIndex = -1;
|
||||
private int AllCharNum ;
|
||||
private int ViewHeight;
|
||||
|
||||
public ChapterList(Context mContext, int ViewHeight, List<IChapter> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<Message> 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<String>() {
|
||||
@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<ChatMessage_bmob> bmobQuery = new BmobQuery<>();
|
||||
bmobQuery.addWhereEqualTo("relatedBook", bookName);
|
||||
bmobQuery.addWhereEqualTo("userid", userid);
|
||||
bmobQuery.findObjects(new FindListener<ChatMessage_bmob>() {
|
||||
@Override
|
||||
public void done(List<ChatMessage_bmob> 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<String>() {
|
||||
@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<ChatRequest.Message> loadMessagesFromDatabase(String bookName, String userId) {
|
||||
List<ChatRequest.Message> messages = new ArrayList<>();
|
||||
BmobQuery<ChatMessage_bmob> bmobQuery = new BmobQuery<>();
|
||||
bmobQuery.addWhereEqualTo("relatedBook", bookName);
|
||||
bmobQuery.addWhereEqualTo("userid", userId);
|
||||
bmobQuery.order("createdAt");
|
||||
bmobQuery.setLimit(10); //设置上下文最多10条历史对话
|
||||
bmobQuery.findObjects(new FindListener<ChatMessage_bmob>() {
|
||||
@Override
|
||||
public void done(List<ChatMessage_bmob> 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<ChatRequest.Message> 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<ChatResponse>() {
|
||||
@Override
|
||||
public void onResponse(Call<ChatResponse> call, Response<ChatResponse> 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<String>() {
|
||||
@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<ChatResponse> call, Throwable t) {
|
||||
Log.e("ChatGPT", "Failure: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 + "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user