当前位置: 首页 > wzjs >正文

昆山网站建设公司苏州爬虫科技谷歌搜索引擎镜像

昆山网站建设公司苏州爬虫科技,谷歌搜索引擎镜像,黄冈网站建设价格,网站产品介绍模板目录 kmp相关的的几个名词说明 app开发与功能 打包与签名 计划实现的功能 kmp相关的的几个名词说明 jetpack compose:这是jetbrains与谷歌开发的一个,主要用于android的ui库. jetbrain kotlin multiplatform,这是jb公司开发的一个多平台库,有native,js等后端. compose m…

 

目录

kmp相关的的几个名词说明

app开发与功能

打包与签名

计划实现的功能


kmp相关的的几个名词说明

jetpack compose:这是jetbrains与谷歌开发的一个,主要用于android的ui库.

jetbrain kotlin multiplatform,这是jb公司开发的一个多平台库,有native,js等后端.

compose multiplatform:这是jb与谷歌一起做的,将compose运用于多平台的库.

as需要安装后面两个插件,kotlin multiplatform, compose multiplatform.

建立项目的时候才会出现相应的.但官方似乎主要针对移动端有示例.上篇文章有写过.

app开发与功能

借用别人的开源项目:

GitHub - zt64/compose-pdf: Kotlin multiplatform library for compose multiplatform to assist in viewing PDF files from various sources

虽然是借用,但我在桌面端作了修改,替换了解析库为Mupdf.完善一些功能.

actual与expect,这两个是新的关键字.主要用于多平台,后者是声明这是一个多平台需要实现的.前者则是每一个平台具体实现.

但官方又建议,不要过度使用它,有时接口更好.

原作者是使用icepdf-core来作为pdf的解析,这个毛病就是慢,好处是java实现.直接使用.

替换为mupdf:

  1. 打包的时候,发布到本地的maven.发一个jar.
  2. dylib/jnilib放到commonMain/resource/下
  3. 替换相应的解码代码

https://github.com/archko/amupdf-nativelib 这个项目里面有打包mupdf aar的代码, 打包为本地的就可以了.

然后可以手动修改打包代码,将生成的aar修改为jar.

或者直接进入./m2/com/xx/mupdf/的aar目录下面,新建一个 1.25.1目录,其它文件与aar一样,就是把aar里面的class.jar拿出来 ,名字修改一下为对应的jar,然后外部的两个配置文件修改为jar,添加版本号就可以了.

在主项目的gradle末尾添加:

tasks.withType<JavaExec> {doFirst {val libDir = "${projectDir}/src/commonMain/resources/macos-aarch64"val existingPath = System.getProperty("java.library.path")val newPath = if (existingPath.isNullOrEmpty()) {libDir} else {"$existingPath:$libDir"}systemProperty("java.library.path", newPath)}
}

将dylib放入macos-aarch64中,这样项目就会引用它了.

修改代码:

