pull/32/head
Administrator 5 years ago
parent b4eb0d0958
commit 8b3d956e7e
  1. 33
      app/src/main/java/io/legado/app/App.kt
  2. 3
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  3. 6
      app/src/main/java/io/legado/app/help/ActivityHelp.kt
  4. 4
      app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
  5. 10
      app/src/main/java/io/legado/app/ui/widget/page/DataSource.kt
  6. 39
      app/src/main/java/io/legado/app/ui/widget/page/PageAnimDelegate.kt
  7. 10
      app/src/main/java/io/legado/app/ui/widget/page/PageFactory.kt
  8. 105
      app/src/main/java/io/legado/app/ui/widget/page/PageView.kt
  9. 8
      app/src/main/java/io/legado/app/ui/widget/page/TextPageFactory.kt
  10. 75
      app/src/main/java/io/legado/app/ui/widget/page/delegate/CoverPageDelegate.kt
  11. 247
      app/src/main/java/io/legado/app/ui/widget/page/delegate/PageDelegate.kt

@ -1,10 +1,12 @@
package io.legado.app package io.legado.app
import android.app.Activity
import android.app.Application import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
@ -12,6 +14,7 @@ import io.legado.app.constant.AppConst.channelIdDownload
import io.legado.app.constant.AppConst.channelIdReadAloud import io.legado.app.constant.AppConst.channelIdReadAloud
import io.legado.app.constant.AppConst.channelIdWeb import io.legado.app.constant.AppConst.channelIdWeb
import io.legado.app.data.AppDatabase import io.legado.app.data.AppDatabase
import io.legado.app.help.ActivityHelp
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore
import io.legado.app.utils.getCompatColor import io.legado.app.utils.getCompatColor
import io.legado.app.utils.getPrefInt import io.legado.app.utils.getPrefInt
@ -53,6 +56,8 @@ class App : Application() {
.supportBroadcast(this) .supportBroadcast(this)
.lifecycleObserverAlwaysActive(true) .lifecycleObserverAlwaysActive(true)
.autoClear(false) .autoClear(false)
registerActivityLife()
} }
/** /**
@ -134,4 +139,32 @@ class App : Application() {
it.createNotificationChannels(listOf(downloadChannel, readAloudChannel, webChannel)) it.createNotificationChannels(listOf(downloadChannel, readAloudChannel, webChannel))
} }
} }
private fun registerActivityLife(){
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks{
override fun onActivityPaused(activity: Activity) {
ActivityHelp.getInstance().add(activity)
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityDestroyed(activity: Activity) {
ActivityHelp.getInstance().remove(activity)
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
}
})
}
} }

@ -8,7 +8,6 @@ import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.ActivityHelp
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.ColorUtils
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore
@ -27,7 +26,6 @@ abstract class BaseActivity(private val layoutID: Int, private val fullScreen: B
initTheme() initTheme()
setupSystemBar() setupSystemBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ActivityHelp.getInstance()?.add(this)
setContentView(layoutID) setContentView(layoutID)
onActivityCreated(savedInstanceState) onActivityCreated(savedInstanceState)
observeLiveBus() observeLiveBus()
@ -36,7 +34,6 @@ abstract class BaseActivity(private val layoutID: Int, private val fullScreen: B
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
cancel() cancel()
ActivityHelp.getInstance()?.remove(this)
} }
abstract fun onActivityCreated(savedInstanceState: Bundle?) abstract fun onActivityCreated(savedInstanceState: Bundle?)

@ -16,7 +16,7 @@ class ActivityHelp private constructor() {
@Volatile @Volatile
private var instance: ActivityHelp? = null private var instance: ActivityHelp? = null
fun getInstance(): ActivityHelp? { fun getInstance(): ActivityHelp {
if (null == instance) { if (null == instance) {
synchronized(ActivityHelp::class.java) { synchronized(ActivityHelp::class.java) {
if (null == instance) { if (null == instance) {
@ -24,7 +24,7 @@ class ActivityHelp private constructor() {
} }
} }
} }
return instance return instance!!
} }
} }
@ -69,7 +69,7 @@ class ActivityHelp private constructor() {
*/ */
fun finishActivity(vararg activities: Activity) { fun finishActivity(vararg activities: Activity) {
for (activity in activities) { for (activity in activities) {
activity?.finish() activity.finish()
} }
} }

@ -6,7 +6,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseFragment import io.legado.app.base.BaseFragment
import io.legado.app.ui.chapterlist.ChapterListActivity import io.legado.app.ui.readbook.ReadBookActivity
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import kotlinx.android.synthetic.main.view_title_bar.* import kotlinx.android.synthetic.main.view_title_bar.*
@ -26,7 +26,7 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) {
override fun onCompatOptionsItemSelected(item: MenuItem) { override fun onCompatOptionsItemSelected(item: MenuItem) {
when (item.itemId) { when (item.itemId) {
R.id.action_settings -> startActivity<ChapterListActivity>() R.id.action_settings -> startActivity<ReadBookActivity>()
} }
} }
} }

