欢乐的票圈重构——九宫格控件(中上)

项目重构的Git地址:
https://github.com/razerdp/FriendCircle
项目同步更新的文集:
http://www.jianshu.com/notebooks/3224048/latest
本文控件Git地址:
https://github.com/razerdp/PhotoContents

上集:欢乐的票圈重构——九宫格控件(上)

下集:欢乐的票圈重构——九宫格控件(中下)

上集概述

在上篇文章中,我们选择了继承FlowLayout来实现我们的九宫格,选择它的原因很简单,就是因为其layout过程实现很优秀,可扩展性也很强,所以这就是选择它的原因(我打死也不会说是为了偷懒)

OK,前言说完,本篇将会开始搭建这个控件的基本要素,可能涉及较多的代码方面的东西,也许略微枯燥【于是我这次选择了代码截图,弄得色彩斑斓点,看起来应该会爽很多】,我尽量述说的清晰一点


设计

撸一个控件并不是说随便继承个什么,然后就是一阵狂敲,在撸代码之前我们需要做的就是设计。

设计一个控件,往往都会从以下几个方面入手:

  • 对外的有:

    • 视觉样式
    • 交互方式
    • 数据绑定
  • 对内的有:

    • 逻辑处理
    • 灵活性、耦合性、扩展性、稳定性、可维护性等

如果具体到细节,还可以细到初始化方式、布局时机、绘制性能、touch传递响应、边界裁定、内存控制等等。。。。

当然,我们这里肯定不会有那么细致的描述了,否则感觉都可以写一本书了QAQ;

在这里我们就针对上面的内部和外部进行设计就好了。


1、视觉样式

用惯了朋友圈的我们肯定都知道朋友圈的九宫格是有几个明显的特征的:

  • 单张图片可大可小
  • 4张图片田字摆放
  • 最多九张图片
  • 多图的时候图片皆为边长一样的正方形

因此,针对这几个特征,我们可以根据FlowLayout的特性来进行初步的设想

  • 单张图片的情况下,我们仅仅控制图片大小的上限和下限,至于具体数值随图片大小而自适应。
  • 4张图片需要田字摆放,对于这个的处理我们可以覆写onLayout(),也可以通过设置LayoutParam的newLine参数,让其换行(FlowLayout的LayoutParam有该参数)
  • 最9张图片,这个应该是最好处理的
  • 多图的图片显示为正方形:同第二点,针对LayoutParam做操作,限制宽高即可。

2、交互方式

朋友圈的图片交互是点击图片的时候从图片的位置放大到整屏,退出的时候则是缩回到原来的位置。

在设计的时候,按照我的思路是这个控件顺便把图片浏览实现了,也顺便把动画给实现了,但考虑到后面的数据绑定等,以及单一职责准则,最终还是决定本控件仅仅负责图片的渲染和缓存处理,其余不管。

3、数据绑定

数据绑定其实跟外面使用者挂钩,也跟内部逻辑,扩展性等挂钩,所以这个需要慎重处理。

按照目前的思路,我们的控件所负责的东西是十分明确的:拿到View得到数据(图片地址)加载图片摆放控件渲染或缓存

也就是说,得到数据加载图片这两部分不应该由控件来限定死方式,而是应该暴露给使用者自行处理,因此,适配器模式是一个非常好的选择,而且大家也用的习惯。


对内设计部分主要看大家的码代码水平,,,这里就不着重讨论了。

初步搭建

很多人在写自定义控件的时候往往很久都下不了手,依我的经验来看(以我踩过的坑来看),很多时候都是顾虑的太多,,,换句话说,就是想得太多从而导致第一步都还没迈出,就想着第一百步的情景。

对此,我的解决方法很简单,首先不管三七二十一,先把包弄出来,以及把控件的类给弄出来。。。。至少在没方向的时候给自己一个方向也好。

于是我们就有了下面初步结构:

包结构

接下来,既然选择了适配器模式,我们就首先搭建我们的adapter吧。


1、Adapter的设计:

适配器模式简单地说其实就是对客户端统一接口(统一为设计者希望最终得到对应类的接口),使复杂的使用场景或不兼容的类或接口都能正常的工作在一起。

但是,在数据和View的刷新间,我们总得需要一个桥梁,用来沟通ViewBean

说到通知,我们首当其冲想到的是。。。。EventBus对吧,既然想到了EventBus,也就不难想到观察者这货了。

所以在写Adapter前,我们先把桥搭好。

1.1、Observer

在adapter包下新建一个observer包,我们将在这里完成我们的观察者的搭建。

首先我们需要一个观察者(Observer)

这个观察者只要做的只有两件事:

  • 通知requestLayout()【为了触发measure()/layout()】
  • 通知invalidate()【为了重绘】

因为系统本来就已经有相关的观察者,所以我们直接使用就好,毕竟咱们这个观察者的功能实在有点简单。。。

简单观察者(PhotoBaseDataObserver)

OK,观察者有了,接下来我们需要的是被观察的对象,一般被观察的都是 xxx -able结尾(able就是“可以..XXOO..的”)

当然,被观察的对象可能有好多个,也可能好多种实现,所以我们先抽象出一个被观察对象作为以后其他被观察对象的父类。