package dev.zt64.compose.pdfimport androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.ImageBitmapConfig
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.toComposeImageBitmap
import com.archko.reader.pdf.Item
import com.artifex.mupdf.fitz.ColorSpace
import com.artifex.mupdf.fitz.Cookie
import com.artifex.mupdf.fitz.Document
import com.artifex.mupdf.fitz.DrawDevice
import com.artifex.mupdf.fitz.Matrix
import com.artifex.mupdf.fitz.Pixmap
import com.artifex.mupdf.fitz.Rect
import dev.zt64.compose.pdf.component.Size
import java.awt.image.BufferedImage
import java.io.File
import java.io.InputStream
import java.net.URLinternal const val SCALE = 1.0f@Stable
public actual class LocalPdfState(private val document: Document) : PdfState {public actual override val pageCount: Int = document.countPages()override var pageSizes: List<Size> = listOf()get() = fieldset(value) {field = value}override var outlineItems: List<Item>? = listOf()get() = fieldset(value) {field = value}private fun prepareSizes(): List<Size> {val list = mutableListOf<Size>()for (i in 0 until pageCount) {val page = document.loadPage(i)val bounds = page.boundsval size = Size(bounds.x1.toInt() - bounds.x0.toInt(),bounds.y1.toInt() - bounds.y0.toInt(),i)page.destroy()list.add(size)}return list}private fun prepareOutlines(): List<Item> {return document.loadOutlineItems()}/*public constructor(inputStream: InputStream) : this(document = Document.openDocument(inputStream).apply {setInputStream(inputStream, null)}) {pageSizes = prepareSizes()}*/public actual constructor(file: File) : this(document = Document.openDocument(file.absolutePath)) {pageSizes = prepareSizes()outlineItems = prepareOutlines()}/*public constructor(url: URL) : this(document = Document().apply {setUrl(url)}) {pageSizes = prepareSizes()}*/public actual override fun renderPage(index: Int, viewWidth: Int, viewHeight: Int): Painter {val page = document.loadPage(index)val bounds = page.boundsval scale: Floatif (viewWidth > 0) {scale = (1f * viewWidth / (bounds.x1 - bounds.x0))} else {return BitmapPainter(ImageBitmap(viewWidth, viewHeight, ImageBitmapConfig.Rgb565))}println("renderPage:index:$index, scale:$scale, $viewWidth-$viewHeight, bounds:${page.bounds}")val ctm: Matrix = Matrix.Scale(scale)/* Render page to an RGB pixmap without transparency. */val bmp: ImageBitmap?try {val bbox: Rect = Rect(bounds).transform(ctm)val pixmap = Pixmap(ColorSpace.DeviceBGR, bbox, true)pixmap.clear(255)val dev = DrawDevice(pixmap)page.run(dev, ctm, Cookie())dev.close()dev.destroy()val pixmapWidth = pixmap.widthval pixmapHeight = pixmap.heightval image = BufferedImage(pixmapWidth, pixmapHeight, BufferedImage.TYPE_3BYTE_BGR)image.setRGB(0, 0, pixmapWidth, pixmapHeight, pixmap.pixels, 0, pixmapWidth)bmp = image.toComposeImageBitmap()return BitmapPainter(bmp)} catch (e: Exception) {System.err.println(("Error loading page " + (index + 1)) + ": " + e)}return BitmapPainter(ImageBitmap(viewWidth, viewHeight, ImageBitmapConfig.Rgb565))}override fun close() {document.destroy()}
}/*** Remembers a [LocalPdfState] for the given [inputStream].** @param inputStream* @return [LocalPdfState]*/
@Composable
public fun rememberLocalPdfState(inputStream: InputStream): LocalPdfState {return remember { LocalPdfState(File("")) }
}/*** Remembers a [LocalPdfState] for the given [url].** @param url* @return [LocalPdfState]*/
@Composable
public actual fun rememberLocalPdfState(url: URL): LocalPdfState {return remember { LocalPdfState(File("")) }
}/*** Remembers a [LocalPdfState] for the given [file].** @param file* @return [LocalPdfState]*/
@Composable
public actual fun rememberLocalPdfState(file: File): LocalPdfState {return remember { LocalPdfState(file) }
}

这里我去了url加载的代码,减少麻烦.

这样解码的部分就修改完成了.

这功能太简单了,没有大纲.没有历史记录.所以需要添加这两个功能

历史记录需要存储,用数据库.sqldelight,图片显示用coil.

首页去了url,添加一个grid,显示阅读过的历史记录.

点击每一个历史记录,需要加载保存的页码,而不是从0开始.

这里只写关于桌面端的:

main.kt里面添加coil的初始化

setSingletonImageLoaderFactory { context ->ImageLoader.Builder(context).components { add(CustomImageFetcher.Factory()) }.logger(DebugLogger(minLevel = Logger.Level.Warn)).build()}

