《Android群英传》学习记录(一)

来源:互联网 发布:淘宝异地登录有提示吗 编辑:程序博客网 时间:2024/06/05 06:58

第一章

1.Android系统架构

Android由底层往上分为4个主要功能层,分别是linux内核层(Linux Kernel),系统运行时库层(Libraries和Android Runtime),应用程序架构层(Application Framework)和应用程序层(Applications)。
这里写图片描述

Linux内核层
Android以linux操作系统内核为基础,借助Linux内核服务实现硬件设备驱动,进程和内存管理,网络协议栈,电源管理,无线通信等核心功能。Android4.0版本之前基于Linux2.6系列内核,4.0及之后的版本使用更新的Linux3.X内核,并且两个开源项目开始有了互通。Linux3.3内核中正式包括一些Android代码,可以直接引导进入Android。Linux3.4增添了电源管理等更多功能,以增加与Android的硬件兼容性,使Android在更多设备上得到支持。直到现在最新的android6.0仍然继续延用着linux3.4.0,而linux最新的版本已经到了4.3系列,那么为什么android没有继续去更新Linux kernel的版本也是一个值得探讨的课题。
Android内核 对Linux内核进行了增强,增加了一些面向移动计算的特有功能。例如,低内存管理器LMK(Low Memory Keller),匿名共享内存(Ashmem),以及轻量级的进程间通信Binder机制等。这些内核的增强使Android在继承Linux内核安全机制的同时,进一步提升了内存管理,进程间通信等方面的安全性。下表列举了Android内核的主要驱动模块:

//================================

Android电源管理(Power Management)
针对嵌入式设备的,基于标准Linux电源管理系统的,轻量级的电源管理驱动
低内存管理器(Low Memory keller)
低内存管理器(Low Memory Keller) 可以根据需要杀死进程来释放需要的内存。扩展了Linux的OOM机制,形成独特的LMK机制
匿名共享内存(Ashmem)
为进程之间提供共享内存资源,同时为内核提供回收和管理内存的机制
日志(Android Logger)
一个轻量级的日志设备
定时器(Anroid Alarm)
提供了一个定时器用于把设备从睡眠状态唤醒
物理内存映射管理(Android PMEM)
DSP及其他设备只能工作在连续的物理内存上,PMEM用于向用户空间提供 连续的物理内存区域映射
Android定时设备(Android Timed device)
可以执行对设备的定时控制功能
Yaffs2文件系统
Android采用大容量的NAND闪存作为存储设备,使用Yaffs2作为文件系统管理大容量MTD NAND Flash;Yaffs2占用内存小,垃圾回收简洁迅速。
Android Paranoid网络
对Linux内核的网络代码进行了改动,增加了网络认证机制。可在IPV4,IPV6和蓝牙中设置,由ANDROID_PARANOID_NETWORK宏来启用此特性。

//==============================

系统运行库层
官方的系统架构图中,位于Linux内核层之上的系统运行库层是应用程序框架的支撑,为Android系统中的各个组件提供服务。系统运行库层由系统类库和Android运行时构成。

系统类库
系统类库大部分由C/C++编写,所提供的功能通过Android应用程序框架为开发者所使用。主要的系统类库及说明如下表:

Surface Manager:执行多个应用程序时,管理子系统的显示,另外也对2D和3D图形提供支持
Media Framework:基于PacketVideoOpenCore的多媒体库,支持多种常用的音频和视频格式的录制和回放,所支持的编码格式包括MPEG4,MP3,H264,AAC,ARM
SQLite:本地小型关系数据库,Android提供了一些新的SQLite数据库API,以替代传统的耗费资源的JDBC API
OpenGL|ES:基于OpenGL ES 1.0API标准实现的3D跨平台图形库
FreeType:用于显示位图和矢量字体
WebKit:Web浏览器的软件引擎
SGL:底层的2D图形引擎
Libc(bionic l ibc):继承自BSD的C函数库bionic libc,更适合基于嵌入式Linux的移动设备
SSL:安全套接层,是为网络通信提供安全及数据完整性的一种安全协议

除上表列举的主要系统类库之外,Android NDK(Native Development Kit),即Android原生库,也十分重要。NDK为开发者提供了直接使用Android系统资源,并采用C或C++语言编写程序的接口。因此,第三方应用程序可以不依赖于Dalvik虚拟机进行开发。实际上,NDK提供了一系列从C或C++生成原生代码所需要的工具,为开发者快速开发C或C++的动态库提供方便,并能自动将生成的动态库和Java应用程序一起打包成应用程序包文件,即.apk文件。
注意,使用原生库无法访问应用框架层API,兼容性可能无法保障。而且从安全性角度考虑,Android原生库用非类型安全的程序语言C,C++编写,更容易产生安全漏洞,原生库的缺陷(bug)也可能更容易直接影响应用程序的安全性。