被观察者抽象(PhotoImageObservable)

最后就实现我们的具体观察对象就行了

用于控件adapter的观察对象(PhotoAdapterObservable)

到目前为止,看起来还是很轻松愉快的嘛~


1.2、Adapter

Adapter的设计,我们只需要认准一个东西就行:

不管具体实现类怎么干,反正我只想要我需要的东西,最终实现类必须返回这个东西给我

所以与其说定制一个Adapter,倒不如说我们是在定义一个协议,当然,换到Java说法就是定义一系列的接口。。。

在开干之前,我们可以思考一下,我们需要的是什么。。。

想了大概30秒,几乎可以确定,我们似乎需要的东西不多,就一个:ImageView,其他的似乎都不太需要。。。(真好满足)

目标很明确,所以我们可以动手了。

不过因为是一个初步的搭建,所以就没有抽象为接口,而是以一个抽象类来制定这个Adapter。

PhotoContentsBaseAdapter

代码量很少,我们只做了几件事:

  • 注册观察者(当然,观察者在控件实现)
  • 制定接口:
    • onCreateView(),很明显,跟ListView非常类似,这也是为了方便使用这个控件的人进行开发,这个方法返回的就是ImageView,至于返回的这个View怎么摆放,是我们控件该干的事情了。

    • onBindData(),这个方法是用来加载数据用的,上一个方法目的用来创建View,这一个方法则是用来加载图片

    • getCount(),获取数据(或者说图片)的数量

Adapter的设计就这样轻松愉悦的搞定了,当然这只是一个初步的,我们其实还可以进一步的抽象。。。

比如:

  • 接口化,而不用抽象类,因为对于Java而言,接口简直就是各种干爹啊,我们不能多继承,但接口提供了另一种的“多继承”

  • 泛型:如您所见,我们的onCreateView指定返回ImageView,但实际项目中,我们可能很多自定义的ImageView,如果实现类里强转什么的,既不方便于静态检查,又不那么爽,所以我们其实可以采取泛型,比如:

public abstract class PhotoContentsBaseAdapter<V extends ImageView> {
...注册观察者,略

    public abstract V onCreateView(V convertView, ViewGroup parent, int position);

    public abstract void onBindData(int position, @NonNull V convertView);

    public abstract int getCount();
}

嘛,其实还有好多好多了啦,喜欢就折腾一下咯-V- 这个项目是16年底才粗略的撸了一个,所以就不怎么维护了(基本满足我朋友圈项目使用就好~毕竟懒。。。。)


2、控件的设计(因为篇幅,这里仅讲述部分内容,剩余内容放到【中下】篇):

嘿嘿,终于到了重头戏了,前面干了那么多东东,为的就是养这个亲儿子啊。。。

这个控件代码量不多。。。嗯。。。。至今码了560多行,嘛~看起来这个控件还是很年轻嘛。。。

不过对于一篇文章来说,500多行还是太多太多了,所以接下来就针对几个地方着重讲述吧

整理了一下代码,着重讲述的地方我已经标注了起来(如果图片模糊,PC上可以先放大图片,然后右键新窗口打开再放大就可以看完整了,手机上的话,经过我的小手机测试,直接放大也是可以看得挺清晰的):

咳咳,记重点

因为篇幅,所以我们这里着重说一下Adapter相关吧(毕竟上面那么多空间都在描述adapter)

2.1、观察者

首先我们搭桥,在上面的文字里,我们把桥的桥墩搭好了,所以这里我们就直接铺桥。

要使控件跟Adapter的观察者联合起来,要做的就是把我们的控件作为观察者给塞进去。

我们可选择两种方式:

  • 控件实现观察者的接口,然后把自己塞进去

  • 控件内部类实现观察者接口,然后把它塞进去

这里我采取后者。

首先实现一个内部类:

观察者实现

在onChanged里面我们执行requestLayout(),也就是要求控件重新布局以触发measure和layout,毕竟setAdapter的时候可能是set了不同的Adapter,所以我们需要做这两步操作以适应新的需求。

在控件里我们直接new一个观察者:

    private PhotoImageAdapterObserver mAdapterObserver = new PhotoImageAdapterObserver();

这个观察者就是我们和adapter沟通的桥梁了,以后需要换adapter的时候,我们先取消注册然后再重新new一个并注册就好了。

接下来就是setAdapter方法

setAdapter()

在setAdapter前,我们需要先注销掉原来的观察者,然后执行控件的初始化,接着重新new一个观察者并注册,最后才requestLayout(),也许有人问为何这里需要requestLayout(),上面的观察者不是应该实现了么,这里需要注明的是,我们的观察者的触发条件是外部执行adapter.notifyDataChanged(),平时我们用刀setAdapter也不会说下一行立刻就执行adapter.notifyDataChanged()吧,所以在这个方法里直接就提醒控件进行测量,重绘。

那么,写到这里,关于这个控件的一些细节已经开始渗透了,本篇主要讲的是控件的搭建和Adapter的搭建,下一篇我们将会着重说明一下关于缓存池和View的创建时机以及方法这几个部分。

然后就可以进入下篇了。

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

推荐阅读更多精彩内容