android view 浅析

来源:互联网 发布:淘宝店铺装修在哪里 编辑:程序博客网 时间:2024/06/05 06:39
本文简单介绍一下android 视图,本文的目的是想把大家带入自己绘制view 以及自定义view的学习过程中。但愿本文成文这个过程的开路先锋
从两个方面介绍
一 LayoutInflater:布局加载,
二:view 的绘制原理。如果你去观察TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。
更不用说我们自定义的视图了






关于视图一:
LayoutInflater:
相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用于加载布局的。而刚接触Android的朋友可能对LayoutInflater不怎么熟悉,因为加载布局的任务通常都是在Activity中调用setContentView()方法来完成的。


一:LayoutInflater的使用:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:id="@+id/main_layout"      android:layout_width="match_parent"      android:layout_height="match_parent" >  </LinearLayout>  <Button xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="wrap_content"      android:layout_height="wrap_content"      android:text="Button" >    </Button> 




public class MainActivity extends Activity {        private LinearLayout mainLayout;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          mainLayout = (LinearLayout) findViewById(R.id.main_layout);          LayoutInflater layoutInflater = LayoutInflater.from(this);          View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null);          mainLayout.addView(buttonLayout);      }    }  


其工作原理大家自己看源代码,或者自己了解资料吧。


额。。。。还嫌这个例子中的按钮看起来有点小,想要调大一些?那简单的呀,修改button_layout.xml中的代码,如下所示:

<Button xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="300dp"      android:layout_height="80dp"      android:text="Button" >    </Button>  


这里我们将按钮的宽度改成300dp,高度改成80dp,这样够大了吧?现在重新运行一下程序来观察效果。咦?怎么按钮还是原来的大小,没有任何变化!是不是按钮仍然不够大,再改大一点呢?还是没有用!
其实这里不管你将Button的layout_width和layout_height的值修改成多少,都不会有任何效果的,因为这两个值现在已经完全失去了作用。平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。
再来看一下我们的button_layout.xml吧,很明显Button这个控件目前不存在于任何布局当中,所以layout_width和layout_height这两个属性理所当然没有任何作用。那么怎样修改才能让按钮的大小改变呢?解决方法其实有很多种,最简单的方式就是在Button的外面再嵌套一层布局,如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent" >        <Button          android:layout_width="300dp"          android:layout_height="80dp"          android:text="Button" >      </Button>    </RelativeLayout>  


可以看到,这里我们又加入了一个RelativeLayout,此时的Button存在与RelativeLayout之中,layout_width和layout_height属性也就有作用了。当然,处于最外层的RelativeLayout,它的layout_width和layout_height则会失去作用。现在重新运行一下程序,结果如下图所示:
                      
OK!按钮的终于可以变大了,这下总算是满足大家的要求了吧。
看到这里,也许有些朋友心中会有一个巨大的疑惑。不对呀!平时在Activity中指定布局文件的时候,最外层的那个布局是可以指定大小的呀,layout_width和layout_height都是有作用的。确实,这主要是因为,在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout,所以layout_width和layout_height属性才会有效果。


关于视图二:
何一个视图都不可能凭空突然出现在屏幕上,它们都是要经过非常科学的绘制流程后才能显示出来的。每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),下面我们逐个对这三个阶段展开进行探讨。


一:onMeasure() 


MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,这里只介绍两类 如下所示:
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。


看一段源码:


private int getRootMeasureSpec(int windowSize, int rootDimension) {      int measureSpec;      switch (rootDimension) {      case ViewGroup.LayoutParams.MATCH_PARENT:          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);          break;      case ViewGroup.LayoutParams.WRAP_CONTENT:          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);          break;      default:          measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);          break;      }      return measureSpec;  }  


当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。


