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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章