Android那些“没用“知识(一)

不定期更新一些看源码时候常用的但平时基本用不上的东西

1. addOnPreDrawListener

addOnPreDrawListener方法是在View绘制前回调的方法,在CoordinatorLayoutbehavioronDependentViewChanged的调用场景之一会在这里

 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前触发的,前面的performMeasureperformLayout已经完成,这里也是可以获取视图尺寸大小的

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添加和删除的监听方法,比如会在addViewremoveView方法中回调这个监听

 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方法依次遍历DecorViewView,给设置过这个属性的子View设置padding,比如这里测试机返回的系统限制getSystemWindowInsets()的四个区域是(0,50,0,0),而这里的50也正是状态栏高度的大小,如果有导航栏则bottom的数值也会有变动。

一般多个View都设置的话会以第一个为准生效,这里的insets.consumeSystemWindowInsets()表示消耗掉这个WindowInsets,这个也是默认方法,可以选择不消耗,那么这个WindowInsets就会交付给下一个查找到的有fitSystemWindows的属性的View处理

注意这个返回的getSystemWindowInsets的各个属性都是final类型的,不能修改

可以使用ViewCompat.requestApplyInsets(View)触发一次检测,这个其实最终就会调用到ViewRootImpscheduleTraversals方法,重新触发测量布局绘制三大流程,只是这里把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 ->> 3
E/1234: tr2 ->> 2
E/1234: tr3 ->> 1
E/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方法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值