不定期更新一些看源码时候常用的但平时基本用不上的东西
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
已经完成,这里也是可以获取视图尺寸大小的
使用方式
Choreographer choreographer = Choreographer.getInstance();
choreographer.postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
}
});
这个是渲染帧的回调,下一次渲染帧到达时会回调这个方法,只会触发一次。
这里所谓的渲染帧指的是屏幕刷新的时间点,因为有同步机制,屏幕会在每16ms左右进行一次刷新,涉及到CPU和GPU绘制,以及SurfeFlinger的双缓冲技术,有兴趣可以查一下。这里的话就是一下次渲染到达时的回调处理。
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) {
}
})
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
方法
享元对象池,对于需要频繁创建对象的可以用一下,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 ->> 3
E/1234: tr2 ->> 2
E/1234: tr3 ->> 1
E/1234: tr4 ->> -1
SimplePool
和这个是一样的,只是SynchronizedPool
的方法使用了同步加锁
当我们定义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
方法