BannerViewPager系列文章共三篇,此文为第三篇,其它两篇参看下面连接:java
《BannerViewPager源码剖析》github
很高兴又和你们见面了,本篇文章是《BannerViewPager系列》的第三篇。就在不久前BannerViewPager发布了2.5.0版本,在这个版本中针对Indicator部分的代码进行了重构。本篇文章带你们一块儿来了解下本次重构Indicator中用到的设计思想,顺便回顾及加深认识一下静态代理模式。若是你还不了解BannerViewPager能够先阅读前两篇文章:canvas
也能够点此处到GitHub查看BannerViewPage源码。post
在BannerViewPager中针对IndicatorView已进行了两次较大的重构。第一次重构在第二篇文章《BannerViewPager源码解析》中也有提到。最初的Indicator是在BannerViewPager内部维护了一个icon圆点的List集合,在BannerViewPager内部会根据页面size动态添加指示器圆点。显然这种处理方式存在很大的弊端,即:不灵活、可扩展性低、性能相对较差等诸多问题。针对这一系列问题BannerViewPager在2.0.1中对Indicator进行了第一次重构。此次重构将Indicator改成自定义View,而且抽象出了IIndicator接口,极大的加强了Indicator的可扩展性。所以,在后续若干个版本迭代中Indicator逐渐支持了多种Style(CIRCLE/DASH)、多种滑动模式(SMOOTH/NORMAL)以及彻底自定义Indicator。相比最第一版本,无论在功能仍是性能上都有了很大的提高。可是,在后续版本的迭代中却又暴露出许多问题。而这些问题很大程度上影响了开发和使用。碰到的最大问题以下:性能
在2.5.0版本以前BannerViewPager已经支持了CIRCLE和DASH两种Indicator样式,与之对应的是CircleIndicatorView和DashIndicatorView。在BannerViewPager内部用简单工厂模式根据IndicatorStyle来生成对应的IndicatorView。2.5.0版本以前的代码以下:测试
# BannerViewPager
initIndicator(IndicatorFactory.createIndicatorView(getContext(), mIndicatorStyle));
# IndicatorFactory
public class IndicatorFactory {
public static BaseIndicatorView createIndicatorView(Context context, @AIndicatorStyle int indicatorStyle) {
BaseIndicatorView indicatorView;
if (indicatorStyle == IndicatorStyle.DASH) {
indicatorView = new DashIndicatorView(context);
} else {
indicatorView = new CircleIndicatorView(context);
}
return indicatorView;
}
}
复制代码
这么以来,每当添加一种Indicator Style时候都须要一个与之对应的IndicatorView类,而且须要修改IndicatorFactory 代码生成对应的IndicatorView。当Indicator Style愈来愈多的时候维护成本和使用成本都会随之增长。使用该库的开发人员须要记住每种Indicator Style对应的IndicatorView,做为该库做者的我也要面对愈来愈臃肿的代码结构,这是你们都不肯意看到的。所以,在这样的背景下IndicatorView的第二次重构就势在必行,不得不作。优化
若是关注该库比较早的同窗很早以前就应该看到了项目源码的README上添加了一条“优化重构Indicator”的需求。这个需求大概是在2.3.0时候就已经提出来的,但因为当时BannerViewPager中还有不少功能性的需求未开发完毕,所以在后续的若干版本中开发重点仍是放在了BannerViewPager的功能和优化上。而在2.4.3.1以后,BannerViewPager的功能需求基本开发优化完毕,所以,重构Indicator的需求才在2.5.0版本展开了。
回想初学Java时你们都应该学过Java的23种设计模式。看完设计模式发现也没什么难的,可是在项目中使用的时候就犯了难,每写一个需求都在想着是否是能够用某一种设计模式来实现呢?但当着手写时却又不知道该选取哪种。相信你们都经历过这样的迷茫期。对于这样的状况我以为顺其天然就好,只要平时多写代码,多思考,加之多看优秀的开源代码,这种设计思想逐渐的就会被积淀下来,可能在写代码的时候不经意间就发现本身使用了某一种设计模式。就像我在重构Indicator以前我并无考虑该用怎样的设计模式去写,可是在着手重构的时候就以为我应该这么作啊。等到写完以后回顾本身代码的时候发现这不就是一个标准的静态代理模式吗?
不知道如今你们对代理模式还记得多少,也不知道是否常常会在项目种用到代理模式。无论怎样,咱们先来回顾如下静态代理模式吧:
代理模式即为其它对象提供一种代理控制对这个对象的访问。在代理模式中,一个类表明另外一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,咱们建立具备现有对象的对象,以便向外界提供功能接口。
代理模式的结构图以下:
看定义老是那么的晦涩难懂,咱们仍是来举一个代理模式的场景:
Ryan想在上海买一套房子,可是他又不懂得房地产的行情,因而委托了中介(Proxy)来帮助他买房子。
咱们把这个场景经过Java代码来实现实现一下:
首先咱们把买房子的人抽象出来一个接口,接口中有一个buyHouse的方法:
public interface IPersonBuyHouse {
void buyHouse();
}
复制代码
Ryan想要买房子,因而他就须要实现这个IPersonBuyHouse接口:
public class Ryan implements IPersonBuyHouse{
@Override
public void buyHouse() {
System.out.println("Ryan:I want buy a House...");
}
}
复制代码
因为Ryan不了解房地产行情,因而将买房子的事情委托给了中介(Proxy),所以中介(Proxy)也须要实现IPersonBuyHouse的接口。可是中介不是给本身买房子的,而是买给其它有购房需求者的,因此他应该持有一个IPersonBuyHouse。而此处的购房需求者就是Ryan.因而Proxy代码以下:
public class Proxy implements IPersonBuyHouse{
private IPersonBuyHouse mIPerson;
public Proxy() {
mIPerson=new Ryan();
}
@Override
public void buyHouse() {
System.out.println("Proxy:I can help you to buy house");
mIPerson.buyHouse();
}
}
复制代码
接下来咱们在Main方法种测试一下Proxy类:
public class ProxyTest {
public static void main(String[] args) {
new Proxy().buyHouse();
}
}
复制代码
输出结果:
在第一章节中咱们就已经提到了当前Indicator的弊端:要维护多个IndicatorView,不利于开发也不利于使用。咱们当前的目的就是要将IndicatorView统一成一个。而咱们如今面临的困境是如何让一个IndicatorView承载多个Indicator Style?由于它既要绘制CIRCLE Style又要绘制DASH Style,以及之后可能还会增长更多的Style样式。在这种场景下咱们就能够想到代理模式来解决问题。
上一个章节中咱们举了一个静态代理的例子是正向思惟写下来的,那么本章中咱们就采用反向思惟,看下是如何倒逼出来静态代理模式的。
首先,咱们想要一个IndicatorView承接全部Style的绘制,那么正常来讲咱们就须要在IndicatorView中经过IndicatorStyle判断是哪一种样式,而后在IndicatorView中进行绘制,可是若是IndicatorStyle样式很是多的状况下,IndicatorView必然会变得很是庞大且臃肿。所以,咱们天然而然的就会想到将View的measure和draw的逻辑抽出来单独给一个类来完成,那么这个类中呢至少应该有measure和draw两个方法。所以,咱们将这个类的伪代码写出来大概应该是这样子的:
public class DrawerProxy {
public BaseDrawer.MeasureResult onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(Style==CIRCLE) {
return measureCircleIndicator(int widthMeasureSpec, int heightMeasureSpec);
} else {
return measureDashIndicator(int widthMeasureSpec, int heightMeasureSpec);
}
}
public void onDraw(Canvas canvas) {
if(Style==CIRCLE) {
drawCircleIndicator(canvas);
} else {
drawDashleIndicator(canvas);
}
}
}
复制代码
经过上一小节的操做咱们虽然将测量和绘制逻辑从IndicatorView中剥离了出来,可是DrawerProxy 这个类却承载了全部的测量和绘制逻辑。当Style样式多的时候一样会使DrawerProxy类变得臃肿不堪。所以,咱们又很天然而然的想到了应该把不一样Style的绘制逻辑单独抽出来,因而就有了CircleDrawer和DashDrawer两个类来分别处理各自的逻辑。但由于这两个类又要同时被放在DrawerProxy类中,且这两个类都又共同的方法。很天然的就想到要抽出一个CircleDrawer和DashDrawer的共同接口,因而就有了这样的一个IDrawer的接口:
public interface IDrawer {
BaseDrawer.MeasureResult onMeasure(int widthMeasureSpec, int heightMeasureSpec);
void onDraw(Canvas canvas);
}
复制代码
同时CircleDrawer和DashDrawer都应该实现该接口:
public class CircleDrawer implements IDrawer {
@Override
public MeasureResult onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ... 省略measure逻辑
return mMeasureResult;
}
@Override
public void onDraw(Canvas canvas) {
drawIndicator(canvas);
}
private void drawIndicator(Canvas canvas) {
// ... 省略draw逻辑
}
}
// DashDrawer与此相似,再也不贴出
复制代码
到了这里咱们在再来看DrawerProxy,发现这个类中一样须要onMeasure和onDraw,那他实现IDrawer接口顺利成章,同时它应该持有一个IDrawer类以便完成真实的测量和绘制任务。因而乎,完善以后的DrawerProxy类就成了这个样子:
public class DrawerProxy implements IDrawer {
private IDrawer mIDrawer;
public DrawerProxy(IndicatorOptions indicatorOptions) {
init(indicatorOptions);
}
private void init(IndicatorOptions indicatorOptions) {
mIDrawer = DrawerFactory.createDrawer(indicatorOptions);
}
public void setIndicatorOptions(IndicatorOptions indicatorOptions) {
init(indicatorOptions);
}
@Override
public BaseDrawer.MeasureResult onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
return mIDrawer.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void onDraw(Canvas canvas) {
mIDrawer.onDraw(canvas);
}
}
复制代码
到这里,咱们回过神来看一下,这不就是一个很是标准的静态代理模式吗?固然,这里也结合了简单工厂模式来生成对应的Drawer。因此,即便在彻底不了解静态代理模式的状况下,也不耽误咱们写出静态代理的代码。咱们来看下重构后的IndicatorView
public class IndicatorView extends BaseIndicatorView implements IIndicator {
private DrawerProxy mDrawerProxy;
public IndicatorView(Context context) {
this(context, null);
}
public IndicatorView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public IndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDrawerProxy = new DrawerProxy(getIndicatorOptions());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
BaseDrawer.MeasureResult measureResult = mDrawerProxy.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureResult.getMeasureWidth(), measureResult.getMeasureHeight());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawerProxy.onDraw(canvas);
}
@Override
public void setIndicatorOptions(IndicatorOptions indicatorOptions) {
super.setIndicatorOptions(indicatorOptions);
mDrawerProxy.setIndicatorOptions(indicatorOptions);
}
}
复制代码
能够看到经过静态代理模式简化完后的IndicatorView仅仅剩下了三十多行的代码,全部的测量和绘制逻辑都交给代理类DrawerProxy来处理,而DrawerProxy又将逻辑移交给对应的Drawer来完成。这样,全部的类都各司其职,代码简单明了!开发和维护起来也就变得更加驾轻就熟了!
最后,咱们来看下IndicatorView在BannerViewPager中的使用:
initIndicator(new IndicatorView(getContext()));
复制代码
本篇文章分享了对BannerViewPager的Indicator重构的一些经验。经过本篇文章相信你们对于静态代理模式也会有了更深的认识。重构后的代码在维护和使用上相比之前显然有了更明显的提高。可是并不等于如今的Indicator已经无懈可击了。相反,它还有很长的路要走。就目前而言,Indicator的SlideMode部分仍是又至关大的优化空间的,那么咱们就在后面的版本中拭目以吧。
好了,本篇文章到此就结束了,最后到github顺手点个Star呗,项目连接见文章末尾。