这个作用就是自定义加载器,去解析历史记录中对应的每一个文件,取出首页.当然也可以修改为解码第一页后存储为图片.

自定义一个图片获取器:这个放到lib/jvmMain项目中.

public class CustomImageFetcher(private val data: CustomImageData,private val options: Options
) : Fetcher {override suspend fun fetch(): FetchResult? {return try {val doc = Document.openDocument(data.path)val image = renderPage(doc, 0, data.width, data.height)val outputStream = ByteArrayOutputStream()val awtImage = image.toAwtImage()ImageIO.write(awtImage, "png", outputStream)val byteArray = outputStream.toByteArray()val skiaImage = org.jetbrains.skia.Image.makeFromEncoded(byteArray)val coilImage = Bitmap.makeFromImage(skiaImage).asImage()ImageFetchResult(image = coilImage,isSampled = false,dataSource = DataSource.MEMORY)} catch (e: IOException) {null}}public fun renderPage(document: Document,index: Int,viewWidth: Int,viewHeight: Int): ImageBitmap {val page = document.loadPage(index)val bounds = page.boundsval scale: Floatif (viewWidth > 0) {scale = (1f * viewWidth / (bounds.x1 - bounds.x0))} else {return ImageBitmap(viewWidth, viewHeight, ImageBitmapConfig.Rgb565)}println("renderPage:index:$index, scale:$scale, $viewWidth-$viewHeight, bounds:${page.bounds}")val ctm: Matrix = Matrix.Scale(scale)/* Render page to an RGB pixmap without transparency. */val bmp: ImageBitmap?try {val bbox: Rect = Rect(bounds).transform(ctm)val pixmap = Pixmap(ColorSpace.DeviceBGR, bbox, true)pixmap.clear(255)val dev = DrawDevice(pixmap)page.run(dev, ctm, Cookie())dev.close()dev.destroy()val pixmapWidth = pixmap.widthval pixmapHeight = pixmap.heightval image = BufferedImage(pixmapWidth, pixmapHeight, BufferedImage.TYPE_3BYTE_BGR)image.setRGB(0, 0, pixmapWidth, pixmapHeight, pixmap.pixels, 0, pixmapWidth)bmp = image.toComposeImageBitmap()return bmp} catch (e: Exception) {System.err.println(("Error loading page " + (index + 1)) + ": " + e)}return ImageBitmap(viewWidth, viewHeight, ImageBitmapConfig.Rgb565)}public class Factory : Fetcher.Factory<CustomImageData> {override fun create(data: CustomImageData,options: Options,imageLoader: ImageLoader): Fetcher {return CustomImageFetcher(data, options)}}
}
public data class CustomImageData(val path: String, val width: Int, val height: Int) 这个类放到lib工程的commonMain中.

sqldelight官方有详细的文档,就不写过程了.

创建vm,进入主页面:

val windowInfo = LocalWindowInfo.currentval density = LocalDensity.currentvar screenWidthInPixels by remember { mutableStateOf(0) }var screenHeightInPixels by remember { mutableStateOf(0) }density.run {screenWidthInPixels = windowInfo.containerSize.width.toDp().toPx().toInt()screenHeightInPixels = windowInfo.containerSize.height.toDp().toPx().toInt()}println("app.screenHeight:$screenWidthInPixels-$screenHeightInPixels")val driverFactory: DatabaseDriverFactory = DriverFactory()val database = AppDatabase(driverFactory.createDriver())val viewModelStoreOwner = remember { ComposeViewModelStoreOwner() }CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {val viewModel: PdfViewModel = viewModel()viewModel.database = databaseApplication(screenWidthInPixels, screenHeightInPixels, viewModel)}

vm主要是处理历史记录,对于pdf的加载,使用原项目的state.

