android面试题总结之android进阶,Android 面试题总结之Android 进阶(一)

Android 面试题总结之Android 进阶(一)

在前几篇文章中都是讲的基础,大家应该会觉得非常熟悉,但往往我们可能对于基础某些细节认识不够彻底或贯穿不全,我一直认为基础都是比较难的,那么本章节终于到进阶啦,主要讲的是View 的相关知识,在前面《Android 面试题总结之Android 基础 (六)》中已经对View有了一定的了解,由于内容较多且也是面试必考题,所以将分两篇继续深入的理解View。

在阅读过程中有任何问题,请及时联系。如需转载请注明 fuchenxuan de Blog

本章系《Android 之美 从0到1 – 高手之路》Android进阶(一)自定义View的过程

掌握

什么是View?

View 坐标的基本概念

View的生命周期

如何自定义View

什么是View?

android.app.View 就是手机的UI,View 负责绘制UI,处理事件(evnet),Android 利用 View 打造出所 Widgets,利用 Widget 可打造出互动式的使用者介面,每个View 负责一定区域的绘制。

一张图理解常用控件层级关系

0818b9ca8b590ca3270a3433284dd417.png

View 坐标的基本概念

View的宽高是有top、left、right、bottom参数决定的 而X,Y和translationX,和translationY则负责View位置的改变。

从Android3.0开始,加入了translation的概念,即相对于父容器的偏移量以及X,Y坐标的概念,X,Y代表左上顶点的横纵坐标。当View在发生平移时,getX,getY,setX,setY

get/setTranslationX/Y来获得当前左上点的坐标。

X=left+translationX Y同理。

注意:在View发生改变的过程中,top,left等值代表原始位置,是不会改变的。改变的只有X,Y,translationX/Y。

一张图理解View的坐标概念

0818b9ca8b590ca3270a3433284dd417.png

View的生命周期

对实现自定义View,不需要重写所有这些方法。事实上,你可以只onDraw(android.graphics.Canvas)

View 的几个构造函数

public MyView(Context context)

java代码直接new一个Custom View实例的时候,会调用第一个构造函数

public MyView(Context context, AttributeSet attrs)

在xml创建但是没有指定style的时候被调用.多了一个AttributeSet类型的参数,自定义属性,在通过布局文件xml创建一个view时,会把XML内的参数通过AttributeSet带入到View内。

public MyView(Context context, AttributeSet attrs, int defStyleAttr)

构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或Activity所用的Theme中的默认Style,且只有在明确调用的时候才会调用

@TargetApi(Build.VERSION_CODES.LOLLIPOP)

public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

该构造函数是在api21的时候才添加上的

0818b9ca8b590ca3270a3433284dd417.png

View 的几个重要方法

requestLayout

View重新调用一次layout过程

invalidate

View重新调用一次draw过程

forceLayout

标识View在下一次重绘,需要重新调用layout过程。

postInvalidate

这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。

自定义View

简单理解View的绘制

这里我们先简单理解View 的绘制,后续文章我们会深入理解。

1.测量——onMeasure():决定View的大小

2.布局——onLayout():决定View在ViewGroup中的位置

3.绘制——onDraw():如何绘制这个View。

0818b9ca8b590ca3270a3433284dd417.png

自定义View的分类

继承View

继承ViewGroup

继承系统控件(Button,LinearLayout…)

自定义View的过程

自定义 View 首先要实现一个继承自 View 的类

添加类的构造方法,通常是三个构造方法,不过从 Android5.0 开始构造方法已经添加到 4 个了

override 父类的方法,如 onDraw,(onMeasure)等

自定义属性,需要在 values 下建立 attrs.xml 文件,在其中定义属性

通过context.obtainStyledAttributes将构造函数中的attrs进行解析出来,就可以拿到相对应的属性.

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);

mColor = typedArray.getColor(R.styleable.MyView_myColor, 0XFF00FF00);

【注意】三个函数获取尺寸的区别:

getDimension()是基于当前DisplayMetrics进行转换,获取指定资源id对应的尺寸

getDimensionPixelSize()与getDimension()功能类似,不同的是将结果转换为int,并且小数部分四舍五入

getDimensionPixelOffset()与getDimension()功能类似,不同的是将结果转换为int,取整去除小数。举个例子

列如getDimension()返回结果是20.5f,那么getDimensionPixelSize()返回结果就是 21,getDimensionPixelOffset()返回结果就是20。

打开布局文件我们可以看到有很多的以xmlns开头的字段。其实这个就是XML name space 的缩写。我们可以使用res-atuo命名空间,就不用在添加自定义View全类名。

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

/** * Created by fuchenxuan on 16/6/4. */

