這次的博客主要實現這樣一個功能,當點擊文檔中的某個點時,調整光標Caret的位置.
那麼到底如何實現這樣的功能呢?剛開始的時候我們肯定先考慮比較直接一點的方法.先使用一個controller類來接收鼠標點擊事件.在相應的處理方法中可以拿到鼠標點擊的座標.然後遍歷所有的page,row,basicGlyph來獲得鼠標點擊的具體圖元,然後再去做具體的處理.
但是我寫了一會之後,馬上就否決了這個方案,因爲這個方案需要在同一個事件處理函數處理的代碼實在太多了,比如我們需要給caret設置frame.最直接的方法就是挨個遍歷Glyph比較點擊點和Glyph的frame,還要根據是偏左偏右來決定caret是放在左邊還是右邊.
這是點擊到了Glyph的情況,如果點擊到了某一行的空白區域,就要把caret設置到最右邊.如果點擊了空行,則更加複雜,因爲僅僅設置frame是不可以的,還是更改caret的文檔索引,以便於用戶在caret所在的位置插入文字.
最最關鍵的是如果後期我們要對鼠標的點擊事件做進一步擴充的話,那麼這個函數就會無休止的變得臃腫,因此肯定要進行封裝.把和每個圖元相關的方法封裝到圖元自己的身上去.
具體的設計是怎樣的呢?首先我們給GlyphImpl類增加的一個函數public Boolean hitRect(x,y),傳入一個座標值,返回該座標點是否落在本對象內.
具體函數實現代碼如下:
public boolean hitRect(int x, int y) {
Frame frame = getFrame();
if (x > (frame.getX() + frame.getWidth()) || x < frame.getX())
return false;
return !(y > (frame.getY() + frame.getHeight()) || y < frame.getY());
}
整個文檔結構是分層級的,每個層級的對象都應該有機會處理到點擊事件,並且上層決定事件時候分發給下層,下層接受到事件之後,還要告訴上層是否已經處理完了,如果沒有處理完應該由上層繼續協助處理.
寫到這裏大家應該就非常清楚了,這樣的設計就是借鑑了Android系統的事件處理機制.
我們給Glyph添加如下的的幾個方法:
@Override
public boolean dispatchClickEvent(MouseEvent e) {
if (interceptClickEvent(e))
return true;
return onClickEvent(e);
}
@Override
public boolean onClickEvent(MouseEvent e) {
return false;
}
@Override
public boolean interceptClickEvent(MouseEvent e) {
return false;
}
這是GlyphImpl的默認實現,主要針對的是基本圖元.從dispatchClickEvent()可以看出,首先詢問了本類的interceptClickEvent函數是否截斷該事件的傳遞,如果不截斷,就發送給本類的onclickEvent()函數進行處理.
下面是GlyphImplGroup的默認實現,主要區別在於dispatchClickEvent()方法.
@Override
public boolean dispatchClickEvent(MouseEvent e) {
if (interceptClickEvent(e))
return true;
List<GlyphImpl> children = getChildren();
Glyph toBeHandledGlyph = null;
for (GlyphImpl glyph : children) {
if (glyph.hitRect(e.getX(), e.getY())) {
toBeHandledGlyph = glyph;
break;
}
}
//toBeHandledGlyph可能爲空
if (toBeHandledGlyph == null)
//進入這裏就表示,點到空白位置了,這裏需要區別對待,如果是row就要把caret放到row的最右邊,如果是page就要把caret放到最後一行.
//應該直接分發給本類對象的onClickEvent()方法
return onClickEvent(e);
if (!toBeHandledGlyph.dispatchClickEvent(e))
return onClickEvent(e);
return true;
}
從函數的具體實現可以看出,首先詢問本類的interceptEvent()方法是否截斷,如果不截斷,就遍歷子圖元調用hitrect()方法找到具體擊中的子圖元.如果沒有找到子圖元就發送給本類的onclickEvent()方法.如果找到了就根據子圖元的dispatch方法的返回值決定是否還要繼續分發給本類onclickEvent();
這樣就可以實現上層的對象通過intercept()函數進行控制是否發送事件給下層,下層通過onclickEvent()決定事件是否要拋給上層.
經過這樣的設計,就使得複雜的點擊事件處理變得簡單了,當需要擴充時,可以根據具體的業務邏輯,找到對應的對象並在其onclickEvent()中進行處理了.