Added screen stabilization
This commit is contained in:
parent
81cd765543
commit
dc63129b50
6 changed files with 173 additions and 0 deletions
|
@ -78,6 +78,11 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
val fullscreenPref = readerPreferences.fullscreen()
|
||||
val fullscreen by fullscreenPref.collectAsState()
|
||||
|
||||
val stabilizationNumberFormat = remember { NumberFormat.getPercentInstance() }
|
||||
val stabilizationStrengthPref = readerPreferences.stabilization()
|
||||
val stabilizationStrength by stabilizationStrengthPref.collectAsState()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.pref_category_display),
|
||||
preferenceItems = listOf(
|
||||
|
@ -101,6 +106,17 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
pref = fullscreenPref,
|
||||
title = stringResource(R.string.pref_fullscreen),
|
||||
),
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = stabilizationStrength,
|
||||
title = stringResource(R.string.pref_stabilization_strength),
|
||||
subtitle = stabilizationNumberFormat.format(stabilizationStrength / 100f),
|
||||
min = ReaderPreferences.STABILIZATION_STRENGTH_MIN,
|
||||
max = ReaderPreferences.STABILIZATION_STRENGTH_MAX,
|
||||
onValueChanged = {
|
||||
stabilizationStrengthPref.set(it)
|
||||
true
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.cutoutShort(),
|
||||
title = stringResource(R.string.pref_cutout_short),
|
||||
|
|
|
@ -10,6 +10,10 @@ import android.graphics.Color
|
|||
import android.graphics.ColorMatrix
|
||||
import android.graphics.ColorMatrixColorFilter
|
||||
import android.graphics.Paint
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
@ -73,6 +77,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
|||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.stabilization.StabilizationProvider
|
||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||
import eu.kanade.tachiyomi.util.system.isNightMode
|
||||
|
@ -138,6 +143,11 @@ class ReaderActivity : BaseActivity() {
|
|||
|
||||
private var loadingIndicator: ReaderProgressIndicator? = null
|
||||
|
||||
private lateinit var sensorManager: SensorManager
|
||||
private lateinit var accelerometerEventListener: SensorEventListener
|
||||
private lateinit var accelerometer: Sensor
|
||||
private lateinit var stabilizationProvider: StabilizationProvider
|
||||
|
||||
var isScrollingThroughPages = false
|
||||
private set
|
||||
|
||||
|
@ -175,6 +185,7 @@ class ReaderActivity : BaseActivity() {
|
|||
|
||||
config = ReaderConfig()
|
||||
initializeMenu()
|
||||
initializeStabilization()
|
||||
|
||||
// Finish when incognito mode is disabled
|
||||
preferences.incognitoMode().changes()
|
||||
|
@ -239,6 +250,7 @@ class ReaderActivity : BaseActivity() {
|
|||
override fun onPause() {
|
||||
viewModel.flushReadTimer()
|
||||
super.onPause()
|
||||
pauseStabilization()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -249,6 +261,7 @@ class ReaderActivity : BaseActivity() {
|
|||
super.onResume()
|
||||
viewModel.restartReadTimer()
|
||||
setMenuVisibility(viewModel.state.value.menuVisible, animate = false)
|
||||
startStabilization()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -478,6 +491,42 @@ class ReaderActivity : BaseActivity() {
|
|||
setMenuVisibility(viewModel.state.value.menuVisible)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes screen shake stabilization.
|
||||
*/
|
||||
private fun initializeStabilization() {
|
||||
stabilizationProvider = StabilizationProvider(binding.readerContainer,
|
||||
readerPreferences.stabilization())
|
||||
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
|
||||
accelerometerEventListener = object : SensorEventListener {
|
||||
override fun onSensorChanged(event: SensorEvent?) {
|
||||
if (event != null) {
|
||||
stabilizationProvider.stabilize(event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(p0: Sensor?, p1: Int) { }
|
||||
}
|
||||
|
||||
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts screen shake stabilization.
|
||||
*/
|
||||
private fun startStabilization() {
|
||||
sensorManager.registerListener(accelerometerEventListener,
|
||||
accelerometer, SensorManager.SENSOR_DELAY_FASTEST)
|
||||
stabilizationProvider.resetStabilization()
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses screen shake stabilization.
|
||||
*/
|
||||
private fun pauseStabilization() {
|
||||
sensorManager.unregisterListener(accelerometerEventListener)
|
||||
}
|
||||
|
||||
private fun initBottomShortcuts() {
|
||||
// Reading mode
|
||||
with(binding.actionReadingMode) {
|
||||
|
|
|
@ -25,6 +25,8 @@ class ReaderPreferences(
|
|||
|
||||
fun fullscreen() = preferenceStore.getBoolean("fullscreen", true)
|
||||
|
||||
fun stabilization() = preferenceStore.getInt("stabilization", 40)
|
||||
|
||||
fun cutoutShort() = preferenceStore.getBoolean("cutout_short", true)
|
||||
|
||||
fun keepScreenOn() = preferenceStore.getBoolean("pref_keep_screen_on_key", true)
|
||||
|
@ -146,6 +148,9 @@ class ReaderPreferences(
|
|||
const val WEBTOON_PADDING_MIN = 0
|
||||
const val WEBTOON_PADDING_MAX = 25
|
||||
|
||||
const val STABILIZATION_STRENGTH_MIN = 0
|
||||
const val STABILIZATION_STRENGTH_MAX = 100
|
||||
|
||||
val TapZones = listOf(
|
||||
R.string.label_default,
|
||||
R.string.l_nav,
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package eu.kanade.tachiyomi.util.stabilization
|
||||
|
||||
import android.hardware.SensorEvent
|
||||
import android.view.View
|
||||
import tachiyomi.core.preference.Preference
|
||||
|
||||
const val NANOSECONDS_TO_SECONDS = 1e-9f
|
||||
const val BASE_VELOCITY_FRICTION = .2f
|
||||
const val VELOCITY_SHIFT = .1f
|
||||
const val BASE_POSITION_FRICTION = BASE_VELOCITY_FRICTION / 2
|
||||
const val POSITION_SHIFT = VELOCITY_SHIFT / 2
|
||||
const val MAX_ACCELERATION = 5.0f
|
||||
const val ALPHA = 0.85f
|
||||
|
||||
/**
|
||||
* This class represents stabilization provider.
|
||||
* @param targetView view to be stabilized.
|
||||
* @param strengthPreference strength of stabilization.
|
||||
* Note that strength will be changed according to settings even after object creation.
|
||||
*/
|
||||
class StabilizationProvider(
|
||||
private val targetView: View,
|
||||
private val strengthPreference: Preference<Int>,
|
||||
) {
|
||||
private val position: MutableList<Float> = MutableList(2) { 0f }
|
||||
private val velocity: MutableList<Float> = MutableList(2) { 0f }
|
||||
private val acceleration: MutableList<Float> = MutableList(2) { 0f }
|
||||
private var timestamp: Long = 0L
|
||||
|
||||
/**
|
||||
* Stabilizes view according to provided event.
|
||||
* @param sensorEvent acceleration sensor event.
|
||||
*/
|
||||
fun stabilize(sensorEvent: SensorEvent) {
|
||||
val strength = strengthPreference.get()
|
||||
|
||||
if (strength != 0) {
|
||||
if (timestamp != 0L) {
|
||||
val deltaTime = (sensorEvent.timestamp - timestamp) * NANOSECONDS_TO_SECONDS
|
||||
|
||||
val velocityFriction = BASE_VELOCITY_FRICTION * ((100 - strength) / 100f) + VELOCITY_SHIFT
|
||||
val positionFriction = BASE_POSITION_FRICTION * ((100 - strength) / 100f) + POSITION_SHIFT
|
||||
|
||||
for (i in 0..1) {
|
||||
acceleration[i] = alphaFilter(
|
||||
rangeValue(sensorEvent.values[i], -MAX_ACCELERATION, MAX_ACCELERATION),
|
||||
acceleration[i],
|
||||
)
|
||||
|
||||
velocity[i] += acceleration[i] * deltaTime - velocityFriction * velocity[i]
|
||||
velocity[i] = fixNanOrInfinite(velocity[i])
|
||||
|
||||
position[i] += velocity[i] * deltaTime * 10000 - positionFriction * position[i]
|
||||
}
|
||||
|
||||
targetView.translationX = -position[0]
|
||||
targetView.translationY = position[1]
|
||||
}
|
||||
} else {
|
||||
targetView.translationX = 0f
|
||||
targetView.translationY = 0f
|
||||
}
|
||||
|
||||
timestamp = sensorEvent.timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully resets stabilization state.
|
||||
*/
|
||||
fun resetStabilization() {
|
||||
position[0] = 0f
|
||||
position[1] = 0f
|
||||
|
||||
velocity[0] = 0f
|
||||
velocity[1] = 0f
|
||||
|
||||
acceleration[0] = 0f
|
||||
acceleration[1] = 0f
|
||||
|
||||
timestamp = 0
|
||||
|
||||
targetView.translationX = 0f
|
||||
targetView.translationY = 0f
|
||||
}
|
||||
|
||||
private fun alphaFilter(current: Float, previous: Float): Float {
|
||||
return previous + ALPHA * (current - previous)
|
||||
}
|
||||
|
||||
private fun rangeValue(value: Float, min: Float, max: Float): Float {
|
||||
return value.coerceAtLeast(min).coerceAtMost(max)
|
||||
}
|
||||
|
||||
private fun fixNanOrInfinite(value: Float): Float {
|
||||
return if (value.isFinite()) {
|
||||
value
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
}
|
|
@ -131,6 +131,7 @@
|
|||
<string name="pref_enable_acra">Отправлять отчёты об ошибках</string>
|
||||
<string name="pref_page_transitions">Анимированные переходы страниц</string>
|
||||
<string name="pref_fullscreen">Полноэкранный режим</string>
|
||||
<string name="pref_stabilization_strength">Сила стабилизации</string>
|
||||
<string name="pref_image_scale_type">Масштабирование</string>
|
||||
<string name="pref_keep_screen_on">Не выключать экран</string>
|
||||
<string name="pref_library_columns">Размер сетки</string>
|
||||
|
|
|
@ -319,6 +319,7 @@
|
|||
|
||||
<!-- Reader section -->
|
||||
<string name="pref_fullscreen">Fullscreen</string>
|
||||
<string name="pref_stabilization_strength">Stabilization strength</string>
|
||||
<string name="pref_show_navigation_mode">Show tap zones overlay</string>
|
||||
<string name="pref_show_navigation_mode_summary">Briefly show when reader is opened</string>
|
||||
<string name="pref_dual_page_split">Split wide pages</string>
|
||||
|
|
Reference in a new issue