前面介绍了MVC,MVP两种架构模式,使用MVC开发中Android应用的时候,Activity、Fragment相当于控制层, 布局视图归于视图层, 数据库等数据模型相当于数据层。
这种方式一般控制层不仅仅只是处理业务逻辑,还负责View的展示等,并且数据层和视图层是有交互的,不利于解耦。
而MVP则是使用Presenter层将数据层和视图层完全隔离开来,比如下图作为例子:
(图来自http://tech.vg.no/2015/07/17/android-databinding-goodbye-presenter-hello-viewmodel/)
视图层与用户交互的时候用户请求loadUser命令,这时候视图层并不直接处理这个任务,而是将其传递给Presenter层,紧接着,presenter层,将showLoading请求发送到View层,并请求
数据层从数据库中获取用户信息,通常情况下数据层也会通过设置回调的方式持有Presenter引用,在Presenter接收到数据加载结束的时候,请求View层调用showUsers方法,显示用户信息。
整个结构十分清晰,但是它有个很大问题就是会照成Presenter层会变得十分庞大,并且Presenter层类会变得繁多。

下图是上面例子用MVVM替代后的架构图,在用户发送请求的时候,View会将事件发送给ViewModule,ViewModel从Module层获取数据,在数据获取完后只要改变Bean对象ViewModule就可以直接将数据呈现在View上。
整个结构的核心层是ViewModule层,在Android开发中我们使用的是DataBinding机制来实现这层:这样的好处显而易见,整个框架少了更新View的逻辑,也就是少了MVP模式中的showXXXX方法。只要数据一变视图层就立刻改变类了。

[Reference]

blog.csdn.net/jdsjlzx/article/details/51174396
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0603/2992.html
https://www.aswifter.com/2015/07/11/android-data-binding-example/
http://www.tuicool.com/articles/7Zz6J3r
http://www.zhihu.com/question/30976423
http://tech.vg.no/2015/07/17/android-databinding-goodbye-presenter-hello-viewmodel/
http://blog.csdn.net/fancylovejava/article/details/50821616
http://www.zhihu.com/question/33538477?sort=created
http://hannesdorfmann.com/categories/
http://hannesdorfmann.com/android/mosby
http://hannesdorfmann.com/mosby/first-app/
http://hannesdorfmann.com/android/mosby-playbook
https://github.com/konmik/nucleus
https://github.com/square/mortar
https://github.com/inloop/AndroidViewModel
https://github.com/Nilzor/mvp-to-mvvm-transition

MVP 引入的必要性

在上一篇博文结束的时候提到了MVC模式的缺点–View层和Module层会产生直接交互,这有何不可?我们知道一般View层都是一些Activity和Fragment,如果采用MVC模式就会导致整个Activity的臃肿。因为View层和Module层之间大部分时候是有业务逻辑的,拿回来的数据不一定就能直接展示,这就导致
Activity不但需要负责与用户之间的交互,又引入了大量的逻辑代码。为了解决这个问题,我们引入了MVP模式。

MVP的简介

MVP 实际上是从MVC模式演变过来的,它使用Presenter层将Module层和View层隔离开。它也是由三层结构组成:
下面是MVP模式下各层的划分规则:
View层:
View层通常是一些Activity、Fragment,以及各种View,它负责显示和处理和相应用户的交互事件,并且每个View中往往会包含一个或多个Presenter的引用。每个Presenter一般与一个Module相关,View层和Presenter层的交互是双向的,即View层可以调用Presenter的逻辑方法,Presenter也可以控制View层的信息展示。
Presenter层:
Presenter是Model层和View层的桥梁,在MVC模式中M,V是直接耦合的,而在MVP模式中通过Presenter将二者隔离开来,它负责从Model拿到数据进行处理并返回给View。Presenter和View层以及Modle层之间是通过接口进行交互的
Model层:
和MVC一样,只包含数据的模型以及数据模型的操作处理,它也可以包含两个部分dao和bean
总而言之:View层负责显示,Presenter负责逻辑处理,Model提供数据以及数据处理操作。下面是MVC和MVP之间结构的示例图:

二者的最大区别集中在控制层上,MVC的控制层相对分散,并且仅仅相当于一个转发其的作用,而在MVP模式下控制层的占的比重更大,也更加集中。使用MVP可以将Activty中包含的大量逻辑操作放到控制层中,从而避免Activity的臃肿。

下面是一个简单的例子:
读出和存入用户信息
首先实现Module层:

  • 创建用于封装用户信息的bean对象:

    public class UserBean {  

    private String mFirstName;
    private String mLastName;

    public UserBean(String firstName, String lastName) {
    this. mFirstName = firstName;
    this. mLastName = lastName;
    }

    public String getFirstName() {
    return mFirstName;
    }

    public String getLastName() {
    return mLastName;
    }
    }
  • 创建操作bean对象的dao 对象,这里用于将用户信息存储到数据库中,这里仅仅给出接口信息,具体实现不是该博文的重点。

    public interface IUserDao {  
    public UserBean load(int id);
    public void saveUser(int id, String firstName, String lastName)
    }

    我们先看下上面的Module层,它的特点很明显,十分灵活,目前是将数据存储到数据库中,如果要将数据存储到远程服务器或者以其他形式存储,只要实现IUserDao,并根据需求覆写load 和 saveUser方法即可。

  • 创建view层 这里仅仅列出接口,它的特点也是上述提到的十分灵活,要实现那种方式展示直接实现IUserView接口,并覆写这些方法即可。

    public interface IUserView {  
    int getID();
    String getFristName();
    String getLastName();
    void setFirstName(String firstName);
    void setLastName(String lastName);
    }
  • 创建presenter 通过IUserView和IUserDao接口操作model层和view层,Activity可以把所有逻辑转交给presenter处理,从而将逻辑从activity中分离出来,从而避免Activity的臃肿。

    public class UserPresenter {  

    private IUserView mUserView;
    private IUserDao mUserModel;

    public UserPresenter(IUserView view) {
    mUserView = view;
    mUserModel = new UserDao();
    }

    public void saveUser( int id, String firstName, String lastName) {
    mUserModel.saveUser(id, firstName, lastName)
    }

    public void loadUser( int id) {
    UserBean user = mUserModel.load(id);
    mUserView.setFirstName(user.getFirstName());
    mUserView.setLastName(user.getLastName());
    }
    }
  • Activity中实现IUserView接口,在其中操作view,实例化一个presenter变量,将Activity的逻辑转移给它。

    public class MainActivity extends Activity implements OnClickListener,IUserView {  

    UserPresenter presenter;
    EditText id,first,last;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout. activity_main);

    findViewById(R.id. save).setOnClickListener( this);
    findViewById(R.id. load).setOnClickListener( this);
    id = (EditText) findViewById(R.id. id);
    first = (EditText) findViewById(R.id. first);
    last = (EditText) findViewById(R.id. last);

    presenter = new UserPresenter( this);
    }

    @Override
    public void onClick(View v) {
    switch (v.getId()) {
    case R.id. save:
    presenter.saveUser(getID(), getFristName(), getLastName());
    break;
    case R.id. load:
    presenter.loadUser(getID());
    break;
    default:
    break;
    }
    }

    @Override
    public int getID() {
    return new Integer( id.getText().toString());
    }

    @Override
    public String getFristName() {
    return first.getText().toString();
    }

    @Override
    public String getLastName() {
    return last.getText().toString();
    }

    @Override
    public void setFirstName(String firstName) {
    first.setText(firstName);
    }

    @Override
    public void setLastName(String lastName) {
    last.setText(lastName);
    }

    }

    有的时候比如Moduel存储结束后需要通知给Presenter,这个可以将presenter也抽出一个接口,并在调用Model层的Dao的时候将这个接口传入,在Module层操作完后,回调这个接口中的对应方法即可。

