feat: 优化代码

pull/103/head
kunfei 5 years ago
parent 7f0a484693
commit 948e87a3e7
  1. 218
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  2. 20
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/EventExtensions.kt
  3. 17
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  4. 231
      app/src/main/java/io/legado/app/ui/widget/text/InertiaScrollTextView.kt
  5. 2
      app/src/main/res/layout/dialog_text_view.xml

@ -1,21 +1,11 @@
package io.legado.app.ui.book.read.page
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.ViewConfiguration
import android.view.animation.Interpolator
import android.widget.OverScroller
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.view.ViewCompat
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import io.legado.app.ui.widget.text.InertiaScrollTextView
class ContentTextView : AppCompatTextView {
class ContentTextView : InertiaScrollTextView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@ -23,43 +13,6 @@ class ContentTextView : AppCompatTextView {
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr)
private val scrollStateIdle = 0
private val scrollStateDragging = 1
val scrollStateSettling = 2
private val mViewFling: ViewFling by lazy { ViewFling() }
private var velocityTracker: VelocityTracker? = null
private var mScrollState = scrollStateIdle
private var mLastTouchY: Int = 0
private var mTouchSlop: Int = 0
private var mMinFlingVelocity: Int = 0
private var mMaxFlingVelocity: Int = 0
//滑动距离的最大边界
private var mOffsetHeight: Int = 0
//f(x) = (x-1)^5 + 1
private val sQuinticInterpolator = Interpolator {
var t = it
t -= 1.0f
t * t * t * t * t + 1.0f
}
init {
val vc = ViewConfiguration.get(context)
mTouchSlop = vc.scaledTouchSlop
mMinFlingVelocity = vc.scaledMinimumFlingVelocity
mMaxFlingVelocity = vc.scaledMaximumFlingVelocity
}
fun atTop(): Boolean {
return scrollY <= 0
}
fun atBottom(): Boolean {
return scrollY >= mOffsetHeight
}
/**
* 获取当前页总字数
*/
@ -75,171 +28,4 @@ class ContentTextView : AppCompatTextView {
return layout?.getLineForVertical(topOfLastLine) ?: 0
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
initOffsetHeight()
}
override fun onTextChanged(
text: CharSequence?,
start: Int,
lengthBefore: Int,
lengthAfter: Int
) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
initOffsetHeight()
}
private fun initOffsetHeight() {
val mLayoutHeight: Int
//获得内容面板
val mLayout = layout ?: return
//获得内容面板的高度
mLayoutHeight = mLayout.height
//计算滑动距离的边界
mOffsetHeight = mLayoutHeight + totalPaddingTop + totalPaddingBottom - measuredHeight
}
override fun scrollTo(x: Int, y: Int) {
super.scrollTo(x, min(y, mOffsetHeight))
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain()
}
velocityTracker?.addMovement(it)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
setScrollState(scrollStateIdle)
mLastTouchY = (event.y + 0.5f).toInt()
}
MotionEvent.ACTION_MOVE -> {
val y = (event.y + 0.5f).toInt()
var dy = mLastTouchY - y
if (mScrollState != scrollStateDragging) {
var startScroll = false
if (abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop
} else {
dy += mTouchSlop
}
startScroll = true
}
if (startScroll) {
setScrollState(scrollStateDragging)
}
}
if (mScrollState == scrollStateDragging) {
mLastTouchY = y
}
}
MotionEvent.ACTION_UP -> {
velocityTracker?.computeCurrentVelocity(1000, mMaxFlingVelocity.toFloat())
val yVelocity = velocityTracker?.yVelocity ?: 0f
if (abs(yVelocity) > mMinFlingVelocity) {
mViewFling.fling(-yVelocity.toInt())
} else {
setScrollState(scrollStateIdle)
}
resetTouch()
}
MotionEvent.ACTION_CANCEL -> {
resetTouch()
}
}
}
return super.onTouchEvent(event)
}
private fun resetTouch() {
velocityTracker?.clear()
}
private fun setScrollState(state: Int) {
if (state == mScrollState) {
return
}
mScrollState = state
if (state != scrollStateSettling) {
mViewFling.stop()
}
}
/**
* 惯性滚动
*/
private inner class ViewFling : Runnable {
private var mLastFlingY = 0
private val mScroller: OverScroller = OverScroller(context, sQuinticInterpolator)
private var mEatRunOnAnimationRequest = false
private var mReSchedulePostAnimationCallback = false
override fun run() {
disableRunOnAnimationRequests()
val scroller = mScroller
if (scroller.computeScrollOffset()) {
val y = scroller.currY
val dy = y - mLastFlingY
mLastFlingY = y
if (dy < 0 && scrollY > 0) {
scrollBy(0, max(dy, -scrollY))
} else if (dy > 0 && scrollY < mOffsetHeight) {
scrollBy(0, min(dy, mOffsetHeight - scrollY))
}
postOnAnimation()
}
enableRunOnAnimationRequests()
}
fun fling(velocityY: Int) {
mLastFlingY = 0
setScrollState(scrollStateSettling)
mScroller.fling(
0,
0,
0,
velocityY,
Integer.MIN_VALUE,
Integer.MAX_VALUE,
Integer.MIN_VALUE,
Integer.MAX_VALUE
)
postOnAnimation()
}
fun stop() {
removeCallbacks(this)
mScroller.abortAnimation()
}
private fun disableRunOnAnimationRequests() {
mReSchedulePostAnimationCallback = false
mEatRunOnAnimationRequest = true
}
private fun enableRunOnAnimationRequests() {
mEatRunOnAnimationRequest = false
if (mReSchedulePostAnimationCallback) {
postOnAnimation()
}
}
internal fun postOnAnimation() {
if (mEatRunOnAnimationRequest) {
mReSchedulePostAnimationCallback = true
} else {
removeCallbacks(this)
ViewCompat.postOnAnimation(this@ContentTextView, this)
}
}
}
}