public class PdfViewModel : ViewModel() {public var database: AppDatabase? = nullprivate val _recentList = MutableStateFlow<List<Recent>>(mutableListOf())public val recentList: StateFlow<List<Recent>> = _recentListpublic var progress: Progress? = nullpublic fun insertOrUpdate(path: String, pageCount: Long) {viewModelScope.launch {println("insertOrUpdate:${progress}")}}public fun updateProgress(page: Long, pageCount: Long, zoom: Double, crop: Long) {println("updateProgress:$page, pc:$pageCount, old:$progress")progress?.run {viewModelScope.launch {progress = database?.appDatabaseQueries?.selectProgress(path)?.executeAsOneOrNull()loadRecents()}}}public fun loadRecents() {viewModelScope.launch {val progresses = database?.appDatabaseQueries?.selectProgresses()?.executeAsList()if (progresses != null) {if (progresses.isNotEmpty()) {val list = mutableListOf<Recent>()_recentList.value = list}}}}public fun clear() {viewModelScope.launch {database?.appDatabaseQueries?.removeAllProgresses()loadRecents()}}
}

pdf=null里面url那部分代码去了,换成grid

LazyVerticalGrid(columns = GridCells.FixedSize(140.dp),verticalArrangement = Arrangement.spacedBy(16.dp),horizontalArrangement = Arrangement.spacedBy(16.dp)) {items(count = recentList.size,key = { index -> "$index" }) { i ->recentItem(recentList[i]) {val file = KmpFile(File(it.path))pdf = LocalPdfState(file)loadProgress(viewModel, file, pdf)}}}
private fun recentItem(recent: Recent, click: (Recent) -> Unit) {Column(modifier = Modifier.fillMaxSize().padding(1.dp).clickable { click(recent) }) {Box(modifier = Modifier.fillMaxWidth().height(180.dp).shadow(elevation = 8.dp,shape = RoundedCornerShape(8.dp),clip = false,ambientColor = Color.Black.copy(alpha = 0.2f),spotColor = Color.Black.copy(alpha = 0.4f)).border(BorderStroke(1.dp, Color.LightGray))) {AsyncImage(model = recent.path?.let {CustomImageData(it,180.dp.toIntPx(),135.dp.toIntPx())},contentDescription = null,contentScale = ContentScale.Crop,modifier = Modifier.fillMaxSize())Text(modifier = Modifier.align(Alignment.BottomEnd).padding(2.dp),color = Color.Blue,maxLines = 1,text = "${recent.page?.plus(1)}/${recent.pageCount}",fontSize = 11.sp,overflow = TextOverflow.Ellipsis)}Text(modifier = Modifier.padding(2.dp),color = Color.Black, maxLines = 2,text = "${recent.path?.inferName()}",fontSize = 13.sp,overflow = TextOverflow.Ellipsis)}
}

这样历史记录就出来了.

优化列表的显示:

