《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.更新发布(破解更新)].MyEclipse10cr2.查看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位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。
测量的模式可以为以下三种:
- EXACTLY :即精确值模式,当我们将控件的layout_width属性或layout_height属性指定为具体数值时,或者指定为match_parent属性时,系统使用的是EXACTLY模式。
- AT_MOST :即最大值模式,当控件的layout_width属性或layout_height属性指定为wrap_content时,控件大小一般随着控件的子控件或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。(自适应)
- 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 控件架构灵活性的地方;
通常情况下,有以下三种方法来实现自定义控件:
- 对现有控件进行扩展
- 通过组合来实现新的控件
- 重写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,载给它添加指定功能的控件,从而组成新的复合控件。通过这种方式创建的控件,我们一般会给它指定一些可配置的属性,让他具有更强的拓展性
- 定义属性:在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,同理向下也是。
- 在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页
- 《Android群英传》学习记录(一)
- 《Android群英传》学习记录(三)
- 《Android群英传》学习记录(二)
- 《Android群英传》学习记录(四)
- Android群英传学习记录-第一章
- Android群英传学习记录-第二章
- Android群英传--自定义View详解(一)
- [温故与知新]Android群英传学习记录
- Android控件---Android群英传记录
- Android群英传学习笔记(1-20)
- 《Android群英传》学习笔记
- 《Android群英传》学习笔记
- android群英传___文章记录
- 自定义View(一)(Android群英传)
- Android学习记录(一)
- Android学习记录(一)
- android学习记录(一)
- Android群英传--绘图机制与处理技巧(一)
- 数据库回顾
- 第43天(就业班) jQuery-AJAX、mysql的优化
- JavaScript简介
- Java设计模式之单例模式
- 吹蜡烛问题
- 《Android群英传》学习记录(一)
- 80% 应聘者都不及格的 JS 面试题
- Android 自定义 省份、车牌号键盘。
- java 序列化和反序列化
- socket 网络编程
- aws
- java编程思想 -- 接口
- python常用类库系列---cPickle模块
- Team 更新或提交失败3