处理一段文本使其显示各项信息,显然我们需要使用正则表达式。正则表达式基本的东西我们需要有点了解。这里我们一边来看这几个正则表达式。

    private static final String AT = "@[\u4e00-\u9fa5\\w]+";// @人
    private static final String TOPIC = "#[\u4e00-\u9fa5\\w]+#";// ##话题
    private static final String EMOJI = "\\[[\u4e00-\u9fa5\\w]+\\]";// 表情
    private static final String URL = "http://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";// url

首先我们要熟悉一下,返回的文本中各个格式是:
* @直接加用户名
* #包含话题#
* [表情文字] * 直接就是URL
然后,我们就要开始写各个正则表达式来匹配这些字符串。这里我们要明白的有:
* \ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,’n’ 匹配字符 “n”。’\n’ 匹配一个换行符。序列 ‘\’ 匹配 “\” 而 “(” 则匹配 “(“。
* +匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
* [a-z] 字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。
* \un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。这里\u4e00-\u9fa5指的是中日韩象形文字集中的字符,这两个unicode值正好是Unicode表中的汉字的头和尾
* ? 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 中的”do” 。? 等价于 {0,1}。
* \w 匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9_]‘。
* x|y 匹配 x 或 y。例如,’z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 则匹配 “zood” 或 “food”。
* * 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
需要了解其他符号表示含义的戳这里
那么在第一个AT字符串中,我们可以看出来,它是以@开头,匹配\u4e00-\u9fa5\\w,即汉字和包括下划线的任何单词字符,并进行多次匹配,是不是就完成了匹配@用户名的操作呢?\\w是为了配合编译器而进行的转义。那么话题和表情都是同样道理。
对于URL这类常用的东西,都有大家已经写好的正则表达式,往往我们都不在费精力去写一遍,例如邮箱,手机号,人名等,面向搜索引擎都可以找到,懒癌患者戳此链接,接下来我们看看怎么去使用这几个正则表达式:
首先我们自然的想到,最后肯定要变色处理,所以我们就返回一个SpannableString对象,对需要特殊处理的文本进行变革,我们需要的有Context,一个SpannableString类型的对象(经过处理的文本),最后盛放文本的容器:

public static SpannableString getWeiBoContent(final Context context, SpannableString source, TextView textView) {
        SpannableString spannableString = new SpannableString(source);

        //设置正则
        Pattern pattern = Pattern.compile(REGEX);
        //设置待测字符串
        Matcher matcher = pattern.matcher(spannableString);

        if (matcher.find()) {
            // 要实现文字的点击效果,这里需要做特殊处理
            textView.setMovementMethod(LinkMovementMethod.getInstance());
            // 从起始位置重新匹配
            matcher.reset();
        }

        while (matcher.find()) {
            // 根据group的括号索引,可得出具体匹配哪个正则(0代表全部,1代表第一个括号)
            final String at = matcher.group(1);
            final String topic = matcher.group(2);
            String emoji = matcher.group(3);
            final String url = matcher.group(4);

            // 处理@符号
            if (at != null) {
                //获取匹配位置
                int start = matcher.start(1);
                int end = start + at.length();
                MyClickableSpan clickableSpan = new MyClickableSpan() {

                    @Override
                    public void onClick(View widget) {
                        //这里需要做跳转用户的实现,先用一个Toast代替
                        Toast.makeText(context, "点击了用户:" + at, Toast.LENGTH_LONG).show();
                    }
                };
                clickableSpan.updateDrawState(textView.getPaint());
                spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }

            // 处理话题##符号
            if (topic != null) {
                int start = matcher.start(2);
                int end = start + topic.length();
                MyClickableSpan clickableSpan = new MyClickableSpan() {

                    @Override
                    public void onClick(View widget) {
                        Toast.makeText(context, "点击了话题:" + topic, Toast.LENGTH_LONG).show();
                    }
                };
                spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }

            if (emoji != null) {

                int start = matcher.start(3);
                int end = start + emoji.length();
                EmotionUtils emotionUtils=new EmotionUtils();
                int ResId = emotionUtils.getImgByName(emoji);
                Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResId);
                if (bitmap != null) {
                    // 获取字符的大小
                    int size = (int) textView.getTextSize();
                    // 压缩Bitmap
                    bitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
                    // 设置表情
                    ImageSpan imageSpan = new ImageSpan(context, bitmap);
                    spannableString.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }

            // 处理url地址
            if (url != null) {
                int start = matcher.start(4);
                int end = start + url.length();
                MyClickableSpan clickableSpan = new MyClickableSpan() {

                    @Override
                    public void onClick(View widget) {
                        Toast.makeText(context, "点击了网址:" + url, Toast.LENGTH_LONG).show();
                    }
                };
                spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }

        return spannableString;
    }

Java正则表达式通过java.util.regex包下的Pattern和Matcher类实现。Pattern类用于创建一个正则表达式,也可以说是创建一个匹配模式,可以通过两个静态方法创建:compile(String regex)和compile(String regex,int flags),这里使用了前一个。
然后就要过渡到Matcher类,Pattern类中的matcher(CharSequence input)会返回一个Matcher对象。 Matcher类提供了对正则表达式的分组支持,以及对正则表达式的多次匹配支持,要想得到更丰富的正则匹配操作,那就需要将Pattern与Matcher联合使用。
Matcher类提供了三个返回boolean值得匹配方法:matches(),lookingAt(),find(),find(int start),其中matches()用于全字符串匹配,lookingAt从字符串最开头开始匹配满足的子串,find可以对任意位置字符串匹配,其中start为起始查找索引值。
组的概念:组是用括号划分的正则表达式,可以根据组的编号来引用这个组。组号为0表示整个表达式,组号为1表示被第一对括号括起的组,依次类推,例如A(B©)D,组0是ABCD,组1是BC,组2是C。所以我们将以上四个正则表达式用括号连接起来,即我这里的REGEX,正则匹配以后,在使用我们自定义的

/**
     * 继承ClickableSpan复写updateDrawState方法,自定义所需样式
     * @author Rabbit_Lee
     *
     */
    public static class MyClickableSpan extends ClickableSpan {

        @Override
        public void onClick(View widget) {

        }

        @Override
        public void updateDrawState(TextPaint ds) {
            super.updateDrawState(ds);
            ds.setColor(Color.BLUE);
            ds.setUnderlineText(false);
        }

    }

设置字体高亮。
接下来就是九宫图的处理。这里我使用了第三方库,首先我们依旧是要添加依赖。

compile 'com.w4lle.library:NineLayout:1.0.0'
//根据我们的实际需求对适配器进行修改,显然这里我们需要的是一条微博所匹配的一个或多个图片,故我们传入的是List<Image>
class Adapter extends NineGridAdapter {

        public Adapter(Context context, List<Image> list) {
            super(context, list);
        }

        @Override
        public int getCount() {
            return (list == null) ? 0 : list.size();
        }

        @Override
        public String getUrl(int position) {
            return getItem(position) == null ? null : ((Image) getItem(position)).getUrl();
        }

        @Override
        public Object getItem(int position) {
            return (list == null) ? null : list.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int i, View view) {
            ImageView iv = null;
            if (view != null && view instanceof ImageView) {
                iv = (ImageView) view;
            } else {
                iv = new ImageView(context);
            }
            iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
            iv.setBackgroundColor(context.getResources().getColor((android.R.color.transparent)));
            String url = getUrl(i);
            Picasso.with(context).load(getUrl(i)).placeholder(new ColorDrawable(Color.parseColor("#f5f5f5"))).into(iv);
            if (!TextUtils.isEmpty(url)) {
                iv.setTag(url);
            }
            return iv;
        }
    }

我们只需要在布局中添加

<com.w4lle.library.NineGridlayout
            android:id="@+id/iv_ngrid_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingRight="20dp"
            android:paddingLeft="10dp"
            android:layout_marginTop="8dp" />

即可,其余的事情就交给第三方就可以啦
在下一篇中我们将解决最后一个也是最好玩的,微博发布功能