@ -4,9 +4,13 @@ interface DataSource {
fun getChapterPosition() fun getChapterPosition()
fun getChapter(position: Int): TextChapter fun getChapter(position: Int): TextChapter?
fun getNextChapter(): TextChapter fun getNextChapter(): TextChapter?
fun getPreviousChapter(): TextChapter fun getPreviousChapter(): TextChapter?
fun hasNextChapter(): Boolean
fun hasPrevChapter(): Boolean
} }

@ -1,39 +0,0 @@
package io.legado.app.ui.widget.page
import android.widget.Scroller
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
abstract class PageAnimDelegate(protected val pageView: PageView) {
protected val scroller: Scroller = Scroller(pageView.context, FastOutLinearInInterpolator())
//起始点
protected var startX: Float = 0.toFloat()
protected var startY: Float = 0.toFloat()
//触碰点
protected var touchX: Float = 0.toFloat()
protected var touchY: Float = 0.toFloat()
//上一个触碰点
protected var lastX: Float = 0.toFloat()
protected var lastY: Float = 0.toFloat()
protected var isRunning = false
protected var isStarted = false
fun setStartPoint(x: Float, y: Float) {
startX = x
startY = y
lastX = startX
lastY = startY
}
fun setTouchPoint(x: Float, y: Float) {
lastX = touchX
lastY = touchY
touchX = x
touchY = y
}
}

@ -2,10 +2,14 @@ package io.legado.app.ui.widget.page
abstract class PageFactory<DATA>(protected val dataSource: DataSource) { abstract class PageFactory<DATA>(protected val dataSource: DataSource) {
abstract fun pageAt(index: Int): DATA abstract fun pageAt(index: Int): DATA?
abstract fun nextPage(): DATA abstract fun nextPage(): DATA?
abstract fun previousPage(): DATA abstract fun previousPage(): DATA?
abstract fun hasNext(): Boolean
abstract fun hasPrev(): Boolean
} }

@ -1,44 +1,30 @@
package io.legado.app.ui.widget.page package io.legado.app.ui.widget.page
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.* import android.graphics.Canvas
import android.graphics.drawable.GradientDrawable import android.graphics.Color
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.widget.FrameLayout import android.widget.FrameLayout
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.page.delegate.CoverPageDelegate
import io.legado.app.ui.widget.page.delegate.PageDelegate
import io.legado.app.utils.dp import io.legado.app.utils.dp
import io.legado.app.utils.screenshot
import kotlinx.android.synthetic.main.view_book_page.view.* import kotlinx.android.synthetic.main.view_book_page.view.*
import org.jetbrains.anko.backgroundColor import org.jetbrains.anko.backgroundColor
import org.jetbrains.anko.horizontalPadding import org.jetbrains.anko.horizontalPadding
class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) { class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs), PageDelegate.PageInterface {
private var bitmap: Bitmap? = null
private var downX: Float = 0.toFloat() private var pageDelegate: PageDelegate? = null
private var offset: Float = 0.toFloat()
private val shadowDrawableR: GradientDrawable private var prevPage: ContentView? = null
private val shadowDrawableL: GradientDrawable private var curPage: ContentView? = null
private var nextPage: ContentView? = null
private val bitmapMatrix = Matrix()
private var cover: Boolean = true
init { init {
val shadowColors = intArrayOf(0x66111111, 0x00000000)
shadowDrawableR = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT, shadowColors
)
shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT
shadowDrawableL = GradientDrawable(
GradientDrawable.Orientation.RIGHT_LEFT, shadowColors
)
shadowDrawableL.gradientType = GradientDrawable.LINEAR_GRADIENT
inflate(context, R.layout.view_book_page, this) inflate(context, R.layout.view_book_page, this)
setWillNotDraw(false) setWillNotDraw(false)
@ -47,72 +33,41 @@ class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, att
page_panel.horizontalPadding = 16.dp page_panel.horizontalPadding = 16.dp
pageDelegate = CoverPageDelegate(this)
} }
override fun dispatchDraw(canvas: Canvas?) { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.dispatchDraw(canvas) super.onSizeChanged(w, h, oldw, oldh)
bitmap?.let { pageDelegate?.setViewSize(w, h)
val dx = if (offset > 0) offset - width else offset + width }
bitmapMatrix.setTranslate(dx, 0.toFloat()) override fun dispatchDraw(canvas: Canvas) {
canvas?.drawBitmap(it, bitmapMatrix, null) super.dispatchDraw(canvas)
if (cover) { // bringChildToFront(prevPage)
addShadow(dx.toInt(), canvas)
}
}
}
private fun addShadow(left: Int, canvas: Canvas?) { pageDelegate?.onPerform(canvas)
canvas?.let {
if (left < 0) {
shadowDrawableR.setBounds(left + width, 0, left+ width + 30, height)
shadowDrawableR.draw(it)
} else {
shadowDrawableL.setBounds(left - 30, 0, left, height)
shadowDrawableL.draw(it)
}
}
} }
override fun onTouchEvent(event: MotionEvent?): Boolean { override fun computeScroll() {
when (event?.action) { pageDelegate?.scroll()
MotionEvent.ACTION_DOWN -> {
bitmap = page_panel.screenshot()
downX = event.x
offset = 0.toFloat()
if (!cover) {
page_panel.translationX = 0.toFloat()
}
invalidate()
} }
MotionEvent.ACTION_MOVE -> { @SuppressLint("ClickableViewAccessibility")
offset = event.x - downX override fun onTouchEvent(event: MotionEvent): Boolean {
if (!cover) { return pageDelegate?.onTouch(event) ?: super.onTouchEvent(event)
page_panel.translationX = offset
}
invalidate()
} }
MotionEvent.ACTION_UP -> { fun setPageFactory(factory: PageFactory<*>) {
bitmap = null
if (!cover) {
page_panel.translationX = 0.toFloat()
}
invalidate()
}
}
return true
}
fun setTranslate(translationX: Float, translationY: Float) {
page_panel.translationX = translationX
page_panel.translationY = translationY
} }
fun setPageFactory(factory: PageFactory<*>) { override fun hasNext(): Boolean {
return true
}
override fun hasPrev(): Boolean {
return true
} }
} }

