Android仿微博@好友,#话题#及links处理方案

概述

在使用微博的时候,我们都会发现这两个功能

  • EditText可输入 @好友,#话题#和链接links
  • 动态中可展示@好友,#话题#和链接links

很容易想到是通过Span正则表达式来实现的,但是其中还涉及到了一些细节需要处理

EditText

输入部分的细节功能

  • 关键词变色高亮
  • 删除的时候要选中整体删除
  • 焦点及光标不可以落在关键词中间
  • 一般都会带有附加信息

对于最后一点,是基于以下考虑,比如 用户名可相同,id不相同

一般都是类似这样的处理, <name id="xxx">@杨幂</name>或者(@杨幂,id=xxx)

根据不同的id,跳转界面,比如用户详情页(获取详细用户信息)。

参考实现:

优化实现

最后一点针对MentionEditText做了一些优化,源码:Mentions中的EditText部分。
抽象其功能主要涉及到这几个方面:

  • 界面显示的CharSequence
  • 带有附加字段的CharSequence
  • 高亮颜色
先来看一下用法,再看实现吧:
  • User
public class User implements InsertData{
  //...

  @Override public CharSequence charSequence() {
      return "@"+userName; //provide the CharSequence insert to edittext
    }

    @Override public FormatRange.FormatData formatData() {
      return new UserConvert(this);//provide the formater for the insert data
    }

    @Override public int color() {
      return Color.MAGENTA;//provide the range color
    }

    private class UserConvert implements FormatRange.FormatData {

      public static final String USER_FORMART = "(@%s,id=%s)";
      private final User user;

      public UserConvert(User user) {
        this.user = user;
      }

      @Override public CharSequence formatCharSequence() {//format
        return String.format(USER_FORMART, user.getUserName(), user.getUserId());
      }
    }
}
  • Activity
public class MainActivity extends AppCompatActivity{
@BindView(R.id.mentionedittext) MentionEditText mMentionedittext;
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    if (resultCode == Activity.RESULT_OK && null != data) {
      switch (requestCode) {
        case REQUEST_USER_APPEND:
          User user = (User) data.getSerializableExtra(UserList.RESULT_USER);
          mMentionedittext.insert(user);//insert data to edittext
          break;
        //...
      }
    }

    super.onActivityResult(requestCode, resultCode, data);
  }
}
  • 获取发送到服务器的数据
CharSequence convertMetionString = mMentionedittext.getFormatCharSequence();// 按照上面的format格式,这里会得到 (@xxx,id=xxx-xx-x)
接下来看实现

!. 我们首先提供一个接口,定义插入的数据,如下:

public interface InsertData {

  CharSequence charSequence(); //提供界面显示的CharSequence

  FormatRange.FormatData formatData();//提供CharSequence的转换器

  int color();//提供高亮显示的颜色
}

!!. 为什么定义这儿接口,先看一下字符的插入过程

// 伪代码
public void insert(InsertData insertData) {
//1.插入需要显示在界面上的CharSequence
//2.将插入的CharSequence及其属性用一个类管理起来
//3.将字符串变色
    }
  }

如上,第二步中可能需要的信息可能包括: 字符的起始位置,字符,转换后的字符…
但是后面发现,字符接口已经提供、、、而转换后的字符(可能需要经过不同的转换),还是提供一个转换器,让用户自己实现。

!!!. 因此有了转换接口,如下:

  public interface FormatData {

    CharSequence formatCharSequence();//转换为带有特殊字段的CharSequence
  }

!!!!. 那么上面的插入代码即可采用如下方式写:

public void insert(InsertData insertData) {
    if (null != insertData) {
      CharSequence charSequence = insertData.charSequence();
      Editable editable = getText();
      int start = getSelectionStart();//获取插入的开始位置
      int end = start + charSequence.length();//获取文本长度
      editable.insert(start, charSequence);//插入
      FormatRange.FormatData format = insertData.formatData();
      Range range = new FormatRange(start, end, format);
      mRangeManager.add(range);//将相关信息存储起来,用于获取转换信息

      int color = insertData.color();
      editable.setSpan(new ForegroundColorSpan(color), start, end,
          Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);//关键字变色
    }
  }