最后给大家泼点冷水,虽然MVP是很好的框架模式,但是在实际项目由于是多人开发维护的所以有时候并不是每个人都会遵守这个模式,特别是项目紧张的时候,所以很多时候会看到四不像的MVP。如果逻辑耦合性不是很强的话尚可重构,
如果耦合性大的话,就十分无奈了,并且一般经过测试的代码,大家都很“敬畏”,生怕动了一点引起了其他不容易发现的问题,所以很经常看到这种情况,也只能呵呵了。

由于并非计算机科班出生,并且之前都是搞嵌入式的,使用的大多数是C语音,大家都知道C语音是面向过程的语言在设计模式方面强调得比较弱,其实在嵌入式设计中也有对应的设计模式,但是和这里要讲解的设计模式有较大的区别,在嵌入式设计中记得最深的就是策略与实现分开,这也是为什么需要有HAL层的原因,HAL层之上都是一些策略相关的东西,而HAL层之下则是一些与实际硬件相关的接口,HAL层通过一些接口将策略与实现结合起来。如果有兴趣了解这部分思想的话,大家可以看下实时嵌入式系统FreeRTOS,它是一个可以移植到各种嵌入式主控芯片上的操作系统,代码不多,但是麻雀虽小五脏俱全。之前研究生作项目的时候用的就是它。
好像扯远了,其实设计模式要解决的是如何尽可能得复用现有代码,如何使得框架以不变应万变。接下来的这几个专题将会介绍23种设计模式,MVC,MVP,MVVM等。在介绍这些内容之前我在这里先推荐几本自己觉得不错的书籍,干我们这行的基本都熟悉这几本书。


