#阅读笔记 Android群英传-第3章-Android控件框架与自定义控件详解

学习目标:

了解Android框架与自定义控件


学习内容:

Android控件框架与自定义控件详解
3.1 Android控件架构
3.2 View的测量与绘制
3.3 ViewGroup的测量与绘制
3.4 自定义控件的三种方式
3.5 事件的拦截机制

3.1 Android控件架构

UI界面架构图
View树结构
程序在OnCreate()中通过调用setContentView()后,ActivityManagerService会回调onResume()方法,此时系统才会把整个DecorView添加到PhoneWindow中,让其显示出来从而完成绘制。

3.2 View的测量

该过程在onMeasure()方法中进行,通过调用MeasureSpec类来对View进行测量。MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小,在计算中使用位运算是为了提高并优化效率。测量模式有以下三种:

  1. EXACTLY:精确值模式。有具体的宽高值或match_parent
  2. AT_MOST:最大值模式。wrap_content
  3. UNSPECIFIED:不指定模式。自定义View中使用

以measureWidth为例,代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
	//重写setMeasuredDimension方法
	setMeasuredDimension(measuredWidth(widthMeasureSpec), measuredHeight(heightMeasureSpec));
}

private int measureWidth(int measureSpec){
	if result = 0;
	int specMode = MeasureSpec.getMode(measureSpec);//获取测量模式
	int specSize = MeasureSpec.getSize(measureSpec);//获取测量大小
	
	if(specMode == MeasureSpec.EXACTLY){
		result = specSize;
	}else if(specMode == MeasureSpec.AT_MOST){
		result = Math.min(200, specSize);
	}else{//UNSPECIFIED
		result = 200;
	}
	return result;
}

3.3 View的绘制

该过程在onDraw()中进行、通过重写该方法在Canvas上使用Paint实现View的绘制。

Canvas canvas = new Canvas(bitmap);//装在画布
canvas.drawBitmap(bitmap1, 0, 0, null);
canvas.drawBitmap(bitmap2, 0, 0, null);
Canvas mCanvas = new Canvas(bitmap2);//将bitmap2装载到另一个Canvas对象中
mCanvas.drawXXX
//通过该操作后、刷新View会发现在canvas中的bitmap2被改变,因为bitmap2同时承载了两个canvas的画图操作

3.4 ViewGroup的测量

遍历子View进行测量,调用子View的Measure方法来获得每一个子View的测量结果,测量完毕后调用Layout方法放置在具体显示的位置。

3.5 ViewGroup的绘制

通常不需要绘制,除非是指定了背景颜色否则不会调用onDraw()。ViewGroup会使用dispatchDraw()方法来绘制其子View,及遍历所有的子View,然后调用子View的方法来绘制。

3.6 自定义View

在View中通常有以下一些比较重要的回调。

  • onFinishInflate():从XML加载组件后回调
  • onSizeChanged():组件大小改变时回调
  • onMeasure():回调该方法来进行测量
  • onLayout():回调该方法来确定显示的位置
  • onTouchEvent:监听到触摸事件时回调

通常有以下三种方式来实现自定义控件:

  • 对现有的控件进行拓展
  • 通过组合来实现新的控件
  • 重写View来实现全新控件

3.6.1 对现有控件进行拓展

以“抛光”效果的TextView为例,实现一段文字的颜色渐变。需要对其onSizeChanged方法中设置LinearGradient和onDraw方法中对矩阵进行平移的操作来重写。

public PolishedTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mViewWidth == 0) {
            mViewWidth = getMeasuredWidth();
            if (mViewWidth > 0) {
                mPaint = getPaint();
                mLinearGradient = new LinearGradient(-mViewWidth, 0, 0, 0,
                        new int[]{0xFF9EADD9, 0xffffffff, 0xFF9EADD9},
                        new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
                mPaint.setShader(mLinearGradient);
                mGradientMatrix = new Matrix();
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mAnimating && mGradientMatrix != null) {
            mTranslate += mViewWidth / 10;
            if (mTranslate > 2 * mViewWidth) {
                mTranslate = -mViewWidth;
            }
            mGradientMatrix.setTranslate(mTranslate, 0);
            mLinearGradient.setLocalMatrix(mGradientMatrix);
            postInvalidateDelayed(50);
        }else{//通过set mAnimating值决定该自定义View是否显示抛光效果
            setTextColor(0xFF9EADD9);
        }
    }

3.6.2 创建复合控件

该方法为通常需要继承一个合适的ViewGroup,并给它添加指定的功能的控件,从而组合成新的复合控件。通过该方式的创建的控件,我们一般人为给它指定一些可配置的属性,使其具有更强的拓展性。

3.6.2.1 定义属性

为一个View提供属性,只需要在res资源目录的values目录下创建一个attrs.xml的属性定义文件。然后再标签声明了使用自定义属性,并通过name属性来确定引用的字体大小等。确定属性之后即可创建一个自定义控件——TopBar,并让它继承ViewGroup。在构造方法中,通过如下代码来获取XML布局文件中自定义的那些属性。

TypeArray ta = context.obtainStyleAttributes(attrs,R.style.TopBar);

系统提供了TypedArray这样的数据结构来获取自定义属性集,后面引用的styleable的TopBar,就是我们在XML中通过所制定的name名。通过ta.getColor等方法可获取这些定义的属性值。需要注意的是,当获取完所有的饿属性值后,需要调用TypeaArray的recycle方法完成资源回收。

3.6.2.2 组合控件

也可由现成的多个控件进行组合,再通过接口调用来实现具体的实现,

3.6.2.3 引用UI模板
xmlns:android="http://schemas.android.com/apk/res/android"

指定引用的名字空间。使用系统属性时才可使用"android: "来引用Android的系统属性。若要使用自定义属性,就需要创建自己的名字空间,第三方控件都使用如下代码引入名字空间。

xmlns:custom="http://schemas.android.com/apk/res-auto"

之后在XML文件中使用自定义属性,都可以通过这个名字空间来引用。使用自定义View时需要指定完整的包名。而在引用自定义的属性时,需要使用自定义的xlms的名字。

3.7 自定义ViewGrop

自定义ViewGroup的修改实际上是通过改变其子View来实现、先onMeasure() 对子View大小进行测量、重写onLayout()确定子View的位置,重写onTouchEvent()方法增加响应事件。
例如:使用ViewGroup实现类似ScrollView的功能。但在滑动过程中增加一个黏性效果,即当一个子View向上滑动大于一定距离之后松开手指,它将自动向上滑显示下一个View.。下滑同理。

1、首先对ViewGroup的子类大小进行测量

@Overrrie
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	int count = getChildCount();//获取ViewGroup的子V
	for(int i = 0; i < count; i++){
		View  childView = getChildAt(i);
		measureChild(childView, widthMeasureSpec, heightMeasureSpec);
	}
}

2、对子View进行防治位置的设定。让每个子View都显示完整的一屏。因此整个ViewGroup的高度即子View的个数乘以屏幕的高度。获取之后通过遍历来设定每个子View需要放置的位置、直接调用layout()方法。代码如下所示

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b){
	int childCount = getChildCount();
	//设置ViewGroup的高度
	MarginLayoutParams mlp = (MarginLayoutParams)getLayoutParams();
	mlp.height = mScreenHeight * childCount;
	setLayoutParams(mlp);
	for(int i = 0; i < childCount; i++){
		View child = getChildAt(i);
		if(child.getVisibility()!= View.GONE){
			child.layout(1, i*mScreenHeight, r, (i+1)*mScreenHeight);
		}
	}
}

3、重写onTouchEvent()方法,为ViewGroup重写触摸事件。通常使用scrollBy()来辅助滑动。

case MotionEvent.ACTION_DOWN:
	mLastY = y;
	break;
case MotionEvent.ACYION_MOVE:
	if(!mScroller.isFinished()){
		mScroller.abortAnimation();
	}
	int dy = mLastY-y;
	if(getScrollY() < 0){
		dy = 0;
	}
	if(getScrollY() > getHeight() - mScreenHeight()){
		dy = 0;
	}
	scrollBy(0, dy);
	mLastY = Y;
	break;

