diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index a6c6d9514..f0bf61b89 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -1,10 +1,12 @@ package io.legado.app +import android.app.Activity import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.os.Build +import android.os.Bundle import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatDelegate 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.channelIdWeb import io.legado.app.data.AppDatabase +import io.legado.app.help.ActivityHelp import io.legado.app.lib.theme.ThemeStore import io.legado.app.utils.getCompatColor import io.legado.app.utils.getPrefInt @@ -53,6 +56,8 @@ class App : Application() { .supportBroadcast(this) .lifecycleObserverAlwaysActive(true) .autoClear(false) + + registerActivityLife() } /** @@ -134,4 +139,32 @@ class App : Application() { 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?) { + } + + }) + } } diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt index b769f54f4..96d3d21d5 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -8,7 +8,6 @@ import android.view.View import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity import io.legado.app.R -import io.legado.app.help.ActivityHelp import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.ThemeStore @@ -27,7 +26,6 @@ abstract class BaseActivity(private val layoutID: Int, private val fullScreen: B initTheme() setupSystemBar() super.onCreate(savedInstanceState) - ActivityHelp.getInstance()?.add(this) setContentView(layoutID) onActivityCreated(savedInstanceState) observeLiveBus() @@ -36,7 +34,6 @@ abstract class BaseActivity(private val layoutID: Int, private val fullScreen: B override fun onDestroy() { super.onDestroy() cancel() - ActivityHelp.getInstance()?.remove(this) } abstract fun onActivityCreated(savedInstanceState: Bundle?) diff --git a/app/src/main/java/io/legado/app/help/ActivityHelp.kt b/app/src/main/java/io/legado/app/help/ActivityHelp.kt index c9eccea96..a1e535bfe 100644 --- a/app/src/main/java/io/legado/app/help/ActivityHelp.kt +++ b/app/src/main/java/io/legado/app/help/ActivityHelp.kt @@ -16,7 +16,7 @@ class ActivityHelp private constructor() { @Volatile private var instance: ActivityHelp? = null - fun getInstance(): ActivityHelp? { + fun getInstance(): ActivityHelp { if (null == instance) { synchronized(ActivityHelp::class.java) { 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) { for (activity in activities) { - activity?.finish() + activity.finish() } } diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt index d64f3c6f0..d88b87669 100644 --- a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt @@ -6,7 +6,7 @@ import android.view.MenuItem import android.view.View import io.legado.app.R 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 kotlinx.android.synthetic.main.view_title_bar.* @@ -26,7 +26,7 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) { override fun onCompatOptionsItemSelected(item: MenuItem) { when (item.itemId) { - R.id.action_settings -> startActivity() + R.id.action_settings -> startActivity() } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/page/DataSource.kt b/app/src/main/java/io/legado/app/ui/widget/page/DataSource.kt index f4cb075ed..f13cea296 100644 --- a/app/src/main/java/io/legado/app/ui/widget/page/DataSource.kt +++ b/app/src/main/java/io/legado/app/ui/widget/page/DataSource.kt @@ -4,9 +4,13 @@ interface DataSource { 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 } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/page/PageAnimDelegate.kt b/app/src/main/java/io/legado/app/ui/widget/page/PageAnimDelegate.kt deleted file mode 100644 index eb4301588..000000000 --- a/app/src/main/java/io/legado/app/ui/widget/page/PageAnimDelegate.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/page/PageFactory.kt b/app/src/main/java/io/legado/app/ui/widget/page/PageFactory.kt index f2bebfbf5..d99e2976d 100644 --- a/app/src/main/java/io/legado/app/ui/widget/page/PageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/widget/page/PageFactory.kt @@ -2,10 +2,14 @@ package io.legado.app.ui.widget.page abstract class PageFactory(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 } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/page/PageView.kt b/app/src/main/java/io/legado/app/ui/widget/page/PageView.kt index 353087726..e96545c68 100644 --- a/app/src/main/java/io/legado/app/ui/widget/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/page/PageView.kt @@ -1,44 +1,30 @@ package io.legado.app.ui.widget.page +import android.annotation.SuppressLint import android.content.Context -import android.graphics.* -import android.graphics.drawable.GradientDrawable +import android.graphics.Canvas +import android.graphics.Color import android.util.AttributeSet import android.view.MotionEvent import android.widget.FrameLayout 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.screenshot import kotlinx.android.synthetic.main.view_book_page.view.* import org.jetbrains.anko.backgroundColor 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 offset: Float = 0.toFloat() + private var pageDelegate: PageDelegate? = null - private val shadowDrawableR: GradientDrawable - private val shadowDrawableL: GradientDrawable - - private val bitmapMatrix = Matrix() - - private var cover: Boolean = true + private var prevPage: ContentView? = null + private var curPage: ContentView? = null + private var nextPage: ContentView? = null 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) setWillNotDraw(false) @@ -47,72 +33,41 @@ class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, att page_panel.horizontalPadding = 16.dp + pageDelegate = CoverPageDelegate(this) } - override fun dispatchDraw(canvas: Canvas?) { - super.dispatchDraw(canvas) + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) - bitmap?.let { - val dx = if (offset > 0) offset - width else offset + width + pageDelegate?.setViewSize(w, h) + } - bitmapMatrix.setTranslate(dx, 0.toFloat()) - canvas?.drawBitmap(it, bitmapMatrix, null) + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) - if (cover) { - addShadow(dx.toInt(), canvas) - } - } - } +// bringChildToFront(prevPage) - private fun addShadow(left: Int, canvas: 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) - } - } + pageDelegate?.onPerform(canvas) } - override fun onTouchEvent(event: MotionEvent?): Boolean { - when (event?.action) { - MotionEvent.ACTION_DOWN -> { - bitmap = page_panel.screenshot() - downX = event.x - offset = 0.toFloat() - if (!cover) { - page_panel.translationX = 0.toFloat() - } - invalidate() - } - - MotionEvent.ACTION_MOVE -> { - offset = event.x - downX - if (!cover) { - page_panel.translationX = offset - } - invalidate() - } - - MotionEvent.ACTION_UP -> { - bitmap = null - if (!cover) { - page_panel.translationX = 0.toFloat() - } - invalidate() - } - } - return true + override fun computeScroll() { + pageDelegate?.scroll() } - fun setTranslate(translationX: Float, translationY: Float) { - page_panel.translationX = translationX - page_panel.translationY = translationY + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + return pageDelegate?.onTouch(event) ?: super.onTouchEvent(event) } fun setPageFactory(factory: PageFactory<*>) { } + + override fun hasNext(): Boolean { + return true + } + + override fun hasPrev(): Boolean { + return true + } } diff --git a/app/src/main/java/io/legado/app/ui/widget/page/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/widget/page/TextPageFactory.kt index e42895454..0799d97c2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/page/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/widget/page/TextPageFactory.kt @@ -12,6 +12,14 @@ class TextPageFactory private constructor(dataSource: DataSource) : PageFactory< private var index: Int = 0 + override fun hasPrev(): Boolean { + return true + } + + override fun hasNext(): Boolean { + return true + } + override fun pageAt(index: Int): TextPage { TODO("todo...") } diff --git a/app/src/main/java/io/legado/app/ui/widget/page/delegate/CoverPageDelegate.kt b/app/src/main/java/io/legado/app/ui/widget/page/delegate/CoverPageDelegate.kt new file mode 100644 index 000000000..68b3f67c9 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/page/delegate/CoverPageDelegate.kt @@ -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) + } + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/widget/page/delegate/PageDelegate.kt new file mode 100644 index 000000000..3c1130c9e --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/page/delegate/PageDelegate.kt @@ -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 + } +} \ No newline at end of file