private fun PdfScreen(screenWidth: Int,screenHeight: Int,pdf: PdfState,onClickBack: () -> Unit,scope: CoroutineScope,viewModel: PdfViewModel,lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
) {val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()val snackbarHostState = remember { SnackbarHostState() }var scale by rememberSaveable { mutableFloatStateOf(1f) }val lazyListState = rememberLazyListState()// 创建一个 FocusRequester 用于请求焦点val focusRequester1 = FocusRequester()val focusRequester2 = FocusRequester()var width by remember { mutableIntStateOf(screenWidth) }var height by remember { mutableIntStateOf(screenHeight) }val tocVisibile = remember { mutableStateOf(false) }val currentPage by remember {derivedStateOf { lazyListState.firstVisibleItemIndex + 1 }}val scrollbarState = lazyListState.scrollbarState(itemsAvailable = pdf.pageCount,)// 在组合完成后请求焦点,这个用于键盘控制LaunchedEffect(Unit) {focusRequester1.requestFocus()println("launch.progress:${viewModel.progress}")viewModel.progress?.page?.let { lazyListState.scrollToItem(it.toInt()) }}DisposableEffect(pdf) {val observer = LifecycleEventObserver { _, event ->//println("event:$event") ,暂停的时候,其实是不可见,保存历史记录if (event == Lifecycle.Event.ON_PAUSE) {viewModel.updateProgress(lazyListState.firstVisibleItemIndex.toLong(),pdf.pageCount.toLong(),1.0,1)}}lifecycleOwner.lifecycle.addObserver(observer)onDispose {//println("onDispose")/页面销毁时保存历史记录viewModel.updateProgress(lazyListState.firstVisibleItemIndex.toLong(),pdf.pageCount.toLong(),1.0,1)lifecycleOwner.lifecycle.removeObserver(observer)}}Scaffold(//modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),modifier = Modifier.background(Color.White),topBar = {TopAppBar(title = {Text(color = Color.White,text = "$currentPage/${pdf.pageCount}")},navigationIcon = {IconButton(onClick = onClickBack) {Icon(Icons.Default.Close, contentDescription = null)}},actions = {IconButton(onClick = {scope.launch { tocVisibile.value = !tocVisibile.value }}) {Icon(Icons.AutoMirrored.Filled.Toc, contentDescription = null)}IconButton(onClick = { scale -= 0.1f }) {Icon(Icons.Default.ZoomOut, contentDescription = null)}IconButton(onClick = { scale += 0.1f }) {Icon(Icons.Default.ZoomIn, contentDescription = null)}},scrollBehavior = scrollBehavior)},//snackbarHost = { SnackbarHost(snackbarHostState) }
//将原来的滚动topbar去了.固定的) { paddingValues ->@Composablefun screen() {Box(modifier = Modifier.fillMaxSize().background(Color.Transparent).padding(paddingValues)) {PdfColumn(modifier = Modifier.fillMaxSize()//.padding(end = 8.dp) // 为滚动条留出空间.onSizeChanged {width = it.widthheight = it.heightprintln("app.LazyColumn:$width-$height, $screenWidth-$screenHeight")}.pointerInput(Unit) {detectTapGestures(onTap = {scope.launch {focusRequester1.requestFocus()}})}.focusRequester(focusRequester1).focusable(enabled = true).onKeyEvent { event ->// 处理按键按下事件if (event.type == KeyEventType.KeyDown) {//println("${event.type}, ${event.key}")when (event.key) {Key.Enter,Key.Spacebar -> {scope.launch {lazyListState.scrollBy(height.toFloat() - 10)}return@onKeyEvent true}Key.PageUp -> {scope.launch {lazyListState.scrollBy(-height.toFloat() + 10)}return@onKeyEvent true}Key.PageDown -> {scope.launch {lazyListState.scrollBy(height.toFloat() - 10)}return@onKeyEvent true}Key.DirectionUp -> {scope.launch {lazyListState.scrollBy(-120f)}return@onKeyEvent true}Key.DirectionDown -> {scope.launch {lazyListState.scrollBy(120f)}return@onKeyEvent true}else -> return@onKeyEvent false}} else {return@onKeyEvent false // 返回 false 表示事件未处理}},//.scale(scale),viewWidth = width,viewHeight = height,state = pdf,lazyListState = lazyListState)//这是一个滚动条,从谷歌项目中拿的,右侧可以有一个滑块用于拖动lazyListState.DraggableScrollbar(modifier = Modifier.fillMaxHeight().padding(horizontal = 2.dp).align(Alignment.CenterEnd),state = scrollbarState,orientation = Orientation.Vertical,onThumbMoved = lazyListState.rememberDraggableScroller(itemsAvailable = pdf.pageCount,),)}}//显示大纲.目前无法聚焦是个问题.@Composablefun toc() {if (pdf.outlineItems == null || pdf.outlineItems!!.isEmpty()) {Text(modifier = Modifier.width(240.dp).fillMaxHeight(),fontSize = 24.sp,text = "No Outline")} else {LazyColumn(modifier = Modifier.width(240.dp).background(Color.White).fillMaxHeight().hoverable(enabled = true, interactionSource = MutableInteractionSource()).focusRequester(focusRequester2).focusable(enabled = true),state = lazyListState,) {items(count = pdf.outlineItems!!.size,key = { index -> "$index" }) { i ->Row {Text(modifier = Modifier.weight(1f),fontSize = 14.sp,color = Color.Black,maxLines = 1,overflow = TextOverflow.Ellipsis,text = pdf.outlineItems!![i].title.toString())Text(modifier = Modifier,fontSize = 12.sp,color = Color.Black,text = pdf.outlineItems!![i].page.toString())VerticalDivider(thickness = 0.5.dp,modifier = Modifier.fillMaxHeight())}}}}}if (tocVisibile.value) {Row(modifier = Modifier.fillMaxSize().padding(paddingValues).background(Color.Transparent)) {toc()screen()}} else {screen()}}
}