接下来我们看下View的measure()方法里面的代码吧
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {          //....省略        onMeasure(widthMeasureSpec, heightMeasureSpec);          //....省略}  


1:注意观察,measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。
2:onMeasure 这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大


//这里传入的measureSpec是一直从measure()方法中传递过来的public static int getDefaultSize(int size, int measureSpec) {         int result = size;      //然后调用MeasureSpec.getMode()方法可以解析出specMode,int specMode = MeasureSpec.getMode(measureSpec);  //调用MeasureSpec.getSize()方法可以解析出specSize。    int specSize = MeasureSpec.getSize(measureSpec);  //接下来进行判断,如果specMode等于AT_MOST或EXACTLY就返回specSize,这也是系统默认的行为。    switch (specMode) {      case MeasureSpec.UNSPECIFIED:          result = size;          break;      case MeasureSpec.AT_MOST:      case MeasureSpec.EXACTLY:          result = specSize;          break;      }      return result;  }  


之后会在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。
ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小 以及measure 的重写就不多做解释,因为本文的目的是要把大家带入view的绘制以及自定义view中
视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。到此为止,视图绘制流程的第一阶段分析完毕




二 onLayout()

protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  //left top right bottom

     SimpleLayout吧,代码如下所示:  由于LinearLayout和RelativeLayout的布局规则都比较复杂,就不单独拿出来进行分析了

public class SimpleLayout extends ViewGroup {        public SimpleLayout(Context context, AttributeSet attrs) {          super(context, attrs);      }        @Override  //onMeasure()方法会在onLayout()方法之前调用,因此这里在onMeasure()方法中判断SimpleLayout中是否有包含一个子视图,//如果有的话就调用measureChild()方法来测量出子视图的大小  上文没有提到measureChild()    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          super.onMeasure(widthMeasureSpec, heightMeasureSpec);          if (getChildCount() > 0) {              View childView = getChildAt(0);              measureChild(childView, widthMeasureSpec, heightMeasureSpec);          }      }        @Override      protected void onLayout(boolean changed, int l, int t, int r, int b) {          if (getChildCount() > 0) {              View childView = getChildAt(0);              childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());          }      }    }  


        getWidth()方法和getMeasureWidth()方法到底有什么区别呢?
        首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
getWidth()方法得到的值就是childView.getMeasuredWidth() - 0 = childView.getMeasuredWidth() ,所以此时getWidth()方法和getMeasuredWidth() 得到的值就是相同的,但如果你将onLayout()方法中的代码进行如下修改就不同了。
三. onDraw()
 // Step 1, draw the background, if needed   //这里会先得到一个mBGDrawable对象,然后根据layout过程确定的视图位置来设置背景的绘制区域, //之后再调用Drawable的draw()方法来完成背景的绘制工作。 //mBGDrawable其实就是在XML中通过android:background属性设置的图片或颜色。当然你也可以在代码中通过setBackgroundColor()、setBackgroundResource()等方法进行赋值。     int saveCount;      if (!dirtyOpaque) {          final Drawable background = mBGDrawable;          if (background != null) {              final int scrollX = mScrollX;              final int scrollY = mScrollY;              if (mBackgroundSizeChanged) {                  background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);                  mBackgroundSizeChanged = false;              }              if ((scrollX | scrollY) == 0) {                  background.draw(canvas);              } else {                  canvas.translate(scrollX, scrollY);                  background.draw(canvas);                  canvas.translate(-scrollX, -scrollY);              }          }      }  
   // Step 3, draw the content     //空方法这部分的功能交给子类来去实现也是理所当然的。        if (!dirtyOpaque) onDraw(canvas);  
        // Step 4, draw the children         //View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码        dispatchDraw(canvas);  
 // Step 6, draw decorations (scrollbars)    我只想告诉你,任何一个组件都有滚动条onDrawScrollBars(canvas);  

step 2 step 5 基本不用

好了,下面的问题就是敲代码,正如开始所说,先绘制自己的简单view ,蓝后自定义view 代码后续发布
0 0