一,写在前面
如何自定义一个继承ViewGroup的控件呢?在实现的过程中涉及哪些知识点?需要注意哪些地方呢?接下来以一个简易的ViewPager来展示继承ViewGroup的自定义控件。做出来是这样一个效果图,如下:
完成一个这样的效果:水平方向由SimpleViewPager处理,竖直方向由ListView处理,SimpleViewPager有三个子元素->ListView。快速水平方向滑动时,可以进行翻页;慢速水平滑动时,若滑动页超过一半,则进行另一页,否则回到原页面。
自定义控件ViewGroup,需要了解View的测量,布局,绘制流程。在前面博文Android自定义控件之测量onMeasure 中,从源码角度对测量流程进行了分析;布局流程相对比较简单,在继承ViewGroup时,在onLayout中设置子元素的布局即可;绘制流程常用于继承View的自定义控件。还需要了解Android事件分发的机制,从而去解决滑动冲突。在前面两篇博文Android事件分发机制之ViewGroup ,Android事件处理之View$dispatchTouchEvent(ev)中对事件分发机制从源码角度进行了解析。接下里,直接看代码,并作分析。
二,实例展示之onMeasure
首先看重写的onMeasure(w,h)方法,如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//先测量子控件,再测量自己;
measureChildren(widthMeasureSpec, heightMeasureSpec);
//获取宽高的模式,大小
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//获取子view的个数
int childCount = getChildCount();
if (childCount == 0) {
//如果没有子元素,则设置宽高大小为0
setMeasuredDimension(0, 0);
return;
}
//获取子View的宽,高
View childAt = getChildAt(0);
int childMeasuredWidth = childAt.getMeasuredWidth();
int childMeasuredHeight = childAt.getMeasuredHeight();
//分四种情况讨论
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
//宽度设置为3个子view宽度相加,高度设置为一个子View高度
setMeasuredDimension(childMeasuredWidth * 3, childMeasuredHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
//宽度设置为3个子View宽度相加;
//高度为exactly模式,直接取测量高度大小即可(分析ViewGroup$getChildMeasureSpec源码可知)
setMeasuredDimension(childMeasuredWidth * 3, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST){
//宽度为exactly模式,直接取测量的宽度值;高度为一个子View高度
setMeasuredDimension(widthSpecSize, childMeasuredHeight);
} else {
//宽高都是exactly模式,则直接使用父view给的建议值大小
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
}
查看FrameLayout$onMeasure(w,h)可知,该方法做了两件事,先测量子元素,再测量自己,测量逻辑见前面提到的博文,后面阐述会直接给结论,不再提供源码角度的详细分析。
SimpleViewPager中有三个ListView,且这三个子元素的宽高都是一样的,于是我们调用ViewGroup$measureChildren(w,h)方法测量三个子元素,如果子元素宽高各不相同,ViewGroup还提供了ViewGroup$measureChild(View child,int w,int h),以及measureChildWithMargins(View child,int w, int widthUsed, int h, int heightUsed)方法,分别一个个测量子元素。
那SimpleViewPager如何测量自己呢?分析FrameLayout的onMeasure(w,h)可知,测量自己需要判断specMode,然后取值。于是,判断宽高的测量模式,分为4种情况,不需要考虑UNSPECIFIED模式,那么宽高的测量模式只可能有AT_MOST和EXACTLY两种,总共4种情况。那么,分别为AT_MOST和EXACTLY两种情况时,如何设置测量大小?分析FrameLayout源码,查看View$resolveSizeAndState方法可知:在exactly时,分别取建议宽高测量大小即可;在at_most时,宽度的大小取三个子元素的宽度之和,高的大小取一个子元素的高度。最后,调用View$