View相关面试题

merge和viewstub使用区别?

merge标签作为跟标签,不会增加view嵌套层数,一般与include一起使用
ViewStub是一个不可见的,实际上是把宽高设置为0的View。ViewStub 标签最大的优点是当你需要时才会加载。
inflate()方法只能调用一次。

如何将一个Activity设置成窗口的样式。

中配置:android :theme="@android:style/Theme.Dialog"

View与SurfaceView、Textureview的区别:

surfaceView 是在一个新起的单独线程中可以重新绘制画面而 View 必须在 UI 的主线程中更新画面 ,SurfaceView类就是双缓冲机制。
Surfaceview是view子类,可用于视频播放,摄像头预览等;
SurfaceView不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换;

SurfaceView双缓冲:
双缓冲:在运用时可以理解为:SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。例如,如果你已经先后两次绘制了视图A和B,那么你再调用lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你将重绘的C视图上传,那么C将取代B作为新的frontCanvas显示在SurfaceView上,原来的B则转换为backCanvas。

Textureview与SurfaceView不同,它不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。

参考:https://blog.csdn.net/while0/article/details/81481771

子view比父view大时,怎么让子view能超出父view?
在父布局中,加上android:clipChildren="false"这个属性,不限制子view大小,默认为true。

canvas可以画Bitmap么?
Canvas的drawBitmap有两个构造方法
(1) public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
第一个参数为要绘制的bitmap对象,第二个参数为图片左上角的x坐标值,第三个参数为图片左上角的y坐标的值,第三个参数为Paint对象。

(2) public void drawBitmap(Bitmap bitmap, Rect src, RectF dst,Paint paint)
第一个参数为要绘制的bitmap对象,第二个参数为要绘制的Bitmap对象的矩形区域,第三个参数为要将bitmap绘制在屏幕的什么地方,第四个参数为Paint对象。

invalidate()、requestLayout() 区别

★ requestLayout()方法请求重新布局,会调用measure过程和layout过程,但不会调用draw过程,也不会重新绘制任何View包括该调用者本身。
★ invalidate()系列方法请求重绘视图(View树),如果View大小没有发生变化就不会调用measure和layout过程,相反,View的大小发生改变了就会调用measure和layout的过程,并且哪个View请求invalidate()系列方法,就绘制该View。

结论:
requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用;invalidate方法则只会导致View的onDraw方法被调用。

在View的requestLayout方法中,首先会设置View的标记位,PFLAG_FORCE_LAYOUT表示当前View要进行重新布局,PFLAG_INVALIDATED表示要进行重新绘制。invalidate方法没有标记PFLAG_FORCE_LAYOUT,所以不会执行测量和布局流程,而只是对需要重绘的View进行重绘,也就是只会调用onDraw方法。

requestLayout方法中会一层层向上调用父布局的requestLayout方法,最终调用的是ViewRootImpl中的requestLayout方法。

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

scheduleTraversals刷新view,会向messageQueue中发出同步屏障,优先去执行view刷新。scheduleTraversals方法最后会调用performTraversals方法开始执行View的三大流程,会分别调用View的measure、layout、draw方法。

参考:
https://www.cnblogs.com/normalandy/p/12408665.html

为什么系统不建议在子线程访问UI

UI控件不是线程安全的,如果多线程并发访问UI控件可能会出现不可预期的状态。
那为什么系统不对UI控件的访问加上锁机制呢?
缺点有两个:
加上锁机制会让UI访问的逻辑变复杂;
锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行;
将于这两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作。

  • 今日头条的轻量级适配方案了解吗,说说原理

主流的屏幕适配方案:
1.smallestWidth适配:
指的是Android会识别屏幕可用高度和宽度的最小尺寸的dp值,然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件,如果没有找到对应尺寸,就寻找最近的。
2.AndroidAutoLayout
3.今日头条适配方案
如果每个 View 的 dp 值是固定不变的,那我们只要保证每个设备的屏幕总 dp 宽度不变,就能保证每个 View 在所有分辨率的屏幕上与屏幕的比例都保持不变,从而完成等比例适配,并且这个屏幕总 dp 宽度如果还能保证和设计图的宽度一致的话,那我们在布局时就可以直接按照设计图上的尺寸填写 dp 值

屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度

在这个公式中我们要保证 屏幕的总 dp 宽度 和 设计图总宽度 一致,并且在所有分辨率的屏幕上都保持不变,我们需要怎么做呢?屏幕的总 px 宽度 每个设备都不一致,这个值是肯定会变化的,这时今日头条的公式就派上用场了。