代码没有很复杂,主要是PdfColumn,显示一个列表,但现在列表没有缩放功能,只是可以加载页面.

添加了屏幕的高宽,参数的原因是,当页面未加载的时候,如果没有高宽,那么就无法定位到目标页,滚动来回时会有问题.

在onSizeChanged中,获取了view的高宽.而加载的pdfstate中,取得的是一个pagesize列表,这样每一页就有高宽了,去计算当前与view的关系.

每一个页面
 

@Composable
public fun PdfPage(state: PdfState,index: Int,width: Int,height: Int,modifier: Modifier = Modifier,loadingIconTint: Color = Color.White,errorIconTint: Color = Color.Red,iconSize: Dp = 40.dp,loadingIndicator: @Composable () -> Unit = {val h: Intif (state.pageSizes.isNotEmpty()) {val size = state.pageSizes[index]val xscale = 1f * width / size.widthh = (size.height * xscale).toInt()println("PdfPage: image>height:$h, view.w-h:$width-$height, page:${size.width}-${size.height}")} else {h = height}val hdp = with(LocalDensity.current) {h.toDp()}Box(modifier = modifier.fillMaxWidth().height(hdp).background(loadingIconTint)) {LoadingView("Page $index")/*Text(modifier = Modifier.align(Alignment.Center),color = Color.Black,fontSize = 30.sp,text = "Page $index")*/}},errorIndicator: @Composable () -> Unit = {Image(painter = state.renderPage(index, width, height),contentDescription = null,colorFilter = if (errorIconTint ==Color.Unspecified) {null} else {ColorFilter.tint(errorIconTint)},modifier = Modifier.size(iconSize))},contentScale: ContentScale = ContentScale.Fit
) {//val loadState = if (state is RemotePdfState) state.loadState else LoadState.Successval imageState: MutableState<Painter?> = remember { mutableStateOf(null) }DisposableEffect(index) {val scope = CoroutineScope(SupervisorJob())scope.launch {snapshotFlow {if (isActive) {return@snapshotFlow state.renderPage(index, width, height)} else {return@snapshotFlow null}}.flowOn(Dispatcher.DECODE).collectLatest {imageState.value = it}}onDispose {scope.cancel()}}if (imageState.value != null) {Image(painter = imageState.value!!,contentDescription = null,contentScale = ContentScale.FillWidth)} else {loadingIndicator()}/*when (loadState) {LoadState.Success -> Image(modifier = modifier.background(Color.White),painter = imageState.value!!,//state.renderPage(index, width, height),contentDescription = null,contentScale = contentScale)LoadState.Loading -> loadingIndicator()LoadState.Error -> loadingIndicator()}*/
}@Composable
private fun LoadingView(text: String = "Decoding",modifier: Modifier = Modifier
) {Column(verticalArrangement = Arrangement.Center,modifier = modifier.fillMaxWidth().fillMaxHeight()) {Text(text,style = TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color.Black),modifier = Modifier.align(Alignment.CenterHorizontally))Spacer(modifier = Modifier.height(24.dp))LinearProgressIndicator(modifier = Modifier.height(10.dp).align(alignment = Alignment.CenterHorizontally))}
}

