博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一起撸个朋友圈吧 点赞列表背景问题修复
阅读量:7019 次
发布时间:2019-06-28

本文共 8320 字,大约阅读时间需要 27 分钟。

项目地址:https://github.com/razerdp/FriendCircle

这是本文所处文集,所有更新都会在这个文集里面哦,欢迎关注


首先非常感谢在文章中的评论,他发现在这篇文章中,我写的点赞列表并没有实现selector效果。

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

首先上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看一看,就会发现很多这种类型:

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

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

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

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

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


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

@Overridepublic void updateDrawState(TextPaint ds) {      ds.bgColor = mColor;}复制代码

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

(同时我也惊讶于,,,原来有这么多的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事件。

预览图:

具体看代码: 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-

转载地址:http://wxzxl.baihongyu.com/

你可能感兴趣的文章
转: ㊣华哥日记㊣ 12.14-如何去了解更多的东西
查看>>
centos7中没有安装ifconfig命令的解决办法
查看>>
【资料整理】vsftpd安装配置
查看>>
Python 字符串操作方法大全
查看>>
连接远程桌面必要操作
查看>>
Python文件(夹)基本操作
查看>>
heartbeat v1(CRM)+DRBD实现数据库服务器高可用集群搭建
查看>>
-bash: snmpgetnext: command not found
查看>>
ELK+Redis日志流程展示
查看>>
python之轻松玩转微信(wechat)机器人
查看>>
D-Link光猫超管密码破解教程
查看>>
碰撞器和触发器
查看>>
cisco acs 搭建radius
查看>>
jQuery|隐藏和显示
查看>>
VMware驱动程序"vmci.sys"的版本不正确 怎么解决
查看>>
除了默认的docker0网桥,启动Docker服务怎么指定使用的网桥
查看>>
Maven Tips
查看>>
win7下匿名ftp的搭建
查看>>
嵌入式开发 NVIDIA官方资源汇总
查看>>
Saltstack配置管理-业务引用haproxy
查看>>