hadoop学习路线图哪家专业

Android自定义控件:图片比例适配,解决图片白边(详解View中onMeasure方法)
当App中涉及到布局需要展示大量图片时,你就应该考虑到&图片比例适配&的问题。当图片的宽高规格不同时,你设置展示的ImageViewvc3Ryb25nPsrHt/G/ydLUPHN0cm9uZz7N6rrDtdjVucq+PC9zdHJvbmc+o6w8c3Ryb25nPszus+TC+jwvc3Ryb25nPqO/0rK+zcrHy7VJbWFnVmlld7XEscjA/brNzbzGrLXEscjA/bK7xqXF5KOssrvIu7XEu7C74bW81sLNvMasxdS74cH009A8c3Ryb25nPr/VsNc8L3N0cm9uZz6jrNXi0fnSu8+1wdC1xNfpzbzEo7/pz8LAraOs09C1xNPQPHN0cm9uZz6w17HfPC9zdHJvbmc+o6zT0LXEw7vT0KOst8ezo9Owz+zDwLnboaM8c3Ryb25nPr3Tz8LAtLXE19S2qNLlv9i8/r2rv8nS1M/7s/0g1bnKvs28xqzT0LDXsd+1xM7KzOKjrNTasru21M28xqy9+NDQyM66zrLDvPShosCtyey1xMewzOHPwqOs1+6088/etsizys/Ws/bNvMas0fnKvaOhPC9zdHJvbmc+PGJyIC8+DQo8aW1nIGFsdD0="这里写图片描述" src="/uploadfile/Collfiles/77.png" title="\" />
一. 自定义 按比例展示高度 的控件
以上图的组图页面为例,为了达到更好地显示图片的效果,我们来自定义一个控件,这个控件的宽度填充屏幕,但是高度不确定,根据图片比例具体情况动态的设置高度值。
比如说组图页面展现的图片样式都是这种:图片尺寸为 444 x 183 ,所以比例为 444/183 =2.43 (具体比例根据你APP中需要展示的图片类型而定!)有了比例之后,自定义控件中的 高度 就根据这个 比例 来动态设置。
1.自定义控件 继承帧布局
有了以上规划后,我们可以用一个 帧布局,以它来作为一个容器,让它的宽高严格按照图片的宽高来设置,使得图片填充这个帧布沮喎"/kf/yidong/wp/" target="_blank" class="keylink">WPC9zdHJvbmc+o6zNvMasSW1hZ2VWaWV3zt7Q6Nf2yM66zrjEtq+jrNDeuMTWobK8vta8tL/JoaOjqNXiwO+yu9Do0qq/vMLH19S2qNLlv9i8/silvMyz0EltYWdlVmlld6OszKu5/bi01NOjrNahsry+1rj8yN3S18q1z9ajrLb4x9LU2tfUtqjS5b/YvP66zbavzKzM7rPk0rPD5tXit73D5sq508O3x7OjueOjqTwvcD4NCjxwcmUgY2xhc3M9"brush:">
自定义控件, 按照比例来决定布局高度
* Created by gym on .
public class RatioLayout extends FrameLayout {
public RatioLayout(Context context) {
super(context);
public RatioLayout(Context context, AttributeSet attrs) {
super(context, attrs);
public RatioLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
2.自定义属性,布局编写
xml文件中部分代码
自定义属性
如以上布局文件中代码所示,帧布局RatioLayout的宽度填充屏幕,高度随比例而定,也就是会让图片来填充满这个帧布局,所以原先设定的ImageView的属性要修改!
android:layout_height=&wrap_content&
android:scaleType=&fitXY&
3. 在构造方法中获取 属性值
public RatioLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// 获取属性值
// attrs.getAttributeFloatValue(&&, &ratio&, -1);
// 当自定义属性时, 会自动生成属性相关id, 此id通过R.styleable来引用
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.RatioLayout);
// id = 属性名_具体属性字段名称 (此id系统自动生成)
ratio = typedArray.getFloat(R.styleable.RatioLayout_ratio, -1);
typedArray.recycle();// 回收typearray, 提高性能
System.out.println(&ratio:& + ratio);
获取属性值的两种方法:
一般谷歌最常用的是attrs.getAttributeFloatValue(&&, &ratio&, -1);
(1)首先使用 context 的方法获取属性的集合数组。
这里的RatioLayout是在自定义属性里定义的,底层已经将它编译成一个R文件了,就是一个int数组。
(2)调用完方法后,它会返回一个TypedArray类的数组集合,再去get到自定义的属性字段即可。(注意:typedArray.getFloat(R.styleable.RatioLayout_ratio, -1); 中的RatioLayout_ratio id 是系统自动生成:&属性名称_具体字段&)
(3) typeArray 回收,提高性能。
分析完后,明显发现第一种取属性值的方法要简单,所以仅作了解即可。
二. 重写 onMeasure 方法(详解onMeasure )
经过以上步骤,我们已经拿到了 比例值,宽度也确定下拉,现在要根据比例值来设置 自定义控件高度。这就涉及到 尺寸的调整,一个布局,它有三个核心的方法:Measure测量调整它的大小;layout设置它的位置;draw具体绘制。
而我们需要在测量&&Measure里重新修改它的宽高,重写onMeasure方法。方法中需要做的步骤:
a. 获取宽度
b. 根据宽度和比例ratio, 计算控件的高度
c.. 重新测量控件
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1. 获取宽度
// 2. 根据宽度和比例ratio, 计算控件的高度
// 3. 重新测量控件
System.out.println(&widthMeasureSpec:& + widthMeasureSpec)
int width = MeasureSpec.getSize(widthMeasureSpec);// 获取宽度值
int widthMode = MeasureSpec.getMode(widthMeasureSpec);// 获取宽度模式
int height = MeasureSpec.getSize(heightMeasureSpec);// 获取高度值
int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 获取高度模式
// 宽度确定, 高度不确定, ratio合法, 才计算高度值
if (widthMode == MeasureSpec.EXACTLY
&& heightMode != MeasureSpec.EXACTLY && ratio & 0) {
// 图片宽度 = 控件宽度 - 左侧内边距 - 右侧内边距
int imageWidth = width - getPaddingLeft() - getPaddingRight();
// 图片高度 = 图片宽度/宽高比例
int imageHeight = (int) (imageWidth / ratio + 0.5f);
// 控件高度 = 图片高度 + 上侧内边距 + 下侧内边距
height = imageHeight + getPaddingTop() + getPaddingBottom();
// 根据最新的高度来重新生成heightMeasureSpec(高度模式是确定模式)
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
// 按照最新的高度测量控件
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1. onMeasure 方法中的参数分析
void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法中两个参数widthMeasureSpec、heightMeasureSpec并非是真正的宽高,而是带了一种模式的数值表示。
MeasureSpec类的两个方法,一个是getSize,一个是getMode。首先打印日志查看这个widthMeasureSpec具体值
System.out.println(&widthMeasureSpec:& + widthMeasureSpec)
这么大的值,怎么可能是它的宽度呢?将它转为二进制:
最前面的 &1&代表的就是模式,后面的数值才是真正的宽度。将 转换成 十进制,是462个像素(如下图蓝线所示)。我的模拟器是 480x800 ,所以这个值是合理的
所以这个widthMeasureSpec值表示的是:模式+宽度值
2. MeasureSpec中模式分析
而MeasureSpec的Mode 模式类型 有三种:
MeasureSpec.AT_MOST:至多模式, 控件有多大显示多大,类似于wrap_content
MeasureSpec.EXACTLY:确定模式, 类似宽高写死成dip,类似 match_parent
MeasureSpec.UNSPECIFIED:未指定模式,不确定宽高,动态计算测量。(举例scrollView,高度决定于它的孩子数量所占的高度)
所以之前转换成那么大的数,最前头的1代表模式 EXACTLY,而根据源码定义 1 && 30,widthMeasureSpec数值之所以那么大,是因为它左移了30位。
所以真正的宽高值是 将widthMeasureSpec 转换为二进制后的后面几位数,再转换为十进制即可,当然方法中毋须那么复杂,MeasureSpec.getSize(widthMeasureSpec);即可。
3. 计算控件高度
详解完 onMeasure方法后,则可以开始重新测量布局,步骤a已完成,进行步骤b ,首先 if 判断 当宽度确定(这里的宽度一定要是一个准确值), 高度不确定, ratio合法, 才计算高度值。
if (widthMode == MeasureSpec.EXACTLY
&& heightMode != MeasureSpec.EXACTLY && ratio & 0)
注意:在计算imageView时,一定要考虑 Padding 值,图片宽度 = 控件宽度 - 左侧内边距 - 右侧内边距
根据图片真正的宽度,使用比例计算它的高度,这时我们自定义控件的高度也出来了
控件高度 = 图片高度 + 上侧内边距 + 下侧内边距,最后我们有了真正高度数值后,再获取当前Mode类型,封装好heightMeasureSpec,让父类执行测量的最新高度值即可。
// 按照最新的高度测量控件
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
三.展示效果
总结就是我们就是希望有一个布局,让它完全按照图片的比例来展现原样式,所以我们自定义一个帧布局FrameLayout,让图片去填充这个帧布局,帧布局有多宽多高,展示的图片就有多宽多高。
可以明显看出,前一张小白边左右多出来的,后者是完全无贴合的,而且并没有裁剪、拉伸图片,按照完美比例展现图片。以下动图就是最后成果,彻底消灭图片留有白边的情况
如果你还是认为这个 图片比例适配的自定义控件没起到什么作用,那我现在把 比例改成1 ,呈现如下,宽高值相同,但是图片的左右两边仍然是无缝衔接,未留有白边!
希望对你有帮助:)Android 自定义控件开发入门(一)
作为一个有创意的开发者,或者软件对UI设计的要求比较高,你经常会遇到安卓自带的控件无法满足你的需求的情况,这种时候,我们只能去自己去实现适合项目的控件。同时,安卓也允许你去继承已经存在的控件或者实现你自己的控件以便优化界面和创造更加丰富的用户体验。
那么怎样来创建一个新的控件呢?&
这得看需求是怎样的了。
1.需要在原生控件的基本功能上进行扩展,这个时候你只需要继承并对控件进行扩展。通过重写它的事件,onDraw ,但是始终都保持都父类方法的调用。如从已有的高级控件上继承,例如继承一个TextView。
2.需要几个控件的功能的加和,这个时候要把控件组合起来,就是通过合并几个控件来生成一个新控件。比如在ListView中用适配器来将多种控件有机的结合在一起,又如写一个控件是多个控件的组合,一般是自定义布局,可以用一个类继承一个布局。这个布局中包含多个控件。
3.白手起家自己创建一个新的控件。即直接从View,ViewGroup开始绘制控件
<span style="color:#.另外大家不要忘了,还有一个好用的东西&include&标签。& 在一个项目中我们可能会需要用到相同的布局设计,如果都写在一个xml文件中,代码显得很冗余,并且可读性也很差,所以我们可以把相同布局的代码单独写成一个模块,然后用到的时候可以通过&include /& 标签来重用layout代码。
作过 &应用开发的朋友都知道,Android的UI界面都是由View和ViewGroup及其派生类组合而成的。基于安卓UI设计原理,我们 作为开发者,完全能够按照自己的意愿开发出项目定制的。其中,View是所有UI组件的基类,而ViewGroup是容纳这些组件的容器,其本身也是 从View派生出来的。AndroidUI界面的一般结构可参见下面的示意图:
可见, 作为容器的ViewGroup可以包含作为叶子节点的View,也可以包含作为更低层次的子ViewGroup,而子ViewGroup又可以包含下一层 的叶子节点的View和ViewGroup。事实上,这种灵活的View层次结构可以形成非常复杂的UI布局,开发者可据此设计、开发非常精致的UI界面。
ViewGroup可以通过重写onMeasure,onLayout为加入其中的View进行布局和处理,功能十分强大,我们这次先学习View类派生自定义组件:
View组件的作用类似于JAVA中Swing里的Panel,是一个矩形的空白区域,不带有任何内容,对于Android应用的其他UI控件来说,都是继承了View组件,然后绘制出来的。所以我们通过View子类并重写View类的方法来派生我们自己的控件。
Android自定义View实现很简单:
继承View,重写构造函数、onDraw,(onMeasure)等函数,下面会逐一列举。
如果自 定义的View需要有自定义的属性,需要在values下建立attrs.xml。在其中定义你的属性。在使用到自定义View的xml布局文件中需要加 入xmlns:前缀=&/apk/res/你的自定义View所在的包路径&.在使用自定义属性的 时候,使用前缀:属性名,如my:textColor=&&&&。
让我们先看一下View类的方法:
Description
Constructors
There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.
onFinishInflate()
Called after a view and all of its children has been inflated from XML.
onMeasure(int, int)
Called to determine the size requirements for this view and all of its children.
onLayout(boolean, int, int, int, int)
Called when this view should assign a size and position to all of its children.
onSizeChanged(int, int, int, int)
Called when the size of this view has changed.
onDraw(android.graphics.Canvas)
Called when the view should render its content.
Event processing
onKeyDown(int, KeyEvent)
Called when a new hardware key event occurs.
onKeyUp(int, KeyEvent)
Called when a hardware key up event occurs.
onTrackballEvent(MotionEvent)
Called when a trackball motion event occurs.
onTouchEvent(MotionEvent)
Called when a touch screen motion event occurs.
onFocusChanged(boolean, int, android.graphics.Rect)
Called when the view gains or loses focus.
onWindowFocusChanged(boolean)
Called when the window containing the view gains or loses focus.
onAttachedToWindow()
Called when the view is attached to a window.
onDetachedFromWindow()
Called when the view is detached from its window.
onWindowVisibilityChanged(int)
Called when the visibility of the window containing the view has changed.
通常可能需要重写以下方法:
1.构造器,至少用来获取Context
2.onFinishlnflate()这是一个回调方法, 当应用从 XML 布局文件加载该组件并利用
它来构建界面之后, 该方法就会被回调。
3.onMeasure(int,int):调用该方法来检测View组件及它所包含的所有子组件的大小.
4.onlayout(boolean,int,int,int,int):当该组件需要分配其子组件的位置、大小时,
该方法就会被回调. View类中布局发生改变时会调用的方法,这个方法是所有View、ViewGroup及其派生类都具有的方法,重载该类可以在布局发生改变时作定制处理,这在实现一些特效时非常有用。
5.onSizeChanged(int,int, int, int):当该组件的大小被改变时回调该方法.
6.onDraw(canves): 当该组件将要绘制它的内容时回调该方法迸行绘制. View类中用于重绘的方法,这个方法是所有View、ViewGroup及其派生类都具有的方法,也是Android&UI绘制最重要的方法。开发者可 重载该方法,并在重载的方法内部基于参数canvas绘制自己的各种图形、图像效果。
7.onKeyDown(int,KeyEvent): 当某个键被按下时触发该方法.
8.onKayUp(int,KeyEvent), 当松开某个键时触发该方法.
9.onTrackballEvent (MotionEvent): 当发生轨迹球事件时触发该方法.
10.onTouchEvent (MotionEvent): 当发生触摸屏事件时触发该方法.
11.onWindowFocuschanged(boolean): 当该组件得到、失去焦点时触发该方法.
12.onAttachedToWindow():当把该组件放入某个窗口时触发该方法.
13.onDetachedFromWindow(): 当把该组件从某个窗口上分离时触发该方法.
14.onWindowVisibilityChanged(int):当包含该组件的窗口的可见性发生改变时触发该
另外再补充两个ViewGroup类经常重载的方法:
1.protected&void&dispatchDraw(Canvas&canvas):ViewGroup类及其派生类具有的方法,这个方法主要用于控制子View的绘制分发,重载该方法可改变子View的绘制,进而实现一些复杂的视效。
2.protected&boolean&drawChild(Canvas&canvas,&View&child,&long&drawingTime)):ViewGroup 类及其派生类具有的方法,这个方法直接控制绘制某局具体的子view,重载该方法可控制具体某个具体子View。
在需要开发自定义View的时候,我们不需要列举出上面所有的方法,,而是可以根据业务需要来有选择的使用&上面的方法,下面我们看一个简单的示例程序,在这个示例程序里面我们只需要重写onDraw方法就可以了!
示例程序一:
我们要写一个跟随手指移动的小球,思路很简单,只要获取到用户点击屏幕的位置,并且在该位置处重绘小球即可:
下面我们看一下程序:
我注释写的比较清楚,我就说的简略一点:
首先我们写一个类DrawView,也就是我们自定义的控件,继承自View。
然后我们先写出构造器,获取到Context,这里如果用只含有Context的构造器会在xml里调用控件的时候出错,详情请看我的另外一篇博文:
http://blog.csdn.net/sunmc/article/details/
下面我们开始写:
// 构造方法
public DrawView(Context context,AttributeSet attrs){
super(context,attrs);
// 重写ondraw方法
public void onDraw(Canvas canvas){
super.onDraw(canvas);
Paint paint = new Paint();
设置画笔颜色
paint.setColor(Color.RED);
canvas.drawCircle(circleX, circleY, circleR, paint);
然后不要忘了设置这些数据的setter和getter,因为我们需要再使用这个View的时候加上监听才可以:
// get set 方法
public float getCircleX() {
return circleX;
public void setCircleX(float circleX) {
this.circleX = circleX;
public float getCircleY() {
return circleY;
public void setCircleY(float circleY) {
this.circleY = circleY;
public float getCircleR() {
return circleR;
public void setCircleR(float circleR) {
this.circleR = circleR;
这样我们的一个简单地自定控件就大功告成了,下面是该类的完整代码:
package com.example.
import android.content.C
import android.graphics.C
import android.graphics.C
import android.graphics.P
import android.util.AttributeS
import android.view.V
public class DrawView extends View{
private float circleX = 40;
private float circleY = 50;
private float circleR = 15;
// 构造方法
public DrawView(Context context,AttributeSet attrs){
super(context,attrs);
// 重写ondraw方法
public void onDraw(Canvas canvas){
super.onDraw(canvas);
Paint paint = new Paint();
设置画笔颜色
paint.setColor(Color.RED);
canvas.drawCircle(circleX, circleY, circleR, paint);
// get set 方法
public float getCircleX() {
return circleX;
public void setCircleX(float circleX) {
this.circleX = circleX;
public float getCircleY() {
return circleY;
public void setCircleY(float circleY) {
this.circleY = circleY;
public float getCircleR() {
return circleR;
public void setCircleR(float circleR) {
this.circleR = circleR;
之后我们就是像平时使用安卓原生控件那样使用就可以了,我们看一下Activity的代码:
package com.example.
import android.os.B
import android.app.A
import android.view.M
import android.view.MotionE
import android.view.V
import android.view.View.OnTouchL
public class MainActivity extends Activity {
// 定义DrawView组件
DrawView drawView =
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
创建DrawView组件
drawView = (DrawView)this.findViewById(R.id.drawView);
为DrawView组件绑定Touch事件
drawView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View arg0, MotionEvent event) {
获取坐标并改变小球的坐标
drawView.setCircleX(event.getX());
drawView.setCircleY(event.getY());
通知draw组件重绘
drawView.invalidate();
返回true表明被执行
以及xml格式的布局文件:
&?xml version=&1.0& encoding=&utf-8&?&
&RelativeLayout xmlns:android=&/apk/res/android&
xmlns:tools=&/tools&
android:layout_width=&match_parent&
android:layout_height=&match_parent&
tools:context=&.MainActivity& &
&com.example.moveball.DrawView
android:id=&@+id/drawView&
android:layout_width=&match_parent&
android:layout_height=&match_parent& &
&/com.example.moveball.DrawView&
&/RelativeLayout&
这样一个简单的例子就呈现在大家面前了,无论是多么复杂的自定义控件,思路总是这样子的,大家是不是觉得怪怪的,对了,作为一个控件,我们居然还要为了他的实现为其增加麻烦的监听,这就是因为我们重写的方法太少的原因,下一讲再给大家介绍一个经常重写的方法:publicboolean&onTouchEvent&(MotionEvent&event)。
源代码上面已经很详细了,我在最后一篇的最后还会发一个工程上来,欢迎大家一起学习!
我也还是学生,写的不好或者有问题的地方还请多多指教~话道一个有十年的履历的老夫,决意转业书法,正在一个热水炎炎的午时,老夫拿着羊毫,正在一张黑纸上写了个&Hello World!&,今后开启了他的书法路程。那末问]
& & & & & &话说一个有十年的编程经验的老汉,决定改行书法,在一个热火炎炎的中午,老汉拿着毛笔,在一张白纸上写了个“Hello World!”,从此开启了他的书法旅程。那么问题来了请问自定义一个控件需要怎样的流程?我们经常说自定义控件,那么究竟怎样去自定义一个控件?可能大家都听过自定义控件是开发人员的一个槛,其实对于这个我们个人而言是赞同的,因为如果你掌握了自定义控件那么你对android的了解肯定更深了一个档次,为什么这样说呢?学过自定义控件你自然会知道。自定义控件相对来说还是比较复杂的,可能在阅读第一遍你理解的不是特别好,但是不要灰心你就会发现很清晰,我相信认真读完此,你肯定会有收获。如有谬误欢迎批评指针,如有疑问欢迎留言,谢谢
Android开发之自定义控件(二)---onLayout详解
通过本篇博客你将学到以下点:
①自定义控件onMeasure的过程
②彻底理解MeasureSpec
③了解View的绘制流程
④对测量过程中需要的给我们准备好的其它的一些方法的源码深入理解。
& & & & 为了响应文章的开头,我们从一个“Hello World!”的小例子说起,这个例子我们自定义一个View让它显示“Hello World!”非常简单,代码如下
com.example.
import android.content.C
import android.graphics.C
import android.graphics.C
import android.graphics.P
import android.util.AttributeS
import android.view.V
public class CustomView1 extends View {
private Paint mP
private String str = &Hello World!&;
public CustomView1(Context context, AttributeSet attrs) {
super(context, attrs);
private void init() {
// 实例化一个画笔工具
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 设置字体大小
mPaint.setTextSize(50);
// 设置画笔颜色
mPaint.setColor(Color.RED);
// 重写onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 重写onDraw方法
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
* getWidth() / 2 - mPaint.measureText(str) / 2让文字在水平方向居中
canvas.drawText(str, getWidth() / 2 - mPaint.measureText(str) / 2,
getHeight()/2, mPaint);
它的布局文件如下
&LinearLayout
xmlns:android=&/apk/res/android&
xmlns:tools=&/tools&
android:layout_width=&match_parent&
android:layout_height=&match_parent&
android:orientation=&vertical& &
&com.example.customviewpractice.CustomView1
android:id=&@+id/cus_textview&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content&
android:background=&@android:color/darker_gray& /&
&/LinearLayout&
运行结果如下
& & & & &这样一个大大的&Hello World!&呈现在我们面前,可能有的人会问到底怎样去自定义一个控件呢?别急我们慢慢的,一点一点的去学习,首先你可以想象一下,假如我要求你去画一个空心的圆,你会怎么做,首先你要拿张白纸,然后你会问我圆的半径多大?圆的位置在哪?圆的线条颜色是什么?圆的线条粗细是多少?等我把这些问题都告诉你之后,你就会明白要求,并按照这个要求去画一个圆。我们自定义控件呢,也是这样需要下面三个步骤:
①重写onMeasure(获得半径的大小)
②重写onLayout(获得圆的位置)
③重写onDraw(用实例化的画笔包括:颜色,粗细等去绘画)
待这三个方法都重写完后我们的自定义控件就完成了,为了讲的能够详细我们这一篇专门来讲解onMeasure以及和其相关的方法,首先我们需要明白的是Android给我提供了可以操纵控件测量的方法是onMeasure()方法,在上面的自定义控件中我们采用了其默认的实现
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}看到这,可能大部分人都要问,这里的widthMeasureSpec和heightMeasureSpec是从何处来?要到哪里去?其实这两个参数是由View的父容器传递过来的测量要求,在上述自定义控件中也就是我们的LinearLayout,为什么这么说?这么说是有依据的我们都知道在Activity中可以调用其setContentView方法为界面填充一个布局
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}在setContentView方法中做了哪些事情呢?我们看看他的源码
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
}我们看到它调用了getWindow方法,没什么可说的,跟着步骤去看getWindow方法的源码
public Window getWindow() {
这里返回一个Window实例,其本质是继承Window的PhoneWindow,所以在Acitivity中的setContentView中getWindow.setContentView()getWindow.setContentView()其实就是PhoneWindow.setContentView()我们来Look
Look它的代码
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
mContentParent.removeAllViews();
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}该方法首先会判断是否是第一次调用setContentView方法,如果是第一次调用则调用installDecor()方法,否则将mContentParent中的所有View移除掉
然后调用LayoutInflater将我们的布局文件加载进来并添加到mContentParent视图中。跟上节奏我们来看看installDecor()方法的源码
private void installDecor() {
if (mDecor == null) {
//mDecor为空,则创建一个Decor对象
mDecor = generateDecor();
mDecor.setIsRootNamespace(true);
if (mContentParent == null) {
//generateLayout()方法会根据窗口的风格修饰,选择对应的修饰布局文件
//并且将id为content(android:id=&@+id/content&)的FrameLayout赋值给mContentParent
mContentParent = generateLayout(mDecor);
。。。省略部分代码。。。
可以发现在这个方法中首先会去判断mDecor是否为空如果为空会调用generateDecor方法,它干了什么呢?
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}可以看到它返回了一个DecorView,DecorView类是FrameLayout的子类,是一个内部类存在于PhoneWindow类中,这里我们知道它是FrameLayout的子类就ok了。
在installDecor方法中判断了mDecor是否为空后,接着会在该方法中判断mContentParent是否为空,如果为空就会调用generateLayout方法,我们来看看它做了什么。。。
protected ViewGroup generateLayout(DecorView decor) {
。。。省略部分代码。。。
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(FILL_PARENT, FILL_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
。。。省略部分代码。。。
return contentP
根据窗口的风&#26684;修饰类型为该窗口选择不同的窗口布局文件(根视图),这些窗口修饰布局文件指定一个用来存放Activity自定义布局文件的ViewGroup视图,一般为FrameLayout
其id 为: android:id=&@android:id/content&,并将其赋给mContentParent,到这里mContentParent和mDecor均已生成,而我们xml布局文件中的布局则会被添加至mContentParent。接着对上面的过程做一个简单的总结如下图
我们用张图来说明下层次结构
注:此图引自http://blog.csdn.net/qinjuning/article/details/7226787这位大神的博客。
所以说实际上我们在写xml布局文件的时候我们的根布局并不是我们能在xml文件中能看到的最上面的那个,而是FrameLayout,我们再用谷歌给我提供的hierarchyviewer这个工具来看看我们最开始的那个小例子的布局情况,看完你就明白了
看到了吧,在LinearLayout的上面是FrameLayout。到这可能有的人会说你这是写的啥?跟自定义控件一点关系都没有,其实只有了解了上面过程我们才能更好的去理解自定义控件
到这里我们回到最初我们提出的问题widthMeasureSpec和heightMeasureSpec是从哪来?我们在上面提到是从其父View传递过来的,那么它的父View的这两个参数又是从哪来,这样一步一步我们就需要知道View绘制的时候是从儿开始的,其实担任此重任的是ViewRootImpl,绘制开始是从ViewRootImpl中的performTraversals()这个方法开始的,我们来看看源码,可能有的人会说又看源码,只有看源码才能学的更透彻,这里我们只看主要的代码,理解其流程即可,其实performTraversals()方法的代码很多,我们省略后如下
private void performTraversals() {
int desiredWindowW
int desiredWindowH
int childWidthMeasureS
int childHeightMeasureS
。。。省略部分代码。。。
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthP
desiredWindowHeight = packageMetrics.heightP
。。。省略部分代码。。。
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
。。。省略部分代码。。。
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
。。。省略部分代码。。。
}我们清楚的看到在此调用了getRootMeasureSpec方法后会得到childWidthMeasureSpec和childHeightMeasureSpec,得到的这个数据作为参数传给host(这里的host是View)measure方法。在调用getRootMeasureSpec时需要两个参数desiredWindowWidth
,lp.width和desiredWindowHeight &, lp.height这里我们看到desiredWindowWidth 和desiredWindowHeight就是我们窗口的大小而lp.width和lp.height均为MATCH_PARENT,其在mWindowAttributes(WindowManager.LayoutParams类型)将&#20540;赋予给lp时就已被确定。参数搞明白后我们来看看getRootMeasureSpec的源码,看看它都是干了个啥。
* Figures out the measure spec for the root view in a window based on it&#39;s
* layout params.
* @param ize
The available width or height of the window
* @param rootDimension
The layout params for one dimension (width or height) of the
* @return The measure spec to use to measure the root view.
private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureS
switch (rootDimension) {
case ViewGroup.LayoutParams.FILL_PARENT:
// Window can&#39;t resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
return measureS
}上面三种情况的英文注释很简单自己翻译下即可理解。总之这个方法执行后不管是哪一种情况我们的根视图都是全屏的。在上面中大家看到MeasureSpec这个类有点陌生,MeasureSpec这个类的设计很精妙,对于学习自定义View也非常重要,理解它对于学习自定义控件非常有用接下来我们就花点篇幅来详细的讲解一下这个类,measurespec封装了父类传递给子类的测量要求,每个measurespec代表宽度或者高度的要求以及大小,也就是说一个measurespec包含size和mode。它有三种mode(模式)
&①UNSPECIFIED:父View没有对子View施加任何约束。它可以是任何它想要的大小。&
&②EXACTLY:父View已经确定了子View的确切尺寸。子View将被限制在给定的界限内,而忽略其本身的大小。&
&③AT_MOST:子View的大小不能超过指定的大小
它有三个主要的方法:
①getMode(imeasureSpec)它的作用就是根据规&#26684;提取出mode,这里的mode是上面的三种模式之一
②getSize(int
measureSpec)它的作用就是根据规&#26684;提取出size,这里的size就是我们所说的大小
③makeMeasureSpec(int
size, int mode)根据size和mode,创建一个测量要求。
说了这些可能大家仍然是一头雾水接下来我们看看它的源码,MeasureSpec是View的内部类,它的源码如下
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
//转化为就是11向左移30位,其结果为:11 0000...(11后跟30个0)
private static final int MODE_MASK
= 0x3 && MODE_SHIFT;
* 下面就是MeasureSpec的三种模式
//0左移30位变为 :00
0000...(00后跟30个0)
public static final int UNSPECIFIED = 0 && MODE_SHIFT;
//01左移30位变为:01 0000...(01后跟30个0)
public static final int EXACTLY
= 1 && MODE_SHIFT;
//10左移30位变为:10 0000...(10后跟30个0)
public static final int AT_MOST
= 2 && MODE_SHIFT;
//创建一个测量的规格其高位的前两位代表mode,后面30为代表size,即measureSpec=size+
public static int makeMeasureSpec(int size, int mode) {
return size +
//与运算获得mode,这里为什么可以得到mode?因为从measureSpec=size+mode,而MODE_MASK=11 0000...(11后跟30个0)
//我们都知道
& 运算的规则是&遇0为0,遇1不变&,而MODE_MASK的前两位为11后面30为全为0,这样进行运算后就可以得到measureSpec的前两位,而刚好
//这前两位就代表了mode。
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
//这里的思想跟getMode方法是一样的,首先对MODE_MASK进行取反,得到的结果为00 1111...(00后跟30个1)& 运算的规则是&遇0为0,遇1不变&,而此时~MODE_MASK
//的前两位为0后面30为全为1,所以measureSpec&~MODE_MASK得到的结果去后面30位,这后面的30位就是我们的size
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
public static String toString(int measureSpec) {
。。。内容省略。。。
MeasureSpec这个类的设计是非常巧妙的,用int类型占有32位,它将其高2位作为mode,后30为作为size这样用32位就解决了size和mode的问题
看完的它的源码大家可能&#20284;懂非懂,那么我们就举个例子画个图,让你彻底理解它的设计思想。
假如现在我们的mode是EXACTLY,而size=101(5)那么size&#43;mode的&#20540;为:
这时候通过size&#43;mode构造除了MeasureSpec对象及测量要求,当需要获得Mode的时候只需要用measureSpec与MODE_TASK相与即可如下图
我们看到得到的&#20540;就是上面的mode,而如果想获得size的话只需要只需要measureSpec与~MODE_TASK相与即可如下图
我们看到得到&#20540;就是上面的size。关于这个设计思想大家好好的,慢慢的体会下。
好了到这里我们应该对MeasureSpec有了一定的理解。这时返回去看看我们的getRootMeasureSpec方法,你是不是能看懂了?看懂后回到performTraversals方法,通过getRootMeasureSpec方法得到childWidthMeasureSpec和childHeightMeasureSpec后,我们看到在performTraversals方法中会调用host.measure(childWidthMeasureSpec,childHeightMeasureSpec),这样childWidthMeasureSpec和childHeightMeasureSpec这两个测量要求就一步一步的传下去并由当前View与其父容器共同决定其测量大小,在这里View与ViewGroup中的递归调用过程中有几个重要的方法,而对于View是measure方法,接着我们看看host.measure也就是View的measure方法的源码吧
public class View implements ... {
。。。省略了部分代码。。。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判断是否为强制布局,即带有“FORCE_LAYOUT”标记 以及 widthMeasureSpec或heightMeasureSpec发生了改变
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
//清除MEASURED_DIMENSION_SET标记
,该标记会在onMeasure()方法后被设置
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
// 1、 测量该View本身的大小
onMeasure(widthMeasureSpec, heightMeasureSpec);
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException(&onMeasure() did not set the&
+ & measured dimension by calling&
+ & setMeasuredDimension()&);
//下一步是layout了,添加LAYOUT_REQUIRED标记
mPrivateFlags |= LAYOUT_REQUIRED;
mOldWidthMeasureSpec = widthMeasureS//保存值
mOldHeightMeasureSpec = heightMeasureS//保存值
}看到了吧,在measure方法中调用了onMeasure方法,你是不是应该笑30分钟?终于见到我们的onMeasure方法了,这里的onMeasure就是我们重写的onMeasure,它接收两个参数widthMeasureSpec和heightMeasureSpec这两个参数由父View构建,表示父View对子View的测量要求。它有它的默认实现,即重写后我们什么都不做直接调用super.onMeasure方法它的默认实现如下
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}在onMeasure方法中直接调用setMeasuredDimension方法,在这里它会调用getSuggestedMinimumWidth方法得到的数据传递给getDefaultSize方法,首先来看看getSuggestedMinimunWidth,getDefaultSize以及setMeasuredDimension这三个方法的源码吧
protected int getSuggestedMinimumWidth() {
//获得android:minHeight这个属性的值,一般不设置此属性如果没有设置的话mMinWidth=0
int suggestedMinWidth = mMinW
if (mBGDrawable != null) {
//获得背景的宽度
final int bgMinWidth = mBGDrawable.getMinimumWidth();
//从背景的宽度和minHeight属性中选出一个最大的值作为返回值
if (suggestedMinWidth & bgMinWidth) {
suggestedMinWidth = bgMinW
return suggestedMinW
//在这里这里size是getSuggestedMinimumWidth方法的返回值,这也是默认的大小
//measureSpec是父View传过来的measureSpec,测量要求
public static int getDefaultSize(int size, int measureSpec) {
int result =
//获得测量的模式
int specMode = MeasureSpec.getMode(measureSpec);
//获得测量的大小
int specSize =
MeasureSpec.getSize(measureSpec);
switch (specMode) {
//模式为Unspecified及未指定大小
case MeasureSpec.UNSPECIFIED:
//将上面的size作为结果返回
case MeasureSpec.AT_MOST://模式为At_Most,此时使用默认的大小size
case MeasureSpec.EXACTLY://模式为Exactly,此时返回测量值
result = specS
//为View设置宽和高
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredW
mMeasuredHeight = measuredH
mPrivateFlags |= MEASURED_DIMENSION_SET;
}这只是一个自定义View的默认实现,如果想按照我们的要求来进行绘制的话,重写onMeasure需要添加我们自己的逻辑去实现,最终在onMeasure方法中会调用setMeasureDimenSion决定我们的View的大小,这也是我们重写onMeasure方法的最终目的。
上面这些是对于一个View的测量,android中在进行测量时有两种情况,一种是一个View如Button,ImaeView这中,不能包含子View的对于这种测量一下就ok了,另外一种就是ViewGroup像LinearLayout,FrameLayout这种可以包含子View的,对于这种我们就需要循环遍历每一个子View并为其设置大小,在自定义的ViewGroup中重写onMeasure如下的伪代码
//某个ViewGroup类型的视图
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//必须调用super.ononMeasure()或者直接调用setMeasuredDimension()方法设置该View大小,否则会报异常。
super.onMeasure(widthMeasureSpec , heightMeasureSpec)
//setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
//一、遍历每个子View
for(int i = 0 ; i & getChildCount() ; i++){
View child = getChildAt(i);
//调用子View的onMeasure,设置他们的大小,
child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//二、直接调用ViewGroup中给我们提供好的measureChildren方法、
measureChildren(widthMeasureSpec, heightMeasureSpec);
}其实ViewGroup已经为我们提供了测量子View的方法,主要有measureChildren,measureChild和getMeasureSpec,下面我们来分别看看这三个方法都是干了个啥?
measureChildren方法的源码如下
//widthMeasureSpec和heightMeasureSpec:父View传过来的测量要求
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenC
final View[] children = mC
//遍历所有的View
for (int i = 0; i & ++i) {
final View child = children[i];
//Gone掉的View排除
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}可以看到在measureChildren方法中会遍历所有的View然后对每一个View(不包括gone的View)调用measureChild方法,顺其自然我们来看看measureChild方法的源码
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 获取子的布局参数
final LayoutParams lp = child.getLayoutParams();
//将父容器的测量规格以及上下和左右的边距还有子元素本身的布局参数传入getChildMeasureSpec方法计算最终测量要求
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 将计算好的宽高详细测量值传入measure方法,完成最后的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}在measureChild方法中通过getChildMeasureSpec得到最终的测量要求,并将这个测量要求传递给childView的measure方法,就会按照View的那一套逻辑运行。在这里看到调用了getChildMeasureSpec方法我们来看看这个方法的源码
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取父View的测量模式
int specMode = MeasureSpec.getMode(spec);
//获取父View的测量大小
int specSize = MeasureSpec.getSize(spec);
//父View计算出的子View的大小,子View不一定用这个值
int size = Math.max(0, specSize - padding);
//声明变量用来保存实际计算的到的子View的size和mode即大小和模式
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
//如果父容器的模式是Exactly即确定的大小
case MeasureSpec.EXACTLY:
//子View的高度或宽度&0说明其实一个确切的值,因为match_parent和wrap_content的值是&0的
if (childDimension &= 0) {
resultSize = childD
resultMode = MeasureSpec.EXACTLY;
//子View的高度或宽度为match_parent
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize =//将size即父View的大小减去边距值所得到的值赋值给resultSize
resultMode = MeasureSpec.EXACTLY;//指定子View的测量模式为EXACTLY
//子View的高度或宽度为wrap_content
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can&#39;t be
// bigger than us.
resultSize =//将size赋值给result
resultMode = MeasureSpec.AT_MOST;//指定子View的测量模式为AT_MOST
// Parent has imposed a maximum size on us
//如果父容器的测量模式是AT_MOST
case MeasureSpec.AT_MOST:
if (childDimension &= 0) {
// Child wants a specific size... so be it
resultSize = childD
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize =
// 因为父View的大小是受到限制值的限制,所以子View的大小也应该受到父容器的限制并且不能超过父View
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can&#39;t be
// bigger than us.
resultSize =
resultMode = MeasureSpec.AT_MOST;
// Parent asked to see how big we want to be
//如果父容器的测量模式是UNSPECIFIED即父容器的大小未受限制
case MeasureSpec.UNSPECIFIED:
//如果自View的宽和高是一个精确的值
if (childDimension &= 0) {
// Child wants a specific size... let him have it
//子View的大小为精确值
resultSize = childD
//测量的模式为EXACTLY
resultMode = MeasureSpec.EXACTLY;
//子View的宽或高为match_parent
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
//resultSize=0;因为父View的大小是未定的,所以子View的大小也是未定的
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
//根据resultSize和resultMode调用makeMeasureSpec方法得到测量要求,并将其作为返回值
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
我们经常说View的大小是由父View以及当前View共同决定的,这一点从上面这个方法也可以看出。但是这只是一个期望的大小,其大小的最终决定权由setMeasureDimenSion方法决定。
所以最终View的大小将受以下几个方面的影响(以下三点摘自:http://blog.csdn.net/qinjuning/article/details/8074262此博客,这是一个大神。。)
&1、父View的MeasureSpec属性;
&2、子View的LayoutParams属性;
&3、setMeasuredDimension()或者其它类&#20284;设定&mMeasuredWidth 和&mMeasuredHeight &#20540;的方法。
关于View的测量过程就介绍完了,可能你一遍没有读懂,只要你认真的去看我相信你一定会有收获,如果你一遍就读懂了,千万别告诉我,我会伤心的,哈哈,因为我花了一周的时间才对onMeasure有了点理解。
如果你觉得本篇博客对你有帮助就留言顶一个呗。
转载注明出处:http://blog.csdn.net/dmk877/article/details/
如有谬误欢迎批评指正,如有疑问欢迎留言。我将在第一时间改正或回答。。。
感谢关注 Ithao123Android频道,是专门为人打造的学习交流平台,全面满足互联网人工作与学习需求,更多互联网尽在 IThao123!

我要回帖

 

随机推荐