4、实现ViewGroup黏性效果。在ACTION_UP事件中判断手指滑动的距离,如果超过一定距离,则使用Scroller类来平滑移动到下一个View,若小于一定距离,则回滚到原来的位置。

@Override
public boolean onTouchEvent(MotionEvent event){
	int y = (int)event.getY();
	swtich(event.getAction()){
		case MotionEvent.ACTION_DOWN:
			mLastY = y;
			mStart = getScrollY();//记录触摸起点
			break;
		case MotionEvent.ACTION_MOVE:
			if(!mScroller.isFinished()){
				mScroller.abortAnimation();
			}
			int dy = mLastY - y;
			if(getScrollY() < 0){
				dy = 0;
			}
			if(getScrollY() > getHeight() - mScreenHeight()){
				dy = 0;
			}
			scrollBy(0, dy);
			mLastY = y;
			break;
		case MotionEvent.ACTION_UP:
			mEnd = getScrollY();//记录触摸终点
			if(dScrollY > 0){
				int dScrollY = mEnd - mStart;
				if(dScrollY > 0){//上滑
					if(dScrollY < mScreenHeight/3){
						mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
					}else{
						mScroller.startScroll(0, getScrollY(), 0, mScreenHeight - dScrollY);
					}
				}else{//下滑
					if(-dScrollY < mScreenHeight/3){
						mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
					}else{
						mScroller.startScroll(0, getScrollY(), 0, -mScreenHeight - dScrollY);
					}
				}
			}
			break;
	}
	postInvalidate();
	return true;
}

@Override
public void computeScroll(){
	super.computeScroll();
	if(mScroller.computeScrollOffset()){
		scrollTo(0,mScroller.getCurY());
		postInvalidate();
	}
}

3.8 事件拦截机制分析

方法与控件的对应关系如下:
在这里插入图片描述
事件传递过程:从上之下依次为父类与子类。父类通过dispatchTouchEvent()将事件传递给子类,其中onInterceptTouchEvent()决定是否拦截传递的事件,若不拦截则在子类通过onTouchEvent()实现具体的操作事件。
在这里插入图片描述


学习时间:

2021/1/27-2021/1/28


学习产出:

1、 技术博客 1 篇
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图书简介: 本书从由总到分,让读者从整体上把握Android体系结构,融入“群英”这一故事情节,针对各个知识点进行分类阐述,并结合一线实际开发经验和Android 5.0版本新特性来,紧跟市场需求进行讲解。然后通过实战应用案例,综合使用前面讲解到的知识点,进一步提高开发者水平。 相关截图: 图书目录: 第1章 Android体系与系统架构 1 1.1 Google生态系统 2 1.2 Android系统架构 2 1.2.1 Linux 3 1.2.2 Dalvik与ART 3 1.2.3 Framework 3 1.2.4 Standard libraries 4 1.2.5 Application 4 1.3 Android App组件架构 4 1.3.1 Android四大组件如何协同工作 5 1.3.2 应用运行上下文对象 5 1.4 Android系统源代码目录与系统目录 6 1.4.1 Android系统源代码目录 6 1.4.2 Android系统目录 8 1.4.3 Android App文件目录 11 第2章 Android开发工具新接触 13 2.1 Android开发IDE介绍 14 2.1.1 Android Studio初体验 14 2.1.2 Android Studio配置 15 2.2 Android Studio高级使用技巧 19 2.2.1 更新SDK 20 2.2.2 Android Studio常用界面 21 2.2.3 导入Android Studio工程 23 2.3 ADB命令使用技巧 24 2.3.1 ADB基础 24 2.3.2 ADB常用命令 25 2.3.3 ADB命令来源 29 2.4 模拟器使用与配置 29 第3章 Android件架构与自定义详解 32 3.1 Android件架构 33 3.2 View的测量 34 3.3 View的绘制 37 3.4 ViewGroup的测量 38 3.5 ViewGroup的绘制 39 3.6 自定义View 39 3.6.1 对现有件进行拓展 40 3.6.2 创建复合件 43 3.6.3 重写View来实现全新的件 51 3.7 自定义ViewGroup 54 .........................

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值