运行时
Android运行时包含核心库和Dalvik虚拟机两部分。
核心库:核心库提供了Java5 se API的多数功能,并提供Android的核心API,如android.os,android.net,android.media等
Dalvik虚拟机:Dalvik虚拟机是基于apache的java虚拟机,并被改进以适应低内存,低处理器速度的移动设备环境。Dalvik虚拟机依赖于Linux内核,实现进程隔离与线程调试管理,安全和异常管理,垃圾回收等重要功能。
本质而言,Dalvik虚拟机并非传统意义上的java虚拟机(JVM)。Dalvik虚拟机不仅不按照Java虚拟机的规范来实现,而且两者不兼容。
Dalvik和标准Java虚拟机有以下主要区别:

  • Dalvik基于寄存器,而JVM基于栈。一般认为,基于寄存器的实现虽然更多依赖于具体的CPU结构,硬件通用性稍差,但其使用等长指令,在效率速度上较传统JVM更有优势。
  • Dalvik经过优化,允许在有限的内存中同时高效地运行多个虚拟机的实例,并且每一个Dalvik应用作为一个独立的Linux进程执行,都拥有一个独立的Dalvik虚拟机实例。Android这种基于Linux的进程“沙箱”机制,是整个安全设计的基础之一。
  • Dalvik虚拟机从DEX(Dalvik Executable)格式的文件中读取指令与数据,进行解释运行。DEX文件由传统的,编译产生的CLASS文件,经dx工具软件处理后生成。
  • Dalvik的DEX文件还可以进一步优化,提高运行性能。通常,OEM的应用程序可以在系统编译后,直接生成优化文件(.ODEX); 第三方的应用程序则可在运行时在缓存中优化与保存,优化后的格式为DEY(.dey文件)。

这部分内容,即从android4.4开始就出现了ART(android runtime),但是这个ART并不是指这一节的主题,而是一种用来代替Dalvik的新型运行环境。当然在4.4的正式环境中用的还是Dalvik,真正开始用ART取代Dalvik是从android5.0开始的。(todo:针对这个改动,楼主会专门另开一个篇幅的文章去探究ART和Dalvik之间的区别)

另外这一节中有提到NDK,相信对于开发者而言SDK和NDK都是必要要接触和了解的东西,那么先从下图来看看sdk和ndk的关系。
这里写图片描述

很显然地,ndk可以通过native code跨过使用dalvik runtime,直接调用到android内核资源,而sdk则需要在dalvik runtime环境下才能调用到内核资源。然而两者并不是各司其职,各不相关。android提供了JNI(java native interface)使两者可以进行相互调用和通信。

应用程序框架层
应用程序框架层提供开发Android应用程序所需的一系列类库,使开发人员可以进行快速的应用程序开发,方便重用组件,也可以通过继承实现个性化的扩展。具体包括的模块如表:

  • 活动管理器(Activity Mananger):管理各个应用程序生命周期并提供常用的导航回退功能,为所有程序的窗口提供交互的接口
  • 窗口管理器(Window Manager):对所有开启的窗口程序进行管理
  • 内容提供器(Content Provider):提供一个应用程序访问另一个应用程序数据的功能,或者实现应用程序之间的数据共享
  • 视图系统(View System):创建应用程序的基本组件,包括列表(lists),网格(grids),文本框(text boxes),按钮(buttons),还有可嵌入的web浏览器。
  • 通知管理器(Notification Manager):使应用程序可以在状态栏中显示自定义的客户提示信息
  • 包管理器(Package Manager):对应用程序进行管理,提供的功能诸如安装应用程序,卸载应用程序,查询相关权限信息等
  • 资源管理器(Resource Manager):提供各种非代码资源供应用程序使用,如本地化字符串,图片,音频等
  • 位置管理器(Location Manager):提供位置服务
  • 电话管理器(Telephony Manager):管理所有的移动设备功能
  • XMPP服务:是Google在线即时交流软件中一个通用的进程,提供后台推送服务

应用层
Android平台的应用层上包括各类与用户直接交互的应用程序,或由java语言编写的运行于后台的服务程序。例如,智能手机上实现的常见基本功能 程序,诸如SMS短信,电话拨号,图片浏览器,日历,游戏,地图,web浏览器等程序,以及开发人员开发的其他应用程序。

以上部分转自:http://blog.csdn.net/sp6645597/article/details/50472740

2.安卓系统目录

  • /system/app/:这里放一些系统的app
  • /system/bin/:这里是Linux自带的组件
  • /system/build.prop:记录的是系统属性信息
  • /system/fonts/:系统文字存放目录
  • /system/framework/:系统的核心文件,架构层
  • /system/lib/:所有共享so文件
  • /system/media/:系统提示音
  • /system/usr/:保存用户的配置文件
  • /data/app/:data目录包含了用户的大部分数据信息
  • /data/data/:应用的数据信息,用包名来区分
  • /data/system/:包含手机各项系统信息
  • /data/misc/:保存大部分wifi,VPN信息

