android view 面试题

view 自定义

https://www.jianshu.com/p/32c75a1c58df

  • View中比较重要的回调方法:

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

    • 边框效果
    • 创建画笔(背景和边框),绘制外边距边框,绘制内边距边框
    • 绘制文字位移边框像素
    • 闪动文字效果
    • 在onSizeChanged 中创建一些对象,通过获取的组件高宽,创建画笔,创建LinearGradient对象,给画笔设置setShader(linearGradient),实例化矩阵
    • 在onDraw中通过矩阵来不断平移渐变效果,实现闪动
  • 复合控件

    • 自定义属性
    • 在res定义资源文件
    • 在java代码中获取资源文件设置属性,并且最后回收资源。recycler
    • 组合控件
    • 为创建的组件元素赋值,并且设置元素布局,添加到布局里面
    • 重写view,实现新控件
    • 通过WindowManager获取屏幕宽度,设置圆心距离,设置圆半径,设置画笔,设置扇形矩形区域
    • 绘制圆,绘制弧线
  • 绘制柱状图渐变

    • 构造函数中定义画笔,获取屏幕宽度
    • 在onSizeChanged的方法里面增加LinearGradient渐变效果
    • 在onDraw里面循环绘制
  • gif 图

    • 构造函数获取屏幕宽度
    • 在onMeasure里面策略子view
    • onLayout 遍历设置view位置,通过调用layout方法
    • 在onTouchEvent中实现滚动的逻辑和"粘性"的逻辑。

LinearGradient也称作线性渲染,LinearGradient的作用是实现某一区域内颜色的线性渐变效果:https://www.cnblogs.com/itchq/p/4138966.html

  • android 使用LinearGradient进行字体渐变的效果
  • 构造函数:
    • public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)
    • 其中,参数x0表示渐变的起始点x坐标;参数y0表示渐变的起始点y坐标;参数x1表示渐变的终点x坐标;参数y1表示渐变的终点y坐标 ;color0表示渐变开始颜色;color1表示渐变结束颜色;参数tile表示平铺方式。
    • 平铺方式:Shader.TileMode有3种参数可供选择,分别为CLAMP、REPEAT和MIRROR:
      • CLAMP的作用是如果渲染器超出原始边界范围,则会复制边缘颜色对超出范围的区域进行着色
      • REPEAT的作用是在横向和纵向上以平铺的形式重复渲染位图
      • MIRROR的作用是在横向和纵向上以镜像的方式重复渲染位图
    • public LinearGradient (float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile);
    • 其中,参数x0表示渐变的起始点x坐标;参数y0表示渐变的起始点y坐标;参数x1表示渐变的终点x坐标;参数y1表示渐变的终点y坐标;参数colors表示渐变的颜色数组;参数positions用来指定颜色数组的相对位置;参数tile表示平铺方式。通常,参数positions设为null,表示颜色数组以斜坡线的形式均匀分布。

