1.Android控件架构

     在Android中,控件大致分为两类:ViewGroup控件与View控件。ViewGroup作为父控件时,可以包含多个View,通过ViewGroup,整个界面形成一个树结构,上层控件负责下层控件的测量与绘制。

View数结构:

android 数字变化 控件 android控件数组_ide

android 数字变化 控件 android控件数组_控件_02

android 数字变化 控件 android控件数组_父类_03

 

     每一个Activity都包含一个Window对象,而这个对象是由PhoneWindow来实现,PhoneWindow将一个DecorView设置为整个窗口的根View,DecorView由两个部分组成:TitleView和ContentView。ContentView实际上是一个Framelayout,里面容纳的就是我们在XML中的布局文件。UI界面结构图如3.2UI界面结构图。

当设置了requestWindowFeature(Window.FEATURE_NO_TITLE)时,为什么要放在setContentView()方法前面?

这是因为,当程序在onCreate()方法中调用setContentView()方法后,ActivityManagerSerVice会回调onResume()方法,此时系统才会将整个DecorView添加到PhoneWindow中。


2.View的测量:

   在需要对View进行绘制之前,需要知道View的位置和大小。

   涉及的类与方法:onMeasure()进行测量、MeasureSpec。

  MeasureSpec:一个32位的int值,高两位为测量模式,低30位为测量大小。

测量模式有:

  EXACTLY:精确值模式。如需对layout_width、layout_height设置具体数值 或者使用match_parent时。

  AT_MOST:最大值模式。对layout_width、layout_height设置为wrap_content,让空间尺寸碎内容的大小进行改变,但注意不能超过父控件允许的最大尺寸。

  UNSPECIFIED:不指定大小测量模式,通常情况下在绘制自定义View时才会用到

View默认的onMeasure()方法只支持EXACTLY模式,所以如果要支持wrap_content属性,就必须重写onMeasure()方法,并在方法中给定wrap_content的大小。

重写onMeasure方法的最终工作就是把测量后的宽高值作为参数设置给setMeasuredDimension方法。

以下是用法:

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		//在这里进行计算
		setMeasuredDimension(
				measureWidth(widthMeasureSpec),
				measureHight(heightMeasureSpec)
				);
	}
	
	private int measureWidth(int measureSpec){
		int width=0;//设置返回的数值
		int specMode=MeasureSpec.getMode(measureSpec);//判断是哪一种测量模式
		int specSize=MeasureSpec.getSize(measureSpec);//值的大小
		if (specMode==MeasureSpec.EXACTLY) {//如果是精确测量 则直接返回值
			width=specSize;
		}else{//指定宽度的大小
		     width=200;
		     if (specMode==MeasureSpec.AT_MOST) {//如果是最大值模式  取当中的小值  防止超出父类控件的最大值
		    	 width=Math.max(width, measureSpec);
				
			}
		}
		return measureSpec;
	}
	
	private int measureHight(int measureSpec){
		int higth=0;//设置返回的数值
		int specMode=MeasureSpec.getMode(measureSpec);//判断是哪一种测量模式
		int specSize=MeasureSpec.getSize(measureSpec);//值的大小
		if (specMode==MeasureSpec.EXACTLY) {//如果是精确测量 则直接返回值
			higth=specSize;
		}else{//指定高度的大小
			higth=200;
		     if (specMode==MeasureSpec.AT_MOST) {//如果是最大值模式  取当中的小值  防止超出父类控件的最大值
		    	 higth=Math.max(higth, measureSpec);
				
			}
		}
		return measureSpec;
	}



3.View和ViwGroup的绘制

View的onDraw()方法包含一个参数Canvas对象,使用这个Canvas对象就可以进行绘图了。
通常情况下,Canvas对象的创建需要传入参数Bitmap,这是因为传进去的Bitmap与通过这个Bitmap创建的Canvas画布是紧紧联系在一起的,这个Bitmap用来存储所有绘制在Canvas上的像素信息,当通过Canvas之后,后面调用所有的Canvas.drawXXX方法都发生在这个Bitmap上。

