主頁 > 移動端開發 > 當我再次用Kotlin完成五年前已經通過Kotlin完成的專案后

當我再次用Kotlin完成五年前已經通過Kotlin完成的專案后

2023-05-08 08:29:26 移動端開發

  > 近日來對Kotlin的使用頻率越來越高, 也對自己近年來寫過的Kotlin代碼嘗試進行一個簡單的整理. 翻到了自己五年前第一次使用Kotlin來完成的一個專案([貝塞爾曲線](https://juejin.cn/post/6844903556173004807)), 一時興起, 又用發展到現在的Kotlin和Compose再次完成了這個專案. 也一遍來看看這幾年我都在Kotlin中學到了什么.
關于貝塞爾曲線, 這里就不多贅述了. 簡單來說, 針對每一個線段, 某個點到兩端的比例都是一樣的, 而貝塞爾曲線就是這個程序的中線段兩端都在同一位置的線段(點)程序的集合.
如圖, AD和AB的比例, BE和BC的比例還有DF和DE的比例都是一樣的.這個比例從0到1, F點的位置連成線, 就是ABC這三個點的貝塞爾曲線.
![Bezier](https://clwater-obsidian.oss-cn-beijing.aliyuncs.com/img/449809-20191009163226592-1802036977.png)

# 兩次完成的感受
雖然時隔五年, 但是對這個專案的印象還是比較深刻的(畢竟當時找啥資料都不好找).
當時的專案還用的是Kotlin Synthetic來進行資料系結(雖然現在已經被棄用了), 對于當時還一直用findViewById和@BindView的我來說, 這是對我最大的驚喜. 是的, 當時用Kotlin最大驚喜就是這個. 其它的感覺就是這個"語法糖"看起來還挺好用的. 而現在, 我可以通過Compose來完成頁面的布局. 最直觀的結果是代碼量的減少, 初版功能代碼(帶xml)大概有800行, 而這次完成整個功能大概只需要450行.
在使用程序中對"Compose is function"理念的理解更深了一步, 資料就是資料. 將資料作為一個引數放到Compose這個function中, 在資料變化的時候重新呼叫function, 達到更新UI的效果. 顯而易見的事情是我們不需要的額外的持有UI的物件了, 我們不必考慮UI中某個元素和另一個元素直接的關聯, 不必考慮某個元素回應什么樣的操作. 我們只需要考慮某個Compose(function) 在什么樣的情況下(入參)需要表現成什么樣子.
比如Change Point按鈕點下時, 會更改`mInChange`的內容, 從而影響許多其它元素的效果, 如果通過View來實作, 我需要監聽Change Point的點擊事件, 然后依次修改影響到的元素(這個程序中需要持有大量其它View的物件). 不過當使用Compose后, 雖然我們仍要監聽Change Point的點擊事件, 但是對對應Change Point的監聽動作來說, 它只需要修改`mInChange`的內容就行了, 修改這個值會發生什么變化它不需要處理也不要知道. 真正需要變化的Compose來處理就可以了(可以理解為引數變化了, 重新呼叫了這個function)
特性的部分使用的并不多, 比較專案還是比較小, 很多特性并沒有體現出來.
最令我感到開心的是, 再一次完成同樣的功能所花費的時間僅僅只有半天多, 而5年前完成類似的功能大概用了一個多星期的時間. 也不知道我和Kotlin這5年來哪一方變化的更大??.
# 貝塞爾曲線工具 先來看一下具有的功能, 主要的功能就是繪制貝塞爾曲線(可繪制任意階數), 顯示計算程序(輔助線的繪制), 關鍵點的調整, 以及新增的繪制進度手動調整. 為了更本質的顯示繪制的結果, 此次并沒有對最終結果點進行顯示優化, 所以在短時間變化位置大的情況下, 可能出現不連續的現象.

![3_point_bezier](https://clwater-obsidian.oss-cn-beijing.aliyuncs.com/img/bezier_1.gif)
![more_point_bezier](https://clwater-obsidian.oss-cn-beijing.aliyuncs.com/img/202305061905728.gif)

![bizier_change](https://clwater-obsidian.oss-cn-beijing.aliyuncs.com/img/202305061932923.gif)

![bezier_progress](https://clwater-obsidian.oss-cn-beijing.aliyuncs.com/img/202305061926327.gif)
# 代碼的比較 既然是同樣的功能, 不同的代碼, 即使是由不同時期所完成的, 將其相互比較一下還是有一定意義的. 當然比較的內容都盡量提供相同實作的部分.
## 螢屏觸摸事件監測層 主要在于對螢屏的觸碰事件的監測
初版代碼: ```kotlin override fun onTouchEvent(event: MotionEvent): Boolean {

    touchX = event.x     touchY = event.y     when (event.action) {         MotionEvent.ACTION_DOWN -> {             toFindChageCounts = true             findPointChangeIndex = -1             //增加點前點擊的點到螢屏中             if (controlIndex < maxPoint || isMore == true) {                 addPoints(BezierCurveView.Point(touchX, touchY))             }             invalidate()         }         MotionEvent.ACTION_MOVE ->{             checkLevel++             //判斷當前是否需要檢測更換點坐標             if (inChangePoint){                 //判斷當前是否長按 用于開始查找附件的點                 if (touchX == lastPoint.x && touchY == lastPoint.y){                     changePoint = true                     lastPoint.x = -1F                     lastPoint.y = -1F                 }else{                     lastPoint.x = touchX                     lastPoint.y = touchY                 }                 //開始查找附近的點                 if (changePoint){                     if (toFindChageCounts){                         findPointChangeIndex = findNearlyPoint(touchX , touchY)                     }                 }
                //判斷是否存在附近的點                 if (findPointChangeIndex == -1){                     if (checkLevel > 1){                         changePoint = false                     }
                }else{                     //更新附近的點的坐標 并重新繪制頁面內容                     points[findPointChangeIndex].x = touchX                     points[findPointChangeIndex].y = touchY                     toFindChageCounts = false                     invalidate()                 }             }
        }         MotionEvent.ACTION_UP ->{             checkLevel = -1             changePoint = false             toFindChageCounts = false         }
    }     return true } ```
二次代碼:
```kotlin  Canvas(         ...                 .pointerInput(Unit) {                     detectDragGestures(                         onDragStart = {                             model.pointDragStart(it)                         },                         onDragEnd = {                             model.pointDragEnd()                         }                     ) { _, dragAmount ->                         model.pointDragProgress(dragAmount)                     }                 }                 .pointerInput(Unit) {                     detectTapGestures {                         model.addPoint(it.x, it.y)                     }                 }         )         ...
    /**      * change point position start, check if have point in range      */     fun pointDragStart(position: Offset) {         if (!mInChange.value) {             return         }         if (mBezierPoints.isEmpty()) {             return         }         mBezierPoints.firstOrNull() {             position.x > it.x.value - 50 && position.x < it.x.value + 50 &&                 position.y > it.y.value - 50 && position.y < it.y.value + 50         }.let {             bezierPoint = it         }     }
    /**      * change point position end      */     fun pointDragEnd() {         bezierPoint = null     }
    /**      * change point position progress      */     fun pointDragProgress(drag: Offset) {         if (!mInChange.value || bezierPoint == null) {             return         } else {             bezierPoint!!.x.value += drag.x             bezierPoint!!.y.value += drag.y             calculate()         }     } ```
可以看到由于Compose提供了Tap和Drag的詳細事件, 從而導致新的代碼少許多的標記位變數.
而我之前一度認為是語法糖的特性來給我帶來了不小的驚喜.
譬如這里查找點擊位置最近的有效的點的方法,
初版代碼: ```kotlin //判斷當前觸碰的點附近是否有繪制過的點 private fun findNearlyPoint(touchX: Float, touchY: Float): Int {     Log.d("bsr"  , "touchX: ${touchX} , touchY: ${touchY}")     var index = -1     var tempLength = 100000F     for (i in 0..points.size - 1){         val lengthX = Math.abs(touchX - points[i].x)         val lengthY = Math.abs(touchY - points[i].y)         val length = Math.sqrt((lengthX * lengthX + lengthY * lengthY).toDouble()).toFloat()         if (length < tempLength){             tempLength = length
            if (tempLength < minLength){                 toFindChageCounts = false                 index = i             }         }     }
    return index }
```
而二次代碼: ```kotlin         mBezierPoints.firstOrNull() {             position.x > it.x.value - 50 && position.x < it.x.value + 50 &&                 position.y > it.y.value - 50 && position.y < it.y.value + 50         }.let {             bezierPoint = it         } ```
和Java的Steam類似, 鏈式結構看起來更加的易于理解.

## 貝塞爾曲線繪制層
主要的貝塞爾曲線是通過遞回實作的

初版代碼:
```kotlin //通過遞回方法繪制貝塞爾曲線 private fun  drawBezier(canvas: Canvas, per: Float, points: MutableList<Point>) {
    val inBase: Boolean
    //判斷當前層級是否需要繪制線段     if (level == 0 || drawControl){         inBase = true     }else{         inBase = false     }

    //根據當前層級和是否為無限制模式選擇線段及文字的顏色     if (isMore){         linePaint.color = 0x3F000000         textPaint.color = 0x3F000000     }else {         linePaint.color = colorSequence[level].toInt()         textPaint.color = colorSequence[level].toInt()     }
    //移動到開始的位置     path.moveTo(points[0].x , points[0].y)
    //如果當前只有一個點     //根據貝塞爾曲線定義可以得知此點在貝塞爾曲線上     //將此點添加到貝塞爾曲線點集中(頁面重新繪制后之前繪制的資料會丟失 需要重新回去前段的曲線路徑)     //將當前點繪制到頁面中     if (points.size == 1){         bezierPoints.add(Point(points[0].x , points[0].y))         drawBezierPoint(bezierPoints , canvas)         val paint = Paint()         paint.strokeWidth = 10F         paint.style = Paint.Style.FILL         canvas.drawPoint(points[0].x , points[0].y , paint)         return     }

    val nextPoints: MutableList<Point> = ArrayList()
    //更新路徑資訊     //計算下一級控制點的坐標     for (index in 1..points.size - 1){         path.lineTo(points[index].x , points[index].y)
        val nextPointX = points[index - 1].x -(points[index - 1].x - points[index].x) * per         val nextPointY = points[index - 1].y -(points[index - 1].y - points[index].y) * per
        nextPoints.add(Point(nextPointX , nextPointY))     }
    //繪制控制點的文本資訊     if (!(level !=0 && (per==0F || per == 1F) )) {         if (inBase) {             if (isMore && level != 0){                 canvas.drawText("0:0", points[0].x, points[0].y, textPaint)             }else {                 canvas.drawText("${charSequence[level]}0", points[0].x, points[0].y, textPaint)             }             for (index in 1..points.size - 1){                 if (isMore && level != 0){                     canvas.drawText( "${index}:${index}" ,points[index].x , points[index].y , textPaint)                 }else {                     canvas.drawText( "${charSequence[level]}${index}" ,points[index].x , points[index].y , textPaint)                 }             }         }     }
    //繪制當前層級     if (!(level !=0 && (per==0F || per == 1F) )) {         if (inBase) {             canvas.drawPath(path, linePaint)         }     }     path.reset()
    //更新層級資訊     level++
    //繪制下一層     drawBezier(canvas, per, nextPoints)
}

```
二次代碼: ```kotlin {             lateinit var preBezierPoint: BezierPoint             val paint = Paint()             paint.textSize = mTextSize.toPx()
            for (pointList in model.mBezierDrawPoints) {                 if (pointList == model.mBezierDrawPoints.first() ||                     (model.mInAuxiliary.value && !model.mInChange.value)                 ) {                     for (point in pointList) {                         if (point != pointList.first()) {                             drawLine(                                 color = Color(point.color),                                 start = Offset(point.x.value, point.y.value),                                 end = Offset(preBezierPoint.x.value, preBezierPoint.y.value),                                 strokeWidth = mLineWidth.value                             )                         }                         preBezierPoint = point
                        drawCircle(                             color = Color(point.color),                             radius = mPointRadius.value,                             center = Offset(point.x.value, point.y.value)                         )                         paint.color = Color(point.color).toArgb()                         drawIntoCanvas {                             it.nativeCanvas.drawText(                                 point.name,                                 point.x.value - mPointRadius.value,                                 point.y.value - mPointRadius.value * 1.5f,                                 paint                             )                         }                     }                 }             }
            ...         }

    /**      * calculate Bezier line points      */     private fun calculateBezierPoint(deep: Int, parentList: List<BezierPoint>) {         if (parentList.size > 1) {             val childList = mutableListOf<BezierPoint>()             for (i in 0 until parentList.size - 1) {                 val point1 = parentList[i]                 val point2 = parentList[i + 1]                 val x = point1.x.value + (point2.x.value - point1.x.value) * mProgress.value                 val y = point1.y.value + (point2.y.value - point1.y.value) * mProgress.value                 if (parentList.size == 2) {                     mBezierLinePoints[mProgress.value] = Pair(x, y)                     return                 } else {                     val point = BezierPoint(                         mutableStateOf(x),                         mutableStateOf(y),                         deep + 1,                         "${mCharSequence.getOrElse(deep + 1){"Z"}}$i",                         mColorSequence.getOrElse(deep + 1) { 0xff000000 }                     )                     childList.add(point)                 }             }             mBezierDrawPoints.add(childList)             calculateBezierPoint(deep + 1, childList)         } else {             return         }     } ```
初版開發的時候受個人能力限制, 遞回方法中既包含了繪制的功能也包含了計算下一層的功能.  而二次編碼的時候受Compose的設計影響, 嘗試將所有的點狀態變為Canvas的入參資訊. 代碼的撰寫程序就變得更加的流程.
當然, 現在的我和五年前的我, 開發的能力一定是不一樣的. 即便如此, 隨著Kotlin的不斷發展, 即使是同樣用Kotlin完成的專案, 隨著新的概念的提出, 更多更適合新的開發技術的出現, 我們仍然從Kotlin和Compose識訓更多.




# 我和Kotlin的小故事
初次認識Kotlin是在2017的5月, 當時Kotlin還不是Google所推薦的Android開發語言. 對我來說, Kotlin更多的是個新的技術, 在實際的作業中也無法進行使用.
即使如此, 我也嘗試開始用Kotlin去完成更多的內容, 所幸如此, 不然這篇文章就無法完成了, 我也錯過了一個更深層次了解Kotlin的機會.
但是即便2018年Google將Kotlin作為Android的推薦語言, 但Kotlin在當時仍不是一個主流的選擇. 對我來說以下的一些問題導致了我在當時對Kotlin的使用性質不高. 一是新語言, 社區構建不完善, 有許多的內容需要大家填充, 帶來就是在實際的使用情況中會遇到各種的問題, 這些問題在網站中沒有找到可行的解決方案. 二是可以和Java十分便捷互相使用的特性, 這個特性是把雙刃劍, 雖然可以讓我更加無負擔的使用Kotlin(不行再用Java寫唄.). 但也使得我認為Kotlin是個Java++或者Java--. 三是無特殊性, Kotlin并沒有帶來什么新的內容, Kotlin能完成的事情Java都能做完成, (空值和data class之類的在我看來更多的是一個語法糖.) 那么我為什么要用一種新的不熟悉的技術來完成我都需求?
所幸的是, 還是有更多的人在不斷的推進和建設Kotlin. 也吸引了越來越多的人加入. 近年來越來越多的專案中都開始有著Kotlin的蹤跡, 我將Kotlin添加到現有的專案中也變得越來越能被大家所接受. 也期待可以幫助到更多的人.

### 相關代碼地址: [初次代碼](https://github.com/clwater/BezierCurve)

[二次代碼](https://github.com/clwater/AndroidComposeCanvas/tree/master/app/src/main/java/com/clwater/compose_canvas/bezier)  

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/551878.html

標籤:Android

上一篇:HMS Core 6.10.0版本發布公告

下一篇:返回列表

標籤雲
其他(158610) Python(38118) JavaScript(25405) Java(18023) C(15222) 區塊鏈(8262) C#(7972) AI(7469) 爪哇(7425) MySQL(7171) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5336) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4567) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2432) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1965) Web開發(1951) HtmlCss(1932) python-3.x(1918) 弹簧靴(1913) C++(1912) xml(1889) PostgreSQL(1874) .NETCore(1857) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 當我再次用Kotlin完成五年前已經通過Kotlin完成的專案后

    > 近日來對Kotlin的使用頻率越來越高, 也對自己近年來寫過的Kotlin代碼嘗試進行一個簡單的整理. 翻到了自己五年前第一次使用Kotlin來完成的一個專案([貝塞爾曲線](https://juejin.cn/post/6844903556173004807)), 一時興起, 又用發展到現在的 ......

    uj5u.com 2023-05-08 08:29:26 more
  • HMS Core 6.10.0版本發布公告

    分析服務 ◆ 事件分析下新增商品訂閱分析報告,幫助開發者了解應用內用戶付費訂閱概況,評估訂閱付費價值; ◆ 營銷分析、用戶質量、轉化分析以及過濾器中,新增廣告系列/廣告任務通過ID進行搜索的功能,通過更便捷高效的資料分析體驗,幫助開發者合理評估廣告投放的后端轉化效果。 查看詳情>> 運動健康服務 ◆ ......

    uj5u.com 2023-05-07 09:27:08 more
  • Flutter(十) 音頻+視頻播放

    在Flutter中,我們有各種插件可供使用,從而實作音頻和視頻的播放功能。 例如,可以使用“text_to_speech”插件來將文字轉換為語音,使用內置的“video_player”插件輕松地實作視頻播放,或者使用“audioplayers”插件實作音頻播放。 對于僅需要簡單播放器功能的情況,也可 ......

    uj5u.com 2023-05-07 09:21:00 more
  • HMS Core 6.10.0版本發布公告

    分析服務 ◆ 事件分析下新增商品訂閱分析報告,幫助開發者了解應用內用戶付費訂閱概況,評估訂閱付費價值; ◆ 營銷分析、用戶質量、轉化分析以及過濾器中,新增廣告系列/廣告任務通過ID進行搜索的功能,通過更便捷高效的資料分析體驗,幫助開發者合理評估廣告投放的后端轉化效果。 查看詳情>> 運動健康服務 ◆ ......

    uj5u.com 2023-05-07 09:20:37 more
  • Flutter 如何將代碼顯示到界面上

    如何優雅的將專案中的代碼,亦或是你的demo代碼展示到界面上?本文對使用簡單、便于維護且通用的解決方案,進行相關的對比和探究 ......

    uj5u.com 2023-05-05 09:45:57 more
  • 當我第一次通過Kotlin和Compose來實作一個Canvas時, 我識訓了

    自從2019年Google推薦Kotlin為Android開發的首選語言以來已經經歷了將近四年的時間, Compose的1.0版本也發布了將近2年的時間, Kotlin+Compose在現階段的Android開發程序中還遠遠達不到主流的程度. 我們是否應該開始嘗試這個組合? 這個組合有會給我們帶來什... ......

    uj5u.com 2023-04-28 12:21:20 more
  • 當我第一次通過Kotlin和Compose來實作一個Canvas時, 我識訓了

    自從2019年Google推薦Kotlin為Android開發的首選語言以來已經經歷了將近四年的時間, Compose的1.0版本也發布了將近2年的時間, Kotlin+Compose在現階段的Android開發程序中還遠遠達不到主流的程度. 我們是否應該開始嘗試這個組合? 這個組合有會給我們帶來什... ......

    uj5u.com 2023-04-28 12:20:38 more
  • xcode歷史版本下載

    一、背景 較早之前做過一個專案,當時使用swift 3.x開發。 專案結束后就沒再有新需求與更新。 但最近呢需要對專案的某些功能進行調整,專案又重新被拾了起來。 我們知道現在的swift 版本已經到了 5.x, 相應的語法上較 3.x版本也有了不小的變化。使用最新版本的xcode都已經不支持swif ......

    uj5u.com 2023-04-27 11:04:14 more
  • xcode歷史版本下載

    一、背景 較早之前做過一個專案,當時使用swift 3.x開發。 專案結束后就沒再有新需求與更新。 但最近呢需要對專案的某些功能進行調整,專案又重新被拾了起來。 我們知道現在的swift 版本已經到了 5.x, 相應的語法上較 3.x版本也有了不小的變化。使用最新版本的xcode都已經不支持swif ......

    uj5u.com 2023-04-27 10:50:06 more
  • (轉)java.sql.SQLException: An attempt by a client to checkout

    jar包下載方式官網地址:MySQL :: Download Connector/J,如果你打不開官網,在下面我為你準備了直接下載jar包的鏈接地址。 在選擇作業系統時,此處選擇platform independent(獨立于平臺)。 8.0版本的jar包下載地址,點擊直接下載。 https://d ......

    uj5u.com 2023-04-27 09:18:15 more