Android那些“没用“知识(一)
目录
- 1. addOnPreDrawListener
- 2. postFrameCallback
- 3. setOnHierarchyChangeListener
- 4. setOnApplyWindowInsetsListener
- 5. SynchronizedPool
- 6. setWillNotDraw
不定期更新一些看源码时候常用的但平时基本用不上的东西
1. addOnPreDrawListener
addOnPreDrawListener
方法是在View绘制前回调的方法,在CoordinatorLayout
的behavior
的onDependentViewChanged
的调用场景之一会在这里
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; if (!cancelDraw) { ... performDraw(); } else { if (isViewVisible) { scheduleTraversals(); } ... } public final boolean dispatchOnPreDraw() { boolean cancelDraw = false; final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners; if (listeners != null && listeners.size() > 0) { CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start(); try { int count = access.size(); for (int i = 0; i < count; i++) { cancelDraw |= !(access.get(i).onPreDraw()); } } finally { listeners.end(); } } return cancelDraw; }
使用方式是
View view;view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { return false; } });
注意因为是在performDraw
前触发的,前面的performMeasure
和performLayout
已经完成,这里也是可以获取视图尺寸大小的
2. postFrameCallback
使用方式
Choreographer choreographer = Choreographer.getInstance(); choreographer.postFrameCallback(new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { } });
这个是渲染帧的回调,下一次渲染帧到达时会回调这个方法,只会触发一次。
这里所谓的渲染帧指的是屏幕刷新的时间点,因为有同步机制,屏幕会在每16ms左右进行一次刷新,涉及到CPU和GPU绘制,以及SurfeFlinger的双缓冲技术,有兴趣可以查一下。这里的话就是一下次渲染到达时的回调处理。
3. setOnHierarchyChangeListener
ViewGroup的子View添加和删除的监听方法,比如会在addView
和removeView
方法中回调这个监听
ViewGroup view;view.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { @Override public void onChildViewAdded(View parent, View child) { } @Override public void onChildViewRemoved(View parent, View child) { } })
4. setOnApplyWindowInsetsListener
ImageView imageView;imageView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { return insets.consumeSystemWindowInsets(); } });
setOnApplyWindowInsetsListener
方法回传的是window的内容区域限制,只有当设置了fitsSystemWindows
才会生效。
定义了fitSystemWindows
后,会从ViewRootImp
出发,调用dispatchApplyInsets
方法依次遍历DecorView
子View
,给设置过这个属性的子View设置padding
,比如这里测试机返回的系统限制getSystemWindowInsets()
的四个区域是(0,50,0,0),而这里的50也正是状态栏
高度的大小,如果有导航栏则bottom的数值也会有变动。
一般多个View都设置的话会以第一个为准生效,这里的insets.consumeSystemWindowInsets()
表示消耗掉这个WindowInsets
,这个也是默认方法,可以选择不消耗,那么这个WindowInsets
就会交付给下一个查找到的有fitSystemWindows
的属性的View处理
注意这个返回的getSystemWindowInsets
的各个属性都是final
类型的,不能修改
可以使用ViewCompat.requestApplyInsets(View)
触发一次检测,这个其实最终就会调用到ViewRootImp
的scheduleTraversals
方法,重新触发测量布局绘制三大流程,只是这里把mApplyInsetsRequested
设置成了true
,可以触发dispatchApplyInsets
方法
5. SynchronizedPool
享元对象池,对于需要频繁创建对象的可以用一下,acquire
去获取池中的对象,release
去存一个新的对象。
Pools.Pool<TestJava> sPool = new Pools.SynchronizedPool<>(3); String TAG = "1234"; TestJava t1 = new TestJava("1"); TestJava t2 = new TestJava("2"); TestJava t3 = new TestJava("3"); sPool.release(t1); sPool.release(t2); sPool.release(t3); TestJava tr1 = sPool.acquire(); Log.e(TAG, "tr1 ->> " + (tr1 != null ? tr1.id : -1)); TestJava tr2 = sPool.acquire(); Log.e(TAG, "tr2 ->> " + (tr2 != null ? tr2.id : -1)); TestJava tr3 = sPool.acquire(); Log.e(TAG, "tr3 ->> " + (tr3 != null ? tr3.id : -1)); TestJava tr4 = sPool.acquire(); Log.e(TAG, "tr4 ->> " + (tr4 != null ? tr4.id : -1));
这里的acquire
方法会把对象从池中移除,当用完需要把这个对象release
再放进去
打印结果是
E/1234: tr1 ->> 3E/1234: tr2 ->> 2E/1234: tr3 ->> 1E/1234: tr4 ->> -1
SimplePool
和这个是一样的,只是SynchronizedPool
的方法使用了同步加锁
6. setWillNotDraw
当我们定义ViewGroup
的子类时,默认是不会走onDraw
绘制方法的,因为在ViewGroup
中有这个标记
private void initViewGroup() { if (!debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); } ......}
而且在View
中
void setFlags(int flags, int mask) {...... if ((changed & DRAW_MASK) != 0) { if ((mViewFlags & WILL_NOT_DRAW) != 0) { if (mBackground != null || mDefaultFocusHighlight != null || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } else { mPrivateFlags |= PFLAG_SKIP_DRAW; } } else { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } } //这个代码块有好几处调用if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); ...... } else { draw(canvas); } public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
在draw
方法中会触发onDraw
,也会触发dispatchDraw
方法;而dispatchDraw
是完全给子类重写的,这应该是性能上的考虑,避免不必要的绘制
这么一来,因为ViewGroup
默认设置了WILL_NOT_DRAW
标记,导致大部分情况下是不会走onDraw
方法的
而setWillNotDraw(false)
可以清除这个标记,回调onDraw
方法
更多相关文章
- 在android中创建包含对象数组对象List 的Parcelable
- android EditText 不自动弹出键盘的方法
- android解析二维数组对象key:value
- Android Studio ADB响应失败解决方法
- Unity 调用 Android Native 方法(一) 获得Android系统音量
- Mac 下面,添加android adb命令(一般环境变量的添加方法)
- Android软键盘挡住输入框的问题及解决方法