豆瓣链接:https://book.douban.com/subject/4260618/


豆瓣链接:https://book.douban.com/subject/5343318/


豆瓣链接:https://book.douban.com/subject/26644935/
买这本书有种失望的感觉,可能是我对它的要求太高了,个人觉得就是一本 设计之禪 + 源码解析,不过还是十分值得一读的。


豆瓣链接:https://book.douban.com/subject/1052241/
这边书我坦白说我没买过,大家都说好。有空的时候准备入手一本看看。

还有Head First 设计模式,大话设计模式,等等等等,反正豆瓣上一搜一大把,不过建议大家选一两本认认真真看下,空余时间找找博客看下就OK了,这些书看多了都一个样,无非就是介绍设计模式的时候背景故事换了一下,被字句换成把字句,把字句换成被字句罢了。
之前自己就是花了好长时间浪费在找书上面,但是学习这东西就像吃饭一样,不能说第3碗吃饱了,就否定前两份的价值,这东西说不准,大家实在这些书都觉得看不下去的时候可以到豆瓣上翻翻,然后下载电子档看下。

好了正式进入正题,介绍下MVC模式:

MVC模型

MVC模型分成三层 业务模型层-视图层(用户界面等信息展示部分)-控制层(控制器)是一种用业务逻辑、数据、界面显示分离的方法组织代码。
使用MVC的目的是将M(业务模型)和V(信息展示层)的实现代码分离,从而使同一个程序可以使用不同的表现形式。通过控制层来确保M和V的同步,一旦M改变,V能够同步更新。
下面是具体的职责分布:

  • Model:是应用程序中用于封装与业务逻辑相关的数据以及对这些数据的处理方法。通常负责在数据库中存取数据。它对数据具有直接访问的权力,Module层不依赖视图层和控制层,换句话说,Module不关心它会被如何显示或如何被操作。它只负责接收存取数据的请求,一般而言Module中数据一旦发生变化都会通过消息机制发布。那些需要监听此模型的部分必须事先在此模型上注册监听事件,这种监听最常见的就是通过观察着模式来完成。
    一般在项目中如果使用MVC模式的话都将为Model层分配两个package,一个是bean 用于封装数据模型,一个是Dao,用于封装对bean包数据模型的操作,最常见的就是从数据库中读出数据放到bean中,或者将bean数据写入到数据库,当然还有网络操作。
  • View:是应用程序中处理数据展示的部分。通常视图是依据模型数据创建的,依赖于数据模型。一般不在视图层中进行业务逻辑处理,业务逻辑处理一般放在控制层。为了实现视图层的实时刷新,视图层需要实时监听数据模型的变化
  • Controller:是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据,实际上是View层与Module层的衔接。

