相关文章
Android View体系(一)视图坐标系
Android View体系(二)实现View滑动的六种方法
Android View体系(三)属性动画
Android View体系(四)从源码解析Scroller
Android View体系(五)从源码解析View的事件分发机制
Android View体系(六)从源码解析Activity的构成
Android View体系(七)从源码解析View的measure流程
Android View体系(八)从源码解析View的layout和draw流程
Android View体系(九)自定义View
Android View体系(十)自定义组合控件
前言
此前讲了很多,终于可以讲到这一节了,本文的例子是一个自定义的ViewGroup,左右滑动切换不同的页面,类似一个特别简化的ViewPager,这篇文章会涉及到这个系列的很多文章的内容比如View的measure、layout和draw流程,view的滑动等等,所以对View体系不大了解的同学看这篇文章前可以先从头阅读本系列的其他文章,再来看这篇文章效果会更好些。需要注意的是我们知道要实现一个自定义的ViewGroup是很复杂的,这个看看LineraLayout等源码我们就会知道,这里我们只需要把主要的功能实现就好了。
1.继承ViewGroup
要实现自定义的ViewGroup,首先要继承ViewGroup并调用父类构造方法,实现抽象方法等。
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
public class HorizontalView extends ViewGroup{
public HorizontalView(Context context) {
super(context);
}
public HorizontalView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
这里我们定义了名字叫HorizontalView的类并继承 ViewGroup,onLayout这个抽象方法是必须要实现的,我们暂且什么都不做。
2.对wrap_content属性进行处理
在Android View体系(九)自定义View这篇文章中我们同样对wrap_content属性进行了处理不明白的可以查看这篇文章或者直接查看Android View体系(七)从源码解析View的measure流程来了解具体的原因,这里就不赘述了。
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class HorizontalView extends ViewGroup {
//...省略此前的构造代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
//如果没有子元素,就设置宽高都为0(简化处理)
if (getChildCount() == 0) {
setMeasuredDimension(0, 0);
}
//宽和高都是AT_MOST,则设置宽度所有子元素的宽度的和;高度设置为第一个元素的高度;
else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
View childOne = getChildAt(0);
int childWidth = childOne.getMeasuredWidth();
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(childWidth * getChildCount(), childHeight);
}
//如果宽度是wrap_content,则宽度为所有子元素的宽度的和
else if (widthMode == MeasureSpec.AT_MOST) {
int childWidth = getChildAt(0).getMeasuredWidth();
setMeasuredDimension(childWidth * getChildCount(), heightSize);
}
//如果高度是wrap_content,则高度为第一个子元素的高度
else if (heightMode == MeasureSpec.AT_MOST) {
int childHeight = getChildAt(0).getMeasuredHeight();
setMeasuredDimension(widthSize, childHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
这里如果没有子元素时采用了简化的写法直接将宽和高直接设置为0,正常的话我们应该根据LayoutParams中的宽和高来做相应的处理,另外我们在测量时没有考虑它的padding和子元素的margin。
3.实现onLayout
接下来我们实现onLayout,来布局子元素,因为每一种布局方式子View的布局都是不同的,所以这个是ViewGroup唯一一个抽象方法,需要我们自己去实现:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
public class HorizontalView extends ViewGroup {
//... 省略构造方法代码和onMeasure的代码
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();