主要是loadingIndicator作了修改,这样可以加载中也与加载完成一样高宽.

使用了DisposableEffect加载页面,单线程,不然会出错.

大致修改就是这些了.

项目的功能不多,如果这个主体用到手机上,是比较难受的.手势功能目前不支持.后续考虑去修改完善它.

打包与签名

打包目前遇到arm上可以打包,intel的cpu上打包不成功,估计官方也不想修改了,毕竟对于mac来说,都是新的cpu了.

签名目前没有写上.

dmg的包大概快300mb了,解码速度略输于它,现在是整个页面解码,这么快的电脑,还不如手机快.

cpu占用略高于pdf expert,应该是java的线程切换带来的.

内存占用,初始化的时候比较大要500m,虚拟机占用大,加载页面后的占用大小与pdf expert是差不多的.

体积自然是因为runtime这个东西大.还有skia都要打包进去.

pdf expert是mac是最优秀的阅读器之一.性能不错.就是要钱.

为什么要去写这个阅读器,因为我有一个完整的手机版的阅读器,自己写的,如果这个顺利,会全部用compose,多端共享,同步阅读.

另一个想法是,epub,目前没有找到一个合适的阅读器,要么就是要复制整个文件过去,要么是其它的原因,而mupdf可以解析它.这样我可以在电脑上直接查看epub,而不用修改为pdf了.

https://download.csdn.net/download/archko/90397912 整个项目资源下载,需要时间审核.免费下载.

计划实现的功能

如果时间允许,会把手机已经实现的功能搬过来.

  • 分块解码,解码重构.
  • compose替换原来的view实现,多平台共用
  • 添加链接高亮,点击.搜索.
  • 修正大纲的焦点问题.
  • 页面放大缩小的实现.
  • 坚果等webdav的历史同步.
  • epub/mobi的布局高宽调整
  • window/linux打包测试.
  • 源码发布到github
http://www.dtcms.com/wzjs/307069.html

相关文章:

  • 海南省住房和城乡建设局网站百度企业推广
  • 怎么网站搜索排名优化设计公司企业网站
  • 步骤的近义词seo排名优化推广报价
  • 郑州建站模板厂家抖音代运营公司
  • 有空间有域名怎么做网站优化设计七年级上册语文答案
  • logo素材网站网络推广公司北京
  • 灯具做外贸的网站有哪些网购平台推广方案
  • 做的网站进不去后台青岛seo外包服务
  • 手机靓号网站建设关键词seo排名怎么选
  • 昭通网站制作互联网营销顾问
  • 网站页头页尾怎样做网站查询关键词排名软件
  • 可以做公司宣传的网站有哪些内容甘肃新站优化
  • 360ssp网站代做seo爱站网
  • 东莞做微网站建设最新国际军事动态
  • 网站优化实习报告外贸网站建设优化推广
  • 报纸做垂直门户网站太原百度seo排名软件
  • 辽宁省建设工程造价管理协会网站开发软件app需要多少钱
  • 台州自助建站公司产品推广方式
  • wordpress弱口令字典关键词快速优化排名软件
  • 朔州公司做网站网络营销课程作业
  • wordpress密码加密方式百色seo快速排名
  • 哪里做网站比较好nba排名最新赛程
  • 国内最好的在线网站建设搜索引擎培训班
  • 南宁营销网站建设如何线上推广引流
  • 专注微信网站建设关键词排名优化易下拉技巧
  • 商城网站设计服务商宁波seo整体优化
  • 一键生成作文的网站百度网站推广怎么做
  • 爱做的小说网站吗长沙网络营销哪家平台专业
  • 建设o2o网站个人网站制作教程
  • 公司网站优化怎么做网站建设与管理主要学什么