当前位置: 首页> 文旅> 艺术 > 世界500强企业是什么意思_前端开发主要工作内容_全国疫情最新情况公布_中美关系最新消息

世界500强企业是什么意思_前端开发主要工作内容_全国疫情最新情况公布_中美关系最新消息

时间:2025/7/10 7:33:03来源:https://blog.csdn.net/archko/article/details/147089519 浏览次数:0次
世界500强企业是什么意思_前端开发主要工作内容_全国疫情最新情况公布_中美关系最新消息

目录

zoomable-image

ZoomableImageSource

Coil3ImageSource

ZoomableImage

SubSamplingImage

SubSamplingImage

RealSubSamplingImageState

ImageCache


zoomable-image

zoomable的流程,事件分析过了,它作用于任何view,zoomable-image主要是针对图片的.

ZoomableImageSource

数据源,定义了两类interface ImageDelegate

PainterDelegate和SubSamplingDelegate.一个是普通的view,一个是根据采样率加载的.

比如coil,则在coil的包下面实现加载数据

有Coil3ImageSource,GlideImageSource,都是返回ResolveResult.

Coil3ImageSource

分析一下它是如何加载的.

override fun resolve(canvasSize: Flow<Size>): ResolveResult {val context = LocalContext.currentval resolver = remember(this) {val requests = models.map { model ->model as? ImageRequest?: ImageRequest.Builder(context).data(model).build()}Resolver(requests = requests,imageLoaders = imageLoaders,sizeResolver = { canvasSize.first().toCoilSize() },)}return resolver.resolved}

建一个ImageRequest,这是coil的请求方式.然后返回Resolver(把request作为参数传入).它由RememberWorker的协程启动,调用work()方法.

