pref: 看图优化
This commit is contained in:
parent
c112cf79eb
commit
cbf1fe64c4
|
|
@ -157,7 +157,6 @@ dependencies {
|
||||||
implementation "io.github.panpf.sketch3:sketch-compose:$sketch_version"
|
implementation "io.github.panpf.sketch3:sketch-compose:$sketch_version"
|
||||||
implementation "io.github.panpf.sketch3:sketch-extensions:$sketch_version"
|
implementation "io.github.panpf.sketch3:sketch-extensions:$sketch_version"
|
||||||
implementation "io.github.panpf.sketch3:sketch-gif-movie:$sketch_version"
|
implementation "io.github.panpf.sketch3:sketch-gif-movie:$sketch_version"
|
||||||
implementation "io.github.panpf.sketch3:sketch-zoom:$sketch_version"
|
|
||||||
implementation "io.github.panpf.sketch3:sketch-okhttp:$sketch_version"
|
implementation "io.github.panpf.sketch3:sketch-okhttp:$sketch_version"
|
||||||
|
|
||||||
def zoomimage_version = "1.0.0-alpha03"
|
def zoomimage_version = "1.0.0-alpha03"
|
||||||
|
|
|
||||||
|
|
@ -29,22 +29,22 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import com.github.panpf.sketch.request.DisplayRequest
|
||||||
import com.github.panpf.sketch.displayImage
|
import com.github.panpf.zoomimage.SketchZoomAsyncImage
|
||||||
import com.github.panpf.sketch.zoom.Edge
|
import com.google.accompanist.systemuicontroller.SystemUiController
|
||||||
import com.github.panpf.sketch.zoom.ReadModeDecider
|
|
||||||
import com.github.panpf.sketch.zoom.SketchZoomImageView
|
|
||||||
import com.huanchengfly.tieba.post.R
|
import com.huanchengfly.tieba.post.R
|
||||||
import com.huanchengfly.tieba.post.arch.BaseComposeActivityWithParcelable
|
import com.huanchengfly.tieba.post.arch.BaseComposeActivityWithParcelable
|
||||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||||
|
|
@ -56,65 +56,40 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ProvideContentColor
|
import com.huanchengfly.tieba.post.ui.widgets.compose.ProvideContentColor
|
||||||
import com.huanchengfly.tieba.post.utils.ImageUtil
|
import com.huanchengfly.tieba.post.utils.ImageUtil
|
||||||
import com.huanchengfly.tieba.post.utils.download
|
import com.huanchengfly.tieba.post.utils.download
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
object MyReadModeDecider : ReadModeDecider {
|
|
||||||
override fun should(
|
|
||||||
imageWidth: Int,
|
|
||||||
imageHeight: Int,
|
|
||||||
viewWidth: Int,
|
|
||||||
viewHeight: Int
|
|
||||||
): Boolean {
|
|
||||||
val imageAspectRatio = imageHeight.toFloat() / imageWidth
|
|
||||||
val viewAspectRatio = viewHeight.toFloat() / viewWidth
|
|
||||||
return if (viewAspectRatio > 1f) {
|
|
||||||
imageAspectRatio >= viewAspectRatio * 1.25f
|
|
||||||
} else {
|
|
||||||
imageAspectRatio >= (1f / viewAspectRatio) * 3f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ViewPhoto(
|
private fun ViewPhoto(
|
||||||
imageUri: String,
|
imageUri: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onDrag: ((dx: Float, dy: Float, isAtEdge: Boolean) -> Unit)? = null
|
onTap: (offset: Offset) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
var progress by remember { mutableStateOf(0f) }
|
val context = LocalContext.current
|
||||||
|
var progress by remember { mutableFloatStateOf(0f) }
|
||||||
var showProgress by remember { mutableStateOf(true) }
|
var showProgress by remember { mutableStateOf(true) }
|
||||||
AndroidView(
|
val request = remember(imageUri) {
|
||||||
factory = {
|
DisplayRequest.Builder(context, imageUri)
|
||||||
SketchZoomImageView(it).apply {
|
.listener(
|
||||||
readModeEnabled = true
|
onStart = { showProgress = true },
|
||||||
readModeDecider = MyReadModeDecider
|
onSuccess = { _, _ -> showProgress = false },
|
||||||
allowParentInterceptOnEdge = true
|
onError = { _, _ -> showProgress = false },
|
||||||
addOnViewDragListener { dx, dy ->
|
onCancel = { showProgress = false }
|
||||||
val isAtEdge = horScrollEdge != Edge.NONE
|
)
|
||||||
onDrag?.invoke(dx, dy, isAtEdge)
|
.progressListener { _, totalLength, completedLength ->
|
||||||
}
|
progress = completedLength.toFloat() / totalLength
|
||||||
}
|
}
|
||||||
},
|
.build()
|
||||||
|
}
|
||||||
|
SketchZoomAsyncImage(
|
||||||
|
request = request,
|
||||||
|
contentDescription = null,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
update = {
|
onTap = onTap,
|
||||||
it.displayImage(imageUri) {
|
|
||||||
listener(
|
|
||||||
onStart = { showProgress = true },
|
|
||||||
onSuccess = { _, _ -> showProgress = false },
|
|
||||||
onError = { _, _ -> showProgress = false },
|
|
||||||
onCancel = { showProgress = false }
|
|
||||||
)
|
|
||||||
progressListener { _, totalLength: Long, completedLength: Long ->
|
|
||||||
progress = (completedLength.toDouble() / totalLength).toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
if (showProgress) {
|
if (showProgress) {
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -150,7 +125,7 @@ class PhotoViewActivity : BaseComposeActivityWithParcelable<PhotoViewData>() {
|
||||||
}
|
}
|
||||||
val items by viewModel.uiState.collectPartialAsState(
|
val items by viewModel.uiState.collectPartialAsState(
|
||||||
prop1 = PhotoViewUiState::data,
|
prop1 = PhotoViewUiState::data,
|
||||||
initial = emptyList()
|
initial = persistentListOf()
|
||||||
)
|
)
|
||||||
val initialIndex by viewModel.uiState.collectPartialAsState(
|
val initialIndex by viewModel.uiState.collectPartialAsState(
|
||||||
prop1 = PhotoViewUiState::initialIndex,
|
prop1 = PhotoViewUiState::initialIndex,
|
||||||
|
|
@ -172,14 +147,17 @@ class PhotoViewActivity : BaseComposeActivityWithParcelable<PhotoViewData>() {
|
||||||
prop1 = PhotoViewUiState::loadPicPageData,
|
prop1 = PhotoViewUiState::loadPicPageData,
|
||||||
initial = LoadPicPageData()
|
initial = LoadPicPageData()
|
||||||
)
|
)
|
||||||
|
|
||||||
val pageCount by remember { derivedStateOf { items.size } }
|
val pageCount by remember { derivedStateOf { items.size } }
|
||||||
|
|
||||||
|
val pagerState = rememberPagerState(initialPage = initialIndex) { pageCount }
|
||||||
|
|
||||||
|
LaunchedEffect(initialIndex) {
|
||||||
|
if (pagerState.currentPage != initialIndex) pagerState.scrollToPage(initialIndex)
|
||||||
|
}
|
||||||
|
|
||||||
Surface(color = Color.Black) {
|
Surface(color = Color.Black) {
|
||||||
if (items.isNotEmpty()) {
|
if (items.isNotEmpty()) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val pagerState = rememberPagerState(initialPage = initialIndex) { pageCount }
|
|
||||||
LaunchedEffect(initialIndex) {
|
|
||||||
if (pagerState.currentPage != initialIndex) pagerState.scrollToPage(initialIndex)
|
|
||||||
}
|
|
||||||
LaunchedEffect(pagerState.currentPage, pageCount, loadPicPageData) {
|
LaunchedEffect(pagerState.currentPage, pageCount, loadPicPageData) {
|
||||||
loadPicPageData?.let {
|
loadPicPageData?.let {
|
||||||
val item = items[pagerState.currentPage]
|
val item = items[pagerState.currentPage]
|
||||||
|
|
@ -205,30 +183,15 @@ class PhotoViewActivity : BaseComposeActivityWithParcelable<PhotoViewData>() {
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
key = {
|
key = { items[it].picId }
|
||||||
"${items[it].overallIndex}"
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
val item = items[it]
|
val item = items[it]
|
||||||
ViewPhoto(
|
ViewPhoto(
|
||||||
imageUri = item.originUrl,
|
imageUri = item.originUrl,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
onDrag = { dx, dy, isAtEdge ->
|
onTap = {
|
||||||
val currentPage = pagerState.currentPage
|
finish()
|
||||||
if (abs(dy) < 15 && abs(dx) > 30 && isAtEdge) {
|
},
|
||||||
val prevPage = currentPage - 1
|
|
||||||
val nextPage = currentPage + 1
|
|
||||||
if (dx > 0 && prevPage >= 0) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
pagerState.animateScrollToPage(prevPage)
|
|
||||||
}
|
|
||||||
} else if (dx < 0 && nextPage < items.size) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
pagerState.animateScrollToPage(nextPage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -313,6 +276,10 @@ class PhotoViewActivity : BaseComposeActivityWithParcelable<PhotoViewData>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateContent(systemUiController: SystemUiController) {
|
||||||
|
systemUiController.isSystemBarsVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||||
return try {
|
return try {
|
||||||
super.dispatchTouchEvent(ev)
|
super.dispatchTouchEvent(ev)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ import com.huanchengfly.tieba.post.arch.UiIntent
|
||||||
import com.huanchengfly.tieba.post.arch.UiState
|
import com.huanchengfly.tieba.post.arch.UiState
|
||||||
import com.huanchengfly.tieba.post.models.protos.LoadPicPageData
|
import com.huanchengfly.tieba.post.models.protos.LoadPicPageData
|
||||||
import com.huanchengfly.tieba.post.models.protos.PhotoViewData
|
import com.huanchengfly.tieba.post.models.protos.PhotoViewData
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
|
@ -62,6 +65,7 @@ class PhotoViewViewModel :
|
||||||
val hasPrev = items.first().overallIndex > 1
|
val hasPrev = items.first().overallIndex > 1
|
||||||
PhotoViewPartialChange.LoadPrev.Success(
|
PhotoViewPartialChange.LoadPrev.Success(
|
||||||
hasPrev = hasPrev,
|
hasPrev = hasPrev,
|
||||||
|
currentPicId = picId,
|
||||||
items = items
|
items = items
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -159,12 +163,15 @@ class PhotoViewViewModel :
|
||||||
val items = localItems + fetchedItems
|
val items = localItems + fetchedItems
|
||||||
val hasNext = items.last().overallIndex < picAmount
|
val hasNext = items.last().overallIndex < picAmount
|
||||||
val hasPrev = items.first().overallIndex > 1
|
val hasPrev = items.first().overallIndex > 1
|
||||||
|
val initialIndex =
|
||||||
|
items.indexOfFirst { it.picId == data.data_.picId }.takeIf { it != -1 }
|
||||||
|
?: (data.data_.picIndex - 1)
|
||||||
PhotoViewPartialChange.Init.Success(
|
PhotoViewPartialChange.Init.Success(
|
||||||
hasPrev = hasPrev,
|
hasPrev = hasPrev,
|
||||||
hasNext = hasNext,
|
hasNext = hasNext,
|
||||||
totalAmount = picAmount,
|
totalAmount = picAmount,
|
||||||
items = items,
|
items = items,
|
||||||
initialIndex = data.data_.picIndex - 1,
|
initialIndex = initialIndex,
|
||||||
loadPicPageData = data.data_
|
loadPicPageData = data.data_
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +205,7 @@ sealed interface PhotoViewPartialChange : PartialChange<PhotoViewUiState> {
|
||||||
override fun reduce(oldState: PhotoViewUiState): PhotoViewUiState =
|
override fun reduce(oldState: PhotoViewUiState): PhotoViewUiState =
|
||||||
when (this) {
|
when (this) {
|
||||||
is Success -> oldState.copy(
|
is Success -> oldState.copy(
|
||||||
data = items,
|
data = items.toImmutableList(),
|
||||||
hasNext = hasNext,
|
hasNext = hasNext,
|
||||||
hasPrev = hasPrev,
|
hasPrev = hasPrev,
|
||||||
totalAmount = totalAmount,
|
totalAmount = totalAmount,
|
||||||
|
|
@ -216,7 +223,7 @@ sealed interface PhotoViewPartialChange : PartialChange<PhotoViewUiState> {
|
||||||
url = if (item.showOriginBtn) item.url else null,
|
url = if (item.showOriginBtn) item.url else null,
|
||||||
overallIndex = index + 1
|
overallIndex = index + 1
|
||||||
)
|
)
|
||||||
},
|
}.toImmutableList(),
|
||||||
hasNext = false,
|
hasNext = false,
|
||||||
hasPrev = false,
|
hasPrev = false,
|
||||||
totalAmount = data.picItems.size,
|
totalAmount = data.picItems.size,
|
||||||
|
|
@ -246,19 +253,26 @@ sealed interface PhotoViewPartialChange : PartialChange<PhotoViewUiState> {
|
||||||
when (this) {
|
when (this) {
|
||||||
Start -> oldState.copy(isLoading = true)
|
Start -> oldState.copy(isLoading = true)
|
||||||
|
|
||||||
is Success -> oldState.copy(
|
is Success -> {
|
||||||
data = items.filterNot { item -> oldState.data.any { item.picId == it.picId } } + oldState.data,
|
val items =
|
||||||
hasPrev = hasPrev,
|
(items.filterNot { item -> oldState.data.any { item.picId == it.picId } } + oldState.data).toImmutableList()
|
||||||
isLoading = false
|
val newIndex = items.indexOfFirst { it.picId == currentPicId }
|
||||||
)
|
oldState.copy(
|
||||||
|
data = items,
|
||||||
|
hasPrev = hasPrev,
|
||||||
|
isLoading = false,
|
||||||
|
initialIndex = newIndex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is Failure -> oldState.copy(isLoading = false)
|
is Failure -> oldState.copy(isLoading = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Start : LoadPrev()
|
data object Start : LoadPrev()
|
||||||
|
|
||||||
data class Success(
|
data class Success(
|
||||||
val items: List<PhotoViewItem>,
|
val items: List<PhotoViewItem>,
|
||||||
|
val currentPicId: String,
|
||||||
val hasPrev: Boolean,
|
val hasPrev: Boolean,
|
||||||
) : LoadPrev()
|
) : LoadPrev()
|
||||||
|
|
||||||
|
|
@ -273,7 +287,7 @@ sealed interface PhotoViewPartialChange : PartialChange<PhotoViewUiState> {
|
||||||
Start -> oldState.copy(isLoading = true)
|
Start -> oldState.copy(isLoading = true)
|
||||||
|
|
||||||
is Success -> oldState.copy(
|
is Success -> oldState.copy(
|
||||||
data = oldState.data + items.filterNot { item -> oldState.data.any { item.picId == it.picId } },
|
data = (oldState.data + items.filterNot { item -> oldState.data.any { item.picId == it.picId } }).toImmutableList(),
|
||||||
hasNext = hasNext,
|
hasNext = hasNext,
|
||||||
isLoading = false
|
isLoading = false
|
||||||
)
|
)
|
||||||
|
|
@ -281,7 +295,7 @@ sealed interface PhotoViewPartialChange : PartialChange<PhotoViewUiState> {
|
||||||
is Failure -> oldState.copy(isLoading = false)
|
is Failure -> oldState.copy(isLoading = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Start : LoadMore()
|
data object Start : LoadMore()
|
||||||
|
|
||||||
data class Success(
|
data class Success(
|
||||||
val items: List<PhotoViewItem>,
|
val items: List<PhotoViewItem>,
|
||||||
|
|
@ -296,7 +310,7 @@ sealed interface PhotoViewPartialChange : PartialChange<PhotoViewUiState> {
|
||||||
|
|
||||||
data class PhotoViewUiState(
|
data class PhotoViewUiState(
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val data: List<PhotoViewItem> = emptyList(),
|
val data: ImmutableList<PhotoViewItem> = persistentListOf(),
|
||||||
val totalAmount: Int = 0,
|
val totalAmount: Int = 0,
|
||||||
val hasNext: Boolean = false,
|
val hasNext: Boolean = false,
|
||||||
val hasPrev: Boolean = false,
|
val hasPrev: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,16 @@
|
||||||
<item name="android:navigationBarColor">?colorNavBar</item>
|
<item name="android:navigationBarColor">?colorNavBar</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.PhotoView" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||||
|
<item name="android:windowAnimationStyle">@style/Animation.Fade</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowFullscreen">true</item>
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
<item name="android:windowBackground">@color/transparent</item>
|
||||||
|
<item name="android:statusBarColor">@color/transparent</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item><!--控制刘海处显示信息-->
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Reference in New Issue