开源代码要慎用,容易中毒

本文原创,转载请以链接形式注明地址:http://kymjs.com/code/2016/08/13/01

开源代码要慎用,容易中毒
先说感受再看看我是怎么中毒以及怎么解毒的。
何为中毒,并不是说性能多么差,也不是代码多么烂,而是你容易受到别人代码的影响,不知不觉间就顺着他的思路走了。
当然,有一种避免的办法就是,拿来主义。我只拿你的代码用,完全不看你怎么写的,也不做功能定制和扩展,那当然也就百毒不侵。

开源实验室-Android划词

事情的经过是这样的(我要开始讲故事了)。

很久很久以前,天地混沌,盘古开天辟地以后有了太阳和月亮,天空和大地,Android 操作系统也随之崛起。但是,还缺少一样东西,那就是自定义控件。有一天,我奉众神之王宙斯之命创建一个通用划词模块,让每条产线都接入这个控件。 何为通用划词模块,就是要通用,要有划词,还是个模块。
😂😂😂 扯不下去了,你们自己看图识意吧。

开源实验室-Android划词

开源实验室-Android划词

开源实验室-Android划词

中毒开始

就是这样两个效果,点按选中文字高亮,并弹出悬浮窗。
这种控件,偷个懒吧,去 GitHub 上找找,这一找,就成了我中毒的,开始。为了不坑大家,我就不说我找的那个项目地址了。

看了代码,那个项目是这样来做的:在 TextView 长按下的时候,通过getOffsetForPosition()来获取到当前点击坐标最近的一个字符在全部文本的第几个位置,以及layout.getPrimaryHorizontal()来根据一个位置获取到这个位置的字符在 View 内部的坐标。然后在这个文本相应的位置显示一个悬浮窗,这个悬浮窗是一个自定义 View,里面有一个 PopupWindow ,在 PopupWindow 里面自定义了一个布局显示自己的内容。
结合我们自己的逻辑,原本网上的开源项目只有一个悬浮窗,而我们自己的业务需要显示三个悬浮窗,分别是:数据加载中的样子、正常显示翻译内容的样子,找不到翻译内容的样子。
既然已经有了一个雏形了,那就在这个项目上做扩展吧。(从有这个想法开始,就跌入了一个大大的深坑)

慢性中毒

扩展的方法就是仿照原有的写法,再自定义两个悬浮窗,然后根据显示逻辑来切换什么时候应该显示哪个悬浮窗。好不容易做好了三种状态要显示的悬浮窗都做好了,又发现长按的时候操作菜单和游标也需要显示在正确的位置上。
那再改改,根据长按的坐标,找到对应的文本在 TextView 第几个字,找到这个字在第几行,找到这行文字的顶部坐标再减去行间距,再把悬浮操作菜单。原项目为了方便直接获取到 TextView 的边界值,直接在 TextView 的外层套了一个 Scrollview,方便实时获取到 TextView 的坐标。

Android划词2

结果又发现如果 TextView 在一个 Scrollview 里面的时候,如果 Scrollview 发生滚动,悬浮窗应该自动 dismiss;
那再改改,滚动状态获取不到啊,那不如让 TextView 在初始化的时候递归遍历父控件,如果是可以滚动的控件就给这个控件添加一个滚动状态监听器,发生滚动直接 dismiss 悬浮窗。
至此,一个划词模块的开发是完成了,功能表现也良好。

中毒太深

我靠,这通用划词模块根本不通用啊,谁特么也不知道业务线接入时候的环境是怎样的。

  1. 你控件使用的是自定义控件,可业务线有可能自己想使用划词功能的控件也是个自定义的 TextView,那没办法让一个 Java 类同时继承两个类啊。
  2. 业务线有可能一个界面同时有多个 TextView 都要接划词功能,我们之前完全没有考虑这种情况啊。照这种状态可能每个界面同时显示多个悬浮窗出来。
  3. 每个 TextView 在使用的时候,外面都套了一个 ScrollView,这要是接入这控件的界面有多个 TextView,界面估计要卡到爆。