视图层View 与用户交互,在接收到用户的请求后,传送指令到控制层,在控制层当中完成业务逻辑处理,然后再发送请求到 Model 层要求改变状态,Model层接收到请求后将新的数据发送到视图层,视图层根据要求做对应的更新,并将数据展示给用户。

下面是在Android开发中较为常用的MVC模式:
我们以最简单的用户登入为例子,如果使用MVC模式开发,那么整个事件流如下,用户在登入界面上输入用户名和密码,然后点击登录按钮,这时候事件流到控制层,控制层对用户名和密码作初步处理后,将请求发送给Module层中的Dao,
Dao 通过网络发送登入请求,View同时通过观察着模式等方式监听Module中的变化,当Dao 从网络中获取到数据后封装到bean,然后通知View层来获取这个Bean,View层接收到这个事件后,取走bean,使用获取到的数据更新界面。
从下图中看到MVC的不足没?View与Model会有一定的交互。并且返回的数据并不通过Control层,如果需要在返回数据后需要做一些处理再更新View呢?是吧,所以就有了下一个博文所要介绍的MVP模式。

和自定义View一样ViewGroup的自定义也分成测量和绘制两个部分:

ViewGroup 的测量:

在ViewGroup大小设置为wrap_content 的时候,ViewGroup就需要知道子View的大小,从而确定自己的大小。获得子View的大小是通过遍历子View调用子View的Measure方法来实现的。
和自定义View不同的是自定义ViewGroup最重要的任务就是确定如何放置子View,这个就是View的Layout过程。Layout过程是通过遍历来来调用子View的Layout方法,并指定其具体显示的位置,从而来决定其布局位置。这个工作是在onLayout方法中完成的。
所以在自定义ViewGroup的时候一般都会去覆写onLayout方法。

总而言之,言而总之:
一般都会覆写onLayout方法,并覆写onMeasure方法,因为很少布局不支持wrap_content属性。

ViewGroup 的绘制:

在ViewGroup本身没有什么内容可以绘制的,它只是一个容器,要呈现的内容都是由其子类来提供的,在ViewGroup中会调用dispachDraw方法来绘制子View。

自定义View有如下三种途径:

  • 继承现有控件,对现有控件进行扩展
    这种方式主要用于在现有的控件基础上通过覆写onDraw方法来改变现有View的外观。通过这种方式还可以添加新的属性,以及添加新的交互事件等。
  • 通过将几个控件组合在一起形成一个新的控件
    这种方式一般通过继承一个ViewGroup,将一些现有控件添加到容器中,从而组合成新的复合控件。
    在这种情况下一般有如下步骤:
    • 新建子View,为子View设置对应的属性
    • 新建LayoutParams以及调用LayoutParams.addRule创建布局属性
    • 调用addView并传入子View,以及上述创建的LayoutParams来确定其位置
  • 从头到尾重新实现一个View
    这种方式见之前总结的自定义View的博文。

自定义的View一般都不是静态的,所以都需要对其进行刷新:
根据实际使用的不同场合需要采用不同的刷新策略,下面是三种常用的刷新方式:

  • 不使用多线程和双缓冲
    这种情况一般使用在不需要频繁刷新的情况下,只需显式地调用View中的invalidate()方法系统会自动调用View的onDraw()方法。

  • 使用多线程但不使用双缓冲
    这种情况需要开启新的线程,但是我们知道如果在非UI线程中访问View对象就会报如下错误:

    android.view.ViewRoot$CalledFromWrongThreadException
    Only the original thread that created a view hierarchy can touch its views.

    这时候就需要结合Handler来处理了,只要新建一个Handler,在新线程创建并发送一个Message,然后再主线程中捕获、处理该消息。

  • 使用多线程和双缓冲
    这个就是通过SurfaceView来实现的,我们在绘图专题中已经对SurfaceView进行介绍了,大家可以翻阅之前的博文,查看。

Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。
Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。
Android程序中可以使用的界面刷新方法有两种,分别是利用Handler和利用postInvalidate()来实现在线程中刷新界面。