@ -1,20 +0,0 @@
package io.legado.app.ui.book.read.page.delegate
import android.view.MotionEvent
fun MotionEvent.toAction(action: Int): MotionEvent {
return MotionEvent.obtain(
downTime,
eventTime,
action,
x,
y,
pressure,
size,
metaState,
xPrecision,
yPrecision,
deviceId,
edgeFlags
)
}

@ -321,4 +321,21 @@ abstract class PageDelegate(protected val pageView: PageView) {
}
return hasNext
}
fun MotionEvent.toAction(action: Int): MotionEvent {
return MotionEvent.obtain(
downTime,
eventTime,
action,
x,
y,
pressure,
size,
metaState,
xPrecision,
yPrecision,
deviceId,
edgeFlags
)
}
}

@ -0,0 +1,231 @@
package io.legado.app.ui.widget.text
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.ViewConfiguration
import android.view.animation.Interpolator
import android.widget.OverScroller
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.view.ViewCompat
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
open class InertiaScrollTextView : AppCompatTextView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr)
private val scrollStateIdle = 0
private val scrollStateDragging = 1
val scrollStateSettling = 2
private val mViewFling: ViewFling by lazy { ViewFling() }
private var velocityTracker: VelocityTracker? = null
private var mScrollState = scrollStateIdle
private var mLastTouchY: Int = 0
private var mTouchSlop: Int = 0
private var mMinFlingVelocity: Int = 0
private var mMaxFlingVelocity: Int = 0
//滑动距离的最大边界
private var mOffsetHeight: Int = 0
//f(x) = (x-1)^5 + 1
private val sQuinticInterpolator = Interpolator {
var t = it
t -= 1.0f
t * t * t * t * t + 1.0f
}
init {
val vc = ViewConfiguration.get(context)
mTouchSlop = vc.scaledTouchSlop
mMinFlingVelocity = vc.scaledMinimumFlingVelocity
mMaxFlingVelocity = vc.scaledMaximumFlingVelocity
}
fun atTop(): Boolean {
return scrollY <= 0
}
fun atBottom(): Boolean {
return scrollY >= mOffsetHeight
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
initOffsetHeight()
}
override fun onTextChanged(
text: CharSequence?,
start: Int,
lengthBefore: Int,
lengthAfter: Int
) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
initOffsetHeight()
}
private fun initOffsetHeight() {
val mLayoutHeight: Int
//获得内容面板
val mLayout = layout ?: return
//获得内容面板的高度
mLayoutHeight = mLayout.height
//计算滑动距离的边界
mOffsetHeight = mLayoutHeight + totalPaddingTop + totalPaddingBottom - measuredHeight
}
override fun scrollTo(x: Int, y: Int) {
super.scrollTo(x, min(y, mOffsetHeight))
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain()
}
velocityTracker?.addMovement(it)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
setScrollState(scrollStateIdle)
mLastTouchY = (event.y + 0.5f).toInt()
}
MotionEvent.ACTION_MOVE -> {
val y = (event.y + 0.5f).toInt()
var dy = mLastTouchY - y
if (mScrollState != scrollStateDragging) {
var startScroll = false
if (abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop
} else {
dy += mTouchSlop
}
startScroll = true
}
if (startScroll) {
setScrollState(scrollStateDragging)
}
}
if (mScrollState == scrollStateDragging) {
mLastTouchY = y
}
}
MotionEvent.ACTION_UP -> {
velocityTracker?.computeCurrentVelocity(1000, mMaxFlingVelocity.toFloat())
val yVelocity = velocityTracker?.yVelocity ?: 0f
if (abs(yVelocity) > mMinFlingVelocity) {
mViewFling.fling(-yVelocity.toInt())
} else {
setScrollState(scrollStateIdle)
}
resetTouch()
}
MotionEvent.ACTION_CANCEL -> {
resetTouch()
}
}
}
return super.onTouchEvent(event)
}
private fun resetTouch() {
velocityTracker?.clear()
}
private fun setScrollState(state: Int) {
if (state == mScrollState) {
return
}
mScrollState = state
if (state != scrollStateSettling) {
mViewFling.stop()
}
}
/**
* 惯性滚动
*/
private inner class ViewFling : Runnable {
private var mLastFlingY = 0
private val mScroller: OverScroller = OverScroller(context, sQuinticInterpolator)
private var mEatRunOnAnimationRequest = false
private var mReSchedulePostAnimationCallback = false
override fun run() {
disableRunOnAnimationRequests()
val scroller = mScroller
if (scroller.computeScrollOffset()) {
val y = scroller.currY
val dy = y - mLastFlingY
mLastFlingY = y
if (dy < 0 && scrollY > 0) {
scrollBy(0, max(dy, -scrollY))
} else if (dy > 0 && scrollY < mOffsetHeight) {
scrollBy(0, min(dy, mOffsetHeight - scrollY))
}
postOnAnimation()
}
enableRunOnAnimationRequests()
}
fun fling(velocityY: Int) {
mLastFlingY = 0
setScrollState(scrollStateSettling)
mScroller.fling(
0,
0,
0,
velocityY,
Integer.MIN_VALUE,
Integer.MAX_VALUE,
Integer.MIN_VALUE,
Integer.MAX_VALUE
)
postOnAnimation()
}
fun stop() {
removeCallbacks(this)
mScroller.abortAnimation()
}
private fun disableRunOnAnimationRequests() {
mReSchedulePostAnimationCallback = false
mEatRunOnAnimationRequest = true
}
private fun enableRunOnAnimationRequests() {
mEatRunOnAnimationRequest = false
if (mReSchedulePostAnimationCallback) {
postOnAnimation()
}
}
internal fun postOnAnimation() {
if (mEatRunOnAnimationRequest) {
mReSchedulePostAnimationCallback = true
} else {
removeCallbacks(this)
ViewCompat.postOnAnimation(this@InertiaScrollTextView, this)
}
}
}
}

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
<io.legado.app.ui.widget.text.InertiaScrollTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"

Loading…
Cancel
Save