解毒,重构的开始

没办法,意识到自己中毒太深了以后,只想说一句,活该你他妈偷懒想用别人代码。
首先理清项目的结构。整个项目分三大块:接入控件(TextView),游标和高亮,悬浮窗。

第一步,为了控件能够通用,把接入控件抽出来做成一个接口,只暴露出该 View 有的方法,然后所有要接入划词功能的 View 都实现这个接口就好了,其中 getTouchX() 和 Y 是返回用户手指按下的坐标,需要在实现接口协议时重写 onTouch() 事件记录下坐标:

public interface IViewProtocol {
    Context getContext();
    CharSequence getText();
    CharSequence getSelectedText(); // 获取当前选中的文本
    int getTop();
    int getBottom();
    int getLeft();
    int getRight();
    float getTouchX();
    float getTouchY();
    int[] getLocationInWindow();
}

第二步:创建一个 Controller 负责控制悬浮窗的显示,并将原项目中的悬浮窗修改为自定义 PopupWindow(原项目是一个 View,包含一个 PopupWindow,又包含一个自定义布局)。 PopupWindow 最大的好处就是,它的显示逻辑和隐藏逻辑都可以交给系统去控制,就不需要我们手动再控制显示隐藏了。
定义一个接口,封装悬浮窗应该包含的方法:

public interface IFloatWindow {
    PopupWindow getFloatWindow();
    boolean isShowing();
    void showAsDropDown(IViewProtocol anchor, int xoff, int yoff, int gravity);
    void showAtLocation(IViewProtocol anchor, int x, int y, int gravity);
    void update(int x, int y, int width, int height, boolean force);
    void dismiss();
    int getHeight();
    int getWidth();
}

悬浮窗有两类,一类是非交互的,类似加载界面、游标。另一类是可交互的,例如上文截图。
不可交互的很简单,直接显示就好了,抽出公共基类 AbsFloatWindow,实现 PopupWindow 创建、初始化、显示位置等方法就够了。

可交互的需要考虑内部控件的事件,他们的内容区域是不同的,但是外部显示框框是一样的。
所以需要再多写一个基类 AbsContentView<T> extends AbsFloatWindow 其中泛型 T 表示这个悬浮窗要显示的内容实体类。例如服务器返回一段翻译好的数据给客户端,客户端要将翻译后的内容显示出来;但如果网络请求失败,应该显示另一种内容;服务器无法翻译的时候,又显示另一种内容的文本。
并且需要注意的是,有交互的控件还得要暴露出控件给业务线,让他们自己根据自己的业务修改控件的图片、文字、点击事件。

第三步:抽出 SelectionInfo,封装高亮显示的文本信息,包括文本的起始坐标,结束坐标,文本长度,高亮的背景颜色,在整个 TextView 文本的位置等。

public class SelectionInfo {
    public static final int DEFAULT_CLOLOR = 0xffb7d9f8;
    private Object mSpan; //用于显示背景颜色
    private int mStart;
    private int mEnd;
    private Spannable mSpannable;
}

最后

从改为使用 PopupWindow 开始,我们已经解决了界面中多 TextView 弹出多个悬浮窗的问题。
使用接口协议也完美的解决了业务线自定义控件的兼容性问题,不过为了他们使用方便,我们还是可以定义一个默认的 TextView 让他们选择,同时也是他们修改自己的自定义控件的一个模板。
把之前所有基于控件内部的坐标全部转换成根据View.getLocationInWindow()获取屏幕绝对坐标,也解决了嵌套一层 ScrollView 的问题。

问题解决,又可以浪了。

浪起来

最后的最后

记划词模块重构感受
——开源代码要慎用,容易中毒

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

推荐阅读更多精彩内容