parent
c34a46e529
commit
aeaa853ece
@ -0,0 +1,12 @@ |
|||||||
|
package xyz.fycz.myreader.entity |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/5 20:18 |
||||||
|
*/ |
||||||
|
data class SearchWord1( |
||||||
|
var bookId: String, |
||||||
|
var chapterNum: Int, |
||||||
|
var chapterTitle: String, |
||||||
|
var searchWord2List: MutableList<SearchWord2> |
||||||
|
) |
@ -0,0 +1,14 @@ |
|||||||
|
package xyz.fycz.myreader.entity |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/5 20:03 |
||||||
|
*/ |
||||||
|
data class SearchWord2( |
||||||
|
var keyword: String, |
||||||
|
var chapterNum: Int, |
||||||
|
var dataStr: String, |
||||||
|
var dataIndex: Int, |
||||||
|
var index: Int, |
||||||
|
var count: Int |
||||||
|
) |
@ -0,0 +1,195 @@ |
|||||||
|
package xyz.fycz.myreader.model |
||||||
|
|
||||||
|
import io.reactivex.* |
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers |
||||||
|
import io.reactivex.disposables.CompositeDisposable |
||||||
|
import io.reactivex.disposables.Disposable |
||||||
|
import io.reactivex.schedulers.Schedulers |
||||||
|
import xyz.fycz.myreader.R |
||||||
|
import xyz.fycz.myreader.application.App |
||||||
|
import xyz.fycz.myreader.entity.SearchWord1 |
||||||
|
import xyz.fycz.myreader.entity.SearchWord2 |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book |
||||||
|
import xyz.fycz.myreader.greendao.entity.Chapter |
||||||
|
import xyz.fycz.myreader.greendao.service.ChapterService |
||||||
|
import xyz.fycz.myreader.util.SharedPreUtils |
||||||
|
import xyz.fycz.myreader.util.ToastUtils |
||||||
|
import xyz.fycz.myreader.util.help.ChapterContentHelp |
||||||
|
import xyz.fycz.myreader.widget.page.PageLoader |
||||||
|
import java.io.File |
||||||
|
import java.util.concurrent.ExecutorService |
||||||
|
import java.util.concurrent.Executors |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/5 21:17 |
||||||
|
*/ |
||||||
|
class SearchWordEngine( |
||||||
|
private val book: Book, |
||||||
|
private val chapters: List<Chapter>, |
||||||
|
private val pageLoader: PageLoader |
||||||
|
) { |
||||||
|
private val TAG = "SearchWordEngine" |
||||||
|
|
||||||
|
//线程池 |
||||||
|
private var executorService: ExecutorService |
||||||
|
|
||||||
|
private var scheduler: Scheduler |
||||||
|
private var compositeDisposable: CompositeDisposable |
||||||
|
private lateinit var searchListener: OnSearchListener |
||||||
|
private val threadsNum = |
||||||
|
SharedPreUtils.getInstance().getInt(App.getmContext().getString(R.string.threadNum), 8); |
||||||
|
private var searchSiteIndex = 0 |
||||||
|
private var searchSuccessNum = 0 |
||||||
|
private var searchFinishNum = 0 |
||||||
|
private var isLocalBook = false |
||||||
|
|
||||||
|
fun setOnSearchListener(searchListener: OnSearchListener) { |
||||||
|
this.searchListener = searchListener |
||||||
|
} |
||||||
|
|
||||||
|
init { |
||||||
|
executorService = Executors.newFixedThreadPool(threadsNum) |
||||||
|
scheduler = Schedulers.from(executorService) |
||||||
|
compositeDisposable = CompositeDisposable() |
||||||
|
} |
||||||
|
|
||||||
|
fun stopSearch() { |
||||||
|
compositeDisposable.dispose() |
||||||
|
compositeDisposable = CompositeDisposable() |
||||||
|
searchListener.loadFinish(searchSuccessNum == 0) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 关闭引擎 |
||||||
|
*/ |
||||||
|
fun closeSearchEngine() { |
||||||
|
executorService.shutdown() |
||||||
|
if (!compositeDisposable.isDisposed) compositeDisposable.dispose() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索关键字(模糊搜索) |
||||||
|
* |
||||||
|
* @param keyword |
||||||
|
*/ |
||||||
|
fun search(keyword: String) { |
||||||
|
if ("本地书籍" == book.type) { |
||||||
|
isLocalBook = true |
||||||
|
if (!File(book.chapterUrl).exists()) { |
||||||
|
ToastUtils.showWarring("当前书籍源文件不存在,无法搜索!") |
||||||
|
searchListener.loadFinish(true) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
if (chapters.isEmpty()) { |
||||||
|
ToastUtils.showWarring("当前书籍章节目录为空,无法搜索!") |
||||||
|
searchListener.loadFinish(true) |
||||||
|
return |
||||||
|
} |
||||||
|
searchSuccessNum = 0 |
||||||
|
searchSiteIndex = -1 |
||||||
|
searchFinishNum = 0 |
||||||
|
for (i in 0 until Math.min(threadsNum, chapters.size)) { |
||||||
|
searchOnEngine(keyword) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
private fun searchOnEngine(keyword: String) { |
||||||
|
searchSiteIndex++ |
||||||
|
if (searchSiteIndex < chapters.size) { |
||||||
|
val chapterNum = searchSiteIndex |
||||||
|
val chapter = chapters[chapterNum] |
||||||
|
Observable.create(ObservableOnSubscribe<SearchWord1> { emitter -> |
||||||
|
val searchWord1 = |
||||||
|
SearchWord1(book.id, chapterNum, chapter.title, mutableListOf()) |
||||||
|
if (!isLocalBook && !ChapterService.isChapterCached(book.id, chapter.title)) { |
||||||
|
emitter.onNext(searchWord1) |
||||||
|
return@ObservableOnSubscribe |
||||||
|
} |
||||||
|
var content = pageLoader.getChapterReader(chapter) |
||||||
|
content = pageLoader.contentHelper.replaceContent( |
||||||
|
book.name + "-" + book.author, |
||||||
|
book.source, |
||||||
|
content, |
||||||
|
true |
||||||
|
) |
||||||
|
if (book.reSeg) { |
||||||
|
content = ChapterContentHelp.LightNovelParagraph2(content, chapter.title) |
||||||
|
} |
||||||
|
val allLine: List<String> = content.split("\n") |
||||||
|
var count = 0 |
||||||
|
allLine.forEach { |
||||||
|
var index: Int = -1 |
||||||
|
while (it.indexOf(keyword, index + 1).also { index = it } != -1) { |
||||||
|
var leftI = 0 |
||||||
|
var rightI = it.length |
||||||
|
var leftS = "" |
||||||
|
var rightS = "" |
||||||
|
if (leftI < index - 20) { |
||||||
|
leftI = index - 20 |
||||||
|
leftS = "..." |
||||||
|
} |
||||||
|
if (rightI > index + 20) { |
||||||
|
rightI = index + 20 |
||||||
|
rightS = "..." |
||||||
|
} |
||||||
|
val str = leftS + it.substring(leftI, rightI) + rightS |
||||||
|
val searchWord2 = |
||||||
|
SearchWord2( |
||||||
|
keyword, |
||||||
|
chapterNum, |
||||||
|
str, |
||||||
|
index - leftI + leftS.length, |
||||||
|
index, |
||||||
|
count |
||||||
|
) |
||||||
|
searchWord1.searchWord2List.add(searchWord2) |
||||||
|
count++ |
||||||
|
} |
||||||
|
} |
||||||
|
emitter.onNext(searchWord1) |
||||||
|
emitter.onComplete() |
||||||
|
}).subscribeOn(scheduler).observeOn(AndroidSchedulers.mainThread()) |
||||||
|
.subscribe(object : Observer<SearchWord1?> { |
||||||
|
override fun onSubscribe(d: Disposable) { |
||||||
|
compositeDisposable.add(d) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onNext(searchWord1: SearchWord1) { |
||||||
|
searchFinishNum++ |
||||||
|
if (searchWord1.searchWord2List.isNotEmpty()) { |
||||||
|
searchSuccessNum++ |
||||||
|
searchListener.loadMore(searchWord1) |
||||||
|
} |
||||||
|
searchOnEngine(keyword) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onError(e: Throwable) { |
||||||
|
searchFinishNum++ |
||||||
|
searchOnEngine(keyword) |
||||||
|
if (App.isDebug()) e.printStackTrace() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onComplete() { |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
} else { |
||||||
|
if (searchFinishNum == chapters.size) { |
||||||
|
if (searchSuccessNum == 0) { |
||||||
|
ToastUtils.showWarring("搜索结果为空") |
||||||
|
searchListener.loadFinish(true) |
||||||
|
} else { |
||||||
|
searchListener.loadFinish(false) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface OnSearchListener { |
||||||
|
fun loadFinish(isEmpty: Boolean) |
||||||
|
fun loadMore(item: SearchWord1) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,155 @@ |
|||||||
|
package xyz.fycz.myreader.ui.activity |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.KeyEvent |
||||||
|
import android.view.View |
||||||
|
import android.view.inputmethod.EditorInfo |
||||||
|
import android.view.inputmethod.InputMethodManager |
||||||
|
import androidx.appcompat.widget.Toolbar |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import org.jetbrains.anko.contentView |
||||||
|
import xyz.fycz.myreader.R |
||||||
|
import xyz.fycz.myreader.application.App |
||||||
|
import xyz.fycz.myreader.base.BaseActivity |
||||||
|
import xyz.fycz.myreader.base.BitIntentDataManager |
||||||
|
import xyz.fycz.myreader.base.adapter.BaseListAdapter |
||||||
|
import xyz.fycz.myreader.base.adapter.IViewHolder |
||||||
|
import xyz.fycz.myreader.base.adapter2.onClick |
||||||
|
import xyz.fycz.myreader.common.APPCONST |
||||||
|
import xyz.fycz.myreader.databinding.ActivitySearchWordBinding |
||||||
|
import xyz.fycz.myreader.entity.SearchWord1 |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book |
||||||
|
import xyz.fycz.myreader.greendao.entity.Chapter |
||||||
|
import xyz.fycz.myreader.model.SearchWordEngine |
||||||
|
import xyz.fycz.myreader.ui.adapter.holder.SearchWord1Holder |
||||||
|
import xyz.fycz.myreader.ui.adapter.holder.SearchWord2Holder |
||||||
|
import xyz.fycz.myreader.util.ToastUtils |
||||||
|
import xyz.fycz.myreader.widget.page.PageLoader |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/5 19:57 |
||||||
|
*/ |
||||||
|
class SearchWordActivity : BaseActivity() { |
||||||
|
|
||||||
|
private lateinit var binding: ActivitySearchWordBinding |
||||||
|
private lateinit var book: Book |
||||||
|
private lateinit var searchWordEngine: SearchWordEngine |
||||||
|
private lateinit var adapter: BaseListAdapter<SearchWord1> |
||||||
|
|
||||||
|
override fun bindView() { |
||||||
|
binding = ActivitySearchWordBinding.inflate(layoutInflater) |
||||||
|
setContentView(binding.root) |
||||||
|
} |
||||||
|
|
||||||
|
override fun setUpToolbar(toolbar: Toolbar?) { |
||||||
|
super.setUpToolbar(toolbar) |
||||||
|
setStatusBarColor(R.color.colorPrimary, true) |
||||||
|
supportActionBar?.title = getString(R.string.search_word) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onWindowFocusChanged(hasFocus: Boolean) { |
||||||
|
super.onWindowFocusChanged(hasFocus) |
||||||
|
if (hasFocus) { |
||||||
|
App.getHandler().postDelayed({ |
||||||
|
binding.etSearchKey.requestFocus() |
||||||
|
val imm = |
||||||
|
getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager |
||||||
|
imm.showSoftInput( |
||||||
|
binding.etSearchKey, |
||||||
|
InputMethodManager.SHOW_IMPLICIT |
||||||
|
) |
||||||
|
}, 400) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun initData(savedInstanceState: Bundle?) { |
||||||
|
super.initData(savedInstanceState) |
||||||
|
book = BitIntentDataManager.getInstance().getData(APPCONST.BOOK_KEY) as Book |
||||||
|
val chapters = |
||||||
|
BitIntentDataManager.getInstance().getData(APPCONST.CHAPTERS_KEY) as List<Chapter> |
||||||
|
val pageLoader = |
||||||
|
BitIntentDataManager.getInstance().getData(APPCONST.PAGE_LOADER_KEY) as PageLoader |
||||||
|
searchWordEngine = SearchWordEngine(book, chapters, pageLoader) |
||||||
|
} |
||||||
|
|
||||||
|
override fun initWidget() { |
||||||
|
//enter事件 |
||||||
|
binding.etSearchKey.setOnEditorActionListener { _, i, keyEvent -> |
||||||
|
if (i == EditorInfo.IME_ACTION_UNSPECIFIED) { |
||||||
|
search() |
||||||
|
return@setOnEditorActionListener keyEvent.keyCode == KeyEvent.KEYCODE_ENTER |
||||||
|
} |
||||||
|
false |
||||||
|
} |
||||||
|
searchWordEngine.setOnSearchListener(object : SearchWordEngine.OnSearchListener { |
||||||
|
override fun loadFinish(isEmpty: Boolean) { |
||||||
|
binding.fabSearchStop.visibility = View.GONE |
||||||
|
binding.rpb.isAutoLoading = false |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
override fun loadMore(item: SearchWord1) { |
||||||
|
if (adapter.itemSize == 0) { |
||||||
|
adapter.addItem(item) |
||||||
|
} else { |
||||||
|
for ((index, searchWord1) in adapter.items.withIndex()) { |
||||||
|
if (index == 0 && item.chapterNum < searchWord1.chapterNum) { |
||||||
|
adapter.addItem(0, item) |
||||||
|
break |
||||||
|
} else if (item.chapterNum >= searchWord1.chapterNum && |
||||||
|
item.chapterNum < adapter.items[index + 1].chapterNum |
||||||
|
) { |
||||||
|
adapter.addItem(index + 1, item) |
||||||
|
break |
||||||
|
} else if (index == adapter.itemSize - 1) { |
||||||
|
adapter.addItem(item) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
adapter = object : BaseListAdapter<SearchWord1>() { |
||||||
|
override fun createViewHolder(viewType: Int): IViewHolder<SearchWord1> { |
||||||
|
return SearchWord1Holder(this@SearchWordActivity) |
||||||
|
} |
||||||
|
} |
||||||
|
binding.rvSearchWord1.layoutManager = LinearLayoutManager(this) |
||||||
|
binding.rvSearchWord1.adapter = adapter |
||||||
|
} |
||||||
|
|
||||||
|
override fun initClick() { |
||||||
|
binding.tvSearchConform.onClick { search() } |
||||||
|
binding.fabSearchStop.onClick { stopSearch() } |
||||||
|
} |
||||||
|
|
||||||
|
private fun search() { |
||||||
|
val keyword = binding.etSearchKey.text.toString() |
||||||
|
if (keyword.isNotEmpty()) { |
||||||
|
adapter.clear() |
||||||
|
binding.fabSearchStop.visibility = View.VISIBLE |
||||||
|
binding.rpb.isAutoLoading = true |
||||||
|
searchWordEngine.search(keyword) |
||||||
|
//收起软键盘 |
||||||
|
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager |
||||||
|
imm.hideSoftInputFromWindow( |
||||||
|
binding.etSearchKey.windowToken, |
||||||
|
InputMethodManager.HIDE_NOT_ALWAYS |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun stopSearch() { |
||||||
|
binding.fabSearchStop.visibility = View.GONE |
||||||
|
binding.rpb.isAutoLoading = false |
||||||
|
searchWordEngine.stopSearch() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDestroy() { |
||||||
|
super.onDestroy() |
||||||
|
searchWordEngine.stopSearch() |
||||||
|
searchWordEngine.closeSearchEngine() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
package xyz.fycz.myreader.ui.adapter.holder |
||||||
|
|
||||||
|
import android.app.Activity |
||||||
|
import android.content.Intent |
||||||
|
import android.view.View |
||||||
|
import android.widget.TextView |
||||||
|
import androidx.appcompat.app.AppCompatActivity |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import xyz.fycz.myreader.R |
||||||
|
import xyz.fycz.myreader.base.adapter.BaseListAdapter |
||||||
|
import xyz.fycz.myreader.base.adapter.IViewHolder |
||||||
|
import xyz.fycz.myreader.base.adapter.ViewHolderImpl |
||||||
|
import xyz.fycz.myreader.entity.SearchWord1 |
||||||
|
import xyz.fycz.myreader.entity.SearchWord2 |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/5 20:28 |
||||||
|
*/ |
||||||
|
class SearchWord1Holder(var activity: AppCompatActivity) : ViewHolderImpl<SearchWord1>() { |
||||||
|
|
||||||
|
private lateinit var tvChapterTitle: TextView |
||||||
|
private lateinit var rvWordList: RecyclerView |
||||||
|
private lateinit var adapter: BaseListAdapter<SearchWord2> |
||||||
|
|
||||||
|
override fun getItemLayoutId(): Int { |
||||||
|
return R.layout.item_search_word1 |
||||||
|
} |
||||||
|
|
||||||
|
override fun initView() { |
||||||
|
tvChapterTitle = findById(R.id.tv_chapter_title) |
||||||
|
rvWordList = findById(R.id.rv_search_word2) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onBind(holder: RecyclerView.ViewHolder?, data: SearchWord1?, pos: Int) { |
||||||
|
tvChapterTitle.text = data?.chapterTitle |
||||||
|
adapter = object : BaseListAdapter<SearchWord2>() { |
||||||
|
override fun createViewHolder(viewType: Int): IViewHolder<SearchWord2> { |
||||||
|
return SearchWord2Holder() |
||||||
|
} |
||||||
|
} |
||||||
|
rvWordList.layoutManager = LinearLayoutManager(context) |
||||||
|
rvWordList.adapter = adapter |
||||||
|
adapter.refreshItems(data?.searchWord2List) |
||||||
|
adapter.setOnItemClickListener { _, pos1 -> |
||||||
|
val searchWord2 = adapter.getItem(pos1) |
||||||
|
val intent = Intent() |
||||||
|
intent.putExtra("chapterNum", searchWord2.chapterNum) |
||||||
|
intent.putExtra("countInChapter", searchWord2.count) |
||||||
|
intent.putExtra("keyword", searchWord2.keyword) |
||||||
|
activity.setResult(Activity.RESULT_OK, intent) |
||||||
|
activity.finish() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
package xyz.fycz.myreader.ui.adapter.holder |
||||||
|
|
||||||
|
import android.graphics.Color |
||||||
|
import android.text.SpannableString |
||||||
|
import android.text.Spanned |
||||||
|
import android.text.style.ForegroundColorSpan |
||||||
|
import android.widget.TextView |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import xyz.fycz.myreader.R |
||||||
|
import xyz.fycz.myreader.base.adapter.ViewHolderImpl |
||||||
|
import xyz.fycz.myreader.entity.SearchWord2 |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/5 20:46 |
||||||
|
*/ |
||||||
|
class SearchWord2Holder : ViewHolderImpl<SearchWord2>() { |
||||||
|
|
||||||
|
private lateinit var tvSearchWord: TextView |
||||||
|
|
||||||
|
override fun getItemLayoutId(): Int { |
||||||
|
return R.layout.item_search_word2 |
||||||
|
} |
||||||
|
|
||||||
|
override fun initView() { |
||||||
|
tvSearchWord = findById(R.id.tv_search_word) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onBind(holder: RecyclerView.ViewHolder, data: SearchWord2, pos: Int) { |
||||||
|
val spannableString = SpannableString(data.dataStr) |
||||||
|
spannableString.setSpan( |
||||||
|
ForegroundColorSpan(Color.RED), |
||||||
|
data.dataIndex, data.dataIndex + data.keyword.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE |
||||||
|
) |
||||||
|
tvSearchWord.text = spannableString |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:width="24dp" |
||||||
|
android:height="24dp" |
||||||
|
android:viewportWidth="1024" |
||||||
|
android:viewportHeight="1024"> |
||||||
|
<path |
||||||
|
android:pathData="M602.3,603.46c9.09,-7.68 33.73,4.93 55.04,28.1l270.14,293.12c21.31,23.17 23.04,54.91 3.97,70.98 -19.2,16 -50.11,8.77 -69.25,-16.19L620.48,662.4c-19.07,-24.96 -27.2,-51.46 -18.18,-59.01z" |
||||||
|
android:fillColor="#FFFFFF"/> |
||||||
|
<path |
||||||
|
android:pathData="M560,633.47a288,288 0,1 0,-288 -498.82,288 288,0 0,0 288,498.82zM592,688.9a352,352 0,1 1,-352 -609.66,352 352,0 0,1 352,609.66z" |
||||||
|
android:fillColor="#FFFFFF"/> |
||||||
|
</vector> |
@ -0,0 +1,77 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<include layout="@layout/toolbar" /> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="60dp" |
||||||
|
android:gravity="center" |
||||||
|
android:orientation="horizontal" |
||||||
|
android:paddingLeft="15dp" |
||||||
|
android:paddingTop="10dp" |
||||||
|
android:paddingRight="15dp" |
||||||
|
android:paddingBottom="10dp"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatEditText |
||||||
|
android:id="@+id/et_search_key" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="35dp" |
||||||
|
android:layout_weight="2" |
||||||
|
android:background="@drawable/search_et_backcolor" |
||||||
|
android:hint="@string/search_word_tip" |
||||||
|
android:imeOptions="actionSearch" |
||||||
|
android:paddingStart="10dp" |
||||||
|
android:textColor="@color/textPrimary" /> |
||||||
|
|
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/tv_search_conform" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="35dp" |
||||||
|
android:layout_weight="8" |
||||||
|
android:background="@drawable/search_btn_backcolor" |
||||||
|
android:gravity="center" |
||||||
|
android:text="@string/common_search" |
||||||
|
android:textColor="@color/textPrimaryInverted" |
||||||
|
android:textSize="18sp" /> |
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
<xyz.fycz.myreader.widget.RefreshProgressBar |
||||||
|
android:id="@+id/rpb" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="3dp" |
||||||
|
android:visibility="visible" /> |
||||||
|
|
||||||
|
<RelativeLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
<xyz.fycz.myreader.widget.scroller.FastScrollRecyclerView |
||||||
|
android:id="@+id/rv_search_word1" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:paddingHorizontal="10dp" |
||||||
|
tools:ignore="SpeakableTextPresentCheck" /> |
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton |
||||||
|
android:id="@+id/fabSearchStop" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_alignParentEnd="true" |
||||||
|
android:layout_alignParentBottom="true" |
||||||
|
android:layout_marginEnd="30dp" |
||||||
|
android:layout_marginBottom="30dp" |
||||||
|
android:src="@drawable/ic_stop_black_24dp" |
||||||
|
android:visibility="gone" |
||||||
|
app:elevation="5dp" |
||||||
|
app:fabSize="mini" |
||||||
|
app:layout_anchorGravity="right|bottom" |
||||||
|
android:contentDescription="@string/stop" /> |
||||||
|
</RelativeLayout> |
||||||
|
</LinearLayout> |
@ -0,0 +1,21 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:paddingBottom="10dp" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView |
||||||
|
android:id="@+id/tv_chapter_title" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:paddingVertical="5dp" |
||||||
|
android:text="@string/app_name" |
||||||
|
android:textColor="@color/textSecondary" |
||||||
|
android:textSize="@dimen/text_normal_size" /> |
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView |
||||||
|
android:id="@+id/rv_search_word2" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" /> |
||||||
|
</LinearLayout> |
@ -0,0 +1,20 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView |
||||||
|
android:id="@+id/tv_search_word" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:textColor="@color/textPrimary" |
||||||
|
android:paddingVertical="10dp" |
||||||
|
android:textSize="@dimen/text_normal_size" |
||||||
|
android:text="@string/app_name"/> |
||||||
|
|
||||||
|
<View |
||||||
|
android:layout_width="fill_parent" |
||||||
|
android:layout_height="0.5dp" |
||||||
|
android:background="@color/sys_window_back" /> |
||||||
|
</LinearLayout> |
@ -1,3 +1,3 @@ |
|||||||
#Fri Jun 18 21:45:31 CST 2021 |
#Fri Jun 18 21:45:31 CST 2021 |
||||||
VERSION_CODE=223 |
VERSION_CODE=224 |
||||||
NEED_CREATE_RELEASE=true |
NEED_CREATE_RELEASE=false |
||||||
|
Loading…
Reference in new issue