当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density

这个公式就是把上面公式中的 屏幕的总 dp 宽度 换成 设计图总宽度,原理都是一样的,只要 density 根据不同的设备进行实时计算并作出改变,就能保证 设计图总宽度 不变,也就完成了适配。

示例1:

屏幕总宽度为 1080 px,根据今日头条的的公式求出 density,1080 / 375 = 2.88 (density)
这个 50dp * 50dp 的 View,系统最后会将高宽都换算成 px,50dp * 2.88 = 144 px (根据公式 dp * density = px)
144 / 1080 = 0.133,View 实际宽度与 屏幕总宽度 的比例和 View 在设计图中的比例一致 (50 / 375 = 0.133),所以完成了等比例缩放。

示例2:

屏幕总宽度为 1440 px,根据今日头条的的公式求出 density,1440 / 375 = 3.84 (density)
这个 50dp * 50dp 的 View,系统最后会将高宽都换算成 px,50dp * 3.84 = 192 px (根据公式 dp * density = px)
192 / 1440 = 0.133,View 实际宽度与 屏幕总宽度 的比例和 View 在设计图中的比例一致 (50 / 375 = 0.133),所以也完成了等比例缩放。

AndroidAutoSize头条适配方案库
参考文章

Bitmap Drawable View 三者之间的联系和区别

bitmap: 仅仅就是一个位图 你可以理解为一张图片在内存中的映射。 就这么简单。这个很多人都知道
view: 这个就是android的核心了,你看到的一切东西都是view 这个很多人也知道。 但是这个理解成都还不够,view最大的作用是2个 一个是draw 也就是canvas的draw方法,还有一个作用 就是测量大小。 要想明白这点。
drawable: 他其实本身和bitmap没有关系, 你可以把他理解为是一个绘制工具,和view的第一个作用是一摸一样的,你能用view的canvas 画出来的东西 你用drawable 一样可以画出来, 不一样的是drawable 仅仅能绘制,但是不能测量自己的大小,但是view可以。换句话说 drawable 承担了view的一半作用。

  • 子线程如何更新Ui

在Activity的onCreate方法中写入代码,在子线程中操作view能正常执行起来。

        Thread {
            textview.text = "子线程中访问"
        }.start()

但是一旦改为如下,在onCreate中耗时200ms,就会出现异常:

        Thread {
            Thread.sleep(200)
            textview.text = "子线程中访问"
        }.start()

Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8509)

报错检测来自ViewRootIml类,当在任何要操作view的地方,都是先调用checkThread方法,检验当前线程是否为主线程。

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

那么onCreate中的子线程中为何不报错,解释就是执行onCreate方法的那个时候ViewRootImpl还没创建,无法去检查当前线程。深入源码查探,ViewRootImpl是在WindowManagerGlobal的addView方法中创建的。

总结:
ViewRootImpl的创建在onResume方法回调之后,而我们一开篇是在onCreate方法中创建了子线程并访问UI,在那个时刻,ViewRootImpl是没有创建的,无法检测当前线程是否是UI线程,所以程序没有崩溃一样能跑起来,而之后修改了程序,让线程休眠了200毫秒后,程序就崩了。很明显200毫秒后ViewRootImpl已经创建了,可以执行checkThread方法检查当前线程。所以在onResume执行之前,都可以进行子线程更新UI。

参考:Android中子线程真的不能更新UI吗?

Canvas.save()跟 Canvas.restore()的调用时机

save:用来保存 Canvas 的状态。save 之后,可以调用 Canvas 的平移、放缩、
旋转、错切、裁剪等操作。
restore:用来恢复 Canvas 之前保存的状态。防止 save 后对 Canvas 执行的操作
对后续的绘制有影响。
save 和 restore 要配对使用(restore 可以比 save 少,但不能多),如果 restore
调用次数比 save 多,会引发 Error。save 和 restore 操作执行的时机不同,就能
造成绘制的图形不同。

在 Activity 中获取某个 View 的宽高

由于View的measure过程和Activity的生命周期方法不是同步执行的,如果View
还没有测量完毕,那么获得的宽/高就是 0。所以在 onCreate、onStart、onResume
中均无法正确得到某个 View 的宽高信息。解决方式如下:
1.Activity/View#onWindowFocusChanged:此时 View 已经初始化完毕,
当 Activity 的窗口得到焦点和失去焦点时均会被调用一次,如果频繁地进
行 onResume 和 onPause,那么 onWindowFocusChanged 也会被频繁地
调用。
2.view.post(runnable): 通过 post 可以将一个 runnable 投递到消息队列的
尾部,初始化好了然后等待 Looper 调用 runnable 的时候,View 也已经
初始化好了。
3.mViewTreeObserver.addOnGlobalLayoutListener:当 View 树的状态发生
改变或者 View 树内部的 View 的可见性发生改变时,onGlobalLayout 方
法将被回调。

  • 折叠屏如何适配?