Android 中最让人感到兴奋的就是看到GitHub上各种自定义View的实现了,而自定义View对自己个人而言是那种痛并快乐的一件事情,一旦次数多了,不痛了也就能够感受到自定义View的所带来的快乐了,
但是自己坦白还没到那种水平,自定义View有一定的固定套路,也有一部分很灵活的部分,而最重要的部分恰恰是后者,该博文将只会介绍Android控件的整体架构,自定义View固定流程,后续将会另起一个专题专门将工作这段时间所实现的一些比较好玩的自定义View。

在开始本篇博文之前,需要介绍下Android 控件的整体架构,这样对整个流程的介绍会更清晰:

Android 控件的整体架构

Android中的所有可视化组件都是从View派生出来的,这些可视化组件通常被称为控件或者小组件,ViewGroup类是对View类的扩展,它用来包含多个视图,ViewGroup主要用于管理子视图布局和创建可重用的复合组件。它是View的容器,可以存放View 还可以存放ViewGroup,从而形成树状结构。
每个控件树都有一个ViewParent,它负责所有交互事件的调度和分配,并且顺着控件树,上层的控件负责下层空间的测量与绘制,每个View占据屏幕上的一个矩形区域,每个View对象负责这个矩形区域的测度、布局、绘图、焦点改变、滚动、触摸,手势等交互事件的处理。
根节点要求它的孩子节点绘制它自己,每个ViewGroup节点要求调用自己的子视图绘制自己。子视图可以往父视图中请求指定的尺寸数据,但是每个子视图的父视图对象对子视图的尺寸有最终决定权,也就是说子视图可以告诉乎视图要求多大的空间,但是父视图会纵观全局来决定实际分配给每个子视图的大小。如果元素有重叠的地方,重叠部分后面绘制的将在之前绘制的上面。经过上述过程至顶向下的绘制最终绘制出整个页面;

每个Activity都持有一个Window对象,它是一个抽象类,它有一个子类PhoneWindow是Window类的具体实现,可以通过PhoneWindow具体去绘制窗口。
DecorView 是PhoneWindow类的内部类,它是窗口界面的顶层视图,是所有应用窗口的根View。大部分事件都是由它传递给view,一般情况下它有上下两部分组成,它将要显示的内容呈现在PhoneWindow上。如下图所示,一个是TitleView,一个是ContentView,我们可以通过setContentView来将布局添加到ContentView中。

View以及ViewGroup的测量

为什么需要测量,这是因为在绘制一个View的时候不仅需要知道它的形状等信息,还需要知道它的大小信息。View的绘制工作在onMeasure方法中进行,通过这个方法可以指定该控件在屏幕上的大小,重写该方法时需要计算控件的实际大小,然后调用setMeasuredDimension(int, int)将确定尺寸数值设置为控件的实际大小。
onMeasure被调用的时候传入两个参数widthMeasureSpec和heightMeasureSpec。每个都是一个32位的int值它分成两个部分高两位为测量模式,低30位为测量的大小。
我们可以通过:
int mode = MeasureSpec.getMode(xxxxxxx)获取到模式,
int size = MeasureSpec.getSize(xxxxxxx)获取到尺寸数值。

测量模式有三种:
EXACTLY:精确模式,当我们将layout_height以及layout_width设置为具体数值时,或者设置为match_parent的时候,使用的是这种模式
AT_MOST:最大值模式,当控件的layout_height以及layout_width被设置为wrap_content属性的时候,使用的是这种模式,这种模式下控件的尺寸只要不超过父控件允许的最大尺寸即可。
UNSPECIFIED:这种情况我见得不是很多,基本上没有用到。

View 默认情况下支持EXACTLY模式,因此如果不重写onMeasure方法时,只能使用EXACTLY模式,所定义的View只能指定具体尺寸,或者是match_parent,而不能是wrap_content.如果需要支持wrap_content就必须重写onMeasure方法。