public class MyView extends View {

private int mRadius=200;

private int mColor;

public MyView(Context context) {

this(context,null);

}

public MyView(Context context, AttributeSet attrs) {

this(context, attrs,0);

}

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

//read custom attrs

TypedArray t = context.obtainStyledAttributes(attrs,

R.styleable.rainbowbar, 0, 0);

mRadius = t.getDimensionPixelSize(R.styleable.coutom_radius, (int) hSpace);

t.getDimensionPixelOffset(R.styleable.coutom_at1, (int) vSpace);

mColor=t.getColor(R.styleable.color, barColor);

t.recycle(); // we should always recycle after used

}

@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);

//set size

setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? (int) mRadius * 3 : widthSize, heightMode == MeasureSpec.AT_MOST ? (int) mRadius * 3 : heightSize);

}

//draw be invoke clire.

int index = 0;

@Override

protected void onDraw(Canvas canvas) {

//super.onDraw(canvas);

mPaint = new Paint();

mPaint.setColor(mColor);

mPaint.setAntiAlias(true);

canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);

}

}

这里是一个普通的自定义View,里面画了圆,根据不同的模式设置了父View的大小。

关于View重写onMeasure()时机:

如果用了wrap_content。那么在onMeasure()中就要调用setMeasuredDimension(),

来指定view的宽高。如果使用的是match_parent或者一个具体的dp值。那么直接使用super.onMeasure()即可。

自定义ViewGroup

自定义ViewGroup的过程

自定义 ViewGroup 和自定义View 一样,只是继承自 ViewGroup 的类,和必须实现onLayout()函数

/** * Created by fuchenxuan on 16-6-6. */

public class CostumViewGroup extends ViewGroup {

public CostumViewGroup(Context context) {

super(context);

}

public CostumViewGroup(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {

View childView = getChildAt(i);

measureChild(childView, widthMeasureSpec, heightMeasureSpec);

}

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

if (changed) {

int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {

View childView = getChildAt(i);

childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());

}

}

}

}

这里是一个简单的自定义ViewGroup,实现类似LinearLayout 横向排放子View位置。这就是一个简单的ViewGroup过程。

彻底理解MeasureSpec三种模式

View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量。他们是由 mode+size两部分组成的。widthMeasureSpec和heightMeasureSpec转化成二进制数字表示,他们都是30位的。前两位代表mode(测量模 式),后面28位才是他们的实际数值(size);MeasureSpec.getMode()获取模式,MeasureSpec.getSize()获取尺寸

测量View大小使用的是onMeasure函数,所以我们需要了解三种测量模式:

EXACTLY:一般是设置了明确的值(100dp)或者是MATCH_PARENT

AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

UNSPECIFIED:表示子布局想要多大就多大,很少使用

关于ViewGroup重写onMeasure()时机:

首先要先测量子View的宽高:

getChildAt(int index)可以拿到index上的子view。

getChildCount()得到子view的个数,再循环遍历出子view。

使用子view自身的测量方法

childView.measure(int wSpec, int hSpec);

或使用viewGroup的测量子view的方法:

measureChild(subView, int wSpec, int hSpec);

测量某一个子view,多宽,多高, 内部加上了viewGroup的padding值

measureChildren(int wSpec, int hSpec);

测量所有子view 都是 多宽,多高, 内部调用了measureChild方法

measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed);测量某一个子view,多宽,多高, 内部加上了viewGroup的padding值、margin值和传入的宽高wUsed、hUsed

问题总结

getWidth()和getMeasuredWidth()的区别?

getMeasuredWidth():只要一执行完 setMeasuredDimension() 方法,就有值了,并且不再改变。

getWidth():必须执行完 onMeasure() 才有值,可能发生改变。

如果 onLayout 没有对子 View 实际显示的宽高进行修改,那么 getWidth() 的值 == getMeasuredWidth() 的值。

onLayout() 和Layout()的区别?

onLayout() ViewGroup中子View的布局方法,layout()是子View布局的方法

View 里面的 onSavedInstanceState和onRestoreInstanceState的作用?

View和Activity一样的,每个View都有onSavedInstanceState和onRestoreInstanceState这两个方法,可用于保存和恢复view的状态。

在本章节中我们知道什么是View?,View 坐标的基本概念,理解了View的生命周期,学习了如何自定义View?虽然全是理论知识总结,在后续我们会一起来自定义View的实战学习。不管有没有任何疑问,欢迎在下方留言吧。

更多Android 面试题总结,请点击下方图片哦。

水平有限,若有错漏,欢迎指正,批评,如需转载,请注明出处–http://blog.csdn.net/vfush,谢谢!

0818b9ca8b590ca3270a3433284dd417.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值