在继承ViewGroup类时,需要重写两个方法,分别是onMeasure和onLayout。
1,在方法onMeasure中调用setMeasuredDimension方法void android.view.View.setMeasuredDimension(int measuredWidth, int measuredHeight)
在onMeasure(int, int)中,必须调用setMeasuredDimension(int width, int height)来存储测量得到的宽度和高度值,如果没有这么去做会触发异常IllegalStateException。
2,在方法onMeasure中调用孩子的measure方法
void android.view.View.measure(int widthMeasureSpec, int heightMeasureSpec)
这个方法用来测量出view的大小。父view使用width参数和height参数来提供constraint信息。实际上,view的测量工作在onMeasure(int, int)方法中完成。因此,只有onMeasure(int, int)方法可以且必须被重写。参数widthMeasureSpec提供view的水平空间的规格说明,参数heightMeasureSpec提供view的垂直空间的规格说明。
3,解析onMeasure(int, int)方法
void android.view.View.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
测量view及其内容来确定view的宽度和高度。这个方法在measure(int, int)中被调用,必须被重写来精确和有效的测量view的内容。
在重写这个方法时,必须调用setMeasuredDimension(int, int)来存储测量得到的宽度和高度值。执行失败会触发一个IllegalStateException异常。调用父view的onMeasure(int, int)是合法有效的用法。
view的基本测量数据默认取其背景尺寸,除非允许更大的尺寸。子view必须重写onMeasure(int, int)来提供其内容更加准确的测量数值。如果被重写,子类确保测量的height和width至少是view的最小高度和宽度(通过getSuggestedMinimumHeight()和getSuggestedMinimumWidth()获取)。
4,解析onLayout(boolean, int, int, int, int)方法
void android.view.ViewGroup.onLayout(boolean changed, int l, int t, int r, int b)
调用场景:在view给其孩子设置尺寸和位置时被调用。子view,包括孩子在内,必须重写onLayout(boolean, int, int, int, int)方法,并且调用各自的layout(int, int, int, int)方法。
参数说明:参数changed表示view有新的尺寸或位置;参数l表示相对于父view的Left位置;参数t表示相对于父view的Top位置;参数r表示相对于父view的Right位置;参数b表示相对于父view的Bottom位置。
5,解析View.MeasureSpec类
android.view.View.MeasureSpec
MeasureSpec对象,封装了layout规格说明,并且从父view传递给子view。每个MeasureSpec对象代表了width或height的规格。
MeasureSpec对象包含一个size和一个mode,其中mode可以取以下三个数值之一:
UNSPECIFIED,1073741824 [0x40000000],未加规定的,表示没有给子view添加任何规定。
EXACTLY,0 [0x0],精确的,表示父view为子view确定精确的尺寸。
AT_MOST,-2147483648 [0x80000000],子view可以在指定的尺寸内尽量大
6,一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。它有三种模式:UNSPECIFIED(未指定),父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小;EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;AT_MOST(至多),子元素至多达到指定大小的值。
它常用的三个函数:
1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)
2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)
3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)
7、技术要点:
a、 onLayout(boolean,int,int,int,int); // 对子view 进行布局,确定子view的位置。
boolean
该view 布局是否发生变化
int,int,int,int
该view相对于其父view,左、上、右、下的位置。
b、对touch事件进行解析,可以自己解析,也可以使用 GestureDetator 进行解析。
c、scrollBy(x,y) //将view的内容移动一定的距离,x,y 是一个距离值,表示多少个像素。x为正值时,视图向左偏移。
scrollTo(x,y) //将view的内容移动到某个点上,x,y是一个具体的坐标值。
d、invaliteDate 刷新页面,会导致一系列的方法执行 -- ViewGroup.drawChild() -- View.draw(canvas,panent,longTime) -- computeScroll()
最终会执行 computeScroll()方法 。
e、scroller 的使用。scroller 是一个工程师,专用于位移、速度 等数据变化 的计算。
scroller.startScroll(startX,startY,disX,disY,duration);
//开始进行新的位移,
scroller.computeScrollOffset();
//计算当前的位移情况,将结果付给scroller.curX和curY。如果返回false,证明,位移结束。
f、touch 事件的传递机制。
主要就是ViewGroup中的几个方法:
dispatchTouchEvent()
//分发事件
onInterceptTouchEvent()
//是否中断事件的传递,返回true 中断。默认返回false
onTouchEvent()
//处理事件、返回true,消费事件。
g、 onMeasure(int,int); // 系统测量控件大小时调用该方法
measure //方法是计算子view的宽度高度
<span style="font-family:Comic Sans MS;">public class MySlideView extends ViewGroup{ private View content; private View menu; private boolean isMenuShow = false; public MySlideView(Context context, AttributeSet attrs) { super(context, attrs); scroller = new Scroller(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); menu = getChildAt(0); content = getChildAt(1); //根据提供的大小值和模式创建测量值,并计算menu的位置 menu.measure(MeasureSpec.makeMeasureSpec(menu.getLayoutParams().width, MeasureSpec.EXACTLY) , heightMeasureSpec); content.measure(widthMeasureSpec, heightMeasureSpec); } /** * 设置view的位置 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { System.out.println("layout:::"+getChildCount()); menu.layout(0-menu.getMeasuredWidth(), 0, 0, b); content.layout(0, 0, r, b); } private int firstX; private int lastX; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: firstX = lastX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: int disX = (int) (lastX - event.getX()); lastX = (int) event.getX(); int nextScrollX = getScrollX()+disX; // 可能的,下一个 mScrollX 的值 if( nextScrollX >= -menu.getWidth() && nextScrollX <=0){ scrollBy(disX, 0); } break; case MotionEvent.ACTION_UP: int curScrollX = getScrollX(); if(curScrollX > -menu.getWidth()/2){ isMenuShow = false; }else{ isMenuShow = true; } flushState(); break; } return true; } private void flushState() { int distance = 0; if(!isMenuShow){ // scrollTo(0,0); distance = 0-getScrollX(); }else{ // scrollTo(-menu.getWidth(),0); distance = -menu.getWidth()-getScrollX(); } scroller.startScroll(getScrollX(), 0, distance, 0); invalidate(); } /** * 滚动,子View滚动 */ @Override public void computeScroll() { if(scroller.computeScrollOffset()){ scrollTo(scroller.getCurrX(),0); invalidate(); } } private Scroller scroller ; /** * 切换状态 */ public void changeState() { isMenuShow = !isMenuShow; flushState(); } } </span>
布局文件
<span style="font-family:Comic Sans MS;"><RelativeLayout xmlns: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" tools:context=".MainActivity" > <com.example.MySlideView android:id="@+id/msv" android:layout_width="match_parent" android:layout_height="match_parent" > <include layout="@layout/main_menu"/> <include layout="@layout/main_contant"/> </com.example.MySlideView> </RelativeLayout></span>
调用changeState()方法即可