1+ package cn .jiguang .imui .messages ;
2+
3+ import android .text .Layout ;
4+ import android .text .NoCopySpan ;
5+ import android .text .Selection ;
6+ import android .text .Spannable ;
7+ import android .text .method .MovementMethod ;
8+ import android .text .method .ScrollingMovementMethod ;
9+ import android .text .style .ClickableSpan ;
10+ import android .view .KeyEvent ;
11+ import android .view .MotionEvent ;
12+ import android .view .View ;
13+ import android .widget .TextView ;
14+
15+ /**
16+ * Created by dowin on 2017/10/9.
17+ */
18+
19+ public class ClickLinkMovementMethod extends ScrollingMovementMethod {
20+ private static final int CLICK = 1 ;
21+ private static final int UP = 2 ;
22+ private static final int DOWN = 3 ;
23+
24+ @ Override
25+ public boolean canSelectArbitrarily () {
26+ return true ;
27+ }
28+
29+ @ Override
30+ protected boolean handleMovementKey (TextView widget , Spannable buffer , int keyCode ,
31+ int movementMetaState , KeyEvent event ) {
32+ switch (keyCode ) {
33+ case KeyEvent .KEYCODE_DPAD_CENTER :
34+ case KeyEvent .KEYCODE_ENTER :
35+ if (KeyEvent .metaStateHasNoModifiers (movementMetaState )) {
36+ if (event .getAction () == KeyEvent .ACTION_DOWN &&
37+ event .getRepeatCount () == 0 && action (CLICK , widget , buffer )) {
38+ return true ;
39+ }
40+ }
41+ break ;
42+ }
43+ return super .handleMovementKey (widget , buffer , keyCode , movementMetaState , event );
44+ }
45+
46+ @ Override
47+ protected boolean up (TextView widget , Spannable buffer ) {
48+ if (action (UP , widget , buffer )) {
49+ return true ;
50+ }
51+
52+ return super .up (widget , buffer );
53+ }
54+
55+ @ Override
56+ protected boolean down (TextView widget , Spannable buffer ) {
57+ if (action (DOWN , widget , buffer )) {
58+ return true ;
59+ }
60+
61+ return super .down (widget , buffer );
62+ }
63+
64+ @ Override
65+ protected boolean left (TextView widget , Spannable buffer ) {
66+ if (action (UP , widget , buffer )) {
67+ return true ;
68+ }
69+
70+ return super .left (widget , buffer );
71+ }
72+
73+ @ Override
74+ protected boolean right (TextView widget , Spannable buffer ) {
75+ if (action (DOWN , widget , buffer )) {
76+ return true ;
77+ }
78+
79+ return super .right (widget , buffer );
80+ }
81+
82+ private boolean action (int what , TextView widget , Spannable buffer ) {
83+ Layout layout = widget .getLayout ();
84+
85+ int padding = widget .getTotalPaddingTop () +
86+ widget .getTotalPaddingBottom ();
87+ int areatop = widget .getScrollY ();
88+ int areabot = areatop + widget .getHeight () - padding ;
89+
90+ int linetop = layout .getLineForVertical (areatop );
91+ int linebot = layout .getLineForVertical (areabot );
92+
93+ int first = layout .getLineStart (linetop );
94+ int last = layout .getLineEnd (linebot );
95+
96+ ClickableSpan [] candidates = buffer .getSpans (first , last , ClickableSpan .class );
97+
98+ int a = Selection .getSelectionStart (buffer );
99+ int b = Selection .getSelectionEnd (buffer );
100+
101+ int selStart = Math .min (a , b );
102+ int selEnd = Math .max (a , b );
103+
104+ if (selStart < 0 ) {
105+ if (buffer .getSpanStart (FROM_BELOW ) >= 0 ) {
106+ selStart = selEnd = buffer .length ();
107+ }
108+ }
109+
110+ if (selStart > last )
111+ selStart = selEnd = Integer .MAX_VALUE ;
112+ if (selEnd < first )
113+ selStart = selEnd = -1 ;
114+
115+ switch (what ) {
116+ case CLICK :
117+ if (selStart == selEnd ) {
118+ return false ;
119+ }
120+
121+ ClickableSpan [] link = buffer .getSpans (selStart , selEnd , ClickableSpan .class );
122+
123+ if (link .length != 1 )
124+ return false ;
125+
126+ link [0 ].onClick (widget );
127+ break ;
128+
129+ case UP :
130+ int beststart , bestend ;
131+
132+ beststart = -1 ;
133+ bestend = -1 ;
134+
135+ for (int i = 0 ; i < candidates .length ; i ++) {
136+ int end = buffer .getSpanEnd (candidates [i ]);
137+
138+ if (end < selEnd || selStart == selEnd ) {
139+ if (end > bestend ) {
140+ beststart = buffer .getSpanStart (candidates [i ]);
141+ bestend = end ;
142+ }
143+ }
144+ }
145+
146+ if (beststart >= 0 ) {
147+ Selection .setSelection (buffer , bestend , beststart );
148+ return true ;
149+ }
150+
151+ break ;
152+
153+ case DOWN :
154+ beststart = Integer .MAX_VALUE ;
155+ bestend = Integer .MAX_VALUE ;
156+
157+ for (int i = 0 ; i < candidates .length ; i ++) {
158+ int start = buffer .getSpanStart (candidates [i ]);
159+
160+ if (start > selStart || selStart == selEnd ) {
161+ if (start < beststart ) {
162+ beststart = start ;
163+ bestend = buffer .getSpanEnd (candidates [i ]);
164+ }
165+ }
166+ }
167+
168+ if (bestend < Integer .MAX_VALUE ) {
169+ Selection .setSelection (buffer , beststart , bestend );
170+ return true ;
171+ }
172+
173+ break ;
174+ }
175+
176+ return false ;
177+ }
178+
179+ @ Override
180+ public boolean onTouchEvent (TextView widget , Spannable buffer ,
181+ MotionEvent event ) {
182+ int action = event .getAction ();
183+
184+ if (action == MotionEvent .ACTION_UP ||
185+ action == MotionEvent .ACTION_DOWN ) {
186+ int x = (int ) event .getX ();
187+ int y = (int ) event .getY ();
188+
189+ x -= widget .getTotalPaddingLeft ();
190+ y -= widget .getTotalPaddingTop ();
191+
192+ x += widget .getScrollX ();
193+ y += widget .getScrollY ();
194+
195+ Layout layout = widget .getLayout ();
196+ int line = layout .getLineForVertical (y );
197+ int off = layout .getOffsetForHorizontal (line , x );
198+
199+ ClickableSpan [] link = buffer .getSpans (off , off , ClickableSpan .class );
200+
201+ if (link .length != 0 ) {
202+ if (action == MotionEvent .ACTION_UP ) {
203+ // Log.w("MoonUtil", System.currentTimeMillis() + "-" + lastClickTime);
204+ if (System .currentTimeMillis () - lastClickTime < CLICK_DELAY ) {
205+ link [0 ].onClick (widget );
206+ }
207+ } else if (action == MotionEvent .ACTION_DOWN ) {
208+ Selection .setSelection (buffer ,
209+ buffer .getSpanStart (link [0 ]),
210+ buffer .getSpanEnd (link [0 ]));
211+ lastClickTime = System .currentTimeMillis ();
212+ }
213+
214+ return true ;
215+ } else {
216+ Selection .removeSelection (buffer );
217+ }
218+ }
219+
220+ return super .onTouchEvent (widget , buffer , event );
221+ }
222+
223+ @ Override
224+ public void initialize (TextView widget , Spannable text ) {
225+ Selection .removeSelection (text );
226+ text .removeSpan (FROM_BELOW );
227+ }
228+
229+ @ Override
230+ public void onTakeFocus (TextView view , Spannable text , int dir ) {
231+ Selection .removeSelection (text );
232+
233+ if ((dir & View .FOCUS_BACKWARD ) != 0 ) {
234+ text .setSpan (FROM_BELOW , 0 , 0 , Spannable .SPAN_POINT_POINT );
235+ } else {
236+ text .removeSpan (FROM_BELOW );
237+ }
238+ }
239+
240+ public static MovementMethod getInstance () {
241+ if (sInstance == null )
242+ sInstance = new ClickLinkMovementMethod ();
243+
244+ return sInstance ;
245+ }
246+
247+ private static MovementMethod sInstance ;
248+ private static Object FROM_BELOW = new NoCopySpan .Concrete ();
249+ private static final long CLICK_DELAY = 500l ;
250+ private long lastClickTime ;
251+ }
0 commit comments