第二章

1.安装Android studio

国内一个比较好的网站:http://www.androiddevtools.cn/

首先配置jdk路径
CLASSPATH
.;C:\java\jdk1.7.0_71\lib
JAVA_HOME
C:\java\jdk1.7.0_71
JRE_HOME
C:\Java\jre7
Path
C:\java\jdk1.7.0_71\bin;C:\ProgramData\Oracle\Java\javapath;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;C:\Program Files\Microsoft SQL Server\110\Tools\Binn\;C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.0\;E:\jky\第二阶段\5-2016-2-17\ziliao[myeclipse.10.0.更新发布(破解更新)].MyEclipse10cr

2.查看adb端口占用
netstat -ano|findstr 8008 找到
tasklist|findstr “5896”杀死

第三章

1.Android 控件架构

在Android中,控件大致被分为两类,即ViewGroup控件与View控件。ViewGroup控件作为父控件可以包含多个View控件,并管理其包含的View控件。通过ViewGroup,整个界面上的控件形成了一个树形结构,即控件树,上层控件负责下层子控件的测量与绘制,并传递交互事件。在每棵控件树的顶部,都拥有一个ViewParent对象,这就是整棵树的控制核心,所有的交互管理事件都由它来统一调度和分配,从而可以对整个视图进行整体控制。如下图展示了一个View视图树:
这里写图片描述

Activity界面的架构图如下所示:
这里写图片描述

每个Activity都包含一个Window对象,在Android中Window对象通常由PhoneWindow来实现。PhoneWindow将一个DecorView设置为整个应用窗口的根View。DecorView作为窗口界面的顶层视图,封装了一些窗口操作的通用方法。可以说,DecorView将要显示的具体内容呈现在了PhoneWindow上,这里面的所有View的监听事件,都通过WindowManagerService来进行接收,并通过Activity对象来回调相应的监听。在显示上,它将屏幕分为两部分,一个是TitleView,另一个是ContentView。ContentView是一个ID为content的FrameLayout,activity_main.xml就是设置在这样一个FrameLayout里。
如果用户通过设置requestWindowFeature(Window.FEATURE_NO_TITLE)来设置全屏显示,那么DecorView中将只有ContentView了,这就解释了为什么调用requestWindowFeature()方法一定要在调用setContentView()方法之前才能生效的原因。
在代码中,当程序在onCreate()方法中调用setContentView()方法后,ActivityManagerService会回调onResume()方法,此时系统才会把整个DecorView添加到PhoneWindow中,并让其显示出来,从而完成界面的绘制。

2.view的测量

View的测量过程是在View的onMeasure()方法中进行的。
Android系统给我们提供了一个设计短小精悍却功能强大的类——MeasureSpec类,通过它来帮助我们测量View。MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。
测量的模式可以为以下三种:

  1. EXACTLY :即精确值模式,当我们将控件的layout_width属性或layout_height属性指定为具体数值时,或者指定为match_parent属性时,系统使用的是EXACTLY模式。
  2. AT_MOST :即最大值模式,当控件的layout_width属性或layout_height属性指定为wrap_content时,控件大小一般随着控件的子控件或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。(自适应)
  3. UNSPECIFIED :这个属性它不指定其大小测量的模式,View想多大就多大,通常情况下在绘制自定义View时才使用。(常用)

View类默认的onMeasure()方法只支持EXACTLY模式,所以如果在自定义控件的时候不重写onMeasure()方法的话,就只能使用EXACTLY模式。控件可以响应你指定的具体宽高值或者是match_parent属性。而如果要让自定义View支持wrap_content属性,那么就必须重写onMeasure()方法来指定wrap_content时的大小。

@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }

要重写的onMeasure()方法如上,点进onMeasure()方法里面发现其实通过setMeasuredDimension(int measuredWidth,int mearsuresHeight)方法测量后将宽高值设置进去的,所以我们要测量就要重写setMeasuredDimension()这个方法,把我们自己测量的结果穿进去。

