在继承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可以在指定的尺寸内尽量大
它常用的三个函数:
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()方法即可