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

第一章

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图书简介: 本书从由总到分,让读者从整体上把握Android体系结构,融入“群英传”这一故事情节,针对各个知识点进行分类阐述,并结合一线实际开发经验和Android 5.0版本新特性来,紧跟市场需求进行讲解。然后通过实战应用案例,综合使用前面讲解到的知识点,进一步提高开发者水平。 相关截图: 图书目录: 第1章 Android体系与系统架构 1 1.1 Google生态系统 2 1.2 Android系统架构 2 1.2.1 Linux 3 1.2.2 Dalvik与ART 3 1.2.3 Framework 3 1.2.4 Standard libraries 4 1.2.5 Application 4 1.3 Android App组件架构 4 1.3.1 Android四大组件如何协同工作 5 1.3.2 应用运行上下文对象 5 1.4 Android系统源代码目录与系统目录 6 1.4.1 Android系统源代码目录 6 1.4.2 Android系统目录 8 1.4.3 Android App文件目录 11 第2章 Android开发工具新接触 13 2.1 Android开发IDE介绍 14 2.1.1 Android Studio初体验 14 2.1.2 Android Studio配置 15 2.2 Android Studio高级使用技巧 19 2.2.1 更新SDK 20 2.2.2 Android Studio常用界面 21 2.2.3 导入Android Studio工程 23 2.3 ADB命令使用技巧 24 2.3.1 ADB基础 24 2.3.2 ADB常用命令 25 2.3.3 ADB命令来源 29 2.4 模拟器使用与配置 29 第3章 Android控件架构与自定义控件详解 32 3.1 Android控件架构 33 3.2 View的测量 34 3.3 View的绘制 37 3.4 ViewGroup的测量 38 3.5 ViewGroup的绘制 39 3.6 自定义View 39 3.6.1 对现有控件进行拓展 40 3.6.2 创建复合控件 43 3.6.3 重写View来实现全新的控件 51 3.7 自定义ViewGroup 54 .........................
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值