iamondTouch是一種多用戶觸摸技術,適用于桌面環境和前投顯示器。它可以實現多個用戶同時在同一個觸摸表面上進行操作,而彼此之間不會相互干擾,也不會受到異物的影響。此外,DiamondTouch還能夠識別哪個用戶正在觸摸表面的哪個位置。
有人構建了一個協作工作空間,多個用戶可以在其中共同使用數據集。該環境包括一個安裝在天花板上的視頻投影儀,將內容顯示在一張白色桌子上,用戶可以坐下操作。每個用戶還配備了一個無線鼠標作為輸入設備。
通過讓用戶獨立地同時與桌面進行互動,并考慮使用多只鼠標,可以改善協作效果。然而,在協作環境中使用多只鼠標存在一些問題。用戶可能會面臨跟蹤指針在大型表面上的挑戰。跟蹤多只鼠標幾乎是不可能的,這使得用戶無法準確地告知其他用戶他們所指向的位置。
此外,依賴于單一的物理設備也無法充分利用人類的自然傾向,例如伸手、觸摸和抓握動作。使用大型觸摸屏作為桌面解決方案看起來是一個答案,但現有的觸摸技術還不足以滿足需求,大多數只允許單點觸摸且無法識別用戶。
而DiamondTouch技術具有多點觸摸、用戶識別、抗干擾、耐用性、無需額外設備和低成本制造的特點可以解決上述問題。
DiamondTouch是一種通過傳輸不同的電信號來工作的技術。它向桌面的每個部分發出獨特的信號以進行確認。當用戶觸摸桌子時,信號從觸摸點正下方進行電容耦合,并通過用戶進入與該用戶相關聯的接收器單元。
然后,接收器可以確定用戶正在觸摸桌面的哪些部分。這種方式可以實現多點觸控,使系統能夠檢測和跟蹤多個用戶在桌面上的觸摸位置和動作。桌面是使用一組嵌入式天線構建的,可以具有任意形狀和大小。
這些天線是由導電材料制成的薄片,并且彼此之間被絕緣隔離。由于向用戶發送的信號是電容耦合的,天線也與用戶絕緣,整個桌面覆蓋著一層絕緣保護材料,如圖1所示。每個天線延伸到一個特定區域,以明確標識出其位置。
圖1
系統無法區分用戶觸摸天線的具體位置,即用戶觸摸的天線。發射器單元通過驅動每個天線發送自己的信號,可以通過信號的特征來區分每個天線。用戶通過他們的椅子與天線發生電容耦合,并且接收器通過共享的電氣接地參考返回到發射器。
當用戶觸摸桌子時,電容耦合電路就會完成連接。該電路從發射器開始,通過桌面上的觸摸點傳輸,然后通過用戶到達用戶的接收器,并返回到發射器。
通過適當的設計,人體的電容耦合可以非常可靠地實現。首先要考慮的是使用"近場"即電容耦合進行操作。通過限制發射頻率,使得整個系統的尺寸相比波長非常小,輻射的能量非常少。
這減少了射頻干擾和不需要的天線之間的耦合問題。對于適當大小的桌子,頻率應在亞兆赫范圍內。由于用戶可能同時觸摸多個天線,因此接收器的能力來區分和區分天線是非常重要的,以識別輸入信號的任何混合。
為了實現這一點,使用了一種信號處理術語中稱為"可分離的"或"相互正交的"的信號。有多種方法可以生成這樣的信號。
例如,可以使用不同頻率的正弦波來驅動每個天線。通過檢查接收信號的頻譜,耦合到多個天線的接收器可以確定它們來自哪些天線。然而,生成大型陣列所需的許多頻率是復雜且相對昂貴的。
時分復用是另一種選擇。在這種情況下,每個天線被逐個驅動,而其他天線則不被驅動。接收器根據接收時間信號來確定哪些天線與之相連。盡管這種系統的實施非常簡單,但它可能不適用于較大的陣列。
這是由于各種制約因素的相互作用。為了提供良好的響應時間,整個陣列必須以每秒10到100次的速率進行掃描。然而,實際可用的調制頻率受限于亞兆赫范圍,這導致每個天線的調制周期非常短,使得接收機設計變得非常困難,特別是在面對其他干擾噪聲源時。
另一種構建一組正交信號的方法是碼分復用,這是一種擴頻技術。事實上,對于大型陣列來說,這是一種特別優雅的方法,因為可以使用非常簡單的硬件操作(如移位、XOR等)來生成擴展碼序列。
簡單的硬件甚至可以級聯,以實現更大規模的觸摸設備。擴頻方法實際上提供了大型陣列的顯著信噪比增益。
正如之前提到的,嵌入在桌面上的天線可以具有任意形狀和大小。設計師可以選擇實現幾個大的"按鈕"或者更復雜的天線陣列。然而,一個通用且可配置的解決方案比特定設計的硬件更可取。
最通用的解決方案是使用"全矩陣"模式,其中大量的天線被排列成一個矩形網格。
這樣,通過單獨驅動每個天線的矩陣"像素",可以明確地確定多個觸摸位置,即使對于單個用戶也是如此。然而,這種模式也是制造最困難的,因為它需要大量的連接和相應的支持電路。實際上,并不是每個應用程序都需要矩陣模式。
雖然同步和多用戶功能是必不可少的,但對于大多數應用程序,每個用戶最多指示一個觸摸點或邊界框已足夠。這種功能可以通過簡單的行/列模式實現,大大減少了所需的天線數量。
行和列通常位于不同的層上。由于屏蔽效應,創建良好的行/列天線方向圖有一些微妙之處。簡單的上層矩形列圖案會覆蓋和重疊太多的較低等效行集層。這將減小通過的面積,使行無法很好地電容耦合信號,從而削弱其靈敏度。
良好的天線方向圖將最小化行和列的重疊區域,同時最大化它們的總面積,連接成菱形圖案(如圖2所示)是一個不錯的選擇。這個圖案具有行導線和列導線相同的有趣特性,只是旋轉了90度。使用相同的導線圖案來驅動行和列,從而節省了制造時間和成本。
圖2
在使用中,觸摸很可能會跨越多行和具有不同的列耦合度。接收到的信號強度可用于估計觸摸的質心,從而獲得比行和列更精細的定位。然而,使用這些信息的另一種方法是為觸摸事件呈現一個邊界框,由最外層具有顯著耦合的行和列定義。
這導致了有趣的使用方式-單個用戶可以觸摸兩個點來定義邊界框。這是一種非常自然的選擇矩形區域的方式。
當耦合區域較小時,假設用戶表示一個點;當它跨越較大區域時,假設用戶試圖指定一個邊界框。最終結果是,即使是這種簡化的行/列設計,也可以實現一些單個用戶的多點觸控功能。
測試了一個小型的DiamondTouch原型的概念(如圖3所示)。該原型具有一個約20厘米x20厘米的活動區域,包含了80個排列的天線,分為40行和40列。天線陣列被設計成印刷在0.5毫米厚的雙面電路板上,具體取決于其旋轉方向。
圖3
為了將天線陣列夾在一起,電路板被夾在堆疊的行板和列板之間,中間有一個非常薄的絕緣體,使其與表面幾乎沒有區別,只是厚度不同。
天線陣列由發射器板(如圖4所示)驅動。實現了時分復用,即每個天線依次用10kHz方波驅動10個周期。盡管該板可以以60伏的振幅驅動天線,但5伏的電壓就足夠了。使用更高的電壓可以產生更好的信噪比,這在電磁噪聲環境中非常有用。
圖4
接收器通過屏蔽電纜連接到軟墊,軟墊被用作將用戶與接收器耦合的金屬椅。幾乎任何導電的椅子都可以用于這個應用,只要在乘員和接收器電纜之間有足夠的電容耦合。
如果使用非導電的椅子,可以使用導電的"緩沖墊"(一層金屬箔,也可以用于舒適填充)來將用戶與接收器耦合。
(圖5)顯示了其中一個原型接收器。為了獲得最大的抗噪性,接收器使用同步解調,因此需要適當的同步信號來自發射器板。接收器將結果數字化,并以原始形式通過快速RS-232串行傳輸到連接的計算機。
圖5
每個用戶都有一個獨立的接收器板。整個表每秒掃描75次,計算機接收每一行和每一列的每個用戶的耦合值。75Hz的更新速率和可忽略的延遲使得原型非常敏感。
當接收到足夠高的信號時,表面被認為被"觸摸"。理論上,可以使用一個簡單的閾值來確定這一點。然而,考慮到組件漂移、用戶變化和不同的噪聲水平,采用基于閾值的方法更為實用,即基于當前對最小耦合和噪聲水平的估計。
這種方法效果令人滿意,但更復雜的方法可能會產生更好的結果,特別是在靜電噪聲較大的情況下,如橡膠腳椅子拖過地毯時。
發送器和接收器板都使用了PIC微控制器和其他廉價的現成電子元件。最昂貴的部件是印刷在桌子上的電路板,這在批量生產的產品中會更便宜。
編寫的測試軟件,可以為每一行和每一列以及每一位生成耦合級別的條形圖,并用不同顏色表示每個用戶。計算得出的觸摸點以圖形方式顯示,小觸摸區域顯示為十字光標,較大的觸摸區域顯示為邊界框。
盡管普通物體不會對桌子產生影響,但可以設計特殊的物體來影響桌子,這對于使用有形和可抓取的應用程序來說非常有用,這些應用程序將這些特殊物體作為其用戶界面的一部分。
由于天線陣列和用戶之間存在絕緣層,用戶不需要具備任何特殊特性,因此可以使用多種材料來制造桌子,使其在不同的環境條件下都能保持堅固。
例如,玻璃或塑料可用于使桌子抗液體和化學品泄漏。原型是使用一種名為GML1000的玻璃纖維層壓板制造的,其熱性能關系允許在臨時操作桌子時(而且沒有損壞),表面被酒精覆蓋。
電阻式和電容式觸摸屏已經銷售幾十年了,但它們容易受到多次觸摸的干擾。對于對壓力敏感的應用而言,無法容忍任何殘留物在其上。
超聲波系統最近在創建電子白板方面變得流行。然而,它們需要主動筆筒,一般不支持多點觸控。此外,較大的碎片對象可能會造成陰影,降低性能。
Wacom Intuos圖形輸入板是一種支持多點觸控和識別所用工具的系統。它具有名為"雙軌"的功能,允許同時使用兩個工具(如手寫筆或鼠標)。不幸的是,Intuos更小更貴,且僅限于兩點觸控。
其他無法識別用戶的多點觸控系統包括FingerWorks FingerBoard和Tactex smart織物技術。盡管FingerBoard尚未發行,但似乎使用了二維數組電容傳感器來獲取放置在其上的物體的二維圖像。Tactex技術則通過感應壓力對材料光學特性的變化進行檢測。
已經設計了一些基于光學的輸入系統,用于跟蹤手或其他物體周圍的二維區域。全息墻使用相機和紅外線照明來尋找附近的物體,而Strickon和Paradiso使用掃描激光測距儀在自由空間中進行類似的工作。
這兩種系統都可以感知多個觸摸,但無法輕松區分不同的用戶。
近場電場(電容)傳感器已經用于簡單應用,例如觸摸開關,幾十年了。最近幾年,用戶界面社區引入了更精細制作的電容感應形式。這些系統試圖檢測手或其他物體與電極之間的距離,然后使用場強來確定位置。
DiamondTouch與其他技術的不同之處在于,它要求被感應的物體非常接近(毫米或更小)的電極,但使用大量的電極來感知位置。
DiamondTouch多用戶觸摸技術可以檢測多個點同時觸摸,并能識別哪個用戶觸摸了每個點。它的設計主要考慮了表面上留下的物體對其性能的影響,并且非常耐用。整個系統的制造成本也相對較低,而且沒有手寫筆容易丟失的問題。
參考文獻:Dietz, Paul H. and Darren Leigh. “DiamondTouch: a multi-user touch technology.” ACM Symposium on User Interface Software and Technology (2001).
對于Android自定義控件開發,多點觸控是一個必須要懂的知識點。因為在正常的情況下操作正常的控件,使用多指操作時,基本上都會出現問題。當需要對多指操作進行兼容時,就需要這方面的知識了。
本文選自《Android自定義控件高級進階與精彩實例》一書,帶你了解多點觸控的基本知識。
—— 正文 ——
假如,我們做了這么一個功能,圖像跟隨手指移動。
在單指操作下,圖像的移動非常流暢、正確,而如果我們使用兩根手指的話,就會出現下面這種情況。
從效果圖可以看出,在第2根手指放下,而第1根手指抬起時,圖像會出現跳躍,直接從第1根手指的位置移動到了第2根手指的位置,這明顯是不對的。這只是一個簡單的例子,一般使用單指操作的控件改到多指操作的時候,都會出現問題。
這便是本文講解多點觸控的初衷。既然多點觸控會造成這么多問題,那么下面就來詳細了解它吧。
▼
單點觸控與多點觸控
1
單點觸控
單點觸控與多點觸控是相對的,單點觸控的意思是,我們只考慮一根手指的情況,而且僅處理一根手指的觸摸事件,而多點觸控是處理多根手指的觸摸事件。
一般我們處理MotionEvent事件,通過MotionEvent.getAction來獲取事件類型,這就是單點觸控。在單點觸控中,會涉及對下面幾個消息的處理。
除了消息外,我們也經常用下面這幾個函數來獲取手指的位置等信息,這些函數都沒有參數,也都只有在單點觸控時才能使用。
對于這幾個函數的使用方法,這里就不再贅述了。可以看到,我們平常所處理的MotionEvent事件,以及常用的MotionEvent函數都只是針對單點觸控的,那么哪些才是多點觸控的事件和函數呢?
2
多點觸控
首先,多點觸控的消息類型只能通過getActionMasked來獲取。因此,判斷當前代碼處理的是單點觸控還是多點觸控,單從獲取消息類型的函數就可以看出。
說明:單點觸控是通過getAction來獲取當前事件類型的,而多點觸控是通過getActionMasked來獲取的。
多點觸控涉及的消息類型與單點觸控的不一樣,它的消息類型如下。
比如以下圖中的手指按下順序,我們來看看其中的事件觸發順序。
在效果圖中,先后有3根手指按下,按下順序是1、2、3,抬起順序是1、3、2,而事件觸發順序如下表。
這里需要注意,
第1根手指按下時,收到的消息是ACTION_DOWN;
隨后的手指再按下時,收到的是ACTION_POINTER_DOWN;
當有手指抬起時,收到的是ACTION_POINTER_UP;
當最后一根手指抬起時,收到的是ACTION_UP。
對多點觸控消息進行處理的代碼如下:
1String TAG = "qijian";
2@Override
3public boolean onTouchEvent(MotionEvent event) {
4 switch (event.getActionMasked()) {
5 case MotionEvent.ACTION_DOWN:
6 Log.e(TAG,"第1根手指按下");
7 break;
8 case MotionEvent.ACTION_UP:
9 Log.e(TAG,"最后一根手指抬起");
10 break;
11 case MotionEvent.ACTION_POINTER_DOWN:
12 Log.e(TAG,"又一根手指按下");
13 break;
14 case MotionEvent.ACTION_POINTER_UP:
15 Log.e(TAG,"又一根手指抬起");
16 break;
17 }
18 return true;
19}
20...
21 }
這里僅列出了手指按下和手指抬起所觸發的消息類型,而在手指移動時,無論是單點觸控還是多點觸控,所觸發的消息都是MotionEvent.ACTION_MOVE。
在多點觸控時,我們可以通過代碼來獲取當前移動的是哪根手指。
多點觸控
1
識別按下的手指
上面講解了在什么情況下會觸發什么消息,但我們怎么來識別當前按下的是哪根手指呢?
在MotionEvent中有一個Pointer的概念:
一個Pointer就代表一個觸摸點,每個Pointer都有自己的消息類型,也有自己的X坐標值。一個MotionEvent對象中可能會存儲多個Pointer的相關信息,每個Pointer都有自己的PointerIndex和PointerId。在多點觸控中,就是用PointerIndex和PointerId來標識用戶手指的。
通過下面這個例子,我們來了解一下PointerIndex與PointerId的區別。
可見同一根手指的id是不變的,而PointerIndex是會變化的,但總是以0、1或者0、1、2這樣的形式出現,而不可能出現0、2這樣間隔了一個數或者1、2這種沒有0索引值的形式。
針對PointerIndex與PointerId,在MotionEvent類中經常使用下面這幾個函數。
用于獲取當前活動手指的PointerIndex值。
用于根據PointerIndex值獲取手指的PointerId,其中pointerIndex表示手指的PointerIndex值。
用于獲取用戶按下的手指個數,一般我們用它來遍歷屏幕上的所有手指,遍歷手指的代碼如下:
1for (int i = 0; i < event.getPointerCount(); i++) {
2 int pointerId = event.getPointerId(i);
3}
前面講過,PointerIndex是從0開始的,表示當前所有手指的索引,值從0到getPointerCount() ? 1,不會出現不連續的數。因此,我們通過event.getPointerCount可以得到當前屏幕上的手指個數。然后從0開始遍歷PointerIndex,同時我們還能通過int pointerId = event.getPointerId(i)來得到每根手指PointerIndex所對應的PointerId。
用于根據PointerId反向找到手指的PointerIndex值。
由此,我們就知道了PointerIndex與PointerId的關系,以及它們相互之間的換算方法。下面再來看看通過PointerIndex和PointerId能得到什么。
2
獲取手指位置信息
通過PointerIndex與PointerId,可以使用以下函數獲得手指的位置信息。
根據PointerIndex得到對應手指的X坐標值,該函數的意義與單點觸控里的getX函數相同。
同樣地,根據PointerIndex得到對應手指的Y坐標值,該函數的意義與單點觸控里的getY函數相同。
實例:追蹤第2根手指
現在,我們將通過一個實例來學習上面講到的函數。
這里實現的效果是:當用戶按下第2根手指時,就開始追蹤這根手指,無論其他手指是否抬起,只要這根手指沒有抬起,就一直顯示這根手指的位置,如下如。
從效果圖可以看出,先后總共按下了3根手指,分別在左(第1根手指)、中(第2根手指)、右(第3根手指)。
抬起手指時,先抬起左側第1根手指,然后抬起右側第3根手指。可以看到,第2根手指的觸摸點,我們使用白色圓圈顯示,無論第3根手指是否按下,還是其他手指是否抬起,白色圓圈總是跟著第2根手指的移動來顯示。這就實現了跟蹤第2根手指軌跡的效果。
下面我們來看看這個效果是怎么實現的吧。
1
自定義View并初始化
布局很簡單,就是一個全屏View,為了在View上畫圓圈,我們必須自定義View,其中的初始化代碼如下:
1public class MultiTouchView extends View {
2 // 用于判斷第2根手指是否存在
3 private boolean haveSecondPoint = false;
4 // 記錄第2根手指的位置
5 private PointF point = new PointF(0, 0);
6 private Paint mDefaultPaint = new Paint();
7
8 public MultiTouchView(Context context) {
9 super(context);
10 init();
11 }
12
13 public MultiTouchView(Context context, @Nullable AttributeSet attrs) {
14 super(context, attrs);
15 init();
16 }
17
18 public MultiTouchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
19 super(context, attrs, defStyleAttr);
20 init();
21 }
22
23 private void init() {
24 mDefaultPaint.setColor(Color.WHITE);
25 mDefaultPaint.setAntiAlias(true);
26 mDefaultPaint.setTextAlign(Paint.Align.CENTER);
27 mDefaultPaint.setTextSize(30);
28 }
29}
這樣我們就自定義了一個View,很明顯它內部不會再包裹其他的View控件,所以繼承自View類即可。
我們定義了3個變量,其中:
2
onTouchEvent
然后,在用戶按下手指時,需要加以判斷,當前是第幾根手指,然后獲取第2根手指的位置,下面列出完整代碼:
1public boolean onTouchEvent(MotionEvent event) {
2 int index = event.getActionIndex();
3
4 switch (event.getActionMasked()) {
5 case MotionEvent.ACTION_POINTER_DOWN:
6 if (event.getPointerId(index) == 1) {
7 haveSecondPoint = true;
8 point.set(event.getX(), event.getY());
9 }
10 break;
11 case MotionEvent.ACTION_MOVE:
12 try {
13 if (haveSecondPoint) {
14 int pointerIndex = event.findPointerIndex(1);
15 point.set(event.getX(pointerIndex), event.getY(pointerIndex));
16 }
17 } catch (Exception e) {
18 haveSecondPoint = false;
19 }
20 break;
21 case MotionEvent.ACTION_POINTER_UP:
22 if (event.getPointerId(index) == 1) {
23 haveSecondPoint = false;
24 }
25 break;
26 case MotionEvent.ACTION_UP:
27 haveSecondPoint = false;
28 break;
29 }
30
31 invalidate();
32 return true;
33}
獲取當前活動手指的PointerIndex值:
1int index = event.getActionIndex();
我們知道,當第1根手指按下的時候觸發的是ACTION_DOWN消息,隨后的手指按下的時候觸發的都是ACTION_POINTER_DOWN消息。因為我們要跟蹤第2根手指,所以這里只需要識別ACTION_POINTER_DOWN消息即可:
1case MotionEvent.ACTION_POINTER_DOWN:
2 if (event.getPointerId(index) == 1) {
3 haveSecondPoint = true;
4 point.set(event.getX(), event.getY());
5 }
6 break;
我們也知道PointerIndex是變化的,而PointerId是不變的,PointerId根據手指按下的順序從0到1逐漸增加。因此,第2根手指的PointerId就是1。當(event.getPointerId(index) == 1時,就表示當前按下的是第2根手指,將haveSecondPoint設為true,并將得到的第2根手指的位置設置到point中。
到這里,大家可能會產生疑問,上面提到的多點觸控獲取手指位置都用的是event.getX(pointerIndex),而這里怎么直接用event.getX了呢?其實這里使用event.getX (pointerIndex)也是可以的,大家可以先記下這個問題,后面我們再詳細講解。
當手指移動時,會觸發ACTION_MOVE消息:
1case MotionEvent.ACTION_MOVE:
2 try {
3 if (haveSecondPoint) {
4 int pointerIndex = event.findPointerIndex(1);
5 point.set(event.getX(pointerIndex), event.getY(pointerIndex));
6 }
7 } catch (Exception e) {
8 haveSecondPoint = false;
9 }
10 break;
需要注意,因為這里使用event.findPointerIndex(1)來強制獲取PointerId為1的手指PointerIndex,在異常情況下可能出現越界,所以使用try…catch…來進行保護。
在這里,我們使用event.getX(pointerIndex)來獲取指定手指的位置信息。同樣地,這個問題也放在后面講解。
當手指抬起時,會觸發ACTION_POINTER_UP消息:
1case MotionEvent.ACTION_POINTER_UP:
2 if (event.getPointerId(index) == 1) {
3 haveSecondPoint = false;
4 }
5 break;
同樣地,使用event.getPointerId(index)來獲取當前抬起手指的PointerId,如果是1,那就說明是第2根手指抬起了,這時就把haveSecondPoint設為false。
當全部手指抬起時,會觸發ACTION_UP消息:
1case MotionEvent.ACTION_UP:
2 haveSecondPoint = false;
3 break;
在最后一根手指抬起時,把haveSecondPoint設為false,白色圓圈從屏幕上消失。
最后,調用invalidate();來重繪界面。
3
onDraw
在重繪界面時,主要是在point中存儲的第2根手指的位置處畫一個白色圓圈:
1protected void onDraw(Canvas canvas) {
2
3 canvas.drawColor(Color.GREEN);
4 if (haveSecondPoint) {
5 canvas.drawCircle(point.x, point.y, 50, mDefaultPaint);
6 }
7
8 canvas.save();
9 canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
10 canvas.drawText("追蹤第2個按下手指的位置", 0, 0, mDefaultPaint);
11 canvas.restore();
12}
首先,為整個屏幕繪一層綠色,把上一屏的內容清掉:
1canvas.drawColor(Color.GREEN);
然后,如果第2根手指按下了,則在它的位置處畫一個圓圈:
1if (haveSecondPoint) {
2 canvas.drawCircle(point.x, point.y, 50, mDefaultPaint);
3}
最后,在布局的中間位置寫上提示文字:
1canvas.save();
2canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
3canvas.drawText("追蹤第2個按下手指的位置", 0, 0, mDefaultPaint);
4canvas.restore();
有關Canvas的操作及寫字的操作,在《Android自定義控件開發入門與實戰》一書中有詳細章節講述,這里就不再贅述了。
在寫好控件以后,直接利用XML引入布局即可,這里不再展示,效果就是我們想要的樣子。
關于作者
啟艦
本名張恩偉,Android研發專家、CSDN博客專家、CSDN博客之星,《Android自定義控件入門與實戰》《Android自定義控件高級進階與精彩實例》作者,電子工業出版社博文視點優秀作者,曾就職于阿里巴巴,現就職于vivo。
圖書推薦
▊《Android自定義控件高級進階與精彩實例》
啟艦 著
本書主要內容有3D特效的實現、高級矩陣知識、消息處理機制、派生類型的選擇方法、多點觸控及輔助類、RecyclerView的使用方法及3D卡片的實現、動畫框架Lottie的講解與實戰等。
讀者可以通過本書從宏觀層面、源碼層面對Android自定義控件建立完整的認識。