仿PhotoShop調(diào)色板:HSV取色器實戰(zhàn))
簡介面向Android開發(fā)者的高級顏色選擇器實現(xiàn)資源旨在將Photoshop中專業(yè)調(diào)色板交互移植至移動端。項目覆蓋色輪選擇、RGB/HSV色彩模式切換、滑塊控制、Alpha透明度調(diào)整及色樣管理等核心功能同時包含UI布局優(yōu)化與觸摸事件處理的技術(shù)細(xì)節(jié)適合想深入理解自定義View與顏色處理的中高級開發(fā)者。資源包共58個文件以Java源碼、編譯后的class、XML布局、PNG資源為主另含測試APK、JAR庫及配置文件便于直接運(yùn)行與二次開發(fā)。已有826人學(xué)習(xí)瀏覽壓縮包僅330KB結(jié)構(gòu)緊湊但內(nèi)容完整。資源中的ColorPanel組件從布局繪制、事件分發(fā)到顏色空間換算均給出完整示例通過閱讀源碼可掌握色彩轉(zhuǎn)換算法、觸控反饋與性能優(yōu)化的實現(xiàn)鏈路既可作為組件集成到實際項目也能作為系統(tǒng)學(xué)習(xí)移動端圖像處理組件的參考案例對提升自定義控件能力有切實幫助。1. 為什么要親手?jǐn)]一個仿PhotoShop調(diào)色板先交代下背景。我之前在做一個偏向設(shè)計工具類的App里面有個很核心的訴求用戶需要像在PhotoShop里那樣自由地取色、調(diào)色。最初圖省事接了個開源的顏色選擇器庫但實際用起來非常難受——交互太簡陋只有色相條加一個方塊的模式放大取色時連基本的微調(diào)都不支持更別提煉色器、最近顏色、Hex實時聯(lián)動這些習(xí)慣了。后來在技術(shù)社區(qū)看到很多人在討論類似的痛點(diǎn)Android原生自帶的ColorPickerDialog丑到?jīng)]法直視第三方庫的自定義程度又有限。于是我決定自己動手基于Android的自繪體系實現(xiàn)一個仿PhotoShop調(diào)色板。這個項目不依賴任何第三方渲染框架核心就是自定義View加Canvas繪制純Kotlin實現(xiàn)最終完整支持色相滑動條、飽和度/亮度二維取色面板、RGB分量調(diào)節(jié)、Hex實時輸入、放大微調(diào)以及屏幕取色器。這篇文章適合誰看如果你正準(zhǔn)備給App加一個專業(yè)級的調(diào)色面板或者你想搞懂PhotoShop那種取色交互背后的色值計算邏輯與手勢處理方案那這篇實戰(zhàn)記錄應(yīng)該能讓你少走很多彎路。我會從色彩模型的選擇、核心View的繪制、手勢交互設(shè)計到大面板的性能優(yōu)化把整個項目的關(guān)鍵環(huán)節(jié)拆開講清楚。別人可能覺得調(diào)色板就是個換個顏色的小功能但真做起來你會發(fā)現(xiàn)它涉及ColorSpace轉(zhuǎn)換、Canvas繪制效率、觸摸事件分發(fā)、View局部刷新、位圖像素讀取等多塊知識。正因為踩了不少坑我才想把完整經(jīng)驗沉淀下來。2. 核心算法先行HSV模型是PhotoShop取色的地基在動手寫任何View之前必須先想明白一個本質(zhì)問題為什么PhotoShop的調(diào)色板長得是一個色相條一個大方塊而不是一堆紅綠藍(lán)數(shù)字滑桿2.1 為什么選HSV而不是RGBRGB是一種面向設(shè)備的顏色模型紅綠藍(lán)三原色疊加表達(dá)顏色。它的問題是不直觀你想讓顏色更青一點(diǎn)需要同時調(diào)整G和B普通人根本轉(zhuǎn)不過彎。而HSV把顏色分解為色相Hue0-360度、飽和度Saturation0-1、明度Value0-1三個維度剛好對應(yīng)人類的直覺感知這是什么顏色、顏色正不正、顏色亮不亮。PhotoShop經(jīng)典取色界面就是圍繞HSV設(shè)計的色相條負(fù)責(zé)選H值二維方塊的橫向是S、縱向是V。這個交互最大的好處是當(dāng)你把H固定在某個色相后方塊的左邊緣是純灰階S0右邊緣是純色S1上邊緣是最亮V1下邊緣是黑色V0一屏之內(nèi)就能看全所有明暗和濃淡的可能組合。2.2 HSV與RGB轉(zhuǎn)換的降級方案Android的Color.colorToHSV()和Color.HSVToColor()其實已經(jīng)提供了現(xiàn)成的轉(zhuǎn)換方法底層計算我第一版也直接用了。但把玩下來發(fā)現(xiàn)有兩點(diǎn)不滿意一是大色塊逐像素轉(zhuǎn)換場景下反復(fù)調(diào)用系統(tǒng)方法會有額外開銷二是HSVToColor對參數(shù)合法性的隱含約束不夠靈活比如H值越界時會算出讓人意外的顏色。所以我在項目中單獨(dú)實現(xiàn)了一組轉(zhuǎn)換函數(shù)核心邏輯保持和Android實現(xiàn)一致但保證輸入H在0-360區(qū)間、S和V在0-1區(qū)間時一定返回預(yù)期的ARGBfun hsvToColor(h: Float, s: Float, v: Float): Int { val hue ((h % 360) 360) % 360 val sat s.coerceIn(0f, 1f) val value v.coerceIn(0f, 1f) val c value * sat val x c * (1 - kotlin.math.abs((hue / 60) % 2 - 1)) val m value - c val (r, g, b) when { hue 60 - Triple(c, x, 0f) hue 120 - Triple(x, c, 0f) hue 180 - Triple(0f, c, x) hue 240 - Triple(0f, x, c) hue 300 - Triple(x, 0f, c) else - Triple(c, 0f, x) } return Color.rgb( ((r m) * 255).toInt().coerceIn(0, 255), ((g m) * 255).toInt().coerceIn(0, 255), ((b m) * 255).toInt().coerceIn(0, 255) ) }這段邏輯本質(zhì)上是把HSV色環(huán)切成六個60度的扇區(qū)每個扇區(qū)RGB三通道的變化規(guī)律是不同的線性插值。理解了這個公式后面做色相條漸變和方塊面板漸變就都順了。2.3 PhotoShop取色交互的分解動作PhotoShop的調(diào)色板看起來很復(fù)雜實際拆解下來就是三個動作的疊加拖動色相條改變H值S保持1、V保持1時得到當(dāng)前色相的頂點(diǎn)純色這個純色是二維面板漸變的基準(zhǔn)色。在二維面板點(diǎn)擊或拖動以基準(zhǔn)色為基礎(chǔ)橫向線性減小S飽和度為0時變灰縱向線性減小V明度為0時變黑。微調(diào)RGB或Hex把HSV結(jié)果和RGB結(jié)果做雙向同步任意一側(cè)改動都會實時刷新另一側(cè)的顯示與色塊預(yù)覽。只要把這三個動作的狀態(tài)機(jī)和回調(diào)設(shè)計好整體功能就算成立了。我的實現(xiàn)里使用一個ColorState數(shù)據(jù)類維護(hù)當(dāng)前色值它同時保存hsv和rgb兩套表示任何UI變更都走同一個applyChange入口保證所有關(guān)聯(lián)控件同步刷新。3. 色相條與二維取色面板的繪制看著像PhotoShop只是第一步這部分是視覺效果的核心。PhotoShop的色相條和二維面板都帶有非常順滑的漸變過渡想在Android里復(fù)現(xiàn)這種質(zhì)感關(guān)鍵是用好SweepGradient和LinearGradient。3.1 色相條的兩種畫法色相條常見的表現(xiàn)有兩種一種是直線條從左到右漸變另一種是圓環(huán)環(huán)繞一周。PhotoShop桌面端常用的是直線豎向色相條。我實現(xiàn)的是直線橫向色相條視覺效果穩(wěn)定手勢也只在一維方向做。線性色相條直接用LinearGradient生成360度的連續(xù)色相漸變val colors IntArray(360) { i - hsvToColor(i.toFloat(), 1f, 1f) } val gradient LinearGradient( 0f, 0f, width.toFloat(), 0f, colors, null, Shader.TileMode.CLAMP )這里colors數(shù)組生成360個顏色每個顏色對應(yīng)一個色相角度的純色S1, V1。LinearGradient默認(rèn)會在這些顏色之間做線性插值視覺上就是一條順滑的彩虹漸變。其實更省內(nèi)存的方式是只用幾個關(guān)鍵顏色讓漸變庫自動插值但實測360個顏色在色相過渡上更接近PhotoShop的連續(xù)感且只初始化一次性能完全可接受。3.2 二維飽和度明度面板的疊加漸變二維面板的核心技巧在于兩層漸變疊加第一層以當(dāng)前色相純色為基準(zhǔn)從左到右從飽和到不飽和即從純色漸變?yōu)榘咨诙訌纳系较聫耐该鳚u變到黑色。PhotoShop里這個面板的左上角是白色S0, V1右上角是當(dāng)前純色S1, V1左下角是黑色S0, V0右下角是黑色S1, V0。理解了這四個角你就知道漸變該怎么寫了// 第一層橫向飽和度漸變 val saturationShader LinearGradient( 0f, 0f, width.toFloat(), 0f, Color.WHITE, currentPureColor, Shader.TileMode.CLAMP ) // 第二層縱向明度漸變 val valueShader LinearGradient( 0f, 0f, 0f, height.toFloat(), Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP )繪制時先畫橫向飽和度漸變再疊加縱向明度漸變。這里有個必須注意的地方千萬不要用Paint的setAlpha去處理明度層因為那是整體半透明會把底下漸變的邊界也弄糊。正確做法是讓LinearGradient自身在TRANSPARENT到BLACK之間插值這樣顏色空間才是按比例壓暗而不是蒙一層玻璃紙。3.3 指示器找準(zhǔn)位置要比畫得好看更重要漸變畫完之后還需要畫取色指示器。PhotoShop里是一個圓環(huán)加十字準(zhǔn)星。我實現(xiàn)的是一個帶白邊的小圓private fun drawIndicator(canvas: Canvas, cx: Float, cy: Float, color: Int) { val outer Paint(Paint.ANTI_ALIAS_FLAG).apply { style Paint.Style.STROKE strokeWidth 6f color Color.WHITE } val inner Paint(Paint.ANTI_ALIAS_FLAG).apply { style Paint.Style.STROKE strokeWidth 2f color if (isColorLight(color)) Color.BLACK else Color.WHITE } canvas.drawCircle(cx, cy, 18f, outer) canvas.drawCircle(cx, cy, 14f, inner) }isColorLight這一步很重要如果當(dāng)前點(diǎn)顏色是淺色比如黃色白色圓環(huán)幾乎看不見得疊加一個深色內(nèi)圈保證對比度。計算公式用的是相對亮度(0.299*R 0.587*G 0.114*B)大于180就算淺色。有不少教程只講畫個圓環(huán)但實際使用中指示器的可見性和準(zhǔn)確位置直接決定調(diào)色體驗。我第一版沒加isColorLight判斷結(jié)果取到亮黃色時圓環(huán)完全隱形調(diào)試了很久才發(fā)現(xiàn)是對比度問題。4. 手勢交互讓拖動、微調(diào)、實時預(yù)覽都按PhotoShop的邏輯走繪制層面搞定后更大的挑戰(zhàn)是交互。PhotoShop的調(diào)色板在主面板取色和色相條選色之間切換非常順手而且支持點(diǎn)擊直接跳轉(zhuǎn)、拖動連續(xù)變化。這些體驗背后是觸摸事件處理和坐標(biāo)映射的精細(xì)設(shè)計。4.1 觸摸事件的命中區(qū)域劃分我的調(diào)色板是三個區(qū)域組合在一個自定義ViewGroup里頂部是二維面板底部是色相條中間留著固定間距。為了讓觸摸邏輯清晰我在onTouchEvent里先判斷觸摸點(diǎn)落在哪個區(qū)域override fun onTouchEvent(event: MotionEvent): Boolean { val x event.x val y event.y when { y panelBottom - handlePanelTouch(event, x, y) y hueBarTop - handleHueTouch(event, x, y) } return true }注意actionDown時也要更新否則只靠actionMove會導(dǎo)致用戶點(diǎn)一下沒有任何反饋。而且返回true后整個View會接管手勢流后續(xù)的move、up都會持續(xù)回調(diào)這點(diǎn)對連續(xù)拖動很重要。4.2 從觸摸坐標(biāo)反推HSV值不管用戶點(diǎn)在哪個位置最終都需要把坐標(biāo)變成顏色值。色相條坐標(biāo)換算hue (x / hueBarWidth) * 360f然后clamp到0-360之間。面板坐標(biāo)換算橫向算飽和度縱向算明度val s (x / panelWidth).coerceIn(0f, 1f) val v (1f - y / panelHeight).coerceIn(0f, 1f)這里有個新手特別容易搞錯的地方屏幕坐標(biāo)的y軸向下增長但HSV的V值越大越亮、越靠上。因此從y坐標(biāo)映射到V值時一定要用1 - y / height否則上下顛倒了用戶往上拖反而變暗。拿到s和v之后再結(jié)合當(dāng)前色相值h調(diào)用前面實現(xiàn)的hsvToColor(h, s, v)得到最新的顏色刷新指示器位置和外部回調(diào)。4.3 放大微調(diào)撐起專業(yè)感的細(xì)節(jié)PhotoShop用戶最喜歡的操作之一是放大取色區(qū)域做精細(xì)調(diào)整。在移動端上我用雙指縮放實現(xiàn)了這個需求當(dāng)用戶雙指在面板區(qū)域捏合時面板會以兩指中心點(diǎn)為錨點(diǎn)逐漸放大再配合單指拖動來微調(diào)顏色。原理并不復(fù)雜維護(hù)一個scaleFactor浮點(diǎn)變量雙指間距變化時更新它繪制時把Canvas按錨點(diǎn)做縮放canvas.save() canvas.scale(scaleFactor, scaleFactor, anchorX, anchorY) // 繪制面板和指示器 canvas.restore()但觸摸坐標(biāo)的映射也要同步變化單指拖動時實際顏色坐標(biāo) 錨點(diǎn) (觸摸坐標(biāo) - 錨點(diǎn)) / scaleFactor。這樣即使面板放大了2倍手指動1像素仍然只會讓顏色移動半像素的效果精細(xì)度翻倍。實測下來雙指縮放加單指取色的組合在圖片精確取色時特別好用。之前我們的設(shè)計師直接在手機(jī)上調(diào)出和PS完全一致的顏色靠的就是這個放大微調(diào)能力。4.4 取色器從Bitmap里讀取像素顏色調(diào)色板還附帶了一個屏幕取色器從相冊選一張圖片點(diǎn)擊圖片任意位置提取顏色。實現(xiàn)上核心是Bitmap.getPixel(x, y)。但這里有個大坑如果直接加載一張高分辨率圖片并顯示縮放后的Bitmap手指點(diǎn)擊的坐標(biāo)和原圖坐標(biāo)會有偏差。所以我的實現(xiàn)分兩步走先加載原圖并統(tǒng)一縮放到屏幕能顯示的尺寸保留采樣率記憶觸摸時記錄的是顯示圖上相對坐標(biāo)拿顏色時用相對坐標(biāo)乘回原圖縮放比例val srcX (touchX / displayWidth * originalWidth).toInt() val srcY (touchY / displayHeight * originalHeight).toInt() val color bitmap.getPixel(srcX, srcY)這個大圖采樣用BitmapFactory.Options的inSampleSize來處理避免直接加載原圖導(dǎo)致OOM。實際項目里選了一張8000x6000的照片測試顯示圖只有1500x1100左右取色依然準(zhǔn)確。5. 聯(lián)動與細(xì)節(jié)打磨Hex輸入、RGB滑桿、最近顏色與狀態(tài)同步如果說上面這些是骨架那聯(lián)動和細(xì)節(jié)就是血肉。調(diào)色板真正好用的狀態(tài)是用戶拖動色相條面板顏色跟著變指示器跟著移Hex輸入框?qū)崟r跳動RGB滑桿同步刷新預(yù)覽色塊無延遲更新。這個多向同步的架構(gòu)是非常值得展開的。5.1 統(tǒng)一狀態(tài)入口避免多個控件互相刷新的死循環(huán)我設(shè)計了一個ColorStateListener接口任何UI控件的變更都會以參數(shù)形式傳給一個中心調(diào)度器ColorPickerMediatorinterface ColorPickerMediator { fun onColorChanged(color: Int, source: ChangeSource) }ChangeSource是一個枚舉標(biāo)記這次變化來自哪個控件HUE_BAR、SAT_VAL_PANEL、RGB_SLIDER、HEX_INPUT、EYE_DROPPER。中心調(diào)度器拿到新顏色后先更新內(nèi)部的currentColor再通知除變更來源之外的所有控件刷新。這樣做的好處是Hex輸入框觸發(fā)顏色變化時不會再反向去更新Hex輸入框自身避免輸入-刷值-觸發(fā)監(jiān)聽-再輸入的無限循環(huán)問題。這個設(shè)計我是在第一版被死循環(huán)折磨了兩次之后才想明白的強(qiáng)烈建議有類似需求的朋友直接采用。5.2 RGB滑桿與Hex輸入的聯(lián)動細(xì)節(jié)RGB滑桿比色相條簡單本質(zhì)是三個0-255范圍的SeekBar。但設(shè)置滑桿進(jìn)度時有個順序問題如果先設(shè)置R再設(shè)置G每次設(shè)置都會觸發(fā)一次onProgressChanged導(dǎo)致中間態(tài)顏色閃爍。解法是加一個isProgrammaticUpdate布爾標(biāo)志在代碼批量設(shè)置進(jìn)度時掛起監(jiān)聽等三個滑桿都設(shè)置完再恢復(fù)fun updateSliders(color: Int) { isProgrammaticUpdate true redSlider.progress Color.red(color) greenSlider.progress Color.green(color) blueSlider.progress Color.blue(color) isProgrammaticUpdate false }Hex輸入框則要處理兩件事合法性校驗和輸入中不全局刷新。我的做法是監(jiān)聽TextWatcher只在文本長度等于6且全是十六進(jìn)制字符時才解析并觸發(fā)onColorChanged否則只改輸入框的字體顏色提示用戶格式不對。這樣用戶在輸入F、FF這些中間狀態(tài)時不會看到整個界面顏色亂跳。5.3 最近使用顏色與透明度棋盤格PhotoShop有最近使用顏色的小色塊列表我也做了類似功能。實現(xiàn)時用一個LinkedHashSetInt保存歷史顏色新顏色加入時如果已存在就先移除再插入保證最新顏色排在最前最多保留12個。每次取色完成時把顏色寫入這個集合并刷新色塊網(wǎng)格。透明度棋盤格是另一個容易被忽略的細(xì)節(jié)。調(diào)色板輸出的ARGB顏色可能帶透明通道如果預(yù)覽色塊直接畫一個純色用戶根本看不出透明效果。因此我在預(yù)覽色塊下方先畫一個灰白相間的小棋盤格再在上面疊加顏色private val checkerPaint Paint().apply { shader BitmapShader(checkerBitmap, REPEAT, REPEAT) } canvas.drawRect(rect, checkerPaint) canvas.drawColor(color)棋盤格的實現(xiàn)是預(yù)先創(chuàng)建一個8x8的小Bitmap左半邊灰右半邊白然后用BitmapShader的REPEAT模式無限平鋪。這個小技巧在很多圖片編輯App里都很常見但自己實現(xiàn)調(diào)色板時容易漏。5.4 深淺色模式適配App一般都會支持Dark Mode調(diào)色板也不能例外。我的做法是給面板和色相條的背景色做主題感知淺色模式下外邊框用淺灰深色模式下用深灰指示器的陰影色也分別適配。如果你的主界面跟隨系統(tǒng)主題這一步就一定要做否則深色模式下調(diào)色板會顯得特別突兀。除了顏色字體、滑桿的thumb和track資源也建議用?attr/colorControlNormal這類主題屬性能省下大量適配代碼。6. 踩坑記錄與性能調(diào)優(yōu)從能用到好用這個項目看著不大實際開發(fā)過程中踩了很多坑。我把幾個典型的、網(wǎng)上資料少的坑記錄在這里希望對后面做類似控件的朋友有幫助。6.1 重繪范圍過大導(dǎo)致卡頓第一版實現(xiàn)里每次顏色變化我都直接調(diào)用invalidate()讓整個ViewGroup重繪。在低端機(jī)上快速拖動色相條時幀率明顯下降面板漸變填充、指示器、預(yù)覽色塊全部重繪CPU占滿。優(yōu)化思路是把復(fù)雜的漸變層做成靜態(tài)緩存色相條漸變整個條不變只有指示器在動。所以把漸變部分單獨(dú)渲染到一個Bitmap每次只重繪指示器區(qū)域。二維面板漸變漸變與當(dāng)前色相相關(guān)色相不變時漸變不變。因此色相變化時才重新生成面板的漸變Bitmap拖動取色時只重繪指示器。預(yù)覽色塊和Hex文本獨(dú)立的View用invalidate只刷它們自身。這樣一改快速拖動時絕大多數(shù)幀只需要重繪兩個指示器CPU占用直接降了60%以上。代碼結(jié)構(gòu)上我用了一個GradientCache對象來緩存面板Bitmapclass GradientCache { private var hueKey: Int -1 private var bitmap: Bitmap? null fun getPanelBitmap(hue: Int, width: Int, height: Int): Bitmap { if (bitmap null || hueKey ! hue) { bitmap renderPanelBitmap(hue, width, height) hueKey hue } return bitmap!! } }6.2LinearGradient起點(diǎn)坐標(biāo)的坑面板漸變的起點(diǎn)坐標(biāo)有人喜歡寫(0f, 0f, width.toFloat(), height.toFloat())做對角線漸變但這不是我們要的效果。二維面板必須嚴(yán)格橫向一層、縱向一層所以起點(diǎn)終點(diǎn)必須控制在水平或垂直方向上// 橫向飽和度 LinearGradient(0f, 0f, width.toFloat(), 0f, ...) // 縱向明度 LinearGradient(0f, 0f, 0f, height.toFloat(), ...)我之前有一版把兩個漸變的終點(diǎn)都寫成了(width, height)結(jié)果面板出現(xiàn)了詭異的斜向色帶怎么調(diào)都不是PhotoShop那種四角分明的效果。排查了很久才意識到是坐標(biāo)寫錯了。6.3 觸摸邊界溢出的處理當(dāng)指示器位于面板最邊緣時它的中心點(diǎn)已經(jīng)貼到邊界畫外圈圓環(huán)時會有半個圓被裁掉。如果Canvas沒裁剪圓環(huán)會超出View邊界如果裁剪了看起來就很別扭。我的處理是不改變指示器的繪制位置而是把面板邊界留出indicatorRadius的padding。也就是說實際可觸摸區(qū)域比視覺面板大一圈繪制時把面板整體內(nèi)縮指示器在視覺邊界上移動時依然完整可見。這屬于比較偏門的UI細(xì)節(jié)但做出來觀感會精致很多設(shè)計師驗收的時候一定會注意到。6.4 像素顏色讀取時ARGB順序的坑從Bitmap取色時getPixel返回的是一個Int高8位是Alpha然后依次是R、G、B。有的老機(jī)型上尤其是Android 7以下某些廠商ROMBitmap默認(rèn)的像素格式是RGB_565而不是ARGB_8888此時getPixel得到的顏色會丟失Alpha信息且R、G、B的精度只有5-6位。我的處理是在創(chuàng)建Bitmap時顯式設(shè)置inPreferredConfig Bitmap.Config.ARGB_8888再加上異常保護(hù)val options BitmapFactory.Options().apply { inJustDecodeBounds true } BitmapFactory.decodeFile(path, options) options.inSampleSize calculateSampleSize(options, targetWidth, targetHeight) options.inJustDecodeBounds false options.inPreferredConfig Bitmap.Config.ARGB_8888 val bitmap BitmapFactory.decodeFile(path, options)如果沒有這個設(shè)置某些ROM上取到的顏色會明顯偏綠因為RGB_565對綠色的位數(shù)分配最多印象很深。6.5 性能建議匯總對自定義View的性能優(yōu)化我最后總結(jié)出幾條規(guī)律放這里供參考盡量不在onDraw里創(chuàng)建Paint和Shader這些對象要在初始化時創(chuàng)建好繪制時只改變參數(shù)。漸變類是重量級對象能用緩存就緩存不要在每幀里new。觸摸事件里不要做耗時操作或日志輸出Log.d在快速拖動時會刷屏并影響性能。如果實時刷新預(yù)覽涉及位圖縮放建議用Canvas.drawBitmap配合Matrix比重新加載Bitmap高效得多。低端機(jī)上如果還是卡可以把色相條漸變色的采樣數(shù)從360降到180或120視覺差異小性能提升明顯。這個項目最終的整體效果已經(jīng)非常接近PhotoShop的基礎(chǔ)取色體驗了橫向色相條順滑切換二維面板四角顏色分明放大微調(diào)精細(xì)取色RGB/Hex雙向同步屏幕取色器也從實際照片中準(zhǔn)確取到了目標(biāo)色值。我把它集成到了App里設(shè)計師、產(chǎn)品、測試用下來都反饋比很多成熟App的取色器還好用。最后再分享一個通用經(jīng)驗任何自定義View開發(fā)的流程都是先定數(shù)據(jù)模型再定繪制方案最后做手勢交互。數(shù)據(jù)模型正確了HSV轉(zhuǎn)RGB、坐標(biāo)映射這些計算就不會出大問題繪制方案定了性能優(yōu)化點(diǎn)在哪里心里有數(shù)手勢交互最后加調(diào)試起來反而最順暢。如果你也想做類似的專業(yè)級調(diào)色板強(qiáng)烈建議按照這個順序推進(jìn)少走很多彎路。本文還有配套的精品資源點(diǎn)擊獲取