!!!!!. 那么我们看一下,上面这些东西如何组合成我们想要的最终信息,上面将Range信息存储起来了,这里将其连接起来即可构成我们想要的信息,如下:

// mRangeManager中有一个ArrayList<Range>
 public CharSequence getFormatCharSequence(String text) {
    if (isEmpty()) {
      return text;
    }

    int lastRangeTo = 0;
    ArrayList<? extends Range> ranges = get();
    Collections.sort(ranges);//Range 实现了Comparable,并且按照 start 排序

    StringBuilder builder = new StringBuilder("");
    CharSequence newChar;
    for (Range range : ranges) {
      if (range instanceof FormatRange) {
        FormatRange formatRange = (FormatRange) range;
        FormatRange.FormatData convert = formatRange.getConvert();
        newChar = convert.formatCharSequence();
        builder.append(text.substring(lastRangeTo, range.getFrom()));//将第一个 `Range` 之前的 字符县存入
        builder.append(newChar); // 将 转换后的字符 存入
        lastRangeTo = range.getTo();
      }
    }

    builder.append(text.substring(lastRangeTo));//存入最后一个 `Range` 之后的字符
    return builder.toString();
  }

如上,即实现了功能。

TextView

因为发出去的数据结构做了一些改变,展示的时候也需要作相应的处理

  • 界面不能 将附加信息 显示出来,可以仿照 Html 类的实现
  • 点击 Span 的时候,获取附加信息,如获取 xmlAttribute 信息
  • 点击Span 之外的地方,响应相应事件,这里有坑,LinkMovementMethod会拦截事件
  • 支持图文混排emoji,支持 ellipse,使用 SpanableString时,ellipse会失效。

参考实现:

优化实现:

其实相比前面的EditText ,这个相对简单多了。

!. 首先定义接口:

public interface ParserConverter {

  Spanned convert(CharSequence source);//将CharSequence转化为需要显示的Spanned,类似 Html.fromHtml()
}

!!. MentionTextView 继承 TextView,通过接口转换

public class MentionTextView extends TextView {
//...
@Override public void setText(CharSequence text, BufferType type) {
    if (!TextUtils.isEmpty(text) && null != mParserConverter) {
      text = mParserConverter.convert(text);
    }
    super.setText(text, type);
    setMovementMethod(new LinkMovementMethod());
  }
  //...
}

!!!. 使用

mMentiontextview.setParserConverter(mUserParser);
CharSequence convertMetionString = mMentionedittext.getFormatCharSequence();
mMentiontextview.setText(convertMetionString);

!!! Parser实现

先看一下,我们的Parser需要实现的功能有如下几个:

  • @功能
  • #tag#功能
  • links功能:如微博将http链接替换为网页链接,并可以跳转

步骤1.

public class LinkUtil {
// 获取网页链接,动态替换
  private static final Pattern URL_PATTERN = Pattern.compile(
      "((http|https|ftp|ftps):\\/\\/)?([a-zA-Z0-9-]+\\.){1,5}(com|cn|net|org|hk|tw)((\\/(\\w|-)+(\\.([a-zA-Z]+))?)+)?(\\/)?(\\??([\\.%:a-zA-Z0-9_-]+=[#\\.%:a-zA-Z0-9_-]+(&amp;)?)+)?");

  public static String replaceUrl(String source) {
    Matcher matcher = URL_PATTERN.matcher(source);
    if (matcher.find()) {
      String url = matcher.group();

      source = source.replace(url, "<a href=" + "\'" + url + "\'" + ">网页链接</a>");
    }
    return source;
  }
}

步骤2:

public class Parser implements ParserConverter {

  public Parser() {
  }

  @Override public Spanned convert(CharSequence source) {
    if (TextUtils.isEmpty(source)) return new SpannableString("");
    String sourceString = source.toString();
    sourceString = LinkUtil.replaceUrl(sourceString);

    return Html.fromHtml(sourceString, null, new HtmlTagHandler());
  }
}

如上,功能实现,详细代码见: Mentions

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值