Android图形系统的绘制创建和交互响应

一. View真实大小的确定在layout之后,draw之前
The size of a view is expressed with a width and a height. A view actually possess two pairs of width and height values.

The first pair is known as measured width and measured height. These dimensions define how big a view wants to be within its parent (see Layout for more details.) The measured dimensions can be obtained by calling getMeasuredWidth() and getMeasuredHeight().

The second pair is simply known as width and height, or sometimes drawing width and drawing height. These dimensions define the actual size of the view on screen, at drawing time and after layout.[注意]

参考:
1.https://developer.android.com/reference/android/view/View#size,-padding-and-margins

二.通过View.post()获取View的宽高是准确的吗?

1.view中的post方法

     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     * @see #postDelayed
     * @see #removeCallbacks
     */
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

方法中第一步如果attachInfo不为空的情况下会将Runnable通过handler发送到主线程的消息队列.
第二步如果attachInfo为空会将Runnable放到队列中

2.mAttachInfo什么时候被赋值?
在View的dispatchAttachedToWindow(AttachInfo info, int visibility)方法中

/**
     * @param info the {@link android.view.View.AttachInfo} to associated with
     *        this view
     */
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ......
    }

3.dispatchAttachedToWindow(AttachInfo info, int visibility) 方法在什么时候调用?
在ViewRootImpl的 performTraversals()方法中;而performTraversal()的作用就是遍历整个
View树,并且按照要求进行measure,layout和draw流程.

 private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
  //判断是不是第一次
  if (mFirst) {
       .....
      //这里调用了 dispatchAttachedToWindow,并且把 mAttachInfo 给子view
       host.dispatchAttachedToWindow(mAttachInfo, 0);
       mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
       dispatchApplyInsets(host);
     //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
      .....
} 
   mFirst=false
    ...
  // Execute enqueued actions on every traversal in case a detached view enqueued an action
    getRunQueue().executeActions(mAttachInfo.mHandler);
    ...
    performMeasure();
    ...
    performLayout();
    ...
    performDraw();


4.那么ViewRootImpl中的mAttachInfo是在什么时候被赋值的?
在构造方法中

public ViewRootImpl(Context context, Display display) {
   ...
   mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);
   

接着去看mHandler在什么时候被赋值

final ViewRootHandler mHandler = new ViewRootHandler();

这个是一个无参数构造函数,默认绑定的是当前线程的Looper;即当前主线程的Looper.

5.综合上述可以看出View的post方法的第一步mAttachInfo != null 的时候,会将Runnable通过主线程的Handler,在执行ViewRootImpl的performTraversals()过程中将Runnable发送到主线程的消息队列中
(这样子Runnable就是在measure,layout,draw之后的一个消息)

6.当View的post方法中mAttachInfo == null的时候,会通过getRunQueue()返回HandlerActionQueue
对象;该类中:

/**
 * Class used to enqueue pending work from Views when no Handler is attached.
 *
 * @hide Exposed for test framework only.
 */
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];  //将runnable放到大小为4的数组
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }
    ......

   //  该方法的调用会在ViewRootImpl的performTraversals()方法中通过
   // Execute enqueued actions on every traversal in case a detached view enqueued an action
   // getRunQueue().executeActions(mAttachInfo.mHandler);
   //  进行调用
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }
    

综上可得mAttachInfo == null的时候;runnable会先入大小为4的数组(当数量大于四的时候如何操作? 如果数组不够时,就会通过 GrowingArrayUtils 来扩充数组);然后等待进行performTraverse的时候调用.

参考:
1.通过View.post()获取View的宽高引发的两个问题:1post的Runnable何时被执行,2为何View需要layout两次;以及发现Android的一个小bug
https://blog.csdn.net/scnuxisan225/article/details/49815269
2.View.post为什么可以拿到View的宽高?
https://juejin.im/post/5cd903af6fb9a0323a01d3f5
3.View.Post () 的身世大揭秘
https://juejin.im/post/5b6104255188251b1a7b4f84
4.View的post方法执行的时机
https://blog.csdn.net/Small_Lee/article/details/79424093

三.在view.post方法中设置view的padding后如何生效?

1.layout 方法大致流程首先会通过 setFrame 方法来设定 View 四个顶点位置,View 的四个顶点一旦确认了那么就会接着调用 onLayout 方法,这个方法的用途是父容器确定子元素的位置。

2.通过我们在 draw 中减去了 各自的 padding 解决了 padding 的问题

     /**
     * 解决 padding
     */
    override fun draw(canvas: Canvas) {
        super.draw(canvas)

        val paddingLeft = paddingLeft
        val paddingRight = paddingRight
        val paddingBottom = paddingBottom
        val paddingTop = paddingTop
        val height = height - paddingBottom - paddingTop
        val width = width - paddingLeft - paddingRight
        val radius = Math.min(width, height) / 2f

        canvas.drawCircle(paddingLeft + width/2f,paddingTop + height/2f,radius,paint)


    }


参考:
1.高级 UI 成长之路 (三) 理解 View 工作原理并带你入自定义 View 门
https://juejin.im/post/5ddff234518825793218d2e4

四.一次触摸,Android 到底干了啥

参考:
1.一次触摸,Android 到底干了啥
https://zhuanlan.zhihu.com/p/31210271
https://wetest.qq.com/lab/view/349.html?from=content_qcloud

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值