自定义ViewGroup
之前说过了自定义View,而对于自定义ViewGroup来说,主要是定义和管理该ViewGroup里的子View。所以自定义ViewGroup里往往需要重写onMearsure()方法来完成对各子View的测量,重写onLayout()方法来完成对各子View的位置布局,重写onTouchEvent()来完成响应事件。至于为啥没有说onDraw()方法呢,其实在draw阶段,ViewGroup都是通过drawchild方法调用子View的draw方法来绘制各子View,而作为各子View的容器,一般情况下不会再做额外的修饰。所以往往在绘制阶段里,只需要调用ViewGroup的默认实现方法就可以了。
以一个简单的ViewGroup为例子吧,我打算做一个含有三个控件,并且三个控件斜着排在我们自定义ViewGroup中。首先依然是新建一个类继承ViewGroup,写好构造函数,并给它添加上三个子View。
public MyViewGroup(Context context) { super(context); mContext = context ; init() ; } public MyViewGroup(Context context , AttributeSet attrs){ super(context,attrs) ; mContext = context ; init() ; } //为MyViewGroup添加三个子View private void init(){ //调用ViewGroup父类addView()方法添加子View //child 对象一 : Button Button btn= new Button(mContext) ; btn.setText("I am Button") ; this.addView(btn) ; //child 对象二 : ImageView ImageView img = new ImageView(mContext) ; img.setBackgroundResource(R.mipmap.ic_launcher) ; this.addView(img) ; //child 对象三 : TextView TextView txt = new TextView(mContext) ; txt.setText("Only Text") ; this.addView(txt) ; }然后是onMearsure()方法,遍历每个子View进行测量,获取每个子View的实际宽,高,然后计算出我们自定义ViewGroup需要的宽和高。
@Override //对每个子View进行measure():设置每子View的大小,即实际宽和高 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ int childCount = getChildCount() ; Log.i(TAG, "**** the size of this ViewGroup is ----> " + childCount+" ****") ; Log.i(TAG, "**** onMeasure start *****") ; //获取该ViewGroup的实际长和宽 涉及到MeasureSpec类的使用 int specSize_Width = MeasureSpec.getSize(widthMeasureSpec) ; int specSize_Height = MeasureSpec.getSize(heightMeasureSpec) ; Log.i(TAG, "**** specSize_Width=== " + specSize_Width+ " * specSize_Height===" + specSize_Height+" ****") ; int new_Width=0; int new_Height=0; for(int i=0 ;i<childCount ; i++){ View child = getChildAt(i) ; //获得每个对象的引用 //或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法 this.measureChild(child, widthMeasureSpec, heightMeasureSpec) ; new_Width+=child.getMeasuredWidth()+10; new_Height+=child.getMeasuredHeight()+10; } Log.i(TAG, "**** new_Width=== " + new_Width+ " * new_Height===" + new_Height+" ****") ; //设置本ViewGroup的宽高 setMeasuredDimension(new_Width , new_Height) ; }接下来就是布局每个子View了,设置好每个子View的起始位置以及长宽。
@Override //对每个子View视图进行布局 protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub int childCount = getChildCount() ; int startLeft = 0 ;//设置每个子View的起始横坐标 int startTop = 0 ; //设置每个子View的起始纵坐标; Log.i(TAG, "**** onLayout start ****") ; for(int i=0 ;i<childCount ; i++){ View child = getChildAt(i) ; //获得每个对象的引用 child.layout(startLeft, startTop, startLeft+child.getMeasuredWidth(), startTop+child.getMeasuredHeight()) ; startLeft =startLeft+child.getMeasuredWidth() + 10; //校准startLeft值,View之间的横向间距设为10px ; startTop =startTop+child.getMeasuredHeight() + 10; //校准startTop值,View之间的高度间距设为10px ; Log.i(TAG, "**** onLayout startLeft===" +startLeft+" ****") ; Log.i(TAG, "**** onLayout startTop===" +startTop+" ****") ; } }最后我们还是写上绘制方法,仅仅来观察下整个流程。
protected boolean drawChild(Canvas canvas , View child, long drawingTime){ Log.i(TAG, "**** drawChild start ****") ; return super.drawChild(canvas, child, drawingTime) ; }我们看下运行结果:
看看我们的运行日志:
书中举的例子包含了点击事件,其实就是重写onTouchEvent()方法,监听触摸点击写出响应事件,这里我也写了个简单例子,代码如下所示:
@Override public boolean onTouchEvent(MotionEvent event){ switch (event.getAction()){ case MotionEvent.ACTION_DOWN: child.layout((int)event.getX()-child.getMeasuredWidth()/2,(int)event.getY()-child.getMeasuredHeight()/2, (int)event.getX()+child.getMeasuredWidth()/2,(int)event.getY()+child.getMeasuredHeight()/2); break; case MotionEvent.ACTION_MOVE: child.layout((int)event.getX()-child.getMeasuredWidth()/2,(int)event.getY()-child.getMeasuredHeight()/2, (int)event.getX()+child.getMeasuredWidth()/2,(int)event.getY()+child.getMeasuredHeight()/2); break; } postInvalidate(); return true; }效果就是我点击以及滑动屏幕时候,child这个控件会跟着我手指头移动,在这就不贴图了。
作者:bit_kaki 发表于2017/5/26 15:09:05 原文链接
阅读:34 评论:0 查看评论