///work又调用私有的work(),进入加载图片
private suspend fun work(request: ImageRequest, imageLoader: ImageLoader, skipMemoryCache: Boolean) {val result = imageLoader.execute(request.newBuilder().size(request.defined.sizeResolver ?: sizeResolver)// There's no easy way to be certain whether an image will require sub-sampling in// advance so assume it'll be needed and force Coil to write this image to disk..diskCachePolicy(when (request.diskCachePolicy) {CachePolicy.ENABLED -> CachePolicy.ENABLEDCachePolicy.READ_ONLY -> CachePolicy.ENABLEDCachePolicy.WRITE_ONLY,CachePolicy.DISABLED -> CachePolicy.WRITE_ONLY}).memoryCachePolicy(if (skipMemoryCache) CachePolicy.WRITE_ONLY else request.memoryCachePolicy)// This will unfortunately replace any existing target, but it is also the only// way to read placeholder images set using ImageRequest#placeholderMemoryCacheKey.// Placeholder images should be small in size so sub-sampling isn't needed here..target(onStart = {resolved = resolved.copy(placeholder = it?.asPainter(request.context),)})// Increase memory cache hit rate because the image will anyway fit the canvas// size at draw time..precision(when (request.defined.precision) {Precision.EXACT -> request.precisionelse -> Precision.INEXACT})// While telephoto will take care of loading the full-sized image, let Coil downsize// this image since there is still a possibility that the image may not be saved to// disk if (e.g., if Cache-Control HTTP headers prevent disk caching)..maxBitmapSize(CoilSize.ORIGINAL).build())val imageSource = when (val it = result.toSubSamplingImageSource(imageLoader)) {null -> nullis EligibleForSubSampling -> it.sourceis ImageDeletedOnlyFromDiskCache -> {if (skipMemoryCache) {error("Coil returned an image that is missing from both its memory and disk caches")} else {// The app's disk cache was possibly deleted, but the image is// still cached in memory. Reload the image from the network.work(request, imageLoader, skipMemoryCache = true)}return}}resolved = resolved.copy(crossfadeDuration = result.crossfadeDuration(),delegate = if (result is SuccessResult && imageSource != null) {ZoomableImageSource.SubSamplingDelegate(source = imageSource,imageOptions = ImageBitmapOptions(from = (result.image as BitmapImage).bitmap))} else {ZoomableImageSource.PainterDelegate(painter = result.image?.asPainter(request.context))},)}

这个加载过程,就是imageLoader.execute()加载数据,然后,判断是哪种类型,返回SubSamplingDelegate或PainterDelegate.

ZoomableImage

数据源有了,数据加载完成后,就是应用

when (val delegate = resolved.delegate) {null -> {Box(Modifier) //没有图片}is ZoomableImageSource.PainterDelegate -> {...Image(modifier = zoomable,painter = animatedPainter(painter),contentDescription = contentDescription,alignment = Alignment.Center,contentScale = ContentScale.Inside,alpha = alpha * animatedAlpha,colorFilter = colorFilter,)}is ZoomableImageSource.SubSamplingDelegate -> {...SubSamplingImage(modifier = zoomable,state = subSamplingState,contentDescription = contentDescription,alpha = alpha * animatedAlpha,colorFilter = colorFilter,)}}

根据不同的数据,显示不同的image.

@Composable
fun ZoomableImage(image: ZoomableImageSource,contentDescription: String?,modifier: Modifier = Modifier,state: ZoomableImageState = rememberZoomableImageState(rememberZoomableState()),alpha: Float = DefaultAlpha,colorFilter: ColorFilter? = null,alignment: Alignment = Alignment.Center,contentScale: ContentScale = ContentScale.Fit,gesturesEnabled: Boolean = true,onClick: ((Offset) -> Unit)? = null,onLongClick: ((Offset) -> Unit)? = null,clipToBounds: Boolean = true,onDoubleClick: DoubleClickToZoomListener = DoubleClickToZoomListener.cycle(),
)

这个参数真不少.

开始先应用对齐与缩放的方式.

state.zoomableState.also {

it.contentAlignment = alignment

it.contentScale = contentScale

}

然后触发加载数据,它最终会触发上面的source里面的协程来加载.

val resolved = key(image) {image.resolve(canvasSize = remember {snapshotFlow { canvasSize }.filter { it.isSpecified && !it.isEmpty() }})}

然后就是Box中监听图片的加载状态.

state.isImageDisplayed.

state.isPlaceholderDisplayed 处理了占位图

展示方面相对要简单一些.image就是整张图片展示.

复杂的是SubSamplingImage

SubSamplingImage
SubSamplingImage

这是根据采样率来加载图片的,如果采样为1,加载原图时会分块加载

zoomableimage可以根据缩放与图片的大小来采取是用哪种,当缩放或图片过大,它会使用SubSamplingImage,或者可以忽略zoomableimage,直接使用SubSamplingImage更简单

sealed interface SubSamplingImageState {/** Raw size of the image, without any scaling applied. */val imageSize: IntSize?val isImageDisplayed: Boolean/** Whether the image is loaded and displayed in its full quality. */val isImageDisplayedInFullQuality: Boolean
...
}

图片的原始大小与是否高质量显示.两个属性.

@Composable
fun rememberSubSamplingImageState(imageSource: SubSamplingImageSource,zoomableState: ZoomableState,imageOptions: ImageBitmapOptions = ImageBitmapOptions.Default,errorReporter: SubSamplingImageErrorReporter = SubSamplingImageErrorReporter.NoOpInRelease
)
zoomableState.autoApplyTransformations = false先将自动应用转换设置为false,避免view的应用.由SubSamplingImage来控制.

接着:

SubSamplingImageState {val transformation by rememberUpdatedState(transformation)val state = remember(imageSource) {RealSubSamplingImageState(imageSource, transformation)}.also {it.imageRegionDecoder = createImageRegionDecoder(imageSource, imageOptions, errorReporter)}state.LoadImageTilesEffect()DisposableEffect(imageSource) {onDispose {imageSource.close()}}return state
}

创建transformation,创建state,再创建decoder.

使用方式可以从示例中找到:

val zoomableState = rememberZoomableState()
val imageState = rememberSubSamplingImageState(zoomableState = zoomableState,imageSource = SubSamplingImageSource.asset("fox.jpg")
)SubSamplingImage(modifier = Modifier.fillMaxSize().zoomable(zoomableState),state = imageState,contentDescription = …,
)

SubSamplingImage中使用

drawBehind(onDraw)来绘制,ondraw是通过遍历state中的tiles去绘制.
val onDraw: DrawScope.() -> Unit = {if (state.isImageDisplayed) {state.viewportImageTiles.fastForEach { tile ->drawImageTile(tile = tile,alpha = alpha,colorFilter = colorFilter,)if (state.showTileBounds) {drawRect(color = Color.Red,topLeft = tile.bounds.topLeft.toOffset(),size = tile.bounds.size.toSize(),style = Stroke(width = 6.dp.toPx()),)}}}}

在view的大小改变时.onSizeChanged { state.viewportSize = it },将当前的view大小传到state里面.

drawImageTile就是把tile画出来,但是会带偏移量.
withTransform(transformBlock = {translate(left = tile.bounds.topLeft.x.toFloat(),top = tile.bounds.topLeft.y.toFloat(),)},drawBlock = {with(painter) {draw(size = tile.bounds.size.toSize(),alpha = alpha,colorFilter = colorFilter,)}})

绘制部分不复杂,关键是tile的计算,偏移量.主要逻辑在RealSubSamplingImageState

RealSubSamplingImageState

它继承SubSamplingImageState,要实现两个属性的计算,imageSize与isImageDisplayed.

imageSize: IntSize?get() = imageRegionDecoder?.imageSize 直接取解码器的值就可以得到原图大小.
override val isImageDisplayed: Boolean by derivedStateOf {isReadyToBeDisplayed && viewportImageTiles.isNotEmpty() &&(viewportImageTiles.fastAny { it.isBase } || viewportImageTiles.fastAll { it.painter != null })
}

图片是否显示,它是由多个状态合并的,tile不能为空,状态为准备好了,才能显示

除此,还有预览图相关的操作.

imageRegionDecoder是在创建这个state后立刻创建的.

这个类创建后,执行了LoadImageTilesEffect(),tile的计算工作就开始了.

@Composablefun LoadImageTilesEffect() {val imageRegionDecoder = imageRegionDecoder ?: returnval scope = rememberCoroutineScope()val imageCache = remember(this, imageRegionDecoder) {ImageCache(scope, imageRegionDecoder)}LaunchedEffect(imageCache) {snapshotFlow { viewportTiles }.collect { tiles ->imageCache.loadOrUnloadForTiles(regions = tiles.fastMapNotNull { if (it.isVisible) it.region else null })}}LaunchedEffect(imageCache) {imageCache.observeCachedImages().collect {loadedImages = it}}}

创建缓存,加载或卸载tile.这里是监控viewportTiles的变化,这是view的tile.除了图片会被划分,先将view划分.当图还没加载的时候,需要去占着位置,所以它是必要的.

viewportImageTiles,这个就是图片的tile,它是根据viewportTiles的变化而变化的.计算原则也不复杂,可见并且不是基础块或可以显示的.

首先产生tile:

private val tileGrid by derivedStateOf {if (isReadyToBeDisplayed) {ImageRegionTileGrid.generate(viewportSize = viewportSize!!,unscaledImageSize = imageOrPreviewSize!!,)} else null}

它有两个值,一个是view的大小,一个是图片的大小.根据这两个值,让图片适应到view中,如果图片过大,会计算sample,缩放到合适的采样.

val baseTile = ImageRegionTile(sampleSize = baseSampleSize,bounds = IntRect(IntOffset.Zero, unscaledImageSize)
)

得到一个基础tile,是整张图片的原始tile与采样.还有一个foregroundTiles.

val foregroundTiles = possibleSampleSizes.associateWith { sampleSize ->val tileSize: IntSize = (unscaledImageSize.toSize() * (sampleSize.size / baseSampleSize.size.toFloat())).discardFractionalParts().coerceIn(min = minTileSize, max = unscaledImageSize.coerceAtLeast(minTileSize))// Number of tiles can be fractional. To avoid this, the fractional// part is discarded and the last tiles on each axis are stretched// to cover any remaining space of the image.val xTileCount: Int = (unscaledImageSize.width / tileSize.width).coerceAtLeast(1)val yTileCount: Int = (unscaledImageSize.height / tileSize.height).coerceAtLeast(1)val tileGrid = ArrayList<ImageRegionTile>(xTileCount * yTileCount)for (x in 0 until xTileCount) {for (y in 0 until yTileCount) {val isLastXTile = x == xTileCount - 1val isLastYTile = y == yTileCount - 1val tile = ImageRegionTile(sampleSize = sampleSize,bounds = IntRect(left = x * tileSize.width,top = y * tileSize.height,// Stretch the last tiles to cover any remaining space.right = if (isLastXTile) unscaledImageSize.width else (x + 1) * tileSize.width,bottom = if (isLastYTile) unscaledImageSize.height else (y + 1) * tileSize.height,))tileGrid.add(tile)}}return@associateWith tileGrid}

它的tile,先根据固定的大小一块一块排列,然后剩下的如果不是一个tile的大小,会把它与前面的合并成为一个tile.每一个tile,除了自己的大小,还有它对应的采样,因为缩放后这些采样有可能不一样.

计算完tile,就要把对应的图片通过decoder加载出来.

前面提到的isbase 就是它是不是基础的完整图片的tile. 只有一个.

viewportTiles的计算:
(listOf(tileGrid.base) + foregroundRegions).sortedByDescending { it.bounds.contains(transformation.centroid) }.fastMapNotNull { region ->val isBaseTile = region == tileGrid.baseval drawBounds = region.bounds.scaledAndOffsetBy(transformation.scale, transformation.offset)ViewportTile(region = region,bounds = drawBounds,isBase = isBaseTile,isVisible = drawBounds.overlaps(viewportSize!!),)}.toImmutableList()

它不只是取上面的分块tile,还加上基础块.生成一个不可变列表.然后通过imagecache去加载这些块.

tile的计算,绘制工作就是这些了.最后个复杂的就是如何加载这些图片.

ImageCache
private val visibleRegions = Channel<List<ImageRegionTile>>(capacity = 10)
private val cachedImages = MutableStateFlow(emptyMap<ImageRegionTile, LoadingState>())

充分利用协程的特性.又看到channel了.

visibleRegions.trySend(regions)发送可见的tile,它通过协程监听可见区的变化:
scope.launch {visibleRegions.consumeAsFlow().distinctUntilChanged().throttleLatest(throttleEvery)  // In case the image is animating its zoom..collect { tiles ->val tilesToLoad = tiles.fastFilter { it !in cachedImages.value }tilesToLoad.fastForEach { tile ->launch(start = CoroutineStart.UNDISPATCHED) {cachedImages.update {check(tile !in it)it + (tile to InFlight(currentCoroutineContext().job))}val painter = decoder.decodeRegion(tile.bounds, tile.sampleSize.size)cachedImages.update {it + (tile to Loaded(painter))}}}val tilesToUnload = cachedImages.value.keys.filter { it !in tiles }tilesToUnload.fastForEach { region ->val inFlight = cachedImages.value[region] as? InFlightinFlight?.job?.cancel()}cachedImages.update { it - tilesToUnload.toSet() }}}

检查是否在缓存中.it !in cachedImages.value, 只有不在的才加载.

decoder.decodeRegion(tile.bounds, tile.sampleSize.size)具体解码,只需要知道tile大小与采样.

除了加载解码,还要处理不在tile中的it !in tiles, 避免浪费资源,如果任务没有完成就停止.已经完成的就删除.

解码部分是AndroidImageRegionDecoder,这个没有什么特别需要注意的,它处理了旋转.最后返回的是RotatedBitmapPainter,而不是普通的painter,onDraw()时处理了旋转,

SubSamplingImage的流程总结一下:

根据view的大小,得到可用空间大小.

根据解码器得到图片的大小.

计算tile,整张图片的与分块的.

调用LoadImageTilesEffect()去加载tile的图片

绘制是监听viewportImageTiles的变化.

SubSamplingImage是基于ZoomableImage,它的缩放功能ZoomableImage已经实现了.ZoomableContentTransformation中有内容的大小,缩放的级别,centroid等信息.tile的可见是与这个centroid匹配的.

每当移动或其它手势产生时.调用

RealZoomableContentTransformation.calculateFrom(

gestureStateInputs = gestureStateInputs,

gestureState = gestureState.calculate(gestureStateInputs),

)重新计算,触发viewtiles的变化.

官方文档说,不只是支持图片,可以支持pdf等文档.要支持pdf的话,需要实现自己的source与解码.pdf似乎只能支持单页.

关键字:世界500强企业是什么意思_前端开发主要工作内容_全国疫情最新情况公布_中美关系最新消息

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: