1. android图形渲染原理上
对于Android开发者来说,我们或多或少有了解过Android图像显示的知识点,刚刚学习Android开发的人会知道,在Actvity的onCreate方法中设置我们的View后,再经过onMeasure,onLayout,onDraw的流程,界面就显示出来了;对Android比较熟悉的开发者会知道,onDraw流程分为软件绘制和硬件绘制两种模式,软绘是通过调用Skia来操作,硬绘是通过调用Opengl ES来操作;对Android非常熟悉的开发者会知道绘制出来的图形数据最终都通过GraphiBuffer内共享内存传递给SurfaceFlinger去做图层混合,图层混合完成后将图形数据送到帧缓冲区,于是,图形就在我们的屏幕显示出来了。
但我们所知道的Activity或者是应用App界面的显示,只属于Android图形显示的一部分。同样可以在Android系统上展示图像的WebView,Flutter,或者是通过Unity开发的3D游戏,他们的界面又是如何被绘制和显现出来的呢?他们和我们所熟悉的Acitvity的界面显示又有什么异同点呢?我们可以不借助Activity的setView或者InflateView机制来实现在屏幕上显示出我们想要的界面吗?Android系统显示界面的方式又和IOS,或者Windows等系统有什么区别呢?……
去探究这些问题,比仅仅知道Acitvity的界面是如何显示出来更加的有价值,因为想要回答这些问题,就需要我们真正的掌握Android图像显示的底层原理,当我们掌握了底层的显示原理后,我们会发现WebView,Flutter或者未来会出现的各种新的图形显示技术,原来都是大同小异。
我会花三篇文章的篇幅,去深入的讲解Android图形显示的原理,OpenGL ES和Skia的绘制图像的方式,他们如何使用,以及他们在Android中的使用场景,如开机动画,Activity界面的软件绘制和硬件绘制,以及Flutter的界面绘制。那么,我们开始对Android图像显示原理的探索吧。
在讲解Android图像的显示之前,我会先讲一下屏幕图像的显示原理,毕竟我们图像,最终都是在手机屏幕上显示出来的,了解这一块的知识会让我们更容易的理解Android在图像显示上的机制。
图像显示的完整过程,分为下面几个阶段:
图像数据→CPU→显卡驱动→显卡(GPU)→显存(帧缓冲)→显示器
我详细介绍一下这几个阶段:
实际上显卡驱动,显卡和显存,包括数模转换模块都是属于显卡的模块。但为了能能详细的讲解经历的步骤,这里做了拆分。
当显存中有数据后,显示器又是怎么根据显存里面的数据来进行界面的显示的呢?这里以LCD液晶屏为例,显卡会将显存里的数据,按照从左至右,从上到下的顺序同步到屏幕上的每一个像素晶体管,一个像素晶体管就代表了一个像素。
如果我们的屏幕分辨率是1080x1920像素,就表示有1080x1920个像素像素晶体管,每个橡素点的颜色越丰富,描述这个像素的数据就越大,比如单色,每个像素只需要1bit,16色时,只需要4bit,256色时,就需要一个字节。那么1080x1920的分辨率的屏幕下,如果要以256色显示,显卡至少需要1080x1920个字节,也就是2M的大小。
刚刚说了,屏幕上的像素数据是从左到右,从上到下进行同步的,当这个过程完成了,就表示一帧绘制完成了,于是会开始下一帧的绘制,大部分的显示屏都是以60HZ的频率在屏幕上绘制完一帧,也就是16ms,并且每次绘制新的一帧时,都会发出一个垂直同步信号(VSync)。我们已经知道,图像数据都是放在帧缓冲中的,如果帧缓冲的缓冲区只有一个,那么屏幕在绘制这一帧的时候,图像数据便没法放入帧缓冲中了,只能等待这一帧绘制完成,在这种情况下,会有很大了效率问题。所以为了解决这一问题,帧缓冲引入两个缓冲区,即 双缓冲机制 。双缓冲虽然能解决效率问题,但会引入一个新的问题。当屏幕这一帧还没绘制完成时,即屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,显卡的像素同步模块就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象。
为了解决撕裂问题,就需要在收到垂直同步的时候才将帧缓冲中的两个缓冲区进行交换。Android4.1黄油计划中有一个优化点,就是CPU和GPU都只有收到垂直同步的信号时,才会开始进行图像的绘制操作,以及缓冲区的交换工作。
我们已经了解了屏幕图像显示的原理了,那么接着开始对Android图像显示的学习。
从上一章已经知道,计算机渲染界面必须要有GPU和帧缓冲。对于Linux系统来说,用户进程是没法直接操作帧缓冲的,但我们想要显示图像就必须要操作帧缓冲,所以Linux系统设计了一个虚拟设备文件,来作为对帧缓冲的映射,通过对该文件的I/O读写,我们就可以实现读写屏操作。帧缓冲对应的设备文件于/dev/fb* ,*表示对多个显示设备的支持, 设备号从0到31,如/dev/fb0就表示第一块显示屏,/dev/fb1就表示第二块显示屏。对于Android系统来说,默认使用/dev/fb0这一个设帧缓冲作为主屏幕,也就是我们的手机屏幕。我们Android手机屏幕上显示的图像数据,都是存储在/dev/fb0里,早期AndroidStuio中的DDMS工具实现截屏的原理就是直接读取/dev/fb0设备文件。
我们知道了手机屏幕上的图形数据都存储在帧缓冲中,所以Android手机图像界面的原理就是将我们的图像数据写入到帧缓冲内。那么,写入到帧缓冲的图像数据是怎么生成的,又是怎样加工的呢?图形数据是怎样送到帧缓冲去的,中间经历了哪些步骤和过程呢?了解了这几个问题,我们就了解了Android图形渲染的原理,那么带着这几个疑问,接着往下看。
想要知道图像数据是怎么产生的,我们需要知道 图像生产者 有哪些,他们分别是如何生成图像的,想要知道图像数据是怎么被消费的,我们需要知道 图像消费者 有哪些,他们又分别是如何消费图像的,想要知道中间经历的步骤和过程,我们需要知道 图像缓冲区 有哪些,他们是如何被创建,如何分配存储空间,又是如何将数据从生产者传递到消费者的,图像显示是一个很经典的消费者生产者的模型,只有对这个模型各个模块的击破,了解他们之间的流动关系,我们才能找到一条更容易的路径去掌握Android图形显示原理。我们看看谷歌提供的官方的架构图是怎样描述这一模型的模块及关系的。
如图, 图像的生产者 主要有MediaPlayer,CameraPrevier,NDK,OpenGl ES。MediaPlayer和Camera Previer是通过直接读取图像源来生成图像数据,NDK(Skia),OpenGL ES是通过自身的绘制能力生产的图像数据; 图像的消费者 有SurfaceFlinger,OpenGL ES Apps,以及HAL中的Hardware Composer。OpenGl ES既可以是图像的生产者,也可以是图像的消费者,所以它也放在了图像消费模块中; 图像缓冲区 主要有Surface以及前面提到帧缓冲。
Android图像显示的原理,会仅仅围绕 图像的生产者 , 图像的消费者 , 图像缓冲区 来展开,在这一篇文章中,我们先看看Android系统中的图像消费者。
SurfaceFlinger是Android系统中最重要的一个图像消费者,Activity绘制的界面图像,都会传递到SurfaceFlinger来,SurfaceFlinger的作用主要是接收图像缓冲区数据,然后交给HWComposer或者OpenGL做合成,合成完成后,SurfaceFlinger会把最终的数据提交给帧缓冲。
那么SurfaceFlinger是如何接收图像缓冲区的数据的呢?我们需要先了解一下Layer(层)的概念,一个Layer包含了一个Surface,一个Surface对应了一块图形缓冲区,而一个界面是由多个Surface组成的,所以他们会一一对应到SurfaceFlinger的Layer中。SurfaceFlinger通过读取Layer中的缓冲数据,就相当于读取界面上Surface的图像数据。Layer本质上是 Surface和SurfaceControl的组合 ,Surface是图形生产者和图像消费之间传递数据的缓冲区,SurfaceControl是Surface的控制类。
前面在屏幕图像显示原理中讲到,为了防止图像的撕裂,Android系统会在收到VSync垂直同步时才会开始处理图像的绘制和合成工作,而Surfaceflinger作为一个图像的消费者,同样也是遵守这一规则,所以我们通过源码来看看SurfaceFlinger是如何在这一规则下,消费图像数据的。
SurfaceFlinger专门创建了一个EventThread线程用来接收VSync。EventThread通过Socket将VSync信号同步到EventQueue中,而EventQueue又通过回调的方式,将VSync信号同步到SurfaceFlinger内。我们看一下源码实现。
上面主要是SurfaceFlinger初始化接收VSYNC垂直同步信号的操作,主要有这几个过程:
经过上面几个步骤,我们接收VSync的初始化工作都准备好了,EventThread也开始运转了,接着看一下EventThread的运转函数threadLoop做的事情。
threadLoop主要是两件事情
mConditon又是怎么接收VSync的呢?我们来看一下
可以看到,mCondition的VSync信号实际是DispSyncSource通过onVSyncEvent回调传入的,但是DispSyncSource的VSync又是怎么接收的呢?在上面讲到的SurfaceFlinger的init函数,在创建EventThread的实现中,我们可以发现答案—— mPrimaryDispSync 。
DispSyncSource的构造方法传入了mPrimaryDispSync,mPrimaryDispSync实际是一个DispSyncThread线程,我们看看这个线程的threadLoop方法
DispSyncThread的threadLoop会通过mPeriod来判断是否进行阻塞或者进行VSync回调,那么mPeriod又是哪儿被设置的呢?这里又回到SurfaceFlinger了,我们可以发现在SurfaceFlinger的 resyncToHardwareVsync 函数中有对mPeriod的赋值。
可以看到,这里最终通过HWComposer,也就是硬件层拿到了period。终于追踪到了VSync的最终来源了, 它从HWCompser产生,回调至DispSync线程,然后DispSync线程回调到DispSyncSource,DispSyncSource又回调到EventThread,EventThread再通过Socket分发到MessageQueue中 。
我们已经知道了VSync信号来自于HWCompser,但SurfaceFlinger并不会一直监听VSync信号,监听VSync的线程大部分时间都是休眠状态,只有需要做合成工作时,才会监听VSync,这样即保证图像合成的操作能和VSync保持一致,也节省了性能。SurfaceFlinger提供了一些主动注册监听VSync的操作函数。
可以看到,只有当SurfaceFlinger调用 signalTransaction 或者 signalLayerUpdate 函数时,才会注册监听VSync信号。那么signalTransaction或者signalLayerUpdate什么时候被调用呢?它可以由图像的生产者通知调用,也可以由SurfaceFlinger根据自己的逻辑来判断是否调用。
现在假设App层已经生成了我们界面的图像数据,并调用了 signalTransaction 通知SurfaceFlinger注册监听VSync,于是VSync信号便会传递到了MessageQueue中了,我们接着看看MessageQueue又是怎么处理VSync的吧。
MessageQueue收到VSync信号后,最终回调到了SurfaceFlinger的 onMessageReceived 中,当SurfaceFlinger接收到VSync后,便开始以一个图像消费者的角色来处理图像数据了。我们接着看SurfaceFlinger是以什么样的方式消费图像数据的。
VSync信号最终被SurfaceFlinger的onMessageReceived函数中的INVALIDATE模块处理。
INVALIDATE的流程如下:
handleMessageTransaction的处理比较长,处理的事情也比较多,它主要做的事情有这些
handleMessageRefresh函数,便是SurfaceFlinger真正处理图层合成的地方,它主要下面五个步骤。
我会详细介绍每一个步骤的具体操作
合成前预处理会判断Layer是否发生变化,当Layer中有新的待处理的Buffer帧(mQueuedFrames>0),或者mSidebandStreamChanged发生了变化, 都表示Layer发生了变化,如果变化了,就调用signalLayerUpdate,注册下一次的VSync信号。如果Layer没有发生变化,便只会做这一次的合成工作,不会注册下一次VSync了。
重建Layer栈会遍历Layer,计算和存储每个Layer的脏区, 然后和当前的显示设备进行比较,看Layer的脏区域是否在显示设备的显示区域内,如果在显示区域内的话说明该layer是需要绘制的,则更新到显示设备的VisibleLayersSortedByZ列表中,等待被合成
rebuildLayerStacks中最重要的一步是 computeVisibleRegions ,也就是对Layer的变化区域和非透明区域的计算,为什么要对变化区域做计算呢?我们先看看SurfaceFlinger对界面显示区域的分类:
还是以这张图做例子,可以看到我们的状态栏是半透明的,所以它是一个opaqueRegion区域,微信界面和虚拟按键是完全不透明的,他是一个visibleRegion,除了这三个Layer外,还有一个我们看不到的Layer——壁纸,它被上方visibleRegion遮挡了,所以是coveredRegion
对这几个区域的概念清楚了,我们就可以去了解computeVisibleRegions中做的事情了,它主要是这几步操作:
2. android绘图,怎么才能做出拖动画布效果
使用卡马克地图缓冲算法。
基本原理是使用drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)方法。
将位图上指定的部分(src矩形部分)绘制到指定的屏幕位置(dst矩形部分),通过改变src矩形的位置改变实现地图的移动。
给你个连接,自己先看看。
http://xys289187120.blog.51cto.com/3361352/656998
3. 求助View.ondraw中绘制view-Android开发问答
我觉得你还是要看看Android中View和ViewGroup的绘制机制
android自定义view–绘制顺序及相关原理
如果你的自定义View需要绘制其他View,建议继承ViewGroup。这样你只要布局好子View就可以了。要是没特殊需要,基本上不需要自己绘制子View。
4. Android 重学系列 View的绘制流程(六) 硬件渲染(上)
本文开始聊聊Android中的硬件渲染。如果跟着我的文章顺序,从SF进程到App进程的绘制流程一直阅读,我们到这里已经有了一定的基础,可以试着进行横向比对如Chrome浏览器渲染流程,看看软件渲染,硬件渲染,SF合成都做了什么程度的优化。
先让我们回顾一下负责硬件渲染的主体对象ThreadedRenderer在整个绘制流程中做了哪几个步骤。
在硬件渲染的过程中,有一个很核心的对象RenderNode,作为每一个View绘制的节点对象。
当每一次进行准备进行绘制的时候,都会雷打不动执行如下三个步骤:
如果遇到什么问题欢迎来到 https://www.jianshu.com/p/c84bfa909810 下进行讨论
实际上整个硬件渲染的设计还是比较庞大。因此本文先聊聊ThreadedRender整个体系中主要对象的构造以及相关的原理。
首先来认识下面几个重要的对象有一个大体的印象。
在java层中面向Framework中,只有这么多,下面是一一映射的简图。
能看到实际上RenderNode也会跟着View 树的构建同时一起构建整个显示层级。也是因此ThreadedRender也能以RenderNode为线索构建出一套和软件渲染一样的渲染流程。
仅仅这样?如果只是这么简单,知道我习惯的都知道,我喜欢把相关总结写在最后。如果把总揽写在正文开头是因为设计比较繁多。因为我们如果以流水线的形式进行剖析容易造成迷失细节的困境。
让我继续介绍一下,在硬件渲染中native层的核心对象。
如下是一个思维导图:
有这么一个大体印象后,就不容易迷失在源码中。我们先来把这些对象的实例化以及上面列举的ThreadedRenderer在ViewRootImpl中执行行为的顺序和大家来聊聊其原理,先来看看ThreadedRenderer的实例化。
当发现mSurfaceHolder为空的时候会调用如下函数:
而这个方法则调用如下的方法对ThreadedRenderer进行创建:
文件:/ frameworks / base / core / java / android / view / ThreadedRenderer.java
能不能创建的了ThreadedRenderer则决定于全局配置。如果ro.kernel.qemu的配置为0,说明支持OpenGL 则可以直接返回true。如果qemu.gles为-1说明不支持OpenGL es返回false,只能使用软件渲染。如果设置了qemu.gles并大于0,才能打开硬件渲染。
我们能看到ThreadedRenderer在初始化,做了三件事情:
关键是看1-3点中ThreadRenderer都做了什么。
文件:/ frameworks / base / core / jni / android_view_ThreadedRenderer.cpp
能看到这里是直接实例化一个RootRenderNode对象,并把指针的地址直接返回。
能看到RootRenderNode继承了RenderNode对象,并且保存一个JavaVM也就是我们所说的Java虚拟机对象,一个java进程全局只有一个。同时通过getForThread方法,获取ThreadLocal中的Looper对象。这里实际上拿的就是UI线程的Looper。
在这个构造函数有一个mDisplayList十分重要,记住之后会频繁出现。接着来看看RenderNode的头文件:
文件:/ frameworks / base / libs / hwui / RenderNode.h
实际上我把几个重要的对象留下来:
文件:/ frameworks / base / core / java / android / view / RenderNode.java
能看到很简单,就是包裹一个native层的RenderNode返回一个Java层对应的对象开放Java层的操作API。
能看到这个过程生成了两个对象:
这个对象实际上让RenderProxy持有一个创建动画上下文的工厂。RenderProxy可以通过ContextFactoryImpl为每一个RenderNode创建一个动画执行对象的上下文AnimationContextBridge。
文件:/ frameworks / base / libs / hwui / renderthread / RenderProxy.cpp
在这里有几个十分重要的对象被实例化,当然这几个对象在聊TextureView有聊过( SurfaceView和TextureView 源码浅析 ):
我们依次看看他们初始化都做了什么。
文件:/ frameworks / base / libs / hwui / renderthread / RenderThread.cpp
能看到其实就是简单的调用RenderThread的构造函数进行实例化,并且返回对象的指针。
RenderThread是一个线程对象。先来看看其头文件继承的对象:
文件:/ frameworks / base / libs / hwui / renderthread / RenderThread.h
其中RenderThread的中进行排队处理的任务队列实际上是来自ThreadBase的WorkQueue对象。
文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h
ThreadBase则是继承于Thread对象。当调用start方法时候其实就是调用Thread的run方法启动线程。
另一个更加关键的对象,就是实例化一个Looper对象到WorkQueue中。而直接实例化Looper实际上就是新建一个Looper。但是这个Looper并没有获取当先线程的Looper,这个Looper做什么的呢?下文就会揭晓。
WorkQueue把一个Looper的方法指针设置到其中,其作用可能是完成了某一件任务后唤醒Looper继续工作。
而start方法会启动Thread的run方法。而run方法最终会走到threadLoop方法中,至于是怎么走进来的,之后有机会会解剖虚拟机的源码线程篇章进行讲解。
在threadloop中关键的步骤有如下四个:
在这个过程中创建了几个核心对象:
另一个核心的方法就是,这个方法为WorkQueue的Looper注册了监听:
能看到在这个Looper中注册了对DisplayEventReceiver的监听,也就是Vsync信号的监听,回调方法为displayEventReceiverCallback。
我们暂时先对RenderThread的方法探索到这里,我们稍后继续看看回调后的逻辑。
文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h
能看到这里的逻辑很简单实际上就是调用Looper的pollOnce方法,阻塞Looper中的循环,直到Vsync的信号到来才会继续往下执行。详细的可以阅读我写的 Handler与相关系统调用的剖析 系列文章。
文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h
实际上调用的是WorkQueue的process方法。
文件:/ frameworks / base / libs / hwui / thread / WorkQueue.h
能看到这个过程中很简单,几乎和Message的loop的逻辑一致。如果Looper的阻塞打开了,则首先找到预计执行时间比当前时刻都大的WorkItem。并且从mWorkQueue移除,最后添加到toProcess中,并且执行每一个WorkItem的work方法。而每一个WorkItem其实就是通过从某一个压入方法添加到mWorkQueue中。
到这里,我们就明白了RenderThread中是如何消费渲染任务的。那么这些渲染任务又是哪里诞生呢?
上文聊到了在RenderThread中的Looper会监听Vsync信号,当信号回调后将会执行下面的回调。
能看到这个方法的核心实际上就是调用drainDisplayEventQueue方法,对ui渲染任务队列进行处理。
能到在这里mVsyncRequested设置为false,且mFrameCallbackTaskPending将会设置为true,并且调用queue的postAt的方法执行ui渲染方法。
还记得queue实际是是指WorkQueue,而WorkQueue的postAt方法实际实现如下:
/ frameworks / base / libs / hwui / thread / WorkQueue.h
情景带入,当一个Vsync信号达到Looper的监听者,此时就会通过WorkQueue的drainDisplayEventQueue 压入一个任务到队列中。
每一个默认的任务都是执行dispatchFrameCallback方法。这里的判断mWorkQueue中是否存在比当前时间更迟的时刻,并返回这个WorkItem。如果这个对象在头部needsWakeup为true,说明可以进行唤醒了。而mWakeFunc这个方法指针就是上面传下来:
把阻塞的Looper唤醒。当唤醒后就继续执行WorkQueue的process方法。也就是执行dispatchFrameCallbacks方法。
在这里执行了两个事情:
先添加到集合中,在上面提到过的threadLoop中,会执行如下逻辑:
如果大小不为0,则的把中的IFrameCallback全部迁移到mFrameCallbacks中。
而这个方法什么时候调用呢?稍后就会介绍。其实这部分的逻辑在TextureView的解析中提到过。
接下来将会初始化一个重要对象:
这个对象名字叫做画布的上下文,具体是什么上下文呢?我们现在就来看看其实例化方法。
文件:/ frameworks / base / libs / hwui / renderthread / CanvasContext.cpp
文件:/ device / generic / goldfish / init.ranchu.rc
在init.rc中默认是opengl,那么我们就来看看下面的逻辑:
首先实例化一个OpenGLPipeline管道,接着OpenGLPipeline作为参数实例化CanvasContext。
文件:/ frameworks / base / libs / hwui / renderthread / OpenGLPipeline.cpp
能看到在OpenGLPipeline中,实际上就是存储了RenderThread对象,以及RenderThread中的mEglManager。透过OpenGLPipeline来控制mEglManager进而进一步操作OpenGL。
做了如下操作:
文件:/ frameworks / base / libs / hwui / renderstate / RenderState.cpp
文件:/ frameworks / base / libs / hwui / renderthread / DrawFrameTask.cpp
实际上就是保存这三对象RenderThread;CanvasContext;RenderNode。
文件:/ frameworks / base / core / jni / android_view_ThreadedRenderer.cpp
能看到实际上就是调用RenderProxy的setName方法给当前硬件渲染对象设置名字。
文件:/ frameworks / base / libs / hwui / renderthread / RenderProxy.cpp
能看到在setName方法中,实际上就是调用RenderThread的WorkQueue,把一个任务队列设置进去,并且调用runSync执行。
能看到这个方法实际上也是调用post执行排队执行任务,不同的是,这里使用了线程的Future方式,阻塞了执行,等待CanvasContext的setName工作完毕。
5. 如何用android显示实时曲线求方法,代码
1. 网格背景,心电图的原理比较简单,首先绘制一个背景,就是网格就以Windows下的任务管理器来说吧,下面绿色的网格是固定的,如果你比较懒或者考虑绘制效率你甚至可以直接使用一个背景图片代替,当然代码绘制效率没有什么问题,直接使用Canvas的drawLine方法即可。由两个for循环控制着横纵坐标,当然Android123推荐大家使用drawLines参数直接是一个数组。
2. K线图,对于真正的曲线或者说K线图,其实就是描点画图了,在Android中我们自绘控件中重写onDraw方法,onDraw的形参Canvas提供了drawPoint(float x,float y,Point point) 。这三个参数前两个正好对应横竖坐标,第三个参数为Point对象,可以控制画笔的颜色、粗细和类型。如果是动态的,你需要使用一个计时器,最简单的使用Handler的postDelay方法,使用一个数组动态保存着每个点即可。
6. 怎么用Android画一个正方形
先来介绍一下画几何图形要用到的,画布(Canvas)、画笔(Paint)。
1. 画一个圆使用的是drawCircle:canvas.drawCircle(cx, cy, radius, paint);x、y代表坐标、radius是半径、paint是画笔,就是画图的颜色;
2. 在画图的时候还要有注意,你所画的矩形是实心(paint.setStyle(Paint.Style.FILL))还是空心(paint.setStyle(Paint.Style.STROKE);
画图的时候还有一点,那就是消除锯齿:paint.setAntiAlias(true);
3. 还有就是设置一种渐变颜色的矩形:
Shader mShader = new LinearGradient(0,0,100,100, new int[]{Color.RED,Color.GREEn,Color.BLUE,Color.YELLO},null,Shader.TileMode.REPEAT);
ShapeDrawable sd;
//画一个实心正方形
sd = new ShapeDrawable(new RectShape());
sd.setBounds(20,20,100,100);
sd.draw(canvas);
//一个渐变色的正方形就完成了
4. 正方形:drawRect:canvas.drawRect(left, top, right, bottom, paint)
这里的left、top、right、bottom的值是:
left:是矩形距离左边的X轴
top:是矩形距离上边的Y轴
right:是矩形距离右边的X轴
bottom:是矩形距离下边的Y轴
5. 长方形:他和正方形是一个原理,这个就不用说了
6. 椭圆形:记住,这里的Rectf是float类型的
RectF re = new Rect(left, top, right, bottom);
canvas.drawOval(re,paint);
好了,说了这么多的的东西,那就让我们来看一下真正的实例吧!!!
package com.hades.game;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.View;
public class CanvasActivity extends Activity {
/**
* 画一个几何图形
* hades
* 蓝色着衣
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView myView = new MyView(this);
setContentView(myView);
}
public class MyView extends View {
public MyView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画布的背景颜色
canvas.drawColor(Color.WHITE);
/**
* 定义矩形为空心
*/
// 定义画笔1
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
// 消除锯齿
paint.setAntiAlias(true);
// 设置画笔的颜色
paint.setColor(Color.RED);
// 设置paint的外框宽度
paint.setStrokeWidth(2);
// 画一个圆
canvas.drawCircle(40, 30, 20, paint);
// 画一个正放形
canvas.drawRect(20, 70, 70, 120, paint);
// 画一个长方形
canvas.drawRect(20, 170, 90, 130, paint);
// 画一个椭圆
RectF re = new RectF(20, 230, 100, 190);
canvas.drawOval(re, paint);
/**
* 定义矩形为实心
*/
paint.setStyle(Paint.Style.FILL);
// 定义画笔2
Paint paint2 = new Paint();
// 消除锯齿
paint2.setAntiAlias(true);
// 设置画笔的颜色
paint2.setColor(Color.BLUE);
// 画一个空心圆
canvas.drawCircle(150, 30, 20, paint2);
// 画一个正方形
canvas.drawRect(185, 70, 130, 120, paint2);
// 画一个长方形
canvas.drawRect(200, 130, 130, 180, paint2);
// 画一个椭圆形
RectF re2 = new RectF(200, 230, 130, 190);
canvas.drawOval(re2, paint2);
}
}
}
7. Android UI绘制之View绘制的工作原理
这是AndroidUI绘制流程分析的第二篇文章,主要分析界面中View是如何绘制到界面上的具体过程。
ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联。
measure 过程决定了 View 的宽/高, Measure 完成以后,可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取 View 测量后的宽/高,在几乎所有的情况下,它等同于View的最终的宽/高,但是特殊情况除外。 Layout 过程决定了 View 的四个顶点的坐标和实际的宽/高,完成以后,可以通过 getTop、getBottom、getLeft 和 getRight 来拿到View的四个顶点的位置,可以通过 getWidth 和 getHeight 方法拿到View的最终宽/高。 Draw 过程决定了 View 的显示,只有 draw 方法完成后 View 的内容才能呈现在屏幕上。
DecorView 作为顶级 View ,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两个部分,上面是标题栏,下面是内容栏。在Activity中,我们通过 setContentView 所设置的布局文件其实就是被加到内容栏中的,而内容栏id为 content 。可以通过下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通过 content.getChildAt(0) 可以得到设置的 view 。 DecorView 其实是一个 FrameLayout , View 层的事件都先经过 DecorView ,然后才传递给我们的 View 。
MeasureSpec 代表一个32位的int值,高2位代表 SpecMode ,低30位代表 SpecSize , SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。
SpecMode 有三类,如下所示:
UNSPECIFIED
EXACTLY
AT_MOST
LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。
对于顶级View,即DecorView和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同确定;
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;
MeasureSpec一旦确定,onMeasure就可以确定View的测量宽/高。
小结一下
当子 View 的宽高采用 wrap_content 时,不管父容器的模式是精确模式还是最大模式,子 View 的模式总是最大模式+父容器的剩余空间。
View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即测量、布局、绘制。其中 measure 确定 View 的测量宽/高, layout 确定 view 的最终宽/高和四个顶点的位置,而 draw 则将 View 绘制在屏幕上。
measure 过程要分情况,如果只是一个原始的 view ,则通过 measure 方法就完成了其测量过程,如果是一个 ViewGroup ,除了完成自己的测量过程外,还会遍历调用所有子元素的 measure 方法,各个子元素再递归去执行这个流程。
如果是一个原始的 View,那么通过 measure 方法就完成了测量过程,在 measure 方法中会去调用 View 的 onMeasure 方法,View 类里面定义了 onMeasure 方法的默认实现:
先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源码:
可以看到, getMinimumWidth 方法获取的是 Drawable 的原始宽度。如果存在原始宽度(即满足 intrinsicWidth > 0),那么直接返回原始宽度即可;如果不存在原始宽度(即不满足 intrinsicWidth > 0),那么就返回 0。
接着看最重要的 getDefaultSize 方法:
如果 specMode 为 MeasureSpec.UNSPECIFIED 即未指定模式,那么返回由方法参数传递过来的尺寸作为 View 的测量宽度和高度;
如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精确模式,那么返回从 measureSpec 中取出的 specSize 作为 View 测量后的宽度和高度。
看一下刚才的表格:
当 specMode 为 EXACTLY 或者 AT_MOST 时,View 的布局参数为 wrap_content 或者 match_parent 时,给 View 的 specSize 都是 parentSize 。这会比建议的最小宽高要大。这是不符合我们的预期的。因为我们给 View 设置 wrap_content 是希望View的大小刚好可以包裹它的内容。
因此:
如果是一个 ViewGroup,除了完成自己的 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行 measure 过程。
ViewGroup 并没有重写 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 这几个方法专门用于测量子元素。
如果是 View 的话,那么在它的 layout 方法中就确定了自身的位置(具体来说是通过 setFrame 方法来设定 View 的四个顶点的位置,即初始化 mLeft , mRight , mTop , mBottom 这四个值), layout 过程就结束了。
如果是 ViewGroup 的话,那么在它的 layout 方法中只是确定了 ViewGroup 自身的位置,要确定子元素的位置,就需要重写 onLayout 方法;在 onLayout 方法中,会调用子元素的 layout 方法,子元素在它的 layout 方法中确定自己的位置,这样一层一层地传递下去完成整个 View 树的 layout 过程。
layout 方法的作用是确定 View 本身的位置,即设定 View 的四个顶点的位置,这样就确定了 View 在父容器中的位置;
onLayout 方法的作用是父容器确定子元素的位置,这个方法在 View 中是空实现,因为 View 没有子元素了,在 ViewGroup 中则进行抽象化,它的子类必须实现这个方法。
1.绘制背景( background.draw(canvas); );
2.绘制自己( onDraw );
3.绘制 children( dispatchDraw(canvas) );
4.绘制装饰( onDrawScrollBars )。
dispatchDraw 方法的调用是在 onDraw 方法之后,也就是说,总是先绘制自己再绘制子 View 。
对于 View 类来说, dispatchDraw 方法是空实现的,对于 ViewGroup 类来说, dispatchDraw 方法是有具体实现的。
通过 dispatchDraw 来传递的。 dispatchDraw 会遍历调用子元素的 draw 方法,如此 draw 事件就一层一层传递了下去。dispatchDraw 在 View 类中是空实现的,在 ViewGroup 类中是真正实现的。
如果一个 View 不需要绘制任何内容,那么就设置这个标记为 true,系统会进行进一步的优化。
当创建的自定义控件继承于 ViewGroup 并且不具备绘制功能时,就可以开启这个标记,便于系统进行后续的优化;当明确知道一个 ViewGroup 需要通过 onDraw 绘制内容时,需要关闭这个标记。
参考:《Android开发艺术探索》
8. Android 之 Project Butter 详细介绍
UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识 、 如何优化 UI 渲染 两部分内容。
《 View 绘制流程之 setContentView() 到底做了什么? 》
《 View 绘制流程之 DecorView 添加至窗口的过程 》
《 深入 Activity 三部曲(3)View 绘制流程 》
《 Android 之 LayoutInflater 全面解析 》
《 关于渲染,你需要了解什么? 》
《 Android 之 Choreographer 详细分析 》
《 Android 之如何优化 UI 渲染(上) 》
《 Android 之如何优化 UI 渲染(下) 》
现在我们已经很少能够听到关于 Android UI 卡顿的话题了,这得益于 Google 长期以来对 Android 渲染性能的重视,基本每次 Google I/O 都会花很多篇幅讲这一块。随着时间的推移,Android 系统一直在不断进化、壮大,并且日趋完善。
其中,Google 在 2012 年的 I/O 大会上宣布了 Project Butter 黄油计划,那个曾经严重影响 Android 口碑的 UI 流程性问题,首先在这得到有效的控制,并且在 Android 4.1 中正式开启了这个机制。
Project Butter 对 Android Display 系统进行了重构,引入了三个核心元素,即 VSYNC 、 Triple Buffer 和 Choreographer 。
其中 VSYNC 是理解 Project Butter 的核心。接下来,我们就围绕 VSYNC 开始介绍 Project Butter 对 Android Display 系统做了哪些优化。
VSYNC 最初是由 GPU 厂商开发的一种,用于防止屏幕撕裂的技术方案,全称 Vertical Synchronization,该方案很早就已经被广泛应用于 PC 上。我们可以把它理解为一种时钟中断。
VSYNC 是一种图形技术,它可以同步 GPU 的 帧速率 和显示器的 刷新频率 ,所以在理解 VSYNC 产生的原因及其作用之前,我们有必要先来了解下这两个概念。
表示屏幕在一秒内刷新画面的次数, 刷新频率取决于硬件的固定参数,单位 Hz(赫兹)。例如常见的 60 Hz、144 Hz,即每秒钟刷新 60 次或 144 次。
逐行扫描
显示器并不是一次性将画面显示到屏幕上,而是从左到右边,从上到下逐行扫描显示,不过这一过程快到人眼无法察觉到变化。以 60 Hz 刷新率的屏幕为例,即 1000 / 60 ≈ 16ms。
表示 GPU 在一秒内绘制操作的帧数,单位 fps。例如在电影界采用 24 帧的速度足够使画面运行的非常流畅。而 Android 系统则采用更加流程的 60 fps,即每秒钟绘制 60 帧画面。更多内容参考《 Why 60 fps 》。
现在,刷新频率和帧率需要一起合作,才能使图形内容呈现在屏幕上,GPU 会获取图形数据进行绘制, 然后硬件负责把图像内容呈现到屏幕上,这一过程在应用程序的生命周期内一遍又一遍的发生。
如上图,CPU / GPU 生成图像的 Buffer 数据,屏幕从 Buffer 中读取数据刷新后显示。理想情况下帧率和刷新频率保持一致,即每绘制完成一帧,显示器显示一帧。不幸的是,刷新频率和帧率并不总是能够保持相对同步,如果帧速率实际比刷新率快,例如帧速率是 120 fps,显示器的刷新频率为 60 Hz。此时将会发生一些视觉上的问题。
当 GPU 利用一块内存区域写入一帧数据时,从顶部开始新一帧覆盖前一帧,并立刻输出一行内容。当屏幕刷新时,此时它并不知道图像缓冲区的状态,因此从缓冲区抓取的帧并不是完整的一帧画面(绘制和屏幕读取使用同一个缓冲区)。此时屏幕显示的图像会出现上半部分和下半部分明显偏差的现象,这种情况被称之为 “tearing”(屏幕撕裂)。
那如何防止 “tearing” 现象的发生呢?由于图像绘制和读取使用的是同一个缓冲区,所以屏幕刷新时可能读取到的是不完整的一帧画面。解决方案是采用 Double Buffer。
Double Buffer(双缓冲)背后的思想是让绘制和显示器拥有各自的图像缓冲区。GPU 始终将完成的一帧图像数据写入到 Back Buffer ,而显示器使用 Frame Buffer ,当屏幕刷新时,Frame Buffer 并不会发生变化,Back Buffer 根据屏幕的刷新将图形数据 到 Frame Buffer,这便是 VSYNC 的用武之地。
在 Android 4.1 之前,Android 便使用的双缓冲机制。怎么理解呢?一般来说,在同一个 View Hierarchy 内的不同 View 共用一个 Window,也就是共用同一个 Surface。
每个 Surface 都会有一个 BufferQueue 缓存队列,但是这个队列会由 SurfaceFlinger 管理,通过匿名共享内存机制与 App 应用层交互。
整个流程如下:
但是 UI 绘制任务可能会因为 CPU 在忙别的事情,导致没来得及处理。所以 从 Android 4.1 开始, VSYNC 则更进一步,现在 VSYNC 脉冲信号开始用于下一帧的所有处理 。
Project Butter 首先对 Android Display 系统的 SurfaceFlinger 进行了改造,目标是提供 VSYNC 中断。每收到 VSYNC 中断后,CPU 会立即准备 Buffer 数据,由于大部分显示设备刷新频率都是 60 Hz(一秒刷新 60 次),也就是说一帧数据的准备工作都要在 16ms 内完成。
这样应用总是在 VSYNC 边界上开始绘制,而 SurfaceFlinger 总是在 VSYNC 边界上进行合成。这样可以消除卡顿,并提升图形的视觉表现。
如果理解了双缓冲机制的原理,那就非常容易理解什么是三缓冲区了。如果只有两个 Graphic Buffer 缓冲区 A 和 B,如果 CPU / GPU 绘制过程较长,超过一个 VSYNC 信号周期。
由上图可知:
为什么 CPU 不能在第二个 16ms 处理绘制工作呢?原因是只有两个 Buffer,缓冲区 B 中的数据还没有准备完成,所以只能继续展示 A 缓冲区的内容,这样缓冲区 A 和 B 都分别被显示设备和 GPU 占用,CPU 则无法准备下一帧的数据。如果再提供一个缓冲区,CPU、GPU 和显示设备都能使用各自的缓冲区工作,互不影响。
简单来说,三重缓冲机制就是在双缓冲机制基础上增加了一个 Graphic Buffer 缓冲区,这样可以最大限度的利用空闲时间,带来的坏处是多使用的一个 Graphic Buffer 所占用的内存。
由上图可知:
Choreographer 也是 Project Butter 计划新增的机制,用于配合系统的 VSYNC 中断信号。它本质是一个 Java 类,如果直译的话为舞蹈指导,这是一个极富诗意的表达,看到这个词不得不赞叹设计者除了 Coding 之外的广泛视野。舞蹈是有节奏的,节奏使舞蹈的每个动作更加协调和连贯;视图刷新也是如此。
Choreographer 可以接收系统的 VSYNC 信号,统一管理应用的输入、动画和绘制等任务的执行时机。Android 的 UI 绘制任务将在它的统一指挥下,井然有序的完成。业界一般通过它来监控应用的帧率。
Choreographer 的构造方法:
优先级的高低和处理顺序有关。当收到 VSYNC 信号时,Choreographer 将首先处理 INPUT 类型的回调,然后 ANIMATION 类型,最后才是 TRAVERSAL 类型。
另外,Android 在 4.1 还对 Handler 机制进行了略微改造,使之支持 Asynchronous Message(异步消息) 和 Synchronization Barrier(同步屏障)。一般情况下同步消息和异步消息的处理方式并没有什么区别,只有在设置了 同步屏障 时才会出现差异。 同步屏障为 Handler 消息机制增加了一种简单的优先级关系,异步消息的优先级要高于同步消息 。简单点说,设置了同步屏障之后,Handler 只会处理异步消息。
以 View 的绘制流程为例:
scheleTraversals 首先禁止了后续消息的处理能力,一旦设置了消息队列的 postSyncBarrier,所有非 Asynchronous 的消息将被停止派发。
UI 绘制任务设置了 CALLBACK 类型为 TRAVERSAL 类型的任务,即 mTraversalRunnable:
Choreographer 的 postCallback 方法将会申请一次 VSYNC 中断信号,通过 DisplayEventReceiver 的 scheleVsync 方法。当 VSYNC 信号到达时,便会回调 Choreographer 的 doFrame 方法,内部会触发已经添加的回调任务:
此时 UI 绘制任务 doTraversal 方法被回调,即在 Android 4.1 之后, UI 绘制任务被放置到了 VSYNC 中断处理中了。Choreographer 确实做到了统一协调管理 UI 的绘制工作。有关 Choreographer 更详细的分析,可以参考《 Android 之 Choreographer 详细分析 》。
在从根本解决 Android UI 不流畅的问题上,Project Butter 黄油计划率先迈出了最重要一步,Android 的渲染性能也确实有了很大改善。
不过优化是无止境的,Google 在后续版本中又引入了一些比较大的改变,例如 Android 5.0 的 RenderThread,Android 将所有的绘制任务都放到了该线程,这样即便主线程有耗时的操作也可以保证动画流畅性。
关于 UI 渲染所涉及的内容非常多,而且 Android 渲染框架演进的非常快,文章最后也会附上一些(8)android绘制原理扩展阅读,便于更好的学习理解。
文中如有不妥或有更好的分析结果,欢迎您的留言或指正。文章如果对你有帮助,请留个赞吧。
其他系列专题
9. android的自定义View的实现原理哪位能给我个思路呢。谢谢。
如果说要按类型来划分的话,自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件。那么下面我们就来依次学习一下,每种方式分别是如何自定义View的。
一、自绘控件
自绘控件的意思就是,这个View上所展现的内容全部都是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的,而这部分内容我们已经在Android视图绘制流程完全解析,带你一步步深入了解View(二)中学习过了。
下面我们准备来自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次。新建一个CounterView继承自View,代码如下所示:
<?xmlversion="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#ffcb05">
<Button
android:id="@+id/button_left"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:background="@drawable/back_button"
android:text="Back"
android:textColor="#fff"/>
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="ThisisTitle"
android:textColor="#fff"
android:textSize="20sp"/>
</RelativeLayout>
在这个布局文件中,我们首先定义了一个RelativeLayout作为背景布局,然后在这个布局里定义了一个Button和一个TextView,Button就是标题栏中的返回按钮,TextView就是标题栏中的显示的文字。
接下来创建一个TitleView继承自FrameLayout,代码如下所示:
{
privateButtonleftButton;
privateTextViewtitleText;
publicTitleView(Contextcontext,AttributeSetattrs){
super(context,attrs);
LayoutInflater.from(context).inflate(R.layout.title,this);
titleText=(TextView)findViewById(R.id.title_text);
leftButton=(Button)findViewById(R.id.button_left);
leftButton.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
((Activity)getContext()).finish();
}
});
}
publicvoidsetTitleText(Stringtext){
titleText.setText(text);
}
publicvoidsetLeftButtonText(Stringtext){
leftButton.setText(text);
}
(OnClickListenerl){
leftButton.setOnClickListener(l);
}
}
TitleView中的代码非常简单,在TitleView的构建方法中,我们调用了LayoutInflater的inflate()方法来加载刚刚定义的title.xml布局,这部分内容我们已经在Android LayoutInflater原理分析,带你一步步深入了解View(一)这篇文章中学习过了。
接下来调用findViewById()方法获取到了返回按钮的实例,然后在它的onClick事件中调用finish()方法来关闭当前的Activity,也就相当于实现返回功能了。
另外,为了让TitleView有更强地扩展性,我们还提供了setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法,分别用于设置标题栏上的文字、返回按钮上的文字、以及返回按钮的点击事件。
到了这里,一个自定义的标题栏就完成了,那么下面又到了如何引用这个自定义View的部分,其实方法基本都是相同的,在布局文件中添加如下代码:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.customview.TitleView
android:id="@+id/title_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.customview.TitleView>
</RelativeLayout>
这样就成功将一个标题栏控件引入到布局文件中了,运行一下程序。
现在点击一下Back按钮,就可以关闭当前的Activity了。如果你想要修改标题栏上显示的内容,或者返回按钮的默认事件,只需要在Activity中通过findViewById()方法得到TitleView的实例,然后调用setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法进行设置就OK了。
10. android地图缩小放大的时候maker之间聚合是什么原理
<1>GeoPoint
表示一个地理坐标点,存放经度和纬度,以微度的整数形式存储。
方法
GeoPoint(int latitudeE6, int longitudeE6)用给定的经纬度构造一个GeoPoint
方法介绍:
public int getLatitudeE6()
返回GeoPoint的纬度,单位微度
public int getLongitudeE6()
返回GeoPoint的经度,单位微度
public void setLatitudeE6(int latitudeE6)
设置GeoPoint的纬度,单位微度
public void setLongitudeE6(int longitudeE6)
设置GeoPoint的经度,单位微度
<2>接口 Projection
该接口用来在屏幕像素x/y坐标系和地球经纬度坐标系之间进行转换,通过 MapView.getProjection()来取得映射类。
GeoPoint fromPixels(int x, int y)
该方法用给定的像素坐标创建一个新的GeoPoint。
给定的像素点是以MapView的左上角为原点的坐标系统,MapView提供了这个像素转换器(PixelConverter)。 参数:result -
搜索结果iError - 错误号,0表示正确返回
Point toPixels(GeoPoint in, Point out)
把给定的GeoPoint变换到相对于MapView左上角的屏幕像素坐标。MapView提供了这种投影变换。 参数:in - 待变换的一对经纬度out
- 一个用于输出预先存在的对象;如果为空,将返回一个新分配的像素点。
<3>MapController
处理地图移动和缩放的工具类。
返回类型方法
voidanimateTo(GeoPoint point)对以给定的点GeoPoint,开始动画显示地图。
voidanimateTo(GeoPoint point, Message message)对以给定的GeoPoint,开始动画显示地图。
booleanonKey(View v, int keyCode, KeyEvent event)处理按键事件,把事件变换为适度的地图平移。
voidscrollBy(int x, int y)按照给定的像素数据量滚动。
voidsetCenter(GeoPoint point)在给定的中心点GeoPoint上设置地图视图。
intsetZoom(int zoomLevel)设置地图的缩放级别。
voidstopAnimation(boolean
jumpToFinish)终止所有未完成的动画,有条件的把地图中心修正到已完成的特殊动画的偏移量上去。
voidstopPanning()重新设置平移状态,使地图静止。
booleanzoomIn()放大一个级别。
booleanzoomInFixing(int xPixel, int yPixel)放大一个级别。
booleanzoomOut()缩小一个级别。
booleanzoomOutFixing(int xPixel, int yPixel)缩小一个级别。
voidzoomToSpan(int latSpanE6, int lonSpanE6)尝试调整地图的缩放,以便显示给定的经纬度范围。
<4>MapView
一个显示地图的视图,当被焦点选中时,它能捕获按键事件和触摸手势去平移和缩放地图。
返回类型方法
booleancanCoverCenter()检查当前是否有地图贴片覆盖地图中心点。
protected booleancheckLayoutParams (android.view.ViewGroup.LayoutParams
p)仅检查p是否是的一个MapView.LayoutParams实例。
voidcomputeScroll()捕获滚动事件,用它们去平移地图。
voiddisplayZoomControls(boolean takeFocus)显示缩放控件,可以选择是否请求焦点选中以便通过按键访问。
protected
android.view.ViewGroup.()返回一个Layout参数的集合,其中参数带有ViewGroup.LayoutParams.WRAP_CONTENT的宽度,ViewGroup.LayoutParams.WRAP_CONTENT高度和坐标(0,0)。
protected
android.view.ViewGroup.(android.view.ViewGroup.LayoutParams
p)
android.view.ViewGroup.(AttributeSet
attrs)
MapControllergetController()返回地图的MapController,这个对象可用于控制和驱动平移和缩放。
intgetLatitudeSpan()当前纬线的跨度(从地图的上边缘到下边缘),十进制度×1,000,000。
intgetLongitudeSpan()当前经度的跨度(从地图的左边缘到地图的右边缘),单位:十进制的度×1,000,000。
GeoPointgetMapCenter()返回当前地图中心点位置,做为一个GeoPoint(经度、纬度)的对象。
intgetMaxZoomLevel()返回当前视图中心点的最大缩放级别。
java.util.ListgetOverlays()获取Overlay列表。
ProjectiongetProjection()获取屏幕像素坐标和经纬度对之间的转换。
ViewgetZoomControls()已过时。
intgetZoomLevel()返回当前地图的缩放级别。
booleanisSatellite()
booleanisStreetView()
booleanisTraffic()是否显示交通流量。
protected voidonDetachedFromWindow()当被分离调用,清除缩放控件。
protected voidonDraw(Canvas canvas)
voidonFocusChanged(boolean hasFocus, int direction, Rect
previouslyFocusedRect)当这个视图的焦点状态变化时被视图系统调用。
booleanonKeyDown(int keyCode, KeyEvent event)把按键传送到overlay。
booleanonKeyUp(int keyCode, KeyEvent event)把按键传送到overlay。
protected voidonLayout(boolean flag, int l, int t, int r, int b)
protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)
voidonRestoreInstanceState(Bundle state)把MapView的状态恢复到一个Bundle。
voidonSaveInstanceState(Bundle state)把MapView的状态存储到一个Bundle中。
protected voidonSizeChanged(int w, int h, int oldw, int
oldh)重新调整地图对象的尺寸。
booleanonTouchEvent(MotionEvent
event)首先把touch事件传送到overlay,如果它不处理它们,就把事件再传送到手势探测器,然后分发探测到的手势。
booleanonTrackballEvent(MotionEvent
event)把trackball事件首先传送到overlay,如果它们不处理消息,尝试取平移和点击。
voidonWindowFocusChanged(boolean hasFocus)当包含这个视图的窗口得到或是去焦点时被调用。
voidpreLoad()
voidsetDrawOverlayWhenZooming(boolean bDraw)设置在缩放动画过程中是否绘制overlay,默认为不绘制。
如果绘制,在覆盖物很多的情况下效率会有损失。 自1.1版本之后支持。
voidsetBuiltInZoomControls(boolean on)设置是否启用内置的缩放控件。
voidsetReticleDrawMode(com..mapapi.MapView.ReticleDrawMode
mode)暂不支持。
voidsetSatellite(boolean on)设置是否打开卫星图。
voidsetStreetView(boolean on)暂不支持。
voidsetTraffic(boolean on)设置是否打开交通流量图层。
voidregMapViewListener(BMapManager bmapMan, MKMapViewListener
listener)注册地图显示事件监听器。
<5>MyLocationOverlay
一个负责显示用户当前位置的Overlay。
Overlay是一个覆盖,它绘制用户当前在地图上的位置(精准度),和/或一个嵌入的指南针。子类能覆盖方法dispatchTap()去处理对当前位置的点击。
为了开启这个overlay的功能,需要去调用enableMyLocation()和/或enableCompass(),
或调用Activity中的Activity.onResume()方法。记住,当在后台是,要在Activity中的Activity.onPause()方法中调用相应的disableMyLocation()和/或disableCompass()关闭这个功能。
返回类型方法
voiddisableCompass()关闭指南针的更新。
voiddisableMyLocation()停止位置更新。
protected booleandispatchTap()在“我的位置”坐标上处理点击事件。
booleandraw(Canvas canvas, MapView mapView, boolean shadow, long
when)绘制方法。
protected voiddrawCompass(android.graphics.Canvas canvas, float
bearing)绘制指南针。
protected voiddrawMyLocation(Canvas canvas, MapView mapView,
android.location.Location lastFix, GeoPoint myLocation, long when)绘制“我的位置”点。
booleanenableCompass()开启指南针更新功能。
booleanenableMyLocation()尝试开启MyLocation功能,并向MKLocationManager.GPS_PROVIDER和MKLocationManager.NETWORK_PROVIDER注册更新。
LocationgetLastFix()返回一个位置,对应于最近设定的用户位置。
GeoPointgetMyLocation()返回一个GeoPoint,对应于一个最近设定的用户位置。
floatgetOrientation()返回最近设定的的指南针朝向。
booleanisCompassEnabled()检查指南针小部件是否被显示。
booleanisMyLocationEnabled()
voidonAccuracyChanged(Sensor sensor, int accuracy)
voidonLocationChanged(Location location)监听并获取位置更新。
voidonProviderDisabled(java.lang.String provider)
voidonProviderEnabled(java.lang.String provider)
voidonSensorChanged(SensorEvent event)当指南针的值变换时,由SensorManager调用。
booleanonSnapToItem(int x, int y, android.graphics.Point snapPoint, MapView
mapView)检查给定的(x,y)是否和引起当前行为(如缩放)的item足够靠近。
booleanonTap(GeoPoint p, MapView mapView)检查点击的位置是否非常接近于当前的位置(如果已知)。
booleanrunOnFirstFix(java.lang.Runnable
runnable)把一个runnable加入队列,一旦收到一个位置信息,这个runnable就被执行。
<6>Overlay
Overlay是一个基类,它表示可以显示在地图上方的覆盖overlay。
添加一个overlay时,从这个基类派生出一个子类,创建一个实例,然后把它加入到一个列表中。这个列表通过调用MapView.getOverlays()得到。为了允许用户触摸去对齐一个点,子类应当实现Overlay.Snappable接口。
返回类型方法
booleandraw(Canvas canvas, MapView mapView, boolean
shadow)在地图上绘制overlay。
booleandraw(Canvas canvas, MapView mapView, boolean shadow, long
when)专门绘制动画overlay的调用。
protected static voiddrawAt(Canvas canvas, Drawable drawable, int x, int y,
boolean bShadow)在某个偏移位置画一个Drawable的便捷方法。
booleanonKeyDown(int keyCode, KeyEvent event, MapView
mapView)处理一个按键被按下的事件。
booleanonKeyUp(int keyCode, KeyEvent event, MapView mapView)处理一个按键放开事件。
booleanonTap(GeoPoint p, MapView mapView)处理一个“点击”事件。
booleanonTouchEvent(MotionEvent e, MapView mapView)处理一个触摸事件。
booleanonTrackballEvent(MotionEvent e, MapView mapView)处理一个轨迹球事件。