首页 > 代码库 > Android自己定义组件系列【1】——自己定义View及ViewGroup

Android自己定义组件系列【1】——自己定义View及ViewGroup

View类是ViewGroup的父类,ViewGroup具有View的全部特性。ViewGroup主要用来充当View的容器。将当中的View作为自己孩子,并对其进行管理。当然孩子也能够是ViewGroup类型。

View类一般用于画图操作,重写它的onDraw方法,但它不能够包括其它组件,没有addView(View view)方法。

ViewGroup是一个组件容器,它能够包括不论什么组件,但必须重写onLayout(boolean changed,int l,int t,int r,int b)和onMesure(int widthMesureSpec,int heightMesureSpec)方法. 否则ViewGroup中加入组件是不会显示的。

package com.example.testrefreshview;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new MyViewGroup(this));

	}

	public class MyViewGroup extends ViewGroup {

		public MyViewGroup(Context context) {
			super(context);
			Button button1 = new Button(context);
			button1.setText("button1");

			Button button2 = new Button(context);
			button2.setText("button2");

			TextView textView = new TextView(context);
			textView.setText("textView");

			addView(button1);
			addView(button2);
			addView(textView);
		}

		@Override
		protected void onLayout(boolean arg0, int arg1, int arg2, int arg3,
				int arg4) {
			
		}
	}
}

View的layout(int left,int top,int right,int bottom)方法负责把该view放在參数指定位置,所以如果我们在自己定义的ViewGroup::onLayout中遍历每个子view并用view.layout()指定其位置。每个子View又会调用onLayout,这就构成了一个递归调用的过程

如果在ViewGroup中重写onDraw方法。须要在构造方法中调用this.setWillNoDraw(flase); 此时,系统才会调用重写过的onDraw(Canvas cancas)方法。否则系统不会调用onDraw(Canvas canvas)方法.

将上面代码改动一下,就能够显示出来。

package com.example.testrefreshview;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new MyViewGroup(this));

	}

	public class MyViewGroup extends ViewGroup {

		public MyViewGroup(Context context) {
			super(context);
			Button button1 = new Button(context);
			button1.setText("button1");

			Button button2 = new Button(context);
			button2.setText("button2");

			TextView textView = new TextView(context);
			textView.setText("textView");

			addView(button1);
			addView(button2);
			addView(textView);
		}

		@Override
		protected void onLayout(boolean arg0, int arg1, int arg2, int arg3,
				int arg4) {
			int childCount = getChildCount();
			int left = 0;
			int top = 10;
			for (int i = 0; i < childCount; i++) {
				View child = getChildAt(i);
				child.layout(left, top, left + 60, top + 60);
				top += 70;
			}
		}
	}
}

再看一段代码:

@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
      
    int childCount = getChildCount();  
//设置该ViewGroup的大小  
    int specSize_width = MeasureSpec.getSize(widthMeasureSpec);  
    int specSize_height = MeasureSpec.getSize(heightMeasureSpec);   
    setMeasuredDimension(specSize_width, specSize_height);  
      
    for (int i = 0; i < childCount; i++) {  
        View childView = getChildAt(i);  
        childView.measure(80, 80);  
    }  
}  

通过重写onMeasure方法不但能够为ViewGroup指定大小,还能够通过遍历为每个子View指定大小。在自己定义ViewGroup中加入上面代码为ViewGroup中的每个子View分配了显示的宽高。 

以下我们让子View动起来吧。加入代码例如以下:

public boolean onTouchEvent(MotionEvent ev) {
	final float y = ev.getY();
	switch(ev.getAction()) {
	case MotionEvent.ACTION_DOWN:
		mLastMotionY = y;
		break; 
	case MotionEvent.ACTION_MOVE:
		int detaY = (int)(mLastMotionY - y);
		mLastMotionY = y;
		scrollBy(0, detaY);
		break; 
	case MotionEvent.ACTION_UP:
		break;
	}
	return true;
}

在上面用到了一个scrollBy方法,打开官方API能够看到View类有例如以下两个方法:

技术分享

这两个函数貌似都是移动视图的。那么它们有什么差别呢?带着这个疑问我们向下看

首先 ,我们必须明确在Android View视图是没有边界的,Canvas是没有边界的,仅仅只是我们通过绘制特定的View时对 Canvas对象进行了一定的操作。比如 : translate(平移)、clipRect(剪切)等,以便达到我们的对该Canvas对象绘制的要求 。我们能够将这样的无边界的视图称为“视图坐标”-----它不受物理屏幕限制。通常我们所理解的一个Layout布局文件仅仅是该视图的显示区域,超过了这个显示区域将不能显示到父视图的区域中 ,相应的,我们能够将这样的有边界的视图称为“布局坐标”------ 父视图给子视图分配的布局(layout)大小。并且, 一个视图的在屏幕的起始坐标位于视图坐标起始处。例如以下图所看到的。

技术分享

由于布局坐标仅仅能显示特定的一块内容。所以我们仅仅有移动布局坐标的坐标原点就能够将视图坐标的不论什么位置显示出来。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="#888888">

<TextView
    android:id="@+id/txt"
    android:layout_width="300dip"
    android:layout_height="120dip"
    android:background="#cccccc"
    android:text="textview" />

<Button
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="button" />
</LinearLayout>
技术分享

当我点击按钮触发事件后例如以下:

技术分享

上面代码中触发点击事件后,运行了textView.scrollTo(-200, -100);scrollTo中的两个參数的含义不是坐标位置。而是相对于视图坐标位置的偏移量,如果我们要移动到(200,100)的位置则偏移量为(0,0)-(200,100) = (-200。 -100)。

如果我们将上面代码换成scrollBy则会发现点击多次按钮后textview就会移出可见界面,这是由于scrollBy是相对我们当前坐标进行偏移。

以下我们来看看源码中对这两个方法是怎样实现的:

    /**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                invalidate(true);
            }
        }
    }
能够看到 mScrollX = x; mScrollY = y; 

    /**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }
能够看到 mScrollX + x, mScrollY + y;

mScrollX和mScrollY是相对于视图坐标的当前偏移量。







Android自己定义组件系列【1】——自己定义View及ViewGroup