private int mearsureWidth(int measureSpec){        int width = 0;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        if(specMode == MeasureSpec.EXACTLY){            width = specSize;        }else {            width = 200;            if(specMode == MeasureSpec.AT_MOST){                width = Math.min(width,specSize);            }        }        return width;    }
private int mearsureHeight(int measureSpec){        int height = 0;        //获取View的测量模式        int specMode = MeasureSpec.getMode(measureSpec);        //获取View的大小        int specSize = MeasureSpec.getSize(measureSpec);        if(specMode == MeasureSpec.EXACTLY){            height = specSize;        }else {        /**             * 需要一个默认值,当控件的宽高属性指定为wrap_content时,如果不重写onMeasure()方法,那么系统             * 就不知道该使用默认多大的尺寸。因此,他就会默认填充整个布局,所以重写onMeasure()方法的目的,             * 就是为了能够给View一个wrap_content属性下的默认大小。             */            height = 200;            if(specMode == MeasureSpec.AT_MOST){                height = Math.min(height,specSize);            }        }        return height;    }
 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    setMeasuredDimension(mearsureWidth(widthMeasureSpec),mearsureHeight(heightMeasureSpec));    }

3.view的绘制

View的绘制过程是在View的onDraw(Canvas canvas)方法中进行的。要想在Android的界面中绘制相应的图像,就必须在Canvas上进行绘制。Canvas就像一个画板,使用Paint就可以在上面作画了。

那如何在代码中创建一个Canvas对象呢?

Canvas canvas = new Canvas(bitmap);

当创建一个Canvas对象时,为什么要传进去一个bitmap对象呢?如果不传入一个bitmap对象,IDE编译虽然不会报错,但是一般我们不会这样做。这是因为传进去的bitmap与通过这个bitmap创建的Canvas画布是紧紧联系在一起了,这个过程我们称之为装载画布。这个bitmap用来存储所有绘制在Canvas上的像素信息。所以当你通过这种方式创建了Canva对象后,后面调用所有的Canvas.drawXXX方法都发生在这个bitmap上。

如果在View类的onDraw(Canvas canvas)方法中,通过下面这段代码,我们可以了解到canvas与bitmap直接的关系。首先在onDraw方法中绘制两个bitmap,代码如下所示:

canvas.drawBitmap(bitmap1, 0, 0 null);
canvas.drawBitmap(bitmap2, 0, 0 null);

而对于bitmap2,我们将它装载到另一个Canvas对象中,代码如下所示:

Canvas bitmapCanvas = new Canvas(bitmap2);

在其他地方使用Canvas对象的绘图方法在装载bitmap2的Canvas对象上进行绘图,代码如下所示:

bitmapCanvas.drawXXX

通过bitmapCanvas将绘制效果作用在了bitmap2上,再刷新View的时候,就会发现通过onDraw()方法画出来的bitmap2已经发生了改变,这就是因为bitmap2承载了在bitmapCanvas上所进行的绘图操作。虽然我们也使用了Canvas的绘制API,但其实并没有将图像直接绘制在onDraw()方法指定的那块画布上,而是通过改变bitmap,然后让View重绘,从而显示改变之后的bitmap。

4.ViewGroup的测量与绘制

ViewGroup会管理其子View,其中一个就是负责子View的显示大小。当ViewGroup的大小为wrap_content时,ViewGroup就需要对子View进行遍历,以便获得所有子View的大小,从而来决定自己的大小。而在其他模式下则会通过具体的指定值来设置自身的大小。

ViewGroup在测量时通过遍历所有子View,从而调用子View的measure()方法来获得每一个子View的测量结果。

当子View测量完毕后,就需要将子View放到合适的位置,这个过程就是View的Layout过程。ViewGroup在执行Layout过程时,同样是使用遍历来调用子View的layout()方法,并指定其具体显示的位置,从而决定其布局位置。

在自定义ViewGroup时,通常会去重写onLayout()方法来控制子View显示位置的逻辑。同样,如果需要支持wrap_content属性,那么它必须重写onMeasure()方法,这点与View是相同的。

ViewGroup通常情况下不需要绘制,因为它本身就没有需要绘制的东西,如果不是指定了ViewGroup的背景颜色,那么ViewGroup的onDraw()方法都不会被调用。但是,ViewGroup会使用dispatchDraw()方法来绘制其子View,其过程同样是通过遍历所有子View,并调用子View的绘制方法来完成绘制工作。

5.自定义View
在自定义view是,我们通常会去重写onDraw()方法来绘制View的显示内容。如果该view还需要使用wrap_content属性,那么还必须重写onMeasure()方法。另外,通过自定义attrs属性,还可以设置新的属性配置值。
在view中通常有一下比较重要的回调方法:

  • onFinishInflate():从XML加载组件后回调。
  • onSizeChanged():组件大小改变时回调。
  • onMeasure():该方法用来测量
  • onLayout:该方法用来确定显示的位置
  • onTouchEvent:监听发到触摸时回调。

当然,创建自定义view的时候,并不需要重写所有的方法,只需要重写特定的方法局可以了,这也是Android 控件架构灵活性的地方;
通常情况下,有以下三种方法来实现自定义控件:

  1. 对现有控件进行扩展
  2. 通过组合来实现新的控件
  3. 重写view来实现全新的控件