而ViewGroup通常不需要绘制,因为它本身没有需要绘制的东西,如果不指定ViewGroup的背景颜色,那么ViewGroup的onDraw方法都不会被调用。但是,ViewGroup会调用dispatchDraw方法来绘制其子view,其过程同样是通过遍历所有子view并调用子view的绘制方法来完成绘制工作的。


4. ViewGroup的测量与绘制

 当ViewGroup的大小设置为wrap_content时,ViewGroup就需对ziView进行遍历,根据子View来设置自身的大小。

通常情况下ViewGroup不需要进行绘制,因为本身就是一个View的集合,没有绘制的东西,甚至如果不是设置背景颜色,其onDraw()方法都不会被调用。ViewGroup通过调用disPatchDraw()方法来绘制子View。自定义ViewGroup时,通常重写onLayout()方法来控制其子View的位置显示逻辑。


5.自定义View

在View中重要的回调方法:

onFinishInFlate():从XML加载组件后回调

onSizeChanged():组件大小改变时回调

onMeasure():回调该方法来进行测量

onLayout():回调该方法来确定显示位置

onTouchEvent():监听触摸事件进行回调


三种实现自定义控件方法:

   1).对现有控件进行拓展

@Override
	protected void onDraw(Canvas canvas) {
		
		//在调用父类方法前,实现自己的逻辑,对TextView来说既是在绘制文本内容前
		super.onDraw(canvas);
		//在调用父类方法后,实现自己的逻辑,对TextView来说既是在绘制文本内容后
	}

如书中绘制书中例子:

@Override
	protected void onDraw(Canvas canvas) {
		//绘制外层矩形
		canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint1);
		//绘制内层矩形
		canvas.drawRect(10, 10, getMeasuredWidth()-10, getMeasuredHeight()-10, mPaint2);
		canvas.save();//保存画布的状态
		canvas.translate(10, 0);//绘制前内容在X轴平移10个像素
		super.onDraw(canvas);//调用父类完成绘制文本
		canvas.restore();//取出保存的状态
		
	}
	private void init(){
		mPaint1=new Paint();
		mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
		mPaint1.setStyle(Paint.Style.FILL);
		mPaint2=new Paint();
		mPaint2.setColor(Color.YELLOW);
		mPaint2.setStyle(Paint.Style.FILL);
	}

在这里需要说的是,为什么要调用canvas.sava()和canvas.restore()?

这里canvas.save();和canvas.restore();是两个相互匹配出现的,作用是用来保存画布的状态和取出保存的状态的.。

当我们对画布进行旋转,缩放,平移等操作的时候其实我们是想对特定的元素进行操作,比如图片,一个矩形等,但是当你用canvas的方法来进行这些操作的时候,其实是对整个画布进行了操作,那么之后在画布上的元素都会受到影响,所以我们在操作之前调用canvas.save()来保存画布当前的状态,当操作之后取出之前保存过的状态,这样就不会对其他的元素进行影响。


 2).通过符合控件来实现自定义控件

这种方式需要需要继承一个合适的ViewGroup,再给它增添新功能,如TopBar标题栏

使用方式为:
  (1)自定义属性

在res资源目录中的values目录下新建arrts.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TopBar">
       <attr name="title" format="string" />
    </declare-styleable>
</resources>



 获取xml布局中的自定义属性且进行赋值:

TypedArray ta=context.obtainStyledAttributes(attr,R.styleable.TopBar);
		String title;
		title=ta.getString(R.styleable.title);



 (2)组合控件,如:网布局中添加一个TextView,并让该TextView显示以上的title

  定义父布局:mParentLayout 、textView;

  将title显示在TextView中:textView.setText(title)

  通过addView(textView,mParentLayout )

   书中例子:

public class TopBar extends RelativeLayout {

    // 包含topbar上的元素:左按钮、右按钮、标题
    private Button mLeftButton, mRightButton;
    private TextView mTitleView;

