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

开源代码要慎用,容易中毒:记划词模块重构感觉
本文原创,转载请以连接形式注明地址: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划词2code

结果又发现若是 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 的问题。

问题解决,又能够浪了。
浪起来

最后的最后

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

相关文章
相关标签/搜索