@ -12,6 +12,14 @@ class TextPageFactory private constructor(dataSource: DataSource) : PageFactory<
private var index: Int = 0 private var index: Int = 0
override fun hasPrev(): Boolean {
return true
}
override fun hasNext(): Boolean {
return true
}
override fun pageAt(index: Int): TextPage { override fun pageAt(index: Int): TextPage {
TODO("todo...") TODO("todo...")
} }

@ -0,0 +1,75 @@
package io.legado.app.ui.widget.page.delegate
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.drawable.GradientDrawable
import io.legado.app.ui.widget.page.PageView
class CoverPageDelegate(pageView: PageView) : PageDelegate(pageView) {
private val shadowDrawableR: GradientDrawable
private val shadowDrawableL: GradientDrawable
private val bitmapMatrix = Matrix()
init {
val shadowColors = intArrayOf(0x66111111, 0x00000000)
shadowDrawableR = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT, shadowColors
)
shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT
shadowDrawableL = GradientDrawable(
GradientDrawable.Orientation.RIGHT_LEFT, shadowColors
)
shadowDrawableL.gradientType = GradientDrawable.LINEAR_GRADIENT
}
override fun onStart() {
val distanceX: Float
when (direction) {
Direction.NEXT -> if (isCancel) {
var dis = viewWidth - startX + touchX
if (dis > viewWidth) {
dis = viewWidth.toFloat()
}
distanceX = viewWidth - dis
} else {
distanceX = -(touchX + (viewWidth - startX))
}
else -> distanceX = if (isCancel) {
-(touchX - startX)
} else {
viewWidth - (touchX - startX)
}
}
scroller.startScroll(touchX.toInt(), 0, distanceX.toInt(), 0, getDuration(distanceX))
start()
}
override fun onPerform(canvas: Canvas) {
val offsetX = touchX - startX
if(offsetX == 0.toFloat()) return
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
bitmap?.let {
bitmapMatrix.setTranslate(distanceX, 0.toFloat())
canvas.drawBitmap(it, bitmapMatrix, null)
addShadow(distanceX.toInt(), canvas)
}
}
private fun addShadow(left: Int, canvas: Canvas?) {
canvas?.let {
if (left < 0) {
shadowDrawableR.setBounds(left + viewWidth, 0, left+ viewWidth + 30, viewHeight)
shadowDrawableR.draw(it)
} else {
shadowDrawableL.setBounds(left - 30, 0, left, viewHeight)
shadowDrawableL.draw(it)
}
}
}
}

