編輯:關於Android編程
在應用程序開發過程經常需要對文本進行處理,比如說對一段描述文字的其中一段加入點擊事件,或者對其設置不一樣的前景色,有什麼方法可以實現要求的功能吶?
比如我們需要實現如下圖所示的功能,將文本:#重磅消息#近日谷歌放出Android N的第二個開發者預覽版(Developer Preview) 處理成第二種或者第三種的形式。
根據上圖,我們可以采用如下的方法來實現上訴要求的效果。
比如顯示效果二你可以能會說,我們可以采用三個TextView來實現,第一個TextView設置不一樣的顏色,第二個正常顯示內容,第三個處理點擊事件。該方式對圖二可能是能夠實現的,但是如果第二行裡面就有部分內容需要進行點擊處理,就比較難以實現了。
對於圖三的效果上述的方式就很難實現了。必須要對TextView的內容進行處理了!!
如果文案的處理只是簡單的對齊,顏色,大小的變換,我們還可以采用自定義view來實現,在前面的文章中我們就采用了自定義view來顯示了一個文字的排版效果,具體實現可以查看Android文本排版實現;
除了上面的方案,我們還可以采用另外一個種方式來實現,采用html來顯示,可以將要顯示的內容轉換成html的格式,用TextView來進行加載。說了這麼多,我們來看看代碼吧!
private void setText() {
String originText = "#重磅消息#近日谷歌放出Android N的第二個開發者預覽版(Developer Preview)";
String effect1 = "#重磅消息#
近日谷歌放出Android " +
"N的第二個開發者預覽版(Developer Preview)";
String effect2 = "#重磅消息# 近日谷歌放出Android " +
"N的第二個開發者預覽版(Developer Preview)";
StringBuilder sb = new StringBuilder(originText);
sb.append("
");
sb.append(effect1);
sb.append("
");
sb.append(effect2);
textView.setText(Html.fromHtml(sb.toString()));
textView.setMovementMethod(LinkMovementMethod.getInstance());
}
寫到這,突然發現要跑題,僅僅是Html的實現就可以分析出很多的知識點,不過這裡還是先契合主題,先這裡挖一個坑,後續對html進行分析,查看鏈接,現在還未實現
終於回到我們的主題了,這裡我們采用SpannableString來實現上述的效果。代碼如下:
private void setSpan() {
String originText = "#重磅消息#近日谷歌放出Android N的第二個開發者預覽版(Developer Preview)";
SpannableStringBuilder sb = new SpannableStringBuilder(originText);
sb.append("\r\n").append("\r\n").append("\r\n");
getEffect1Span(sb);
sb.append("\r\n").append("\r\n").append("\r\n");
getEffect2Span(sb);
textView.setText(sb);
textView.setMovementMethod(LinkMovementMethod.getInstance());
}
private void getEffect1Span(SpannableStringBuilder sb) {
String source1 = "#重磅消息#";
SpannableString span = new SpannableString(source1);
span.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorAccent)), 0, source1.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(span);
sb.append("\n");
String source2 = "近日谷歌放出Android N的第二個開發者預覽版";
sb.append(source2);
final String source3 = "(Developer Preview)";
SpannableString clickSpan = new SpannableString(source3);
clickSpan.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
ToastUtil.showLong(source3);
}
}, 0, source3.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(clickSpan);
}
private void getEffect2Span(SpannableStringBuilder sb) {
String source1 = "#重磅消息#近日谷歌放出Android N的第二個開發者預覽版";
SpannableString span = new SpannableString(source1);
span.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimaryDark)), 0, 6, Spanned
.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(span);
final String source2 = "(Developer Preview)";
SpannableString clickSpan = new SpannableString(source2);
clickSpan.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
ToastUtil.showLong(source2);
}
}, 0, source2.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(clickSpan);
}
上述代碼采用了硬編碼方式實現,正常實現,需要根據需求進行設置。記得要添加textView.setMovementMethod(LinkMovementMethod.getInstance());來接受點擊事件。
SpannableString繼承了SpannableStringInternal,同時實現了CharSequence, GetChars, Spannable三個接口,正常處理文本的函數為setSpan函數:
public void setSpan(Object what, int start, int end, int flags) {
super.setSpan(what, start, end, flags);
}
該函數有四個參數,第一個為一個span類型,第二個參數為開始位置,第三個位置為span的結束位置,最後一個為flag參數。
what可以設置如下類型:
1, AbsoluteSizeSpan 設置文字字體的絕對大小, 有兩個參數,第一個是字體大小,第二個是單位是否是dip
public AbsoluteSizeSpan(int size, boolean dip) {
mSize = size;
mDip = dip;
}
2,AlignmentSpan 主要設置文本的對齊方式,有三種方式正常,居中,相反的方式對齊,默認實現為Standard
public Standard(Layout.Alignment align) {
mAlignment = align;
}
3,BackgroundColorSpan 設置文字的背景色
private void setfCS(){
String source1 = "#重磅消息#";
SpannableString span = new SpannableString(source1);
span.setSpan(new BackgroundColorSpan(getResources().getColor(R.color.colorAccent)), 0, source1.length(),Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(span);
}
4,BulletSpan 給文本的開始處加上項目符號。比如前面加一個 .
private void setBSpan() {
final String source3 = "近日谷歌放出Android N的第二個開發者預覽版";
SpannableString bSpan = new SpannableString(source3);
bSpan.setSpan(new BulletSpan(), 0, source3.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(bSpan);
}
5, ClickableSpan 設置文本的點擊事件,要實現onClick函數,可以復寫updateDrawState,設置下劃線,或者取消下劃線,還可以設置下劃線顏色
private void setCS(){
final String source2 = "(Developer Preview)";
SpannableString clickSpan = new SpannableString(source2);
clickSpan.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
ToastUtil.showLong(source2);
}
}, 0, source2.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(clickSpan);
}
6,DrawableMarginSpan 可以設置一個圖標,並且可以設置與文字的寬度
private void setDMSpan() {
final String source3 = "(Developer Preview)";
SpannableString dmSpan = new SpannableString(source3);
dmSpan.setSpan(new DrawableMarginSpan(getResources().getDrawable(R.mipmap.ic_launcher), 30), 0, source3
.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(dmSpan);
}
7,DynamicDrawableSpan 設置某段文字被圖標替換,需要返回一個drawable
8,EasyEditSpan 當文本改變或者刪除時調用, 例如入下長按可以很容易刪除一行
private void setEdit() {
editText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
editText.setSingleLine(false);
editText.setText("近日\n谷歌放出Android N的\n第二個開發者預覽版");
editText.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
final Layout layout = editText.getLayout();
final int line = layout.getLineForOffset(editText.getSelectionStart());
final int start = layout.getLineStart(line);
final int end = layout.getLineEnd(line);
editText.getEditableText().setSpan(new EasyEditSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return true;
}
});
}
9,ForegroundColorSpan 設置文字前景色
private void setfCS(){
String source1 = "#重磅消息#";
SpannableString span = new SpannableString(source1);
span.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorAccent)), 0, source1.length(),Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(span);
}
寫到這裡我停下來了。天啦噜,30多個span,可以去系統代碼package android.text.style包下查看,這麼多,整個人都不好了。
因此先就針對上面的做了部分樣例,之後會專門實現一下每個span的效果。仔細理解一個就行,其他的都是類似的,我們繼續看看後面的參數。
第二參數start和第三個參數end,表示當時設置的span作用效果的范圍,start表示開始位置,end表示結束位置,第四個參數是一個flag標簽。這裡主要設置以下的值:
/**
* Non-0-length spans of type SPAN_INCLUSIVE_EXCLUSIVE expand
* to include text inserted at their starting point but not at their
* ending point. When 0-length, they behave like marks.
*/
public static final int SPAN_INCLUSIVE_EXCLUSIVE = SPAN_MARK_MARK;
/**
* Spans of type SPAN_INCLUSIVE_INCLUSIVE expand
* to include text inserted at either their starting or ending point.
*/
public static final int SPAN_INCLUSIVE_INCLUSIVE = SPAN_MARK_POINT;
/**
* Spans of type SPAN_EXCLUSIVE_EXCLUSIVE do not expand
* to include text inserted at either their starting or ending point.
* They can never have a length of 0 and are automatically removed
* from the buffer if all the text they cover is removed.
*/
public static final int SPAN_EXCLUSIVE_EXCLUSIVE = SPAN_POINT_MARK;
/**
* Non-0-length spans of type SPAN_EXCLUSIVE_INCLUSIVE expand
* to include text inserted at their ending point but not at their
* starting point. When 0-length, they behave like points.
*/
public static final int SPAN_EXCLUSIVE_INCLUSIVE = SPAN_POINT_POINT;
常用的就是上述的四個值,這裡我們來分別解釋以下:
1. SPAN_INCLUSIVE_EXCLUSIVE表示左閉右開區間 “[ )”
2. SPAN_INCLUSIVE_INCLUSIVE表示左右都是閉區間 ‘( )’
3. SPAN_EXCLUSIVE_EXCLUSIVE表示左右都是閉區間 ‘[ ]’
4. SPAN_EXCLUSIVE_INCLUSIVE表示左右都是閉區間 ‘( ]’
我們繼續來看代碼,SpannableString的setSpan又繼續調用了SpannableStringInternal的setSpan函數。
/* package */ void setSpan(Object what, int start, int end, int flags) {
int nstart = start;
int nend = end;
checkRange("setSpan", start, end);
if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) {
if (start != 0 && start != length()) {
char c = charAt(start - 1);
if (c != '\n')
throw new RuntimeException(
"PARAGRAPH span must start at paragraph boundary" +
" (" + start + " follows " + c + ")");
}
if (end != 0 && end != length()) {
char c = charAt(end - 1);
if (c != '\n')
throw new RuntimeException(
"PARAGRAPH span must end at paragraph boundary" +
" (" + end + " follows " + c + ")");
}
}
int count = mSpanCount;
Object[] spans = mSpans;
int[] data = mSpanData;
for (int i = 0; i < count; i++) {
if (spans[i] == what) {
int ostart = data[i * COLUMNS + START];
int oend = data[i * COLUMNS + END];
data[i * COLUMNS + START] = start;
data[i * COLUMNS + END] = end;
data[i * COLUMNS + FLAGS] = flags;
sendSpanChanged(what, ostart, oend, nstart, nend);
return;
}
}
if (mSpanCount + 1 >= mSpans.length) {
Object[] newtags = ArrayUtils.newUnpaddedObjectArray(
GrowingArrayUtils.growSize(mSpanCount));
int[] newdata = new int[newtags.length * 3];
System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
mSpans = newtags;
mSpanData = newdata;
}
mSpans[mSpanCount] = what;
mSpanData[mSpanCount * COLUMNS + START] = start;
mSpanData[mSpanCount * COLUMNS + END] = end;
mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
mSpanCount++;
if (this instanceof Spannable)
sendSpanAdded(what, nstart, nend);
}
/* package */ void removeSpan(Object what) {
int count = mSpanCount;
Object[] spans = mSpans;
int[] data = mSpanData;
for (int i = count - 1; i >= 0; i--) {
if (spans[i] == what) {
int ostart = data[i * COLUMNS + START];
int oend = data[i * COLUMNS + END];
int c = count - (i + 1);
System.arraycopy(spans, i + 1, spans, i, c);
System.arraycopy(data, (i + 1) * COLUMNS,
data, i * COLUMNS, c * COLUMNS);
mSpanCount--;
sendSpanRemoved(what, ostart, oend);
return;
}
}
}
首先調用了checkRange,判斷了位置的合法性,如果start小於end,或者位置下標越界都會拋出IndexOutOfBoundsException異常。
之後判斷了(flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH是否相等,這裡如果設置的是上述四個值,這裡是不等的,所以不會進入該判斷。
設置了count,第一次count為0,設置了spans數組與data,第一次設置的值是在構造函數中初始化的值。
因為count為0,因此for循環也不會進入
之後判斷了mSpanCount + 1 >= mSpans.length,這裡前面為1,後面為0,因此會進入if判斷,首先申請了一個3個長度的newtags數組,一個9個長度的int數組, 之後進行了兩次數據拷貝,將已有的span拷貝到新申請的數組中,將其他參數拷貝到新的int數組中。
之後將改成設置的span設置到mSpans數組中,將其他的參數設置到mSpanData,三個參數是連續設置的。
最後調用了sendSpanAdded,代碼如下:
private void sendSpanAdded(Object what, int start, int end) {
SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].onSpanAdded((Spannable) this, what, start, end);
}
}
這個調用了getSpans,返回了一個SpanWatcher數組,SpanWatcher是一個接口,MultiTapKeyListener, TextKeyListener實現了該類,因此當調用了TextKeyListener或者MultiTapKeyListener會對相應的span進行處理。
這裡只是大致的解析了SpannableString,他還需要結合TextView進行分析,看看在界面繪制的時候是怎樣解析顯示的。後續有時間會陸續進行解析的。
一、ViewPageIndicator開源框架的基本用法 我們先得去Github上面下載這個庫,下載地址:https://github.com/JakeWharton/A
這篇文章還是針對非gradle build的工程,gradle build有一些差別。在Eclipse要引用別的工程為本工程的library很簡單,但是在Android
這兩天學習了使用Path繪制貝塞爾曲線相關,然後自己動手做了一個類似QQ未讀消息可拖拽的小氣泡,效果圖如下:接下來一步一步的實現整個過程。基本原理其實就是使用Path繪制
一,效果圖。二,工程圖。 三,代碼。RootViewController.h#import <UIKit/UIKit.h>@interface Ro