@Override    
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int finalWidth = getMeasuredLength(widthMeasureSpec, true);
int finalHeight = getMeasuredLength(heightMeasureSpec, false);
setMeasuredDimension(finalWidth, finalHeight);
}

private int getMeasuredLength(int length, boolean isWidth) {
int specMode = MeasureSpec.getMode(length);
int specSize = MeasureSpec.getSize(length);
int size;
int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.EXACTLY) {
size = specSize;
} else {
size = isWidth ? padding + DEFAULT_WIDTH : DEFAULT_HEIGHT + padding;//提供一个默认的值
if (specMode == MeasureSpec.AT_MOST) {
size = Math.min(size, specSize);
}
}
return size;
}

View的绘制:

我们知道自定义一个View需要继承自View并重写构造方法以及onDraw方法。

  • 覆写构造方法
    一般我们在Android Studio创建一个View的时候,会要求复写构造方法,默认情况下会有三个构造函数:
public void CustomView(Context context) {}
public void CustomView(Context context, AttributeSet attrs) {}
public void CustomView(Context context, AttributeSet attrs, int defStyle) {}

第一个构造函数用在代码中动态创建对象时使用的,如果只打算在代码中动态创建一个view而不使用布局文件xml,那么就直接实现这个构造方法就可以了,但是一般都不这么做,因为难保证后面不会用在layout布局中,虽然后面使用的时候添加一个也很容易,但是一般项目代码都很庞大
。如果没有遵守这个规则的话,后面谁来在你的平台上开发的话,将其用在XML布局文件中,就会引起问题,虽然这种错误很好排查,但是如果遇到这种情况一般都会问候下原先维护这个代码的那个人。所以如果不想在离职后还被别人挂念还是继续实现后面两个构造方法吧。

第二个构造方法比第一个构造方法多了一个AttributeSet类型的参数,通过布局文件xml创建一个view时,这个参数会将xml里设定的属性传递给构造函数。如果采用xml方法却没有实现这种构造方法,那么虽然编译的时候会顺利通过但是运行时就会报错。

第三个构造方法比第二个构造方法多了一个defStyle的参数,这个参数用来指定view的默认style,如果为0将不会应用任何默认的style。那么这个值又是从哪里传过来的呢?
一般这个系统是不调用的,一般用于提供给第二个构造方法使用的,在第二个构造方法中会传给第三个构造方法一个默认的style id。

public class CustomView extends View {

public CustomView(Context context) {
super(context);
}

public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.CustomizeStyleRef);
}

public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
  • 自定义属性
  1. 定义属性

在res/values/attrs.xml文件中为添加自定义的属性的定义

<resources>
<declare-styleable name="Customize">
<attr name="attr_1" format="string" />
<attr name="attr_2" format="string" />
</declare-styleable>
<attr name="CustomizeStyle" format="reference" />
</resources>

这里需要了解下属性的类型有哪些:

  • reference:引用某一资源ID。
    定义: 
    <attr name = "background" format = "reference" />
    属性使用:
    android:background = "@drawable/图片ID"
  • color:颜色值。
    定义:
    <attr name = "textColor" format = "color" />
    属性使用:
    android:textColor = "#00FF00"
  • boolean:布尔值。
    定义:
    <attr name = "focusable" format = "boolean" />
    属性使用:
    android:focusable = "true"
  • dimension:尺寸值。
    定义:
    <attr name = "layout_width" format = "dimension" />
    属性使用:
    android:layout_width = "42dip"
  • float:浮点值。
    定义:
    <attr name = "fromAlpha" format = "float" />
    属性使用:
    android:fromAlpha = "1.0"
  • integer:整型值。
    定义:
    <attr name = "framesCount" format="integer" />
    属性使用:
    android:frameDuration = "100"
  • string:字符串。
    定义:
    <attr name = "apiKey" format = "string" />
    属性使用:
    android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g"
  • fraction:百分数。
    定义:
    <attr name = "pivotX" format = "fraction" />
    属性使用:
    android:pivotY = "300%"
  • enum:枚举值。
    定义:
    <attr name="orientation">
    <enum name="horizontal" value="0" />
    <enum name="vertical" value="1" />
    </attr>
    属性使用:
    android:orientation = "vertical"
  • flag:位或运算。
    定义:
    <attr name="windowSoftInputMode">
    <flag name = "stateUnspecified" value = "0" />
    <flag name = "stateUnchanged" value = "1" />
    <flag name = "stateHidden" value = "2" />
    <flag name = "stateAlwaysHidden" value = "3" />
    <flag name = "stateVisible" value = "4" />
    <flag name = "stateAlwaysVisible" value = "5" />
    <flag name = "adjustUnspecified" value = "0x00" />
    <flag name = "adjustResize" value = "0x10" />
    <flag name = "adjustPan" value = "0x20" />
    <flag name = "adjustNothing" value = "0x30" />
    </attr>
    属性使用:
    android:windowSoftInputMode = "stateUnspecified | stateUnchanged | stateHidden">
    属性定义时可以指定多种类型值。
    <attr name = "background" format = "reference|color" />
    android:background = "@drawable/图片ID|#00FF00"

