zoukankan      html  css  js  c++  java
  • 自定义可点击的ImageSpan并在TextView中内置“View“

    有的时候可能想在TextView中添加一些图片,比如下图,发短信输入联系人时,要把联系人号码换成一个图片,但这个图片无法用固定的某张图,而是根据内容进行定制的,这更像一个view。

    image

    当然,如果你不是view而是固定的图片,比如发信息时用表情图片替代特殊符号,那么实现起来会更加简单。又或许,你希望这个图片是可点击的。这里,笔者要介绍的就是怎么用一个自定义的ImageSpan来实现在文本里插入可点击的图片或View。

    在此之前,如果你还不了解SpannableString.setSpan(),不了解LinkMovementMethod是什么,建议先看下笔者的解析TextView中的URL等指定特殊字符串与点击事件

    首先,因为ImageSpan没有继承ClickableSpan,因此没有 onClick()方法。所以我写了个ClickableImageSpan 。

    public abstract class ClickableImageSpan extends ImageSpan {
        public ClickableImageSpan(Drawable b) {
            super(b);
        }
    
        public abstract void onClick(View view);
    }

    同时,我们发现google提供的LinkMovementMethod只会执行ClickableSpan的onClick()方法.下面是LinkMovementMethod的onTouchEvent()的源码。这个方法是在我们点击Spanned的时候响应。

    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]));
                    }
    
                    return true;
                } else {
                    Selection.removeSelection(buffer);
                }
            }
    
            return super.onTouchEvent(widget, buffer, event);
        }

    发现这个方法其实就是通过坐标找到相应的Span。然后,当link数组不为空时,将会得到span并执行他的onClick()方法。这里我们注意到了这一句代码

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

    这说明该方法只获得了ClickableSpan,因为如果我们直接使用系统的LinkMovementMethod类,是无法让ImageSpan响应点击事件的。。因为我们知道,ImageSpan没有继承ClickableSpan。所以,笔者写了一个ClickableMovementMethod

    public class ClickableMovementMethod extends LinkMovementMethod {
    
        private static ClickableMovementMethod sInstance;
    
        public static ClickableMovementMethod getInstance() {
            if (sInstance == null) {
                sInstance = new ClickableMovementMethod();
            }
            return sInstance;
        }
    
        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);
                ClickableImageSpan[] imageSpans = buffer.getSpans(off, off, ClickableImageSpan.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]));
                    }
    
                    return true;
                } else if (imageSpans.length != 0) {
                    if (action == MotionEvent.ACTION_UP) {
                        imageSpans[0].onClick(widget);
                    } else if (action == MotionEvent.ACTION_DOWN) {
                        Selection.setSelection(buffer,
                                buffer.getSpanStart(imageSpans[0]),
                                buffer.getSpanEnd(imageSpans[0]));
                    }
    
                    return true;
                } else {
                    Selection.removeSelection(buffer);
                }
            }
    
            return false;
        }
    }

    只是做了很小的改动,这样,这个类既可以支持ClickableSpan也可以支持我们自己写的ClickableImageSpan。

    到此为止,一个可点击的ImageSpan就完成了。剩下的步骤就跟实现文字样式的方式一样,首先new一个SpannableString传入文本,然后找到你需要放置ImageSpan的位置(一般使用正则表达式),接着new一个ClickableImageSpan传入图片,通过SpannableString的setSpan()方法传入ClickableImageSpan对象。最后别忘了TextView调用setMovementMethod时,传入的是我们的ClickableMovementMethod.getInstance()方法。具体代码实现参照文字样式那边的,稍作修改即可。具体的笔者不再贴这部分的代码了。

    那么,如果我们不是传一个简单的图片,而是需要显示一个定制的View,应该怎么做呢。其实只要把View转化成Drawable就好,下面是主要的实现代码:

        private BitmapDrawable createDrawble(Context ctx, String content) {
            View view = LayoutInflater.from(ctx).inflate(R.layout.viewt, null);
            ((TextView) view.findViewById(R.id.tv_content)).setText(content);
            int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            view.measure(spec, spec);
            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
            Bitmap b = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(b);
            c.translate(-view.getScrollX(), -view.getScrollY());
            view.draw(c);
            view.setDrawingCacheEnabled(true);
            Bitmap cacheBmp = view.getDrawingCache();
            Bitmap viewBmp = cacheBmp.copy(Bitmap.Config.ARGB_8888, true);
            view.destroyDrawingCache();
            return new BitmapDrawable(ctx.getResources(), viewBmp);
        }
    
        public void filter(Spannable sp) {
            /**
                .....此处省略.
        **/
            BitmapDrawable bd = createDrawble(tv.getContext(), sp.toString);        bd.setBounds(0, 0, bd.getIntrinsicWidth(), bd.getIntrinsicHeight());
            MyClickableImageSpan span = new MyClickableImageSpan(bd,text);
            sp.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

    createDrawble()方法是通过View的getDrawingCache()方法将一个View转化成BItmap,然后在获得BitmapDrawable 后别忘了调用setBounds(),这个方法是决定图片的大小,如果不设置,那么图片长宽都为0! 当然,你如果嫌显示的效果太大或太小,也可以通过这个方法调整图片大小。其他步骤相信大家看过笔者的  解析TextView中的URL等指定特殊字符串与点击事件 ,实现起来应该是没有困难的。因此笔者不再赘述了。

    有任何问题或更好的见解可以留言,互相学习!

  • 相关阅读:
    使用公用表表达式(CTE)简化嵌套SQL WITH AS的含义
    C#中Array与ArrayList的区别
    Asp.net 网页中的嵌入式代码
    Asp.net核心对象
    Latex学习(载入图片并居中)
    matlab练习程序(生成加密p文件)
    matlab练习程序(直方图反向投影)
    matlab练习程序(非负矩阵分解)
    matlab练习程序(PSNR)
    matlab练习程序(动感模糊)
  • 原文地址:https://www.cnblogs.com/luction/p/3645210.html
Copyright © 2011-2022 走看看