@ -0,0 +1,247 @@
package io.legado.app.ui.widget.page.delegate
import android.graphics.Bitmap
import android.graphics.Canvas
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.Scroller
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import io.legado.app.ui.widget.page.PageView
import io.legado.app.utils.screenshot
import kotlinx.android.synthetic.main.view_book_page.view.*
import kotlin.math.abs
abstract class PageDelegate(private val pageView: PageView) {
//起始点
protected var startX: Float = 0.toFloat()
protected var startY: Float = 0.toFloat()
//触碰点
protected var touchX: Float = 0.toFloat()
protected var touchY: Float = 0.toFloat()
protected val view: View
get() = pageView.page_panel
protected var bitmap: Bitmap? = null
protected var viewWidth: Int = pageView.width
protected var viewHeight: Int = pageView.height
protected val scroller: Scroller by lazy { Scroller(pageView.context, FastOutLinearInInterpolator()) }
private val detector: GestureDetector by lazy { GestureDetector(pageView.context, GestureListener()) }
private var isMoved = false
private var noNext = true
//移动方向
var direction = Direction.NONE
var isCancel = false
var isRunning = false
var isStarted = false
protected fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) {
startX = x
startY = y
if (invalidate) {
invalidate()
}
}
protected fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) {
touchX = x
touchY = y
if (invalidate) {
invalidate()
}
}
fun setViewSize(width: Int, height: Int) {
viewWidth = width
viewHeight = height
invalidate()
}
fun invalidate() {
pageView.invalidate()
}
fun start() {
isRunning = true
isStarted = true
invalidate()
}
fun stop() {
isRunning = false
isStarted = false
bitmap = null
invalidate()
}
fun scroll() {
if (scroller.computeScrollOffset()) {
setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat())
} else if(isStarted){
setTouchPoint(scroller.finalX.toFloat(), scroller.finalY.toFloat(), false)
stop()
}
}
fun abort() {
if (!scroller.isFinished) {
scroller.abortAnimation()
}
}
fun start(direction: Direction) {
if (isStarted) return
this.direction = direction
if (direction === Direction.NEXT) {
val x = viewWidth.toFloat()
val y = viewHeight.toFloat()
//初始化动画
setStartPoint(x, y, false)
//设置点击点
setTouchPoint(x, y, false)
//设置方向
val hasNext = pageView.hasNext()
if (!hasNext) {
return
}
} else {
val x = 0.toFloat()
val y = viewHeight.toFloat()
//初始化动画
setStartPoint(x, y, false)
//设置点击点
setTouchPoint(x, y, false)
//设置方向方向
val hashPrev = pageView.hasPrev()
if (!hashPrev) {
return
}
}
onStart()
}
fun onTouch(event: MotionEvent): Boolean {
if (isMoved && event.action == MotionEvent.ACTION_UP) {
// 开启翻页效果
if (!noNext) {
onStart()
}
return true
}
return detector.onTouchEvent(event)
}
fun getDuration(distance: Float): Int {
val duration = 300 * abs(distance) / viewWidth
return duration.toInt()
}
abstract fun onStart()
abstract fun onPerform(canvas: Canvas)
enum class Direction {
NONE, PREV, NEXT
}
private inner class GestureListener : GestureDetector.OnGestureListener {
override fun onDown(e: MotionEvent): Boolean {
abort()
//是否移动
isMoved = false
//是否存在下一章
noNext = false
//是否正在执行动画
isRunning = false
//取消
isCancel = false
//是下一章还是前一章
direction = Direction.NONE
//设置起始位置的触摸点
setStartPoint(e.x, e.y)
return true
}
override fun onShowPress(e: MotionEvent) {}
override fun onSingleTapUp(e: MotionEvent): Boolean {
val x = e.x
val y = e.y
direction = if (x > viewWidth / 2) Direction.NEXT else Direction.PREV
if (direction == Direction.NEXT) {
//判断是否下一页存在
val hasNext = pageView.hasNext()
//设置动画方向
if (!hasNext) {
return true
}
} else {
val hasPrev = pageView.hasPrev()
if (!hasPrev) {
return true
}
}
setTouchPoint(x, y)
return true
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
if (!isMoved && abs(distanceX) > abs(distanceY)) {
//上一页或下一页截图,还未处理
bitmap = pageView.screenshot()
if (distanceX < 0) {
//上一页的参数配置
direction = Direction.PREV
//判断是否上一页存在
val hasPrev = pageView.hasPrev()
//如果上一页不存在
if (!hasPrev) {
noNext = true
return true
}
} else {
//进行下一页的配置
direction = Direction.NEXT
//判断是否下一页存在
val hasNext = pageView.hasNext()
//如果不存在表示没有下一页了
if (!hasNext) {
noNext = true
return true
}
}
isMoved = true
}
if (isMoved) {
isCancel = if (direction == Direction.NEXT) distanceX < 0 else distanceX > 0
isRunning = true
//设置触摸点
setTouchPoint(e2.x, e2.y)
}
return isMoved
}
override fun onLongPress(e: MotionEvent) {
}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
return false
}
}
interface PageInterface {
fun hasNext(): Boolean
fun hasPrev(): Boolean
}
}
Loading…
Cancel
Save