所以折叠屏适配的本质是: 当应用运行时,切换大小屏,屏幕的尺寸、密度或比例发生了变化,应用能够继续在变化后的屏幕上正常显示和正常运行。

要适配折叠屏,首先是要让应用支持动态改变尺寸,我们需要在 menifest 中的 Application 或对应的 Activity 下声明:

android:resizeableActivity="true"

当 resizeableActivity 取 false 时,展开折叠屏可能会变成这样的效果:

image

在默认情况下,当屏幕发生了变化,系统会销毁并重新创建整个 Activity。但我们希望屏幕变化之后,程序能够以切换前的状态继续运行,不需要重启页面。
我们可以给 Activity 添加配置:

android:configChanges="screenSize|smallestScreenSize|screenLayout"

在 Android Studio 3.5 里增加了折叠屏设备的虚拟机,我们可以创建一个来调试。

参考:
http://www.xujingzi.com/nav/m/86604.html

px sp dp 三者是什么

dp: Density-independent Pixels, 这个是Android基于物理设备的ppi抽象出来的一个单位。它是以160dpi的屏幕为基准定义的,在160dpi的屏幕的屏幕上1dp=1px

sp: Scale-independent Pixels,其与dp基本一样,也是像素无关的,但是是用在描述字体的大小上。其尺寸会同时相应屏幕密度以及用户对字体的偏好设置。

当我们改变了手机的字体默认设置的字号后,dp描述的字体大小没有变化,但是sp描述的字体大小却相应的发生了变化,除此之外dp与sp再无差异,一般建议字体使用sp作为单位。

参考:
https://zhuanlan.zhihu.com/p/58092930

怎么监听一个view被引入布局使用了

若能实现就极大化简了上层可见性检测的复杂度,只需要如下代码就能实现任意控件的曝光上报埋点:

view.onVisibilityChange { view, isVisible ->
    if(isVisible) { // 曝光埋点 }
    else {}
}

1.捕获全局重绘时机:

public final class ViewTreeObserver {
    public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
        checkIsAlive();
        if (mOnGlobalLayoutListeners == null) {
            mOnGlobalLayoutListeners = new CopyOnWriteArray();
        }
        mOnGlobalLayoutListeners.add(listener);
    }
}

  1. onAttachedToWindow 与 onDetachedFromWindow
    View 被添加到窗体和从窗体中被移除的监听很好实现,只需要继承父 View 重写 onAttachedToWindow 和 onDetachedFromWindow,然后增加自己的监听逻辑即可。实现方式大概像这样:
public class CustomView extends FrameLayout {
    ......
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // TODO 处理监听逻辑
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // TODO 处理监听逻辑
    }

参考:
https://juejin.cn/post/7165427955902971918

滑出子View范围会发生什么?滑回来还能触发click事件吗

问题1、ACTION_CANCEL的触发时机
有四种情况会触发ACTION_CANCEL:

  • 在子View处理事件的过程中,父View对事件拦截
  • ACTION_DOWN初始化操作
  • 在子View处理事件的过程中被从父View中移除时
  • 子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时

问题2、滑出子View为什么不响应onClick()事件。

case MotionEvent.ACTION_MOVE:
    // Be lenient about moving outside of buttons
    // 判断是否超出view的边界
    if (!pointInView(x, y, mTouchSlop)) {
        // Outside button
        if ((mPrivateFlags & PRESSED) != 0) {
            // 这里改变状态为 not PRESSED
            // Need to switch from pressed to not pressed
            mPrivateFlags &= ~PRESSED;
        }
    }
    break;

case MotionEvent.ACTION_UP:
    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
    // 可以看到当move出view范围后,这里走不进去了
    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
        ...
        performClick();
        ...
    }
    mIgnoreNextUpEvent = false;
    break;

1,在ACTION_MOVE中会判断事件的位置是否超出view的边界,如果超出边界则将mPrivateFlags置为not PRESSED状态。
2,在ACTION_UP中判断只有当mPrivateFlags包含PRESSED状态时才会执行performClick()等。
因此滑出view后不会执行onClick()。

参考:
https://juejin.cn/post/7004794729237856287

你可能感兴趣的:(View相关面试题)