feat: photo thumb

This commit is contained in:
cgspine
2022-01-24 01:23:21 +08:00
parent 957ef293ed
commit cbe36c52c2
9 changed files with 872 additions and 65 deletions
@@ -15,26 +15,36 @@
*/
package com.qmuiteam.photo.activity
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.*
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.toSize
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.SavedStateHandle
@@ -57,34 +67,37 @@ class PhotoViewerViewModel(val state: SavedStateHandle) : ViewModel() {
val enterIndex = state.get<Int>(PHOTO_CURRENT_INDEX) ?: 0
val data: PhotoViewerData?
var isEnterTransitionFinished: Boolean = false
private val transitionDeliverKey = state.get<Long>(PHOTO_TRANSITION_DELIVERY_KEY) ?: -1
init {
val transitionDeliverData = PhotoTransitionDelivery.getAndRemove(transitionDeliverKey)
data = if(transitionDeliverData != null){
data = if (transitionDeliverData != null) {
transitionDeliverData
}else{
} else {
val count = state.get<Int>(PHOTO_COUNT) ?: 0
if(count > 0){
val list = arrayListOf<PhotoProvider>()
for(i in 0 until count){
try{
if (count > 0) {
val list = arrayListOf<QMUIPhotoTransition>()
for (i in 0 until count) {
try {
val meta = state.get<Bundle>("${PHOTO_META_KEY_PREFIX}${i}")
val clsName = state.get<String>("${PHOTO_PROVIDER_RECOVER_CLASS_KEY_PREFIX}${i}")
if(meta == null || clsName.isNullOrBlank()){
list.add(LossPhotoProvider.instance)
}else{
val clsName =
state.get<String>("${PHOTO_PROVIDER_RECOVER_CLASS_KEY_PREFIX}${i}")
if (meta == null || clsName.isNullOrBlank()) {
list.add(lossPhotoTransition)
} else {
val cls = Class.forName(clsName)
val recover = cls.newInstance() as PhotoProviderRecover
list.add(recover.recover(meta) ?: LossPhotoProvider.instance)
val recover = cls.newInstance() as PhotoTransitionProviderRecover
list.add(recover.recover(meta) ?: lossPhotoTransition)
}
}catch (e: Throwable){
list.add(LossPhotoProvider.instance)
} catch (e: Throwable) {
list.add(lossPhotoTransition)
}
}
PhotoViewerData(list, enterIndex, null)
}else{
} else {
null
}
}
@@ -101,9 +114,9 @@ open class QMUIPhotoViewerActivity : AppCompatActivity() {
companion object {
fun intentOf(
activity: AppCompatActivity,
activity: ComponentActivity,
cls: Class<in QMUIPhotoViewerActivity>,
list: List<PhotoProvider>,
list: List<QMUIPhotoTransition>,
index: Int
): Intent {
val data = PhotoViewerData(list, index, activity.window.decorView.asBitmap())
@@ -111,12 +124,15 @@ open class QMUIPhotoViewerActivity : AppCompatActivity() {
intent.putExtra(PHOTO_TRANSITION_DELIVERY_KEY, PhotoTransitionDelivery.put(data))
intent.putExtra(PHOTO_CURRENT_INDEX, index)
intent.putExtra(PHOTO_COUNT, list.size)
list.forEachIndexed { i, provider ->
val meta = provider.meta()
val recoverCls = provider.recoverCls()
list.forEachIndexed { i, transition ->
val meta = transition.photoProvider.meta()
val recoverCls = transition.photoProvider.recoverCls()
if (meta != null && recoverCls != null) {
intent.putExtra("${PHOTO_META_KEY_PREFIX}${i}", meta)
intent.putExtra("${PHOTO_PROVIDER_RECOVER_CLASS_KEY_PREFIX}${i}", recoverCls.name)
intent.putExtra(
"${PHOTO_PROVIDER_RECOVER_CLASS_KEY_PREFIX}${i}",
recoverCls.name
)
}
}
return intent
@@ -133,9 +149,9 @@ open class QMUIPhotoViewerActivity : AppCompatActivity() {
it.isAppearanceLightNavigationBars = false
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
window.navigationBarColor = Color.TRANSPARENT
window.navigationBarColor = android.graphics.Color.TRANSPARENT
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
window.navigationBarDividerColor = Color.TRANSPARENT
window.navigationBarDividerColor = android.graphics.Color.TRANSPARENT
}
}
@@ -165,16 +181,173 @@ open class QMUIPhotoViewerActivity : AppCompatActivity() {
@OptIn(ExperimentalPagerApi::class)
@Composable
protected open fun PhotoViewer(list: List<PhotoProvider>, index: Int) {
val currentIndex = rememberSaveable {
mutableStateOf(index)
}
protected open fun PhotoViewer(list: List<QMUIPhotoTransition>, index: Int) {
val pagerState = rememberPagerState(index)
HorizontalPager(
count = list.size,
state = pagerState
) { page ->
PhotoPager(list[page], page == index && !viewModel.isEnterTransitionFinished)
}
}
@Composable
protected open fun PhotoPager(
photoTransition: QMUIPhotoTransition,
animateEnterTarget: Boolean,
) {
var transitionStartOffset = photoTransition.offsetInWindow
var transitionStartSize = photoTransition.size
val transitionStartPhoto = photoTransition.photo
val radio = photoTransition.photoProvider.ratio()
val transitionDurationMs = 1000
PhotoBackgroundWithTransition(animateEnterTarget, transitionDurationMs)
if(transitionStartOffset == null ||
transitionStartSize == null ||
transitionStartPhoto == null ||
radio <= 0f
){
PhotoContentWithAlphaTransition(photoTransition, animateEnterTarget, transitionDurationMs)
}else{
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
val targetViewRatio = constraints.maxWidth * 1f / constraints.maxHeight
val targetWidth = if(radio >= targetViewRatio){
constraints.maxWidth.toFloat()
} else {
constraints.maxHeight * radio
}
val targetHeight = if(radio >= targetViewRatio){
constraints.maxWidth / radio
}else{
constraints.maxHeight.toFloat()
}
val targetLeft = (constraints.maxWidth - targetWidth) / 2f
val targetTop = (constraints.maxHeight - targetHeight) / 2f
PhotoContentWithRectTransition(
photoTransition = photoTransition,
initRect = Rect(transitionStartOffset, transitionStartSize.toSize()),
targetRect = Rect(
targetLeft,
targetTop,
targetLeft + targetWidth,
targetTop + targetHeight
),
transitionDurationMs = transitionDurationMs
)
}
}
}
@Composable
protected open fun PhotoBackgroundWithTransition(
animateEnterTarget: Boolean,
transitionDurationMs: Int
){
val alphaTransitionState = remember {
MutableTransitionState(if(animateEnterTarget) 0f else 1f)
}
alphaTransitionState.targetState = 1f
val transition = updateTransition(transitionState = alphaTransitionState, label = "")
val alpha = transition.animateFloat(
transitionSpec = { tween(durationMillis = transitionDurationMs) },
label = ""
) {
it
}
Box(modifier = Modifier
.fillMaxSize()
.background(Color.Black)
.alpha(alpha.value)
)
}
@Composable
protected open fun PhotoContentWithRectTransition(
photoTransition: QMUIPhotoTransition,
initRect: Rect,
targetRect: Rect,
transitionDurationMs: Int
){
val alphaTransitionState = remember {
MutableTransitionState(initRect)
}
alphaTransitionState.targetState = targetRect
val transition = updateTransition(transitionState = alphaTransitionState, label = "")
val rect = transition.animateRect(
transitionSpec = { tween(durationMillis = transitionDurationMs) },
label = ""
) {
it
}
val left = with(LocalDensity.current){
rect.value.left.toDp()
}
val top = with(LocalDensity.current){
rect.value.top.toDp()
}
val width = with(LocalDensity.current){
rect.value.width.toDp()
}
val height = with(LocalDensity.current){
rect.value.height.toDp()
}
Box(
modifier = Modifier.offset(left, top).width(width).height(height)
){
PhotoContent(photoTransition)
}
}
@Composable
protected open fun PhotoContentWithAlphaTransition(
photoTransition: QMUIPhotoTransition,
animateEnterTarget: Boolean,
transitionDurationMs: Int
){
val alphaTransitionState = remember {
MutableTransitionState(if(animateEnterTarget) 0f else 1f)
}
alphaTransitionState.targetState = 1f
val transition = updateTransition(transitionState = alphaTransitionState, label = "")
val alpha = transition.animateFloat(
transitionSpec = { tween(durationMillis = transitionDurationMs) },
label = ""
) {
it
}
Box(modifier = Modifier
.fillMaxSize()
.alpha(alpha.value)
){
PhotoContent(photoTransition)
}
}
@Composable
protected open fun PhotoContent(
photoTransition: QMUIPhotoTransition
){
Box(modifier = Modifier.fillMaxSize()) {
var isPhotoLoadSuccess by remember {
mutableStateOf(false)
}
photoTransition.photoProvider.photo()?.Compose(
contentScale = ContentScale.Fit,
isContainerFixed = true,
isLongImage = false,
onSuccess = { isPhotoLoadSuccess = true },
onError = null
)
if(!isPhotoLoadSuccess){
photoTransition.photoProvider.thumbnail()?.Compose(
contentScale = ContentScale.Fit,
isContainerFixed = true,
isLongImage = false,
onSuccess = null,
onError = null
)
}
}
}
}
@@ -0,0 +1,315 @@
package com.qmuiteam.photo.compose
import android.graphics.drawable.Drawable
import androidx.activity.ComponentActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.qmuiteam.photo.activity.QMUIPhotoViewerActivity
import com.qmuiteam.photo.data.QMUIPhoto
import com.qmuiteam.photo.data.QMUIPhotoProvider
import com.qmuiteam.photo.data.QMUIPhotoTransition
class QMUIPhotoThumbnailConfig(
val singleSquireImageWidthRatio: Float = 0.5f,
val singleWideImageMaxWidthRatio: Float = 0.667f,
val singleHighImageDefaultWidthRatio: Float = 0.5f,
val singleHighImageMaxHeight: Dp = 320.dp,
val singleLongImageWidthRatio: Float = 0.5f,
val isLongImageIfRatioLessThan: Float = 0.2f,
val longImageShowTopRatio: Float = 0.25f,
val averageIfTwoImage: Boolean = true,
val horGap: Dp = 5.dp,
val verGap: Dp = 5.dp,
val alphaWhenPressed: Float = 0.5f
)
val qmuiDefaultPhotoThumbnailConfig = QMUIPhotoThumbnailConfig()
@Composable
private fun QMUIPhotoThumbnailItem(
thumb: QMUIPhoto?,
width: Dp,
height: Dp,
alphaWhenPressed: Float,
isContainerFixed: Boolean,
isLongImage: Boolean,
onLayout: (offset: Offset, size: IntSize) -> Unit,
onPhotoLoaded: (Drawable) -> Unit,
click: (() -> Unit)?,
) {
val interactionSource = remember { MutableInteractionSource() }
val isPressed = interactionSource.collectIsPressedAsState()
Box(modifier = Modifier
.width(width)
.height(height)
.let {
if (click != null) {
it
.clickable(interactionSource, null) {
click.invoke()
}
.alpha(if (isPressed.value) alphaWhenPressed else 1f)
} else {
it
}
}
.onGloballyPositioned {
onLayout(it.positionInWindow(), it.size)
}
) {
thumb?.Compose(
contentScale = ContentScale.Crop,
isContainerFixed = isContainerFixed,
isLongImage = isLongImage,
onSuccess = {
onPhotoLoaded(it)
},
onError = null
)
}
}
@Composable
fun QMUIPhotoThumbnailWithViewer(
activity: ComponentActivity,
images: List<QMUIPhotoProvider>,
config: QMUIPhotoThumbnailConfig = remember { qmuiDefaultPhotoThumbnailConfig }
){
QMUIPhotoThumbnail(images, config){ list, index ->
val intent = QMUIPhotoViewerActivity.intentOf(activity, QMUIPhotoViewerActivity::class.java, list, index)
activity.startActivity(intent)
activity.overridePendingTransition(0, 0)
}
}
@Composable
fun QMUIPhotoThumbnail(
images: List<QMUIPhotoProvider>,
config: QMUIPhotoThumbnailConfig = remember { qmuiDefaultPhotoThumbnailConfig },
onClick: ((images: List<QMUIPhotoTransition>, index: Int) -> Unit)? = null
) {
if (images.size < 0) {
return
}
val renderInfo = remember {
Array(images.size) {
QMUIPhotoTransition(images[it], null, null, null)
}
}
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
if (images.size == 1) {
val image = images[0]
val thumb = image.thumbnail()
if (thumb != null) {
val ratio = image.ratio()
when {
ratio <= 0 -> {
QMUIPhotoThumbnailItem(
thumb,
Dp.Unspecified,
Dp.Unspecified,
config.alphaWhenPressed,
isContainerFixed = false,
isLongImage = false,
onLayout = { offset, size ->
renderInfo[0].offsetInWindow = offset
renderInfo[0].size = size
},
onPhotoLoaded = {
renderInfo[0].photo = it
},
click = if (onClick != null) {
{
onClick.invoke(renderInfo.toList(), 0)
}
} else null
)
}
ratio == 1f -> {
val wh = maxWidth * config.singleSquireImageWidthRatio
QMUIPhotoThumbnailItem(
thumb,
wh,
wh,
config.alphaWhenPressed,
isContainerFixed = true,
isLongImage = false,
onLayout = { offset, size ->
renderInfo[0].offsetInWindow = offset
renderInfo[0].size = size
},
onPhotoLoaded = {
renderInfo[0].photo = it
},
click = if (onClick != null) {
{
onClick.invoke(renderInfo.toList(), 0)
}
} else null
)
}
ratio > 1f -> {
val width = maxWidth * config.singleWideImageMaxWidthRatio
val height = width / ratio
QMUIPhotoThumbnailItem(
thumb,
width,
height,
config.alphaWhenPressed,
isContainerFixed = true,
isLongImage = false,
onLayout = { offset, size ->
renderInfo[0].offsetInWindow = offset
renderInfo[0].size = size
},
onPhotoLoaded = {
renderInfo[0].photo = it
},
click = if (onClick != null) {
{
onClick.invoke(renderInfo.toList(), 0)
}
} else null
)
}
ratio <= config.isLongImageIfRatioLessThan -> {
val width = maxWidth * config.singleLongImageWidthRatio
val height = width / config.longImageShowTopRatio
QMUIPhotoThumbnailItem(
thumb,
width,
height,
config.alphaWhenPressed,
isContainerFixed = true,
isLongImage = true,
onLayout = { offset, size ->
renderInfo[0].offsetInWindow = offset
renderInfo[0].size = size
},
onPhotoLoaded = {
renderInfo[0].photo = it
},
click = if (onClick != null) {
{
onClick.invoke(renderInfo.toList(), 0)
}
} else null
)
}
else -> {
var width = maxWidth * config.singleHighImageDefaultWidthRatio
var height = width / ratio
if (height > config.singleHighImageMaxHeight) {
height = config.singleHighImageMaxHeight
width = height * ratio
}
QMUIPhotoThumbnailItem(
thumb,
width,
height,
config.alphaWhenPressed,
isContainerFixed = true,
isLongImage = false,
onLayout = { offset, size ->
renderInfo[0].offsetInWindow = offset
renderInfo[0].size = size
},
onPhotoLoaded = {
renderInfo[0].photo = it
},
click = if (onClick != null) {
{
onClick.invoke(renderInfo.toList(), 0)
}
} else null
)
}
}
}
} else if (images.size == 2 && config.averageIfTwoImage) {
RowImages(images, renderInfo, config, maxWidth, 2, 0, onClick)
} else {
Column(modifier = Modifier.fillMaxWidth()) {
for (i in 0 until (images.size / 3 + if (images.size % 3 > 0) 1 else 0).coerceAtMost(
3
)) {
if (i > 0) {
Spacer(modifier = Modifier.height(config.verGap))
}
RowImages(
images,
renderInfo,
config,
this@BoxWithConstraints.maxWidth,
3,
i * 3,
onClick
)
}
}
}
}
}
@Composable
fun RowImages(
images: List<QMUIPhotoProvider>,
renderInfo: Array<QMUIPhotoTransition>,
config: QMUIPhotoThumbnailConfig,
containerWidth: Dp,
rowCount: Int,
startIndex: Int,
onClick: ((images: List<QMUIPhotoTransition>, index: Int) -> Unit)?
) {
val wh = (containerWidth - config.horGap * (rowCount - 1)) / rowCount
Row(
modifier = Modifier
.fillMaxWidth()
.height(wh)
) {
for (i in startIndex until (startIndex + rowCount).coerceAtMost(images.size)) {
if (i != startIndex) {
Spacer(modifier = Modifier.width(config.horGap))
}
val image = images[i]
val ratio = image.ratio()
val isLongImage = ratio > 0f && ratio < config.isLongImageIfRatioLessThan
QMUIPhotoThumbnailItem(
image.thumbnail(),
wh,
wh,
config.alphaWhenPressed,
isContainerFixed = true,
isLongImage = isLongImage,
onLayout = { offset, size ->
renderInfo[i].offsetInWindow = offset
renderInfo[i].size = size
},
onPhotoLoaded = {
renderInfo[i].photo = it
},
click = if (onClick != null) {
{
onClick.invoke(renderInfo.toList(), i)
}
} else null
)
}
}
}
@@ -16,54 +16,53 @@
package com.qmuiteam.photo.data
import android.graphics.Bitmap
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.IntSize
class PhotoViewerData(
val list: List<PhotoProvider>,
val list: List<QMUIPhotoTransition>,
val index: Int,
val background: Bitmap?
)
interface Photo {
interface QMUIPhoto {
@Composable
fun Compose()
fun Compose(
contentScale: ContentScale,
isContainerFixed: Boolean,
isLongImage: Boolean,
onSuccess: ((Drawable) -> Unit)?,
onError: ((Throwable) -> Unit)?
)
}
interface PhotoProvider {
suspend fun thumbnail(): Photo?
suspend fun photo(): Photo?
fun transitionStart(): Rect?
fun transitionEnd(): Rect?
interface QMUIPhotoProvider {
fun thumbnail(): QMUIPhoto?
fun photo(): QMUIPhoto?
fun ratio(): Float = -1f
// used to recover PhotoProvider from arguments if activity recreate.
fun meta(): Bundle?
fun recoverCls(): Class<in PhotoProviderRecover>?
fun recoverCls(): Class<in PhotoTransitionProviderRecover>?
}
class LossPhotoProvider private constructor(): PhotoProvider {
class QMUIPhotoTransition(
val photoProvider: QMUIPhotoProvider,
var offsetInWindow: Offset?,
var size: IntSize?,
var photo: Drawable?
)
companion object {
val instance by lazy {
LossPhotoProvider()
}
}
override suspend fun thumbnail(): Photo? {
val lossPhotoProvider = object: QMUIPhotoProvider {
override fun thumbnail(): QMUIPhoto? {
return null
}
override suspend fun photo(): Photo? {
return null
}
override fun transitionStart(): Rect? {
return null
}
override fun transitionEnd(): Rect? {
override fun photo(): QMUIPhoto? {
return null
}
@@ -71,14 +70,16 @@ class LossPhotoProvider private constructor(): PhotoProvider {
return null
}
override fun recoverCls(): Class<in PhotoProviderRecover>? {
override fun recoverCls(): Class<in PhotoTransitionProviderRecover>? {
return null
}
}
interface PhotoProviderRecover {
fun recover(bundle: Bundle): PhotoProvider?
val lossPhotoTransition = QMUIPhotoTransition(lossPhotoProvider, null, null, null)
interface PhotoTransitionProviderRecover {
fun recover(bundle: Bundle): QMUIPhotoTransition?
}
@@ -24,6 +24,7 @@ fun View.asBitmap(): Bitmap? {
canvas.setBitmap(null)
bm
} catch (e: Throwable) {
e.printStackTrace()
null
}
}
@@ -61,4 +61,8 @@ object Dep {
const val butterknife = "com.jakewharton:butterknife:$ver"
const val compiler = "com.jakewharton:butterknife-compiler:$ver"
}
object Coil {
const val compose = "io.coil-kt:coil-compose:2.0.0-alpha06"
}
}
+3
View File
@@ -79,8 +79,11 @@ dependencies {
implementation(project(":arch"))
implementation(project(":type"))
implementation(project(":compose"))
implementation(project(":photo"))
implementation(Dep.Flipper.soLoader)
implementation(Dep.Flipper.flipper)
kapt(project(":compiler"))
kapt(project(":arch-compiler"))
implementation(Dep.Coil.compose)
}
+7
View File
@@ -72,6 +72,13 @@
android:windowSoftInputMode="stateAlwaysHidden|adjustResize">
</activity>
<activity
android:name="com.qmuiteam.photo.activity.QMUIPhotoViewerActivity"
android:screenOrientation="portrait"
tools:ignore="LockedOrientationActivity">
</activity>
</application>
</manifest>
@@ -0,0 +1,301 @@
package com.qmuiteam.qmuidemo.fragment.lab
import android.graphics.drawable.Drawable
import android.os.Bundle
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.qmuiteam.compose.ui.QMUITopBarBackIconItem
import com.qmuiteam.compose.ui.QMUITopBarWithLazyScrollState
import com.qmuiteam.photo.compose.QMUIPhotoThumbnail
import com.qmuiteam.photo.compose.QMUIPhotoThumbnailWithViewer
import com.qmuiteam.photo.data.PhotoTransitionProviderRecover
import com.qmuiteam.photo.data.QMUIPhoto
import com.qmuiteam.photo.data.QMUIPhotoProvider
import com.qmuiteam.qmui.arch.annotation.LatestVisitRecord
import com.qmuiteam.qmuidemo.R
import com.qmuiteam.qmuidemo.base.ComposeBaseFragment
import com.qmuiteam.qmuidemo.lib.annotation.Widget
@Widget(name = "QMUI Photo", iconRes = R.mipmap.icon_grid_in_progress)
@LatestVisitRecord
class QDPhotoFragment : ComposeBaseFragment() {
@Composable
override fun PageContent() {
Column(modifier = Modifier.fillMaxSize()) {
val scrollState = rememberLazyListState()
QMUITopBarWithLazyScrollState(
scrollState = scrollState,
title = "QMUIPhoto",
leftItems = arrayListOf(
QMUITopBarBackIconItem {
popBackStack()
}
),
)
LazyColumn(
state = scrollState,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.background(Color.White),
contentPadding = PaddingValues(start = 44.dp)
) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 20.dp)
) {
QMUIPhotoThumbnailWithViewer(
requireActivity(),
images = listOf(
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9979/31y68oGufDGL3zQ6TT.jpg",
1f
)
)
)
}
}
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 20.dp)
) {
QMUIPhotoThumbnailWithViewer(
requireActivity(),
images = listOf(
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9136/1yn0KLFwy6Vb0nE6Sg.png",
1.379f
)
)
)
}
}
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 20.dp)
) {
QMUIPhotoThumbnailWithViewer(
requireActivity(),
images = listOf(
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
)
)
)
}
}
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 20.dp)
) {
QMUIPhotoThumbnailWithViewer(
requireActivity(),
images = listOf(
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9136/1yn0KLFwy6Vb0nE6Sg.png",
1.379f
)
)
)
}
}
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 20.dp)
) {
QMUIPhotoThumbnailWithViewer(
requireActivity(),
images = listOf(
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9136/1yn0KLFwy6Vb0nE6Sg.png",
1.379f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9979/31y68oGufDGL3zQ6TT.jpg",
1f
)
)
)
}
}
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 20.dp)
) {
QMUIPhotoThumbnailWithViewer(
requireActivity(),
images = listOf(
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9136/1yn0KLFwy6Vb0nE6Sg.png",
1.379f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9979/31y68oGufDGL3zQ6TT.jpg",
1f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9136/1yn0KLFwy6Vb0nE6Sg.png",
1.379f
),
)
)
}
}
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 20.dp)
) {
QMUIPhotoThumbnailWithViewer(
requireActivity(),
images = listOf(
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9136/1yn0KLFwy6Vb0nE6Sg.png",
1.379f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9979/31y68oGufDGL3zQ6TT.jpg",
1f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9136/1yn0KLFwy6Vb0nE6Sg.png",
1.379f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9136/1yn0KLFwy6Vb0nE6Sg.png",
1.379f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/8779/6WY7guGLeGfp0KK6Sb.jpeg",
0.749f
),
CoilPhotoProvider(
"https://weread-picture-1258476243.file.myqcloud.com/9979/31y68oGufDGL3zQ6TT.jpg",
1f
),
)
)
}
}
}
}
}
}
class CoilPhoto(val url: String) : QMUIPhoto {
@Composable
override fun Compose(
contentScale: ContentScale,
isContainerFixed: Boolean,
isLongImage: Boolean,
onSuccess: ((Drawable) -> Unit)?,
onError: ((Throwable) -> Unit)?
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.allowHardware(false)
.listener(onError = { _, result ->
onError?.invoke(result.throwable)
}) { _, result ->
onSuccess?.invoke(result.drawable)
}.build(),
contentDescription = "",
contentScale = contentScale,
alignment = if (isLongImage) Alignment.TopCenter else Alignment.Center,
modifier = Modifier.let {
if(isContainerFixed){
it.fillMaxSize()
}else{
it
}
}
)
}
}
class CoilPhotoProvider(val url: String, val ratio: Float) : QMUIPhotoProvider {
override fun thumbnail(): QMUIPhoto? {
return photo()
}
override fun photo(): QMUIPhoto? {
return CoilPhoto(url)
}
override fun ratio(): Float {
return ratio
}
override fun meta(): Bundle? {
return null
}
override fun recoverCls(): Class<in PhotoTransitionProviderRecover>? {
return null
}
}
@@ -45,6 +45,7 @@ import com.qmuiteam.qmuidemo.fragment.components.viewpager.QDViewPagerFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDAnimationListViewFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDArchTestFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDContinuousNestedScrollFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDPhotoFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDSchemeFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDSnapHelperFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDWebViewFragment;
@@ -145,6 +146,7 @@ public class QDDataManager {
mLabNames.add(QDArchTestFragment.class);
mLabNames.add(QDWebViewFragment.class);
mLabNames.add(QDSchemeFragment.class);
mLabNames.add(QDPhotoFragment.class);
}
public QDItemDescription getDescription(Class<? extends BaseFragment> cls) {