項目地址:https://github.com/razerdp/FriendCircle
一起擼個朋友圈吧這是本文所處文集位岔,所有更新都會在這個文集里面哦座云,歡迎關(guān)注
首先非常感謝@奉孝安在在文章中的評論,他發(fā)現(xiàn)在《一起擼個朋友圈吧(step5) - 控件篇【點贊列表】》這篇文章中蹬音,我寫的點贊列表并沒有實現(xiàn)selector效果赚抡。
在在幾分鐘前板辽,我留下了一個評論剿干,說是可以在LinkMovementMethod里面處理担神,然后本來想去打Dota2來著,結(jié)果這個問題一直壓在心里不舒服演痒,所以就花了一點事件來解決這個bug亲轨。
首先上preview:
在此之前,點擊span是沒有任何背景色改變的
ps:本次上傳到github只是上傳到了main-dev分支嫡霞,并沒有合并到master分支
關(guān)于Span
事實上瓶埋,在那篇文章的后面我有稍微講解過希柿,但我并沒有談到關(guān)于span的問題诊沪。
事實上养筒,關(guān)于textview,我們的text是如何分辨出那么多的span的端姚?
這里我們拿到BackgroundColorSpan來做例子:
public class BackgroundColorSpan extends CharacterStyle
implements UpdateAppearance, ParcelableSpan {
private final int mColor;
public BackgroundColorSpan(int color) {
mColor = color;
}
public BackgroundColorSpan(Parcel src) {
mColor = src.readInt();
}
public int getSpanTypeId() {
return getSpanTypeIdInternal();
}
/** @hide */
public int getSpanTypeIdInternal() {
return TextUtils.BACKGROUND_COLOR_SPAN;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
writeToParcelInternal(dest, flags);
}
/** @hide */
public void writeToParcelInternal(Parcel dest, int flags) {
dest.writeInt(mColor);
}
public int getBackgroundColor() {
return mColor;
}
@Override
public void updateDrawState(TextPaint ds) {
ds.bgColor = mColor;
}
}
代碼很少晕粪,我們可以看到這個帶背景的span實現(xiàn)了Parcelable這個序列化字眼的接口,看看接口的介紹:
/**
* A special kind of Parcelable for objects that will serve as text spans.
* This can only be used by code in the framework; it is not intended for
* applications to implement their own Parcelable spans.
*/
public interface ParcelableSpan extends Parcelable
這是一個特殊的序列化對象渐裸,它用來保存span巫湘。這個借口僅供framework用,不應(yīng)該應(yīng)用層編寫的時候使用昏鹃。
它實現(xiàn)了三個方法:
/**
* Return a special type identifier for this span class.
*/
int getSpanTypeId();
/**
* Internal implementation of {@link #getSpanTypeId()} that is not meant to
* be overridden outside of the framework.
*
* @hide
*/
int getSpanTypeIdInternal();
/**
* Internal implementation of {@link Parcelable#writeToParcel(Parcel, int)}
* that is not meant to be overridden outside of the framework.
*
* @hide
*/
void writeToParcelInternal(Parcel dest, int flags);
通過著三個方法尚氛,我們很容易猜測到,span是通過getSpanTypeId得到span對應(yīng)的class洞渤,也許是系統(tǒng)有一個工廠類來對應(yīng)創(chuàng)建這些span阅嘶。一般這個方法會調(diào)用getSpanTypeIdInternal(),其實區(qū)別不大载迄,只是因為有了一個@hide的java doc-V-
回到我們的BackgroundColorSpan讯柔,在getSpanTypeIdInternal中,返回的是
return TextUtils.BACKGROUND_COLOR_SPAN;
而這個我們是不可見的护昧,然而我們?nèi)extUtils看一看魂迄,就會發(fā)現(xiàn)很多這種類型:
只不過都不讓我們看而已。
最終這些配置會被這個方法調(diào)用:
如無意外惋耙,最終都是底層的native調(diào)用捣炬。我們暫時先不追溯下去。
由此我們可以看到一個span是怎樣把信息傳遞的绽榛。
比如BackgroundColorSpan遥金,將背景顏色序列化,然后c層調(diào)用的時候反序列并傳到底層進行創(chuàng)建蒜田,這樣一些span的信息就傳遞了出去稿械。
updateDrawState
當然,我們并不需要涉及到那么深冲粤,在BackgroundColorSpan我們可以看到一個地方:
@Override
public void updateDrawState(TextPaint ds) {
ds.bgColor = mColor;
}
而mColor正是初始化時傳遞進去的背景美莫,那么updateDrawState
這個方法到底是何方神圣呢,在as我們查看到這個方法幾乎是所有span都有實現(xiàn)梯捕,畢竟幾乎都繼承CharacterStyle
(同時我也驚訝于厢呵,,傀顾,原來有這么多的span)
然而襟铭,很高興的在開發(fā)者文檔里面我們也找不到它的描述。
但我們通過名字可以猜測,是不是在draw調(diào)用時會回調(diào)寒砖,而且傳入的是一個TextPaint赐劣,如無意外,這正是BackgroundColorSpan實現(xiàn)帶背景的span的做法哩都,使用TextPaint為span設(shè)置背景色魁兼。
改進點贊控件
前文我說過,span的點擊事件都是在LinkMovementMethod處理的漠嵌。而span實現(xiàn)的updateDrawState就是關(guān)鍵性方法咐汞,所以我們可以繼承LinkMovementMethod實現(xiàn)更改背景色:
- 點擊時(ACTION_DOWN),調(diào)用我們的span里面更改顏色的方法
- 松開手(NOT ACTION_DOWN)儒鹿,則設(shè)置為原來的顏色(本例是透明色)
完成這兩點就可以了化撕。
LinkMovementMethod修改:
首先我們繼承LinkMovementMethod,取名PraiseMovementMethod
public class PraiseMovementMethod extends LinkMovementMethod {
private static PraiseMovementMethod sInstance;
private int color;
private int defaultBgColor;
private PraiseMovementMethod(int color, int defaultBgColor) {
this.color = color;
this.defaultBgColor = defaultBgColor;
}
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
}
public static PraiseMovementMethod getInstance(int color, int defaultBackgroungColor) {
if (sInstance == null) {
sInstance = new PraiseMovementMethod(color, defaultBackgroungColor);
}
return sInstance;
}
}
基本跟父類保持一致约炎,但我們只實現(xiàn)三個方法侯谁,一個私有構(gòu)造器,一個getInstance章钾,一個就是touch事件墙贱。
我們傳入兩個值,一個是點擊時候的顏色贱傀,一個是沒有點擊的時候的顏色惨撇。
然后我們在onTouchEvent進行修改,不過請放心府寒,我們并非需要重新梳理一遍事件分發(fā)/事件處理魁衙,因為這些父類已經(jīng)做好了,我們要做的僅僅是copy code from the super class:
@Override
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);
}
我們詳細的看看這段代碼:
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);
}
}
這里處理的是在點擊獲得當前點擊位置后株搔,先拿到ClickableSpan數(shù)組剖淀,如果數(shù)組長度不為0,證明當前點擊的是span纤房,在up時實現(xiàn)其onClick方法纵隔。
很明顯,我們需要在這里做手腳炮姨。不過ClickableSpan并不能滿足我們的需求捌刮,所以我們將其改動一下:
PraiseClick[] link = buffer.getSpans(off, off, PraiseClick.class);
換成得到我們的PraiseClick
接下來回到我們的PraiseClick
PraiseClick修改:
首先添加一個變量,作為背景顏色舒岸,由于我們朋友圈默認是透明的背景色绅作,所以就在定義變量時就賦予了初值:
private int clickBgColor= Color.TRANSPARENT;
然后在updateDrawState進行paint的背景色賦值:
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
...原代碼不變
ds.bgColor=clickBgColor;
...原代碼不變
}
最后暴露一個公共setter方法給外部調(diào)用:
public void setClickBackgroundColor(int color){
this.clickBgColor=color;
}
就是這么粗暴簡單。
**回到我們的PraiseMovementMethod **
繼續(xù)修改touch:
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].setClickBackgroundColor(defaultBgColor);
link[0].onClick(widget);
}
else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]));
link[0].setClickBackgroundColor(color);
}
else {
link[0].setClickBackgroundColor(defaultBgColor);
}
在action_down里面蛾派,設(shè)置點擊顏色俄认,其余情況都設(shè)置為初始值(本例為透明)
就是這樣个少,我們就完成了本次的修復。
最后如果您在使用過程中發(fā)現(xiàn)什么bug或者我沒留意到的眯杏,也可以在評論區(qū)留下您的腳印哦夜焦,如果我有這個能力,我會盡快解決的-V-
最后最后再次感謝@奉孝安在的提議役拴。
03-27更新
在評論區(qū)@奉孝安在貼上了他的解決方案糊探,當然钾埂,方法是不變的河闰。不過他是把本該LinkMovementMethod的處理直接封裝到了clickablespan里面,然后使用內(nèi)部類解決Touch事件褥紫。
預覽圖:
當然姜性,基于他的代碼我稍微做了一點點改動。
這里說一下為什么我會采取這個方案髓考。
首先我們不用針對寫LinkMovementMethod外部念,事實上我覺得既然自己內(nèi)部類接管了Touch事件,我們可以說不需要LinkMovementMethod氨菇。
除了上面這點儡炼,最重要的是事件攔截。
我們的評論控件承擔著三個事件:
- 內(nèi)部用戶的span點擊事件
- 外部TextView點擊彈出輸入法
- 外部TextView長按彈出選擇框(復制/刪除)
然而如果我們點擊span勢必會點到整個TextView查蓉,這意味著如果我們點了span就會觸發(fā)鍵盤彈出事件乌询。
而如果按照原來的做法,我們需要在LinkMovementMethod里面用個標志位記錄豌研,然后在外部處理的時候拿到這個標志位判斷是點的span還是整個textview妹田,但現(xiàn)在因為@奉孝安在直接使用內(nèi)部類處理,所以我們可以直接進行攔截鹃共。
這就是我選擇這個方案的原因鬼佣。
最后感謝@奉孝安在的解決方案-V-
這才叫“一起擼個朋友圈”嘛,最重要的不是弄出一個什么app霜浴,而是一起這兩個字晶衷。(一直玩著單機的我忽然遇到一位留下評論的人,好激動)
如果您有好的建議阴孟,提議房铭,希望能在評論區(qū)看到您的腳印或者GitHub提交PR。在下萬分感謝-V-