GeorgeYang'Blog

my technology blog


常用的使用android:autoLink="all" 属性会将文字中包含网址、电话、email的会自动加入连接地址。

也可以使用html字符串:

 <string name="link_text_manual"><b>text2:</b> This is some other 
       text, with a <a href="http://www.google.com">link</a> specified 
       via an &lt;a&gt; tag.  Use a \"tel:\" URL 
       to <a href="tel:8888888">dial a phone number</a>. 
     </string>

 代码中添加:
     TextView t2 = (TextView) findViewById(R.id.text2); 
 t2.setMovementMethod(LinkMovementMethod.getInstance());

设置指定样式:

 SpannableString ss = new SpannableString("text4: Click here to dial the phone.");

 //0-6位置文字应用粗体
 ss.setSpan(new StyleSpan(Typeface.BOLD), 0, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 
 ss.setSpan(new URLSpan("tel:4155551212"), 13, 17, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

 TextView t4 = (TextView) findViewById(R.id.text4); 
 t4.setText(ss); 
 t4.setMovementMethod(LinkMovementMethod.getInstance());

设置指定文字点击事件:

 TextView useInfo = (TextView) findViewById(R.id.info);
         String url_0_text = "用户协议及隐私条款";
         useInfo.setText("开始即表示您同意遵守");

         SpannableString spStr = new SpannableString(url_0_text);

         spStr.setSpan(new ClickableSpan() {
             @Override
             public void updateDrawState(TextPaint ds) {
                 super.updateDrawState(ds);
                 ds.setColor(Color.WHITE);       //设置文件颜色
                 ds.setUnderlineText(true);      //设置下划线
             }

             @Override
             public void onClick(View widget) {
                 Log.d("", "onTextClick........");
             }
         }, 0, url_0_text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

         useInfo.setHighlightColor(Color.TRANSPARENT); //设置点击后的颜色为透明,否则会一直出现高亮
         useInfo.append(spStr);
         useInfo.setMovementMethod(LinkMovementMethod.getInstance());//开始响应点击事件

以上这种方法不是很通用,在此,我写了一个比较通用的方法,用于构建可以点击文字,并对应点击事件,代码如下:

 /**
  * Created by george.yang on 16/3/5.
  */
 public class LinkSpannableBuilder {
     public interface OnTextClickListener {
         void onClickableTextClicked(View view, int index);
     }


     public static SpannableStringBuilder buildLinkText (String format, String[] params, final OnTextClickListener listener) {
         int[] startIndexs = new int[params.length];
         int currStartIndex = 0;
         for (int i=0;i<params.length;i++) {
             int startIndex;
             if (i==0) {
                 startIndex = format.indexOf("%s");
             } else {
                 startIndex = format.indexOf("%s",currStartIndex);
             }

             PLog.i("i:" + i + " startIndex:" + startIndex);

             currStartIndex=startIndex+2;
             startIndexs[i] = startIndex;
         }


         Object[] temp = params;
         final SpannableStringBuilder ssb = new SpannableStringBuilder(String.format(format,temp));

 //        //非文字部分可以点击
 //        ssb.setSpan(new ClickableSpan() {
 //            @Override
 //            public void onClick(View view) {
 //                view.postDelayed(new Runnable() {
 //                    @Override
 //                    public void run() {
 ////                        Selection.removeSelection(ssb);
 //                        PLog.i("removeSelection:");
 //                    }
 //                },100);
 //                PLog.i("onClick2:");
 //                if (listener != null) {
 //                    listener.onNormalTextClicked(view, 0);
 //                }
 //            }
 //            @Override
 //            public void updateDrawState(TextPaint ds) {
 //                super.updateDrawState(ds);
 ////                ds.setColor(Color.BLACK); // 设置文本颜色
 //                // 去掉下划线
 //                ds.setUnderlineText(false);
 //            }
 //        },0,ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

         currStartIndex = 0;
         for (int i=0;i<params.length;i++) {
             final int index = i;
             if (i==0) {
                 currStartIndex = startIndexs[i];
             } else {
                 currStartIndex = currStartIndex + (startIndexs[i] - startIndexs[i-1] - 2) +  params[i-1].length();
             }
             //指定文字部分可以点击
             ssb.setSpan(new ClickableSpan() {
                 @Override
                 public void onClick(View view) {
                     PLog.i("onClick:" + index);
                     if (listener != null) {
                         listener.onClickableTextClicked(view, index);
                     }
                 }
                 @Override
                 public void updateDrawState(TextPaint ds) {
                     super.updateDrawState(ds);
                     PLog.d("updateDrawState:" + index);
                     ds.setColor(Color.RED); // 设置文本颜色
                     // 去掉下划线
                     ds.setUnderlineText(false);
                 }
             },currStartIndex,currStartIndex + params[i].length(), 0);
         }

         return ssb;
     }

 }

用法:

 textview.settext(LinkSpannableBuilder.buildLinkText("%s,link to:%s",new String[]{"xiaoming","baidu.com"},listener));

博客出处

关于textview设置ClickableSpan后,如果焦点一直停留在textview,加载fragment时会出现不会自动消失问题,设置文字焦点变色的类是LinkMovementMethod,此时需要添加一个类似LinkMovementMethod的类,直接新建类拷贝其代码,添加一个方法:

 public static void autoRemove (TextView textView, final Spannable spannable) {
 //300毫秒后自动失去焦点
         textView.postDelayed(new Runnable() {
             @Override
             public void run() {
                 Selection.removeSelection(spannable);
             }
         },300);
     }

最终代码如下:

 /**
  * Created by george.yang on 16/3/5.
  */
 public class LinkMovementNoSelectionMethod extends ScrollingMovementMethod {
     private static final int CLICK = 1;
     private static final int UP = 2;
     private static final int DOWN = 3;

     @Override
     public boolean canSelectArbitrarily() {
         return true;
     }

     @Override
     protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
                                         int movementMetaState, KeyEvent event) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_CENTER:
             case KeyEvent.KEYCODE_ENTER:
                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
                     if (event.getAction() == KeyEvent.ACTION_DOWN &&
                             event.getRepeatCount() == 0 && action(CLICK, widget, buffer)) {
                         return true;
                     }
                 }
                 break;
         }
         return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
     }

     @Override
     protected boolean up(TextView widget, Spannable buffer) {
         if (action(UP, widget, buffer)) {
             return true;
         }

         return super.up(widget, buffer);
     }

     @Override
     protected boolean down(TextView widget, Spannable buffer) {
         if (action(DOWN, widget, buffer)) {
             return true;
         }

         return super.down(widget, buffer);
     }

     @Override
     protected boolean left(TextView widget, Spannable buffer) {
         if (action(UP, widget, buffer)) {
             return true;
         }

         return super.left(widget, buffer);
     }

     @Override
     protected boolean right(TextView widget, Spannable buffer) {
         if (action(DOWN, widget, buffer)) {
             return true;
         }

         return super.right(widget, buffer);
     }

     private boolean action(int what, TextView widget, Spannable buffer) {
         Layout layout = widget.getLayout();

         int padding = widget.getTotalPaddingTop() +
                 widget.getTotalPaddingBottom();
         int areatop = widget.getScrollY();
         int areabot = areatop + widget.getHeight() - padding;

         int linetop = layout.getLineForVertical(areatop);
         int linebot = layout.getLineForVertical(areabot);

         int first = layout.getLineStart(linetop);
         int last = layout.getLineEnd(linebot);

         ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);

         int a = Selection.getSelectionStart(buffer);
         int b = Selection.getSelectionEnd(buffer);

         int selStart = Math.min(a, b);
         int selEnd = Math.max(a, b);

         if (selStart < 0) {
             if (buffer.getSpanStart(FROM_BELOW) >= 0) {
                 selStart = selEnd = buffer.length();
             }
         }

         if (selStart > last)
             selStart = selEnd = Integer.MAX_VALUE;
         if (selEnd < first)
             selStart = selEnd = -1;

         switch (what) {
             case CLICK:
                 if (selStart == selEnd) {
                     return false;
                 }

                 ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class);

                 if (link.length != 1)
                     return false;

                 link[0].onClick(widget);
                 break;

             case UP:
                 int beststart, bestend;

                 beststart = -1;
                 bestend = -1;

                 for (int i = 0; i < candidates.length; i++) {
                     int end = buffer.getSpanEnd(candidates[i]);

                     if (end < selEnd || selStart == selEnd) {
                         if (end > bestend) {
                             beststart = buffer.getSpanStart(candidates[i]);
                             bestend = end;
                         }
                     }
                 }

                 if (beststart >= 0) {
                     Selection.setSelection(buffer, bestend, beststart);
                     autoRemove(widget,buffer);
                     return true;
                 }

                 break;

             case DOWN:
                 beststart = Integer.MAX_VALUE;
                 bestend = Integer.MAX_VALUE;

                 for (int i = 0; i < candidates.length; i++) {
                     int start = buffer.getSpanStart(candidates[i]);

                     if (start > selStart || selStart == selEnd) {
                         if (start < beststart) {
                             beststart = start;
                             bestend = buffer.getSpanEnd(candidates[i]);
                         }
                     }
                 }

                 if (bestend < Integer.MAX_VALUE) {
                     Selection.setSelection(buffer, beststart, bestend);
                     autoRemove(widget,buffer);
                     return true;
                 }

                 break;
         }

         return false;
     }

     @Override
     public boolean onTouchEvent(TextView widget, Spannable buffer,
                                 MotionEvent event) {
         int action = event.getAction();

         if (action == MotionEvent.ACTION_UP ||
                 action == MotionEvent.ACTION_DOWN) {
             int x = (int) event.getX();
             int y = (int) event.getY();

             x -= widget.getTotalPaddingLeft();
             y -= widget.getTotalPaddingTop();

             x += widget.getScrollX();
             y += widget.getScrollY();

             Layout layout = widget.getLayout();
             int line = layout.getLineForVertical(y);
             int off = layout.getOffsetForHorizontal(line, x);

             ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

             if (link.length != 0) {
                 if (action == MotionEvent.ACTION_UP) {
                     link[0].onClick(widget);
                 } else if (action == MotionEvent.ACTION_DOWN) {
                     Selection.setSelection(buffer,
                             buffer.getSpanStart(link[0]),
                             buffer.getSpanEnd(link[0]));
                     autoRemove(widget,buffer);
                 }

                 return true;
             } else {
                 Selection.removeSelection(buffer);
             }
         }

         return super.onTouchEvent(widget, buffer, event);
     }

     @Override
     public void initialize(TextView widget, Spannable text) {
         Selection.removeSelection(text);
         text.removeSpan(FROM_BELOW);
     }

     @Override
     public void onTakeFocus(TextView view, Spannable text, int dir) {
         Selection.removeSelection(text);

         if ((dir & View.FOCUS_BACKWARD) != 0) {
             text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
         } else {
             text.removeSpan(FROM_BELOW);
         }
     }

     public static MovementMethod getInstance() {
         if (sInstance == null)
             sInstance = new LinkMovementNoSelectionMethod();

         return sInstance;
     }

     private static LinkMovementNoSelectionMethod sInstance;
     private static Object FROM_BELOW = new NoCopySpan.Concrete();


     public static void autoRemove (TextView textView, final Spannable spannable) {
         textView.postDelayed(new Runnable() {
             @Override
             public void run() {
                 Selection.removeSelection(spannable);
             }
         },300);
     }
 }