    // 布局属性,用来控制组件元素在ViewGroup中的位置
    private LayoutParams mLeftParams, mTitlepParams, mRightParams;

    // 左按钮的属性值,即我们在attr.xml文件中定义的属性
    private int mLeftTextColor;
    private Drawable mLeftBackground;
    private String mLeftText;
    // 右按钮的属性值,即我们在attr.xml文件中定义的属性
    private int mRightTextColor;
    private Drawable mRightBackground;
    private String mRightText;
    // 标题的属性值,即我们在attr.xml文件中定义的属性
    private float mTitleTextSize;
    private int mTitleTextColor;
    private String mTitle;

    // 映射传入的接口对象
    private topbarClickListener mListener;
    ......
}





3)重写View来实现全新的控件
创建自定义View的难点在于绘制控件和实现交互,通常需要继承View类,并重写onDraw、onMeasure等方法来实现绘制逻辑,同时通过重写onTouchEvent等触控事件方法来实现交互逻辑。在一部分需要动手自己实践。



5. 简单的 事件拦截机制分析

        事件拦截机制是Android提供的一整套完善的事件传递、处理机制来处理用户从屏幕传来的操作。

       触摸事件:

       按钮按下:MotionEven.ACTION_DOWN

       滑动:       MotionEven.ACTION_MOVE

       抬起:MotionEven.ACTION_UP

  事件传递,书中例子 :

总经理----》MyViewGroupA  最外层ViewGroup

部长------》MyViewGroupB   中间层

干活员工---》View   底层View


事件进行传递时需要调用的方法:

作为父类的ViewGroup需要调用三个方法:

@Override
		public boolean dispatchTouchEvent(MotionEvent event) {
			Log.d("hjo", "MyViewGroupA dispatchTouchEvent");
			return super.dispatchTouchEvent(event);
		}
    @Override
    	public boolean onInterceptTouchEvent(MotionEvent ev) {
    	Log.d("hjo", "MyViewGroupA onInterceptTouchEvent");
    		return super.onInterceptTouchEvent(ev);
    	}
    @Override
    	public boolean onTouchEvent(MotionEvent event) {
    	Log.d("hjo", "MyViewGroupA onTouchEvent");
    		return super.onTouchEvent(event);
    	}



作为底层View调用的方法只有两个:

@Override
		public boolean dispatchTouchEvent(MotionEvent event) {
			Log.d("hjo", "View dispatchTouchEvent");
			return super.dispatchTouchEvent(event);
		}
   
    @Override
    	public boolean onTouchEvent(MotionEvent event) {
    	Log.d("hjo", "View onTouchEvent");
    		return super.onTouchEvent(event);
    	}

运行后打出的log为:

MyViewGroupA dispatchTouchEvent
	MyViewGroupA onInterceptTouchEvent
	MyViewGroupB dispatchTouchEvent
	MyViewGroupB onInterceptTouchEvent
	View dispatchTouchEvent
	View onTouchEvent
	MyViewGroupB onTouchEvent
	MyViewGroupA onTouchEvent

从上可以看出事件拦截的核心是:onInterceptTouchEvent


所以事件的传递顺序为:

总经理(MyViewGroupA)---》部长(MyViewGroupB)---》员工(View)

处理后传递顺序是:

员工(View)---》部长(MyViewGroupB)---》总经理(MyViewGroupA)


事件传递返回值的分析:true,拦截,不继续;false,不拦截,继续传递

事件处理后返回值分析:true,处理了,不用审核;false,给上级处理处理

初始状态为false。


处理过程如下图:

android 数字变化 控件 android控件数组_android 数字变化 控件_04


当部长(MyViewGroupA)拦截事件,也就是自己处理时,,此时不需要View进行处理,传递与处理如下:

android 数字变化 控件 android控件数组_android 数字变化 控件_05


  事件的处理,当View无法进行事件的处理而直接返回true,事件处理如下:

                              

android 数字变化 控件 android控件数组_父类_06



本章完。