android - 模仿微博的@人功能

android - 模仿微博的@人功能_第1张图片

(请忽略内容 - -)


分析

  1. 当我们在点击微博里 @张三 的时候,我们会跳转到 张三 的主页上面去;
  2. 在内容中还要有表情。

分析一下我们需要什么

  1. 一个带有clickablespan的自定义textView;
  2. 两个正则表达式分别来匹配”@xxx”和表情;
  3. “@xxx”还要会变色,跟正文不一样。

    看上去也不是很复杂嘛。ok,开工。

代码模块

首先是textView

public class SpanTextView extends TextView {
    private SpannableString spannableString;
    private Context context;
    private final String AiteRule="@[^,,::\\s@]+"; // @人的正则表达式
    private String MatchRule ="";
    private String str="";
    private MovementMethod mMovement;

    public SpanTextView(Context context) {
        super(context);
        this.context = context;
    }

    public SpanTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    public SpanTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
    }

    @Override
    public boolean hasFocusable(){
        return false;
    }

    /**
     * @param text
     * @return
     */
    public void setText(String text) {
        if(getMatchRule()==null) {
            /* 默认一个@的匹配规则 */
            setMatchRule(AiteRule);
        }
        super.setText(setHighlight(text, getMatchRule()));
    }

    /**
     * '@XXX
     * @param text 文字
     * @param rule 匹配规则
     * @return
     */
    private SpannableString setHighlight(String text,String rule) {
        spannableString = new SpannableString(text);
        Pattern pattern = Pattern.compile(rule);
        Matcher matcher = pattern.matcher(text);
        int length = text.length();
        int end = 0;
        for (int i = 0; i < length; i++) {
            if (matcher.find()) {
                int start = matcher.start();
                end = matcher.end();
                str = text.substring(start + 1, end);
                spannableString.setSpan(new ClickSpan(context, str), start, end, Spanned.SPAN_MARK_POINT);
            }
        }
        return spannableString;
    }


   public String getMatchRule() {
        return MatchRule;
    }
    public void setMatchRule(String matchRule){n

        this.MatchRule = matchRule;
    }}

接下来是clickableSpan

public class ClickSpan extends ClickableSpan{
    private Context context;
    private String memberName="";

    public ClickSpan(Context context, String memberName){
        this.context = context;
        this.memberName = memberName;
    }

    @Override
    public void onClick(View view) {
            /** 跳转 **/
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        /** 给文字染色 **/
        ds.setARGB(255,96,134,174);
        /** 去掉下划线 , 默认自带下划线 **/
        ds.setUnderlineText(false);
    }

那这样可以实现了。。。
。。
。。
。。
。。
。。
。。
。。
。。
。。
。。

慢着。。。。。。。。。怎么会这样。。
SpanTextView 如果添加了 OnClickListener , 那么久会出现灰常坑爹事情:
在android 4.4 或以上版本,clickableSpan的onClick和SpanTextView的onClick是可以分别响应的,即点击了添加了clickableSpan的那部分文字时,跳转的是clickableSpan的onClick事件,点击没有被添加到clickableSpan的时候,跳转的是SpanTextView的onClick事件。
但,在4.4以下。。。这两者是响应先后明显就不一样的。。。变成了先响应SpanTextView的onClick事件,再去响应clickableSpan的onClick事件。。
这明显不是我们想要的。。。。

还好。。在国外网站的stackoverflow里面找到了答案
http://stackoverflow.com/questions/16792963/android-clickablespan-intercepts-the-click-event
有兴趣的去看一下。。
当然,还是要贴一下解决了上述问题的SpanTextView

public class SpanTextView extends TextView {
    private SpannableString spannableString;
    private Context context;
    private final String AiteRule="@[^,,::\\s@]+";
    private String MatchRule ="";
    private String str="";
    private MovementMethod mMovement;

    public SpanTextView(Context context) {
        super(context);
        this.context = context;
    }

    public SpanTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    public SpanTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
    }

    @Override
    public boolean hasFocusable(){
        return false;
    }

    /**
     * @param text
     * @return
     */
    public void setText(String text) {
        if(getMatchRule()==null) {
            /* 默认一个@的匹配规则 */
            setMatchRule(AiteRule);
        }
        super.setText(setHighlight(text, getMatchRule()));
    }

    /**
     * '@XXX
     * @param text 文字
     * @param rule 匹配规则
     * @return
     */
    private SpannableString setHighlight(String text,String rule) {
        spannableString = new SpannableString(text);
        Pattern pattern = Pattern.compile(rule);
        Matcher matcher = pattern.matcher(text);
        int length = text.length();
        int end = 0;
        for (int i = 0; i < length; i++) {
            if (matcher.find()) {
                int start = matcher.start();
                end = matcher.end();
                str = text.substring(start + 1, end);
                spannableString.setSpan(new ClickSpan(context, str), start, end, Spanned.SPAN_MARK_POINT);
            }
           }
        return spannableString;
    }


    /** 这个就是解决之道  **/
    @Override
    public boolean onTouchEvent(MotionEvent event){
        boolean handled = false;
        mMovement = getMovementMethod();
        if (mMovement != null) {
            handled |= mMovement.onTouchEvent(this, (Spannable) getText(), event);
        }
        ClickableSpan[] links = ((Spannable) getText()).getSpans(getSelectionStart(),
                getSelectionEnd(), ClickableSpan.class);
        if (links.length > 0) {
            handled = true;
        }if(handled){
            return true;
        }else {
            final boolean superResult = super.onTouchEvent(event);
            return superResult;
        }
    }

    public String getMatchRule() {
        return MatchRule;
    }
    public void setMatchRule(String matchRule){
        this.MatchRule = matchRule;
    }
}

还有为了解决这个问题,还是需要我们去重写一下LinkMovemenMethod

public class mLinkMovementMethod extends LinkMovementMethod {
    public static MovementMethod getInstance(Context context){
        if(sInstance==null){
            sInstance = new mLinkMovementMethod(context);
        }
        return sInstance;
    }
    public mLinkMovementMethod(Context context){
        this.context = context;
    }
    private Context context;
    private static mLinkMovementMethod sInstance;
    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);
                    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);
                    return true;
                }
                break;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(final TextView widget, Spannable buffer,
                                MotionEvent event) {
        int action = event.getAction();
        /* 只需要检测按下 */
        if (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);

            /* 检测Span */
            if (link.length != 0) {
               link[0].onClick(widget);
                Selection.setSelection(buffer,
                        buffer.getSpanStart(link[0]),
                        buffer.getSpanEnd(link[0]));
            } else {
                Selection.removeSelection(buffer);
                // Your call to your layout's onTouchEvent or it's
                //onClick listener depending on your needs
                context.startActivity(new Intent(context,DetailsActivity.class));
            }
        }
        return true;
    }

    @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);
        }
    }
    private static Object FROM_BELOW = new NoCopySpan.Concrete();

clickablespan 无需改变,所以就参照上面那个就可以了

最后的最后写一下调用方法

tv.setText("@aaa");
tv.setMovementMethod(mLinkMovementMethod.getInstance(context));

你可能感兴趣的:(android开发日志)