2.在xml中为相应的属性声明属性值

  • 直接在layout中使用属性:这个就不做介绍了,一般布局很多都是这种方式 custom:attr_1=”attr one”
  • 设置style并在style中设置属性
    <resources>

    <style name="DirectStyle">
    <item name="attr_1">attr one from DirectStyle</item>
    <item name="attr_2">attr two from DirectStyle</item>
    </style>

    </resources>

    使用方式:
    style="@style/DirectStyle"

  • 在主题中指定在当前Application或Activity中属性的默认值
    <style name="AppTheme" parent="AppBaseTheme">
    <item name="attr_1">attr one from Theme</item>
    <item name="CustomizeStyleRef">@style/CustomizeStyle</item>
    </style>
  • 在defStyle提供默认值

它们的顺序如下:

XML中定义>style定义>由defStyle提供默认值>在Theme中指定的值

下面是个人实现自定义View为View添加属性时候的固定步骤:

<resources>
<style name="AppBaseTheme" parent="android:Theme.Light">
</style>

<style name="AppTheme" parent="AppBaseTheme">
//1 定义一个Style引用
<item name="CustomizeStyleRef">@style/CustomizeStyle</item>
</style>

//定义一个Style CustomizeStyleRef 指向它,这个是为主题提供默认的属性值
<style name="CustomizeStyle">
//Style项
<item name="attr_1">attr one from CustomizeStyle</item>
</style>

//2 定义一个默认的style用于提供默认值,这个在defStyle提供默认值
<style name="DefaultCustomizeStyle">
<item name="attr_1">attr one from defalut style</item>
</style>

//3 这个用于直接在xml通过style方式提供属性值
<style name="DirectStyle">
<item name="attr_1">attr one from DirectStyle</item>
</style>

</resources>
  • 如何获得属性值
    public CustomView(Context context, AttributeSet attrs) {
    this(context, attrs, R.attr.CustomizeStyleRef);//注意这里是从attr获取
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Customize, defStyle,R.style.DefaultCustomizeStyle);
    try {
    attr_1 = a.getString(R.styleable.Customize_attr_1);//注意这里!!!!!格式是定义属性名_属性名
    } finally {
    a.recycle();//TypedArry是一个共享的资源,使用完毕必须回收它。
    }
    ...
    }
  • 覆写onDraw方法绘制View

    [见绘图部分]

  • 覆写onTouch等事件相应方法

    [见事件部分]

  • 设置控件的回调接口

自定义View步骤总结:

  1. 继承View或继承View的子类
  2. 在res/values/attrs.xm 中新增节点定义自定义属性
  3. 将自定义View放到布局文件中,注意命名空间名的格式为http://schemas.android.com/apk/res/[自定义View所在的包路径] 比如
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">
    <com.example.customviews.CustomView
    custom:attr_1="dddddddddddd" />
    </LinearLayout>
  4. 在XML布局中设定指定属性的值
  5. 在CustomView 中获取对应的属性值并覆写构造方法以及onDraw方法
  6. 覆写onTouch等事件相应方法

