一起撸个朋友圈吧 - 点赞列表背景问题修复

项目地址:https://github.com/razerdp/FriendCircle
一起撸个朋友圈吧这是本文所处文集,所有更新都会在这个文集里面哦,欢迎关注


首先非常感谢@奉孝安在在文章中的评论,他发现在《一起撸个朋友圈吧(step5) - 控件篇【点赞列表】》这篇文章中,我写的点赞列表并没有实现selector效果。

在在几分钟前,我留下了一个评论,说是可以在LinkMovementMethod里面处理,然后本来想去打Dota2来着,结果这个问题一直压在心里不舒服,所以就花了一点事件来解决这个bug。

首先上preview:

preview

在此之前,点击span是没有任何背景色改变的

ps:本次上传到github只是上传到了main-dev分支,并没有合并到master分支


关于Span

事实上,在那篇文章的后面我有稍微讲解过,但我并没有谈到关于span的问题。

事实上,关于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实现了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用,不应该应用层编写的时候使用。

它实现了三个方法:

  /**
     * 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对应的class,也许是系统有一个工厂类来对应创建这些span。一般这个方法会调用getSpanTypeIdInternal(),其实区别不大,只是因为有了一个@hide的java doc-V-

回到我们的BackgroundColorSpan,在getSpanTypeIdInternal中,返回的是

return TextUtils.BACKGROUND_COLOR_SPAN;

而这个我们是不可见的,然而我们去TextUtils看一看,就会发现很多这种类型:

TextUtils

只不过都不让我们看而已。

最终这些配置会被这个方法调用:

如无意外,最终都是底层的native调用。我们暂时先不追溯下去。

由此我们可以看到一个span是怎样把信息传递的。

比如BackgroundColorSpan,将背景颜色序列化,然后c层调用的时候反序列并传到底层进行创建,这样一些span的信息就传递了出去。


updateDrawState

当然,我们并不需要涉及到那么深,在BackgroundColorSpan我们可以看到一个地方:

@Override
public void updateDrawState(TextPaint ds) {    
  ds.bgColor = mColor;
}

而mColor正是初始化时传递进去的背景,那么updateDrawState这个方法到底是何方神圣呢,在as我们查看到这个方法几乎是所有span都有实现,毕竟几乎都继承CharacterStyle

updateDrawState

(同时我也惊讶于,,,原来有这么多的span)

然而,很高兴的在开发者文档里面我们也找不到它的描述。

但我们通过名字可以猜测,是不是在draw调用时会回调,而且传入的是一个TextPaint,如无意外,这正是BackgroundColorSpan实现带背景的span的做法,使用TextPaint为span设置背景色。


改进点赞控件

前文我说过,span的点击事件都是在LinkMovementMethod处理的。而span实现的updateDrawState就是关键性方法,所以我们可以继承LinkMovementMethod实现更改背景色:

  • 点击时(ACTION_DOWN),调用我们的span里面更改颜色的方法
  • 松开手(NOT ACTION_DOWN),则设置为原来的颜色(本例是透明色)

完成这两点就可以了。

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;
    }
}

基本跟父类保持一致,但我们只实现三个方法,一个私有构造器,一个getInstance,一个就是touch事件。

我们传入两个值,一个是点击时候的颜色,一个是没有点击的时候的颜色。

然后我们在onTouchEvent进行修改,不过请放心,我们并非需要重新梳理一遍事件分发/事件处理,因为这些父类已经做好了,我们要做的仅仅是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数组,如果数组长度不为0,证明当前点击的是span,在up时实现其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方法给外部调用:

  public void setClickBackgroundColor(int color){
        this.clickBgColor=color;
    }

就是这么粗暴简单。

**回到我们的PraiseMovementMethod **
继续修改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里面,设置点击颜色,其余情况都设置为初始值(本例为透明)

就是这样,我们就完成了本次的修复。

最后如果您在使用过程中发现什么bug或者我没留意到的,也可以在评论区留下您的脚印哦,如果我有这个能力,我会尽快解决的-V-

最后最后再次感谢@奉孝安在的提议。


03-27更新

在评论区@奉孝安在贴上了他的解决方案,当然,方法是不变的。不过他是把本该LinkMovementMethod的处理直接封装到了clickablespan里面,然后使用内部类解决Touch事件。

预览图:

preview

具体看代码:
https://github.com/LiesSu/AndroidExtenders/blob/master/app/src/main/java/com/liessu/extender/span/ClickableSpanEx.java

当然,基于他的代码我稍微做了一点点改动。

这里说一下为什么我会采取这个方案。

首先我们不用针对写LinkMovementMethod外,事实上我觉得既然自己内部类接管了Touch事件,我们可以说不需要LinkMovementMethod。

除了上面这点,最重要的是事件拦截。

我们的评论控件承担着三个事件:

  • 内部用户的span点击事件
  • 外部TextView点击弹出输入法
  • 外部TextView长按弹出选择框(复制/删除)

然而如果我们点击span势必会点到整个TextView,这意味着如果我们点了span就会触发键盘弹出事件。

而如果按照原来的做法,我们需要在LinkMovementMethod里面用个标志位记录,然后在外部处理的时候拿到这个标志位判断是点的span还是整个textview,但现在因为@奉孝安在直接使用内部类处理,所以我们可以直接进行拦截。

这就是我选择这个方案的原因。

最后感谢@奉孝安在的解决方案-V-

这才叫“一起撸个朋友圈”嘛,最重要的不是弄出一个什么app,而是一起这两个字。(一直玩着单机的我忽然遇到一位留下评论的人,好激动)

如果您有好的建议,提议,希望能在评论区看到您的脚印或者GitHub提交PR。在下万分感谢-V-

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 项目地址:https://github.com/razerdp/FriendCircle一起撸个朋友圈吧这是本文所...
    Razerdp阅读 1,948评论 0 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 前言 工作找完了,已经干了两个星期。虽然经常加班,不过相比之前的工作,现在过得更加充实、更有意义。现在有点空闲时间...
    带心情去旅行阅读 72,251评论 42 237
  • 家里好像催婚了,不过90后好像也不是很年轻了,更何况我是1990年的。 每到年关时节,这是很多人欢乐的日子也是很多...
    闲少阅读 394评论 9 1
  • 本文写于 20190506,属于“半吊子”系列连载文章。 1 领路人猫老师 华安有两个微信号。也有两个朋友圈。 ...
    静听旁白阅读 501评论 0 0