setShader(Shader shader)中传入的自然是shader对象了,shader类是Android在图形变换中非常重要的一个类。Shader在三维软件中我们称之为着色器,其作用是来给图像着色:https://www.cnblogs.com/tianzhijiexian/p/4298660.html

  • shader类是Android在图形变换中非常重要的一个类。
  • BitmapShader,
  • 构造函数
    • BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
    • 第一个参数:要处理的bitmap对象,第二个参数:在X轴处理的效果,Shader.TileMode里有三种模式:CLAMP、MIRROR和REPETA,第三个参数:在Y轴处理的效果,Shader.TileMode里有三种模式:CLAMP、MIRROR和REPETA
    • Shader.TileMode.CLAMP:会将边缘的一个像素进行拉伸、扩展
    • Shader.TileMode.MIRROR:镜像模式
    • Shader.TileMode.REPEAT:重复模式
  • ComposeShader:两个Shader组合在一起作为一个新Shader
  • 构造方法:
    • ComposeShader (Shader shaderA, Shader shaderB, Xfermode mode)
    • ComposeShader (Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
  • LinearGradient:https://www.cnblogs.com/itchq/p/4138966.html
  • RadialGradient:径向渐变,径向渐变说的简单点就是个圆形中心向四周渐变的效果
  • 构造函数
    • RadialGradient (float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode) :(centerX,centerY)是圆心的坐标,radius是半径,centerColor是边缘的颜色,edgeColor是外围的颜色,最后是模式。
    • RadialGradient (float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode) :就是添加了多个色彩,设置色彩的位置
  • SweepGradient:梯度渐变,也称之为扫描式渐变,因为其效果有点类似雷达的扫描效果
  • 构造函数
    • SweepGradient(float cx, float cy, int color0, int color1) :(cx,cy)是远行的坐标,产生从color0到color1的渐变
    • SweepGradient(float cx, float cy, int[] colors, float[] positions):
  • 用Matrix进行变换
    • 除了平移外,缩放、旋转、错切、透视都是需要一个中心点作为参照的,如果没有平移,默认为图形的[0,0]点,平移只需要指定移动的距离即可,平移操作会改变中心点的位置,
    • 混合使用后会覆盖前面的设置

android中Invalidate和postInvalidate的区别 https://www.cnblogs.com/rayray/p/3437048.html

  • invalidate 在ui线程自身使用
    • Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。
  • postInvalidate 在非ui线程使用

Android Scroller与computeScroll的调用机制关系:https://www.linuxidc.com/Linux/2016-01/127276.htm

没有直接关系

  • 问:scoller 是什么

  • Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是辅助UI滑动,反而是单纯地为滑动提供计算。

  • 问:Scroller只是提供计算,那谁来调用computeScroll使得ViewGroup滑动

  • computeScroll也不是来让ViewGroup滑动的,真正让ViewGroup滑动的是scrollTo,scrollBy。computeScroll的作用是计算ViewGroup如何滑动。而computeScroll是通过draw来调用的。

  • 问computeScroll和Scroller都是计算,两者有啥关系?

  • 没有直接的关系。computeScroll和Scroller要是飞得拉关系的话,那就是computeScroll可以参考Scroller计算结果来影响scrollTo,scrollBy,从而使得滑动发生改变。也就是Scroller不会调用computeScroll,反而是computeScroll调用Scroller。

  • 问:滑动时连续的,如何让Scroller的计算也是连续的?

  • computeScroll调用Scroller,只要computeScroll调用连续,Scroller也会连续,实质上computeScroll的连续性由invalidate方法控制,scrollTo,scrollBy都会调用invalidate,而invalidate回去触发draw,从而computeScroll被连续调用,综上,Scroller也会被连续调用,除非invalidate停止调用。

  • 问:computeScroll如何和Scroller的调用过程保持一致。

  • computeScroll参考Scroller影响scrollTo,scrollBy,实质上,为了不重复影响scrollTo,scrollBy,那么Scroller必须终止计算currX,currY。要知道计算有没有终止,需要通过mScroller.computeScrollOffset()

下面两篇文档有讲解和实例

https://www.cnblogs.com/manuosex/p/5301252.html

https://www.jianshu.com/p/27665fce3bf5


Android应用层View绘制流程与源码分析

参考 https://blog.csdn.net/yanbober/article/details/46128379/

  • view 绘制流程:measure --> layout --> draw
  • measurce:递归遍历树结构计算高宽,每个view大小有父级控件和自身觉得,在onMeasure方法进行。

MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中specMode只有三种值:

> MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定;
> MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
> MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定; 

View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。

最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。

ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。

只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。

View的布局大小由父View和子View共同决定。

使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。

  • layout: 也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。具体layout核心主要有以下几点:

View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑。

measure操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来说的。

凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的(前面《Android应用setContentView与LayoutInflater加载解析机制源码分析》也有提到过)。

使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程之后被调用才能返回有效值。

  • draw 绘制过程就是把View对象绘制到屏幕上,整个draw过程需要注意如下细节:

如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View。

View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。

View的绘制是借助onDraw方法传入的Canvas类来进行的。

区分View动画和ViewGroup布局动画,前者指的是View自身的动画,可以通过setAnimation添加,后者是专门针对ViewGroup显示内部子视图时设置的动画,可以在xml布局文件中对ViewGroup设置layoutAnimation属性(譬如对LinearLayout设置子View在显示时出现逐行、随机、下等显示等不同动画效果)。

在获取画布剪切区(每个View的draw中传入的Canvas)时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制即可。

默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。

  • invalidate与postInvalidate

RelativeLayout和LinearLayout性能分析

1.性能对比 https://www.jianshu.com/p/b9bd08ffe921

  • 总结:在简单布局可以用单层LinearLayout完成的布局我们可以选择LinearLayout进行布局,如果用单层LinearLayout完成不了而要嵌套的话,那么我们可以考虑用RelativeLayout来完成布局。
  • 绘制流程

2.基础对比 https://www.jianshu.com/p/b9bd08ffe921

这里relativeLayout 的measure 方法 时间会比较长

linearLayout 如果设置weight,那么linearLayout会测量两次

的RelativeLayout会进行两次的测量,这样有可能会成为性能消耗的原因,但是同时RelativeLayout在复杂布局时候有可能减少嵌套层数。所以在复杂嵌套时候我们可以考虑使用他或者LinearLayout使用到weight的情况我们也可以考虑使用它。

描述一下View的绘制原理

Android - View绘图原理总结

  • 1、measure操作:主要用于计算视图的大小,即视图的宽度和长度,其中调用onMeasure(),视图大小的将在这里最终确定,也就是说measure只是对onMeasure的一个包装,子类可以覆写 onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width, height)保存计算结果。
  • 2、layout操作: 用于设置视图在屏幕中显示的位置,其中 (1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;(2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;
  • 3、draw操作:利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作,

基本步骤 绘制背景 --> 视图显示渐变框 --> 绘制视图本身,即调用onDraw()函数(而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view, 而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了)–> 绘制子视图,即dispatchDraw()函数—> 开始绘制渐变框 --> 绘制滚动条

ViewGroup中的扩展操作
  • 对子视图的measure过程

(1)measureChildren(),内部使用一个for循环对子视图进行遍历,分别调用子视图的measure()方法;

(2)measureChild(),为指定的子视图measure,会被 measureChildren调用;

(3)measureChildWithMargins(),为指定子视图考虑了margin和padding的measure;

以上三个方法是ViewGroup提供的3个对子view进行测量的参考方法,设计者需要在实际中首先覆写onMeasure(),之后再对子view进行遍历measure,这时候就可以使用以上三个方法,当然也可以自定义方法进行遍历。

  • 对子视图的layout过程

在ViewGroup中onLayout()被定义为abstract类型,也就是具体的容器必须实现此方法来安排子视图的布局位置,实现中主要考虑的是视图的大小及视图间的相对位置关系,如gravity、layout_gravity。

  • 对子视图的draw过程

(1)dispatchDraw(), 该方法用于对子视图进行遍历然后分别让子视图分别draw,方法内部会首先处理布局动画(也就是说布局动画是在这里处理的),如果有布局动画则会为每个子 视图产生一个绘制时间,之后再有一个for循环对子视图进行遍历,来调用子视图的draw方法(实际为下边的drawChild());

(2)drawChild(),该方法用于具体调用子视图的draw方法,内部首先会处理视图动画(也就是说视图动画是在这里处理的),之后调用子视图的draw()。

从上面分析可以看出自定义viewGroup的时候需要最少覆写onMeasure()和onLayout()方法,其中onMeasure方法中可以直 接调用measureChildren等已有的方法,而onLayout方法就需要设计者进行完整的定义;一般不需要覆写以dispatchDraw() 和drawChild()这两个方法,因为上面两个方法已经完成了基本的事情。但是可以通过覆写在该基础之上做一些特殊的效果

invalidate()方法

invalidate()方法会导致View树的重新绘制,而且view中的状态标志mPrivateFlags中有一个关于当前视图是否需要重绘的标 志位DRAWN,也就是说只有标志位DRAWN置位的视图才需要进行重绘。当视图调用invalidate()方法时,首先会将当前视图的DRAWN标志 置位,之后有一个循环调用parent.invalidateChildinParent(),这样会导致从当前视图依次向上遍历直到根视图 ViewRoot,这个过程会将需要重绘的视图标记DRAWN置位,之后ViewRoot调用performTraversals()方法,完成视图的绘 制过程。

Android UI中的View如何刷新

android中UI刷新如何实现的?

  • 1.所有View的刷新都通过ViewRootImpl的 performTraversals()来实现测量,布局,绘制的。
  • 2.Choreographer是一个控制frame(帧)渲染信号的一个类。Choreographer方法doFrame(),处理四种类型的UI刷新的Callback有4种类型:Input输入、Animation动画、Traversal绘制和布局,Commit是用来解决延迟信号的处理,即丢帧现象.
  • 3.绘制UI的时候分两种方式,
    硬件加速绘制条件下:RenderNode构建DisplayListCanvas,CPU用于控制复杂绘制逻辑、构建或更新DisplayList;GPU用于完成图形计算、渲染DisplayList。
    软件绘制条件下:Canvas绘制构建Bitmap,CPU用于解码运算;bitmap传到底层,GPU直接渲染。
  • 4.默认是software软件绘制,这种方式渲染效率低,硬件加速绘制优点高,原因是将复杂的图形计算在GPU上实现。避免过度重绘,减少CPU资源和压力。

探讨android更新UI的几种方法

  • 1.利用Looper更新UI界面
  • 2.AsyncTask利用线程任务异步更新UI界面
  • 3.利用Runnable更新UI界面
如何理解Activity,View,Window三者之间的关系

Activity、View、Window的理解一篇文章就够了

  • 1、为什么要设计Activity、View、Window?
  • 2、Activity工作过程是什么样的?(理解Activity)
  • 3、Window是什么?它的职能是什么?
  • 4、View跟Window有什么联系?
  • 5、Activity、View、Window三者如何关联?

Activity包含了一个PhoneWindow,而PhoneWindow就是继承于Window的,Activity通过setContentView将View设置到了PhoneWindow上,而View通过WindowManager的addView()、removeView()、updateViewLayout()对View进行管理。Window的添加过程以及Activity的启动流程都是一次IPC的过程。Activity的启动需要通过AMS完成;Window的添加过程需要通过WindowSession完成。

Activity-Window-View三者的关系

  • Activity是安卓四大组件之一,负责界面展示、用户交互与业务逻辑处理;
  • Window就是负责界面展示以及交互的职能部门,就相当于Activity的下属,Activity的生命周期方法负责业务的处理;
  • View就是放在Window容器的元素,Window是View的载体,View是Window的具体展示。

  1. 在Activity中调用attach,创建了一个Window
  2. 创建的window是其子类PhoneWindow,在attach中创建PhoneWindow
  3. 在Activity中调用setContentView(R.layout.xxx)
  4. 其中实际上是调用的getWindow().setContentView()
  5. 调用PhoneWindow中的setContentView方法
  6. 创建ParentView:
    作为ViewGroup的子类,实际是创建的DecorView(作为FramLayout的子类)
  7. 将指定的R.layout.xxx进行填充
    通过布局填充器进行填充【其中的parent指的就是DecorView】
  8. 调用到ViewGroup
  9. 调用ViewGroup的removeAllView(),先将所有的view移除掉
  10. 添加新的view:addView()

一个Activity构造的时候只能初始化一个Window(PhoneWindow),另外这个PhoneWindow有一个”ViewRoot”,这个”ViewRoot”是一个View活ViewGroup,是最初始的跟视图,然后通过addView方法将View一个个层叠到ViewRoot上,这些层叠的View最终放在Window这个载体上面

算法实现统计出Activity中的view树的深度

Android如何计算View的深度

更具递归来计算深度

private int maxDeep(View view) {
    //当前的view已经是最底层view了,不能往下累加层数了,返回0,代表view下面只有0层了
    if (!(view instanceof ViewGroup)) {
        return 0;
    }
    ViewGroup vp = (ViewGroup) view;
    //虽然是viewgroup,但是如果并没有任何子view,那么也已经是最底层view了,不能往下累加层数了,返回0,代表view下面只有0层了
    if (vp.getChildCount() == 0) {
        return 0;
    }
    //用来记录最大层数
    int max = 0;
    //广度遍历view
    for (int i = 0; i < vp.getChildCount(); i++) {
        //由于vp拥有子view,所以下面还有一层,因为可以+1,来叠加一层,然后再递归几岁算它的子view的层数
        int deep = maxDeep(vp.getChildAt(i)) + 1;
        //比较哪个大就记录哪个
        if (deep > max) {
            max = deep;
        }
    }
    return max;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值