自定义View过程中除了上述介绍的onMeasue方法还有如下重要的回调方法:

onFinishInflate() 从XML加载组件后调用
onMeasure() 调用该方法进行测量
onLayout() 调用该方法确定显示的位置
onSizeChange() 组件大小改变的时候这个方法会被掉用
onTouchEvent 当触摸事件来临时被调用

自定义View是一个需要很长时间实践才能掌握的技术,上面进阶是一些死东西,活的东西需要在实践中不断积累,多看别人的作品,并多实践是掌握自定义View的不二法则,下面是一些我之前收集的一些较好的博文,推荐给大家。
[推荐博文]

blog.csdn.net/jdsjlzx/article/details/41113969
http://blog.csdn.net/lmj623565791/article/details/24252901
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0606/3001.html
http://blog.csdn.net/feelang/article/details/45035759
http://www.zhihu.com/question/41101031
http://blog.csdn.net/singwhatiwanna/article/details/38168103
http://blog.csdn.net/aigestudio/article/details/41447349

插值器用于控制每个阶段动画速度的变化速率,Android默认有八种动画插值器,下面分别介绍它的变化规律以及变化曲线:

AccelerateDecelerateInterpolator

在开始和结束时刻速度变化比较慢,在中间时刻加速:

AccelerateInterpolator

开始变化的时候缓慢,在中间时刻加速:

AnticipateInterpolator

开始的时候向后,然后向前急冲

AnticipateOvershootInterpolator

开始的时候向后,然后向前急冲到一定值后,最后回到终点

BounceInterpolator

动画结束的时候弹起

DecelerateInterpolator

开始的时候速度变化很快,然后减速

LinearInterpolator

匀速变化

OvershootInterpolator

开始向前急冲,超过最终值后回来

如何设置到动画上?

setInterpolator(xxxxxxx)

我们在介绍动画集合的时候,介绍到有两个动画集合分别是AnimationSet和AnimatorSet,它们分别继承自Animation和Animator,同样动画事件也分为两类,一种用于监听Animation动画,一种用于监听Animator动画
这些动画的监听方法意义都很简单,从名字就可以看出,这里就不展开介绍了:

AnimationListener

myAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {

}

@Override
public void onAnimationRepeat(Animation animation) {

}
});

AnimatorListener

animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {

}

@Override
public void onAnimationEnd(Animator animation) {

}

@Override
public void onAnimationCancel(Animator animation) {

}

@Override
public void onAnimationRepeat(Animator animation) {

}
});

Android 动画集合有两类,AnimationSet以及AnimatorSet,下面是在网上找的它们两者的区别:

AnimationSet 我们最常用的是调用其 addAnimation 将一个个不一样的动画组织到一起来,然后调用view的startAnimation方法触发这些动画执行。功能较弱不能做到把集合中的动画按一定顺序进行组织然后在执行的定制。
AnimatorSet 我们最常用的是调用其play、before、with、after 等方法设置动画的执行顺序,然后调用其start 触发动画执行。
AnimationSet 与 AnimatorSet 最大的不同在于,AnimationSet 使用的是 Animation 子类、AnimatorSet 使用的是 Animator 的子类。

一般而言我们AnimationSet使用在将多个视图动画组合在一起形成一个动画集合,而AnimatorSet用于将多个属性动画集合在一起,实现有序得播放。

下面是两个的常用方式:

AnimationSet 用法:

AnimationSet set = new AnimationSet(true);
AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f,0f);
ScaleAnimation scaleAnimation = new ScaleAnimation(100.0f, 200.0f, 300.0f, 400.0f);
set.addAnimation(alphaAnimation);
set.addAnimation(scaleAnimation);
view.startAnimation(set);

AnimatorSet 用法

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.start();

设置播放顺序的方法:

public Builder play(Animator anim)
public void playTogether(Animator... items)
public void playSequentially(Animator... items)
public Builder with(Animator anim)
public Builder before(Animator anim)
public Builder after(Animator anim)