ViewGroup初探,自定义LinearLayout

这篇文章我们开始向ViewGroup进军,打造一个简单的LinearLayout,当然该LinearLayout还不能用于项目中,只是为了学习使用。

首先new一个class, 让它继承自ViewGroup, 默认会让你重写onLayout方法,因为该方法在ViewGroup中是abstract的:

 

[java] view plain copy
 
  1. @Override  
  2. protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  


但是只重写onLayout还是不够的, 还要重写onMeasured方法用来测量子view和保存自己最终的大小。

 

 

哦,对了,还记得layout_width、layout_height吗? 这些其实不是view的属性,而是ViewGroup的, 即view告诉ViewGroup我要的宽度和高度,我们的LinearLayout也需要一个LayoutParams:

 

[java] view plain copy
 
  1. @Override  
  2. public LayoutParams generateLayoutParams(AttributeSet attrs) {  
  3.     return new MarginLayoutParams(getContext(), attrs);  
  4. }  

重写ViewGroup的generateLayoutParams方法,直接返回一个简单的MarginLayoutParams,因为我们需要view的margin。

 

 

好吧, 看看我们在onMeasured方法中做了什么,onMeasure方法主要的工作就是测量子view的大小,并确定自己的大小:

 

[java] view plain copy
 
  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     final int childCount = getChildCount();  
  4. //  measureChildren(widthMeasureSpec, heightMeasureSpec);  
  5.           
  6.     int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  7.     int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  8.     int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  9.     int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  10.           
  11.     int width = 0;  
  12.     int height = 0;  
  13.           
  14.     for(int i=0;i<childCount;i++) {  
  15.         View view = getChildAt(i);  
  16.         if(view.getVisibility() == View.GONE) {  
  17.             continue;  
  18.         }  
  19.               
  20.         measureChild(view, widthMeasureSpec, heightMeasureSpec);  
  21.         MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();  
  22.         width = Math.max(width, view.getMeasuredWidth() + p.leftMargin + p.rightMargin);  
  23.         height += view.getMeasuredHeight() + p.topMargin + p.bottomMargin;  
  24.     }  
  25.           
  26.     // 如果mode是EXACTLY, 则设置为父布局传过来的值  
  27.     // 如果是AT_MOST, 则设置为自己测量的结果  
  28.     setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize  
  29.                 : width, heightMode == MeasureSpec.EXACTLY ? heightSize  
  30.                 : height);  
  31.     }  


6~9行,使用MeasureSpec的两个静态方法,分别获取了width的mode、size和height的mode、size,mode是干什么的?如果mode==MeasureSpec.EXACTLY的话,我们就确定ViewGroup的大小为父布局给我们建议的大小,如果是MeasureSpec.AT_MOST的话,就需要我们自己测量了。

 

14~24行,遍历所有的子view,首先调用ViewGroup的一个方法measueChild来测量一下该view,要么view.getMeasuredXXX()获取到的肯定是0。然后获取该view的宽度和高度,让ViewGroup的宽度等于当前ViewGroup宽度和该view宽度的最大值,让ViewGroup的高度累加上当前View的高度。

最后调用setMeasuredDimension来设置测量结果,可以看到这里有一个条件,表示如果mode是MeasureSpec.EXACTLY的话,我们不去理会自己的测量结果,直接将父布局给出的建议大小保存起来就ok。

 

onLayout需要做的是确定子view的位置:

 

[java] view plain copy
 
  1. @Override  
  2. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  3.     final int childCount = getChildCount();  
  4.     int height = 0;  
  5.     for(int i=0;i<childCount;i++) {  
  6.         View view = getChildAt(i);  
  7.         if(view.getVisibility() == View.GONE) {  
  8.             continue;  
  9.         }  
  10.               
  11.         MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();  
  12.         MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();  
  13.         view.layout(p.leftMargin, height + p.topMargin,view.getMeasuredWidth() + p.rightMargin,height + view.getMeasuredHeight() + p.topMargin + p.bottomMargin);  
  14.         height += view.getMeasuredHeight() + p.topMargin + p.bottomMargin;  
  15.     }  
  16. }  



 

 

代码结果和onMeasure很相似,只是在循环中,调用了view.layout方法来设置该view在布局中的位置,逻辑也很简单,就是将height的值累加。

 

在布局文件中使用MyLinearLayout:

 

[html] view plain copy
 
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.   
  11.     <org.loader.mylinearlayout.MyLinearLayout  
  12.         android:layout_width="wrap_content"  
  13.         android:layout_height="wrap_content"  
  14.         android:background="@android:color/holo_green_light" >  
  15.   
  16.         <TextView  
  17.             android:layout_width="wrap_content"  
  18.             android:layout_height="wrap_content"  
  19.             android:text="text1" />  
  20.         <TextView  
  21.             android:layout_width="wrap_content"  
  22.             android:layout_height="wrap_content"  
  23.             android:text="text2" />  
  24.         <TextView  
  25.             android:layout_marginTop="20dp"  
  26.             android:layout_width="wrap_content"  
  27.             android:layout_height="wrap_content"  
  28.             android:text="text3" />  
  29.         <TextView  
  30.             android:layout_width="wrap_content"  
  31.             android:layout_height="wrap_content"  
  32.             android:text="text4" />  
  33.         <TextView  
  34.             android:visibility="gone"  
  35.             android:layout_width="wrap_content"  
  36.             android:layout_height="wrap_content"  
  37.             android:text="text" />  
  38.     </org.loader.mylinearlayout.MyLinearLayout>  
  39.   
  40. </RelativeLayout>  

 

 

看看效果:



达到我们的效果了。当然这还是最简单的ViewGroup,google官方的api中也有一个demo,很适合我们学习,感兴趣的可以看看。

posted @ 2016-11-24 21:11  天涯海角路  阅读(75)  评论(0)    收藏  举报