对现有控件进行扩展:
这是一种非常中要的自定义view方法,它可以在原生的控件上进行扩展,增加新的功能,修改显示的UI,一般情况下我们在OnDraw方法中对原生控件进行扩展,

public class MyTextView extends TextView {    private Paint mPaint1, mPaint2;    public MyTextView(Context context) {        super(context);        initView();    }    public MyTextView(Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    public MyTextView(Context context, AttributeSet attrs,                      int defStyleAttr) {        super(context, attrs, defStyleAttr);        initView();    }    private void initView() {        mPaint1 = new Paint();        mPaint1.setColor(getResources().getColor(                android.R.color.holo_blue_light));        mPaint1.setStyle(Paint.Style.FILL);        mPaint2 = new Paint();        mPaint2.setColor(Color.BLUE);        mPaint2.setStyle(Paint.Style.FILL);    }    @Override    protected void onDraw(Canvas canvas) {        // 绘制外层矩形        canvas.drawRect(                0,                0,                getMeasuredWidth(),                getMeasuredHeight(),                mPaint1);        // 绘制内层矩形        canvas.drawRect(                10,                10,                getMeasuredWidth() - 10,                getMeasuredHeight() - 10,                mPaint2);        canvas.save();        // 绘制文字前平移10像素        canvas.translate(10, 0);        // 父类完成的方法,即绘制文本        super.onDraw(canvas);        canvas.restore();    }}
//设置带渲染效果的public class  ShineTextView extends TextView {    private LinearGradient mLinearGradient;    private Matrix mGradientMatrix;    private Paint mPaint;    private int mViewWidth = 0;    private int mTranslate = 0;    public ShineTextView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        if (mViewWidth == 0) {            mViewWidth = getMeasuredWidth();            if (mViewWidth > 0) {                mPaint = getPaint();                //线性渲染                mLinearGradient = new LinearGradient(                        0,                        0,                        mViewWidth,                        0,                        new int[]{                                Color.BLUE, 0xffffffff,                                Color.BLUE},                        null,                        Shader.TileMode.CLAMP);                mPaint.setShader(mLinearGradient);                mGradientMatrix = new Matrix();            }        }    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (mGradientMatrix != null) {            mTranslate += mViewWidth / 5;            if (mTranslate > 2 * mViewWidth) {                mTranslate = -mViewWidth;            }            mGradientMatrix.setTranslate(mTranslate, 0);            mLinearGradient.setLocalMatrix(mGradientMatrix);            postInvalidateDelayed(100);        }    }}

创建复合控件
创建复合控件可以很好的创建出具有重用功能的控件集合。这种方式通常需要继承一个viewgroup,载给它添加指定功能的控件,从而组成新的复合控件。通过这种方式创建的控件,我们一般会给它指定一些可配置的属性,让他具有更强的拓展性

  1. 定义属性:在res资源目录的values目录下面创建一个attrs.xml的属性定义文件,并通过该文件中通过如下代码定义相应的属性
<resources>    <declare-styleable name="TopBar">        <attr name="title" format="string"/>        <attr name="titleTextSize" format="dimension"/>        <attr name="titleTextColor" format="color"/>        <attr name="leftTextColor" format="color"/>        <attr name="leftBackground" format="reference|color"/>        <attr name="leftText" format="string"/>        <attr name="rightTextColor" format="color"/>        <attr name="rightBackground" format="reference|color"/>        <attr name="rightText" format="string"/>    </declare-styleable></resources>
public class TopBar extends RelativeLayout {    // 包含topbar上的元素:左按钮、右按钮、标题    private Button mLeftButton, mRightButton;    private TextView mTitleView;    // 布局属性,用来控制组件元素在ViewGroup中的位置    private LayoutParams mLeftParams, mTitlepParams, mRightParams;    // 左按钮的属性值,即我们在atts.xml文件中定义的属性    private int mLeftTextColor;    private Drawable mLeftBackground;    private String mLeftText;    // 右按钮的属性值,即我们在atts.xml文件中定义的属性    private int mRightTextColor;    private Drawable mRightBackground;    private String mRightText;    // 标题的属性值,即我们在atts.xml文件中定义的属性    private float mTitleTextSize;    private int mTitleTextColor;    private String mTitle;    private topbarClickListener listener;    public TopBar(Context context) {        super(context);    }    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)    public TopBar(Context context, AttributeSet attrs) {        super(context, attrs);        // 设置topbar的背景        setBackgroundColor(0xFFF59563);        // 通过这个方法,将你在atts.xml中定义的declare-styleable        // 的所有属性的值存储到TypedArray中        TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.TopBar);        // 从TypedArray中取出对应的值来为要设置的属性赋值        //left        mLeftText = ta.getString(R.styleable.TopBar_leftText);        mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);        mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor,0);        //right        mRightText = ta.getString(R.styleable.TopBar_rightText);        mRightBackground = ta.getDrawable(R.styleable.TopBar_rightBackground);        mRightTextColor = ta.getColor(R.styleable.TopBar_rightTextColor,0);        //title        mTitle = ta.getString(R.styleable.TopBar_title);        mTitleTextColor = ta.getColor(R.styleable.TopBar_titleTextColor,0);        mTitleTextSize = ta.getDimension(R.styleable.TopBar_titleTextSize,10);        // 获取完TypedArray的值后,一般要调用        // recyle方法来避免重新创建的时候的错误        ta.recycle();        //        mLeftButton = new Button(context);        mRightButton = new Button(context);        mTitleView = new TextView(context);        //        // 为创建的组件元素赋值        // 值就来源于我们在引用的xml文件中给对应属性的赋值        mLeftButton.setText(mLeftText);        mLeftButton.setTextColor(mLeftTextColor);        mLeftButton.setBackground(mLeftBackground);        //        mTitleView.setTextColor(mTitleTextColor);        mTitleView.setText(mTitle);        mTitleView.setTextSize(mTitleTextSize);        mTitleView.setGravity(Gravity.CENTER);        //        mRightButton.setText(mRightText);        mRightButton.setTextColor(mRightTextColor);        mRightButton.setBackground(mRightBackground);        // 为组件元素设置相应的布局元素        mLeftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);        mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);        addView(mLeftButton,mLeftParams);        mRightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);        mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);        addView(mRightButton,mRightParams);        //        mTitlepParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);        mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);        addView(mTitleView,mTitlepParams);        // 按钮的点击事件,不需要具体的实现,        // 只需调用接口的方法,回调的时候,会有具体的实现        mLeftButton.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                listener.leftClick();            }        });        mRightButton.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                listener.rightClick();            }        });    }    public TopBar(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {    }    public void setListener(topbarClickListener listener) {        this.listener = listener;    }    /**     * 设置按钮的显示与否 通过id区分按钮,flag区分是否显示     *     * @param id   id     * @param flag 是否显示     */    public void setButtonVisable(int id, boolean flag) {        if (flag) {            if (id == 0) {                mLeftButton.setVisibility(View.VISIBLE);            } else {                mRightButton.setVisibility(View.VISIBLE);            }        } else {            if (id == 0) {                mLeftButton.setVisibility(View.GONE);            } else {                mRightButton.setVisibility(View.GONE);            }        }    }    // 接口对象,实现回调机制,在回调方法中    // 通过映射的接口对象调用接口中的方法    // 而不用去考虑如何实现,具体的实现由调用者去创建    public interface topbarClickListener {        // 左按钮点击事件        void leftClick();        // 右按钮点击事件        void rightClick();    }}

引用UI模板,在引用前,需要引用第三方控件的名字空间,可以看以下代码:

xmlns:android=”http://schemas.android.com/apk/res/android”

这行代码就是指定引用的名字空间xmlns,即xml namespace.这里指定了名字空间为“android”,因此在接下来使用系统属性的时候,才可以使用“android”来引用Android的系统属性。同样的,如果要使用自定义的属性,那么需要创建自己的名字空间,在Android studio中,第三方的控件都使用如下代码来引入名字空间

xmlns:custom=”http://schemas.android.com/apk/res-auto”

这里我们将引入的第三方控件的名字空间取名custom,之后再XML文件中使用自定义的属性的时候,就可以使用名字空间来引用,代码如下

<com.xys.mytopbar.Topbar xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:custom="http://schemas.android.com/apk/res-auto"    android:id="@+id/topBar"    android:layout_width="match_parent"    android:layout_height="40dp"    custom:leftBackground="@drawable/blue_button"    custom:leftText="Back"    custom:leftTextColor="#FFFFFF"    custom:rightBackground="@drawable/blue_button"    custom:rightText="More"    custom:rightTextColor="#FFFFFF"    custom:title="自定义标题"    custom:titleTextColor="#123412"    custom:titleTextSize="15sp"></com.xys.mytopbar.Topbar>

通过如上的代码,我们就可以在其他文件布局文件中,直接用
标签来引用这个UI模板的view

<include layout="@layout/topbar">

重写view来实现全新的控件
当安卓原生的控件无法满足我们的需求的时候,我们完全可以写一个自定义的view来实现需要的功能,创建一个自定义的view,难点在于绘制控件和实现交互和实现交互,通常需要继承view类,并重写他的onDraw(),onMeasure()等方法来实现绘制逻辑,同时通过重写OnTouchEvent()等触控事件来实现交互逻辑。当然,我们还可以像实现组合控件那样,通过引入自定义属性,丰富自定义view的可定制性。

1.绘制弧线展示图

public class CircleProgressView extends View {    private int mMeasureHeigth;//测量高度    private int mMeasureWidth;//测量宽度    private Paint mCirclePaint;//圆的画笔    private float mCircleXY;//圆心    private float mRadius;//圆角    private Paint mArcPaint;//圆弧的画笔    private RectF mArcRectF;//矩形    private float mSweepAngle;//扫描角    private float mSweepValue = 66;    private Paint mTextPaint;//文字画笔    private String mShowText;//文字    private float mShowTextSize;//文字大小    public CircleProgressView(Context context, AttributeSet attrs,                              int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    public CircleProgressView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public CircleProgressView(Context context) {        super(context);    }    @Override    protected void onMeasure(int widthMeasureSpec,                             int heightMeasureSpec) {        mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec);        mMeasureHeigth = MeasureSpec.getSize(heightMeasureSpec);        setMeasuredDimension(mMeasureWidth, mMeasureHeigth);        initView();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        // 绘制圆        canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);        // 绘制弧线        canvas.drawArc(mArcRectF, 270, mSweepAngle, false, mArcPaint);        // 绘制文字        canvas.drawText(mShowText, 0, mShowText.length(),                mCircleXY, mCircleXY + (mShowTextSize / 4), mTextPaint);    }    private void initView() {        float length = 0;        if (mMeasureHeigth >= mMeasureWidth) {            length = mMeasureWidth;        } else {            length = mMeasureHeigth;        }        //圆的参数        mCircleXY = length / 2;        mRadius = (float) (length * 0.5 / 3);        //圆形的画笔        mCirclePaint = new Paint();        mCirclePaint.setAntiAlias(true);//去锯齿        mCirclePaint.setColor(getResources().getColor(                android.R.color.holo_blue_bright));        //绘制弧线,需要制定其椭圆的外接矩形        mArcRectF = new RectF(                (float) (length * 0.1),                (float) (length * 0.1),                (float) (length * 0.9),                (float) (length * 0.9));        mSweepAngle = (mSweepValue / 100f) * 360f;        //正方形的画笔        mArcPaint = new Paint();        mArcPaint.setAntiAlias(true);        mArcPaint.setColor(getResources().getColor(                android.R.color.holo_blue_bright));        mArcPaint.setStrokeWidth((float) (length * 0.01));//设置圆弧的线宽        mArcPaint.setStyle(Style.STROKE);        //文字        mShowText = setShowText();        mShowTextSize = setShowTextSize();        mTextPaint = new Paint();        mTextPaint.setTextSize(mShowTextSize);        mTextPaint.setTextAlign(Paint.Align.CENTER);    }    private float setShowTextSize() {        this.invalidate();        return 50;    }    private String setShowText() {        this.invalidate();        return mSweepValue+"%";    }    public void forceInvalidate() {        this.invalidate();    }    public void setSweepValue(float sweepValue) {        if (sweepValue != 0) {            mSweepValue = sweepValue;        } else {            mSweepValue = 25;        }        this.invalidate();    }}

2.绘制音频展示图

public class VolumeView extends View {    private int mWidth;    private int mRectWidth;    private int mRectHeight;    private Paint mPaint;    private int mRectCount;    private int offset = 5;    private double mRandom;    private LinearGradient mLinearGradient;    public VolumeView(Context context) {        super(context);        initView();    }    public VolumeView(Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    public VolumeView(Context context, AttributeSet attrs,                      int defStyleAttr) {        super(context, attrs, defStyleAttr);        initView();    }    private void initView() {        mPaint = new Paint();        mPaint.setColor(Color.BLUE);        mPaint.setStyle(Paint.Style.FILL);        mRectCount = 12;    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mWidth = getWidth();        mRectHeight = getHeight();        mRectWidth = (int) (mWidth * 0.6 / mRectCount);        mLinearGradient = new LinearGradient(                0,                0,                mRectWidth,                mRectHeight,                Color.YELLOW,                Color.BLUE,                Shader.TileMode.CLAMP);        mPaint.setShader(mLinearGradient);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        for (int i = 0; i < mRectCount; i++) {            mRandom = Math.random();//产生一个随机数            float currentHeight = (float) (mRectHeight * mRandom);//当前高度            canvas.drawRect(                    (float) (mWidth * 0.4 / 2 + mRectWidth * i + offset),                    currentHeight,                    (float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1)),                    mRectHeight,                    mPaint);        }        //延迟重新绘制        postInvalidateDelayed(3000);    }}

自定义ViewGroup
前面我们分析了如何自定义view,下面我们继续来分析如何创建自定义viewgroup。viewgroup存在的目的就是为了对其子view进行管理,为其子view添加显示,响应的规则,因此,自定义viewgroup通常要重写onMeasure方法对其子view进行测量,重写onLayout来确定子view的位置,重写onTouchEvent来增加响应事件。下面通过一个实例,来看看如何自定义viewgroup。
例子实现一个类似原生控件Scrollview 的自定义viewgroup,但是他有一个粘性的效果在里面,即当一个字view滑动大于一定距离后,松开手指,图将会自动向上滑动,显示先一个子view,同理向下也是。

  1. 在viewgroup能够滑动之前,需要先放置好他的子view,使用遍历的方式来通知子view对自身进行测量
@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int count = getChildCount();        for (int i = 0; i < count; i++) {            View childView = getChildAt(i);            addView(childView,widthMeasureSpec,heightMeasureSpec);        }    }

2.接下来要对子view进行放置,让每一个子view都充满一屏

//得到屏幕高度  private void initView(Context context) {        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);        DisplayMetrics dm = new DisplayMetrics();        wm.getDefaultDisplay().getMetrics(dm);        mScreenHeight = dm.heightPixels;        mScroller = new Scroller(context);    }
@Override    protected void onLayout(boolean changed,                            int l, int t, int r, int b) {        int childCount = getChildCount();        // 设置ViewGroup的高度        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();        mlp.height = mScreenHeight * childCount;        setLayoutParams(mlp);        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            if (child.getVisibility() != View.GONE) {                child.layout(l, i * mScreenHeight,                        r, (i + 1) * mScreenHeight);            }        }    }

完整代码:

public class MyScrollView extends ViewGroup {    private int mScreenHeight;    private Scroller mScroller;//用于滑动的一个帮助类    private int mLastY;    private int mStart;    private int mEnd;    public MyScrollView(Context context) {        super(context);        initView(context);    }    public MyScrollView(Context context, AttributeSet attrs) {        super(context, attrs);        initView(context);    }    public MyScrollView(Context context, AttributeSet attrs,                        int defStyleAttr) {        super(context, attrs, defStyleAttr);        initView(context);    }    private void initView(Context context) {        WindowManager wm = (WindowManager) context.getSystemService(                Context.WINDOW_SERVICE);        DisplayMetrics dm = new DisplayMetrics();        wm.getDefaultDisplay().getMetrics(dm);        mScreenHeight = dm.heightPixels;        mScroller = new Scroller(context);    }    @Override    protected void onLayout(boolean changed,                            int l, int t, int r, int b) {        int childCount = getChildCount();        // 设置ViewGroup的高度        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();        mlp.height = mScreenHeight * childCount;        setLayoutParams(mlp);        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            if (child.getVisibility() != View.GONE) {                child.layout(l, i * mScreenHeight,                        r, (i + 1) * mScreenHeight);            }        }    }    @Override    protected void onMeasure(int widthMeasureSpec,                             int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int count = getChildCount();        for (int i = 0; i < count; ++i) {            View childView = getChildAt(i);            measureChild(childView,                    widthMeasureSpec, heightMeasureSpec);        }    }    @Override    public boolean onTouchEvent(MotionEvent event) {        int y = (int) event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastY = y;                //记录触摸起点                mStart = getScrollY();                break;            case MotionEvent.ACTION_MOVE:                if (!mScroller.isFinished()) {                    mScroller.abortAnimation();                }                int dy = mLastY - y;                if (getScrollY() < 0) {                    dy = 0;                }                if (getScrollY() > getHeight() - mScreenHeight) {                    dy = 0;                }                scrollBy(0, dy);                mLastY = y;                break;            case MotionEvent.ACTION_UP:                int dScrollY = checkAlignment();                if (dScrollY > 0) {                    if (dScrollY < mScreenHeight / 3) {                        mScroller.startScroll(                                0, getScrollY(),                                0, -dScrollY);                    } else {                        mScroller.startScroll(                                0, getScrollY(),                                0, mScreenHeight - dScrollY);                    }                } else {                    if (-dScrollY < mScreenHeight / 3) {                        mScroller.startScroll(                                0, getScrollY(),                                0, -dScrollY);                    } else {                        mScroller.startScroll(                                0, getScrollY(),                                0, -mScreenHeight - dScrollY);                    }                }                break;        }        postInvalidate();        return true;    }    private int checkAlignment() {        //记录触摸终点        int mEnd = getScrollY();        boolean isUp = ((mEnd - mStart) > 0) ? true : false;        int lastPrev = mEnd % mScreenHeight;        int lastNext = mScreenHeight - lastPrev;        if (isUp) {            //向上的            return lastPrev;        } else {            return -lastNext;        }    }    @Override    public void computeScroll() {        super.computeScroll();        if (mScroller.computeScrollOffset()) {            scrollTo(0, mScroller.getCurrY());            postInvalidate();        }    }}

事件拦截机制分析

在OnTouchEvent中可以通过event.getX()和event.getRawX()方法获得触摸点坐标
间书本59页

0 0