Android探索之路(四)—View的使用

前言

在这篇文章之前已经总结学习了View的工作流程、事件分发机制。这里总结一下在工作过程中使用View的一些感想,主要从常用的View中的一些方法以及View的一些基础知识两方面来进行介绍。

View的位置参数

在Android系统中存在着坐标系用来确定位置。分为两种:一种是Android坐标系(是整个Android设备的坐标系),还有一种是View的坐标系(是一个视图的坐标系)。这两种坐标系都是以左上角为原点,原点向右是X正半轴,原点向下是Y正半轴。

在这里插入图片描述

如图中所示,我们可以看到获取各个位置的方法。其中getX()、getY()、getRawX()、getRawY()是MotionEvent中的方法,用来获取点击事件的位置信息。getTop()、getLeft()、getRight()、getBottom()是View中的方法,用来获取View在其父容器中的位置信息。

  • getX():获取点击事件到View左边的距离。

  • getY():获取点击事件到View上边的距离。

  • getRawX():获取点击事件距离整个屏幕左边的距离,绝对坐标。

  • getRawY():获取点击事件距离整个屏幕上边的距离,绝对坐标。

  • getTop():获取View顶部到其父容器顶部的距离。

  • getLeft():获取View左边到其父容器左边的距离。

  • getRight():获取View右边到其父容器右边的距离。

  • getBottom():获取View底部到其父容器顶部部的距离。

View的post方法

View中post方法的作用是将一个Runnable加入到UI线程的消息队列中。

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        //已经调用过onAttachedToWindow方法才将Runnable加入到UI线程的消息队列中
        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;
    }

从代码中可以看出,只有View已经attach到Window时才将Runnable加入到UI线程的消息队列中。View的attach window过程发生在View绘制之前。在ViewRootImpl中performTraversals()方法中,在View绘制前调用了View的dispatchAttachToWindow方法。这个方法中设置View中的mAttachInfo,并且会执行没有attach window时Runnable加入的队列。

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info; //赋值mAttachInfo
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
       //......
        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        // Transfer all pending runnables.
        //这里执行了没有attach window时加入到的队列中
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        //调用onAttachedToWindow方法
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }
        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
            if (isShown()) {
                onVisibilityAggregated(vis == VISIBLE);
            }
        }
    }

View的invalidate方法

View的invalidate方法的作用是使整个View无效,然后调用View的onDraw方法,引起View的重绘。

   public void invalidate() {
        invalidate(true);
    }
    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }
    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
      	//......
      	//判断是否跳过,如果View在执行动画或者不可见则跳过
        if (skipInvalidate()) {
            return;
        }

        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN; //这里只添加了绘制标志位
            }

            mPrivateFlags |= PFLAG_DIRTY; //设置dirty标志位

            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent; //获取父容器
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);  //设置重绘区域
                p.invalidateChild(this, damage);   //调用父容器的方法,传递invalidate事件
            }
           //......
        }
    }

View的invalidate方法有多个重载方法,最终会调用invalidateInternal方法。在此方法中会执行一系列的操作。先判断View是否在执行动画或者不可见,如果是则跳过。然后添加绘制标志位,这说明invalidate方法只会引起View的onDraw,不会发起measure以及layout。最后获取父容器,调用父容器的invalidateChild方法。

/**
     * Don't call or override this method. It is used for the implementation of
     * the view hierarchy.
     *
     * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
     * draw state in descendants.
     */
    @Deprecated
    @Override
	public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        //该方法已被启用,如果开启了硬件加速,就调用onDescendantInvalidated
        if (attachInfo != null && attachInfo.mHardwareAccelerated) {
            // HW accelerated fast path
            onDescendantInvalidated(child, child);
            return;
        }

        ViewParent parent = this;
        if (attachInfo != null) {
        	//判断子View是否在进行动画
            final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;

            // Check whether the child that requests the invalidate is fully opaque
            // Views being animated or transformed are not considered opaque because we may
            // be invalidating their old position and need the parent to paint behind them.
            Matrix childMatrix = child.getMatrix();
            final boolean isOpaque = child.isOpaque() && !drawAnimation &&
                    child.getAnimation() == null && childMatrix.isIdentity();
            // Mark the child as dirty, using the appropriate flag
            // Make sure we do not set both flags at the same time
            int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;

            if (child.mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }
            //......
            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }

                if (drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                if (view != null) {
                    if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                            view.getSolidColor() == 0) {
                        opaqueFlag = PFLAG_DIRTY;
                    }
                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                    }
                }

                parent = parent.invalidateChildInParent(location, dirty); //最终parent = ViewRootImpl
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        //确定脏区域
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }

invalidateChild方法最终会回溯到ViewRootImpl中。

@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if (dirty == null) {
        	// 直接调用了 invalidate 方法
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
                dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }
        // 调用了 invalidateRectOnScreen 方法, 刷新区域内的视图
        invalidateRectOnScreen(dirty);

        return null;
    }

    private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;
        if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
            mAttachInfo.mSetIgnoreDirtyState = true;
            mAttachInfo.mIgnoreDirtyState = true;
        }
        //脏区域的并集
        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
      
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            scheduleTraversals(); //这个方法出发View的工作流程
        }
    }

View的postInvalidate方法

从本质上来讲View的postInvalidate方法最终调用了View的Invalidate方法。

	public void postInvalidate() {
        postInvalidateDelayed(0);
    }
    public void postInvalidate(int left, int top, int right, int bottom) {
        postInvalidateDelayed(0, left, top, right, bottom);
    }
    public void postInvalidateDelayed(long delayMilliseconds) {
        // We try only with the AttachInfo because there's no point in invalidating
        // if we are not attached to our window
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
        	//调用ViewRootImpl的dispatchInvalidateDelayed方法
            attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
        }
    }
	public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }
 	@Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case MSG_INVALIDATE:
            // 在 mHandler 绑定的线程中调用了 View 的 invalidate
            ((View) msg.obj).invalidate();
            break;
    }

View的requestLayout方法

View的requestLayout方法则作用是发起View的工作流程。

    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout  == null) {
            ViewRootImpl viewRoot = getViewRootImpl();
            // 判断当前 ViewRootImpl 是否正在 Layout
            if (viewRoot != null && viewRoot.isInLayout()) {// isInLayout() 为 true 的条件是 mInLayout = true
                // 判断当前 View 是否可以在 ViewRootImpl 正在进行 Layout 时, 继续执行发起 requestLayout
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    // 返回 false , 则不允许, 直接让此次 View.requestLayout() 返回
                    return;
                }
            }
            // 将自己的状态标记为正在 requestLayout
            mAttachInfo.mViewRequestingLayout = this;
        }
        
        // 给当前 View 设置标志位
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
        
        // 调用父容器的 requestLayout
        if (mParent != null && !mParent.isLayoutRequested()) { 
            // 此方法最终调用当前 Window 的 ViewRootImpl 中的 requestLayout 中去
            mParent.requestLayout();
        }
    }
    
    boolean requestLayoutDuringLayout(final View view) {
        // 将请求 Layout 的 View 添加到 ViewRootImpl 中维护的集合 mLayoutRequesters 中
        if (!mLayoutRequesters.contains(view)) {
            mLayoutRequesters.add(view);
        }
        if (!mHandlingLayoutInLayoutRequest) {
            // mHandlingLayoutInLayoutRequest 为 false
            // 说明当前 ViewRootImpl 的 performLayout() 没有进行 second layout, 它将会在第二次 layout 中执行
            return true;
        } else {
            // 说明当前 ViewRootImpl 的 performLayout() 正在进行 second layout, 此时 view 的 requestLayout 会被 post 到下一帧
            // return false; 由上面代码可知 View 的 requestLayout() 请求会被直接 return;
            return false;
        }
    }
   
    /**
     * ViewRootImpl.requestLayout
     * View.requestLayout() 最终的走向
     */
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            // 这个标志使View进行measure 和 layout 
            mLayoutRequested = true;
            // 这里开始View的工作流程
            scheduleTraversals();
        }
    }

总结

上面就是在使用View的过程中经常用到的内容,这个方法以及知识点可以帮助我们更加有效的使用View。

  • 从View的位置信息中,可以获取到View的确切位置并进行操作。

  • View的post方法只有当View执行Attach Window时才将任务发送到UI线程。如果没有执行Attach Window,则将任务加入到一个队列管理中,这个队列在dispatchWindow时会再次调用,但不能保证正确执行。

  • invalidate、postInvalidate、requestLayout都会引起View的工作流程。但invalidate、postInvalidate不会进行measure和layout。

  • invalidate和postInvalidate本质是没有区别。postInvalidate可以用于非UI线程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 第1章 Activity的生命周期和启动模式 / 1 1.1 Activity的生命周期全面分析 / 1 1.1.1 典型情况下的生命周期分析 / 2 1.1.2 异常情况下的生命周期分析 / 8 1.2 Activity的启动模式 / 16 1.2.1 Activity的Launch Mode / 16 1.2.2 Activity的Flags / 27 1.3 Intent Filter的匹配规则 / 28 第2章 IPC机制 / 35 2.1 Android IPC简介 / 35 2.2 Android中的多进程模式 / 36 2.2.1 开启多进程模式 / 36 2.2.2 多进程模式的运行机制 / 39 2.3 IPC基础概念介绍 / 42 2.3.1 Serializable接口 / 42 2.3.2 Parcelable接口 / 45 2.3.3 Binder / 47 2.4 Android中的IPC方式 / 61 2.4.1 使用Bundle / 61 2.4.2 使用文件共享 / 62 2.4.3 使用Messenger / 65 2.4.4 使用AIDL / 71 2.4.5 使用 Content Provider / 91 2.4.6 使用Socket / 103 2.5 Binder连接池 / 112 2.6 选用合适的IPC方式 / 121 第3章 View的事件体系 / 122 3.1 View基础知识 / 122 3.1.1 什么是View / 123 3.1.2 View的位置参数 / 123 3.1.3 Motion Event和Touch Slop / 125 3.1.4 VelocityT racker、Gesture Detector和Scroller / 126 3.2 View的滑动 / 129 3.2.1 使用scroll To/scroll By / 129 3.2.2 使用动画 / 131 3.2.3 改变布局参数 / 133 3.2.4 各种滑动方式的对比 / 133 3.3 弹性滑动 / 135 3.3.1 使用Scroller / 136 3.3.2 通过动画 / 138 3.3.3 使用延时策略 / 139 3.4 View的事件分发机制 / 140 3.4.1 点击事件的传递规则 / 140 3.4.2 事件分发的源码解析 / 144 3.5 View的滑动冲突 / 154 3.5.1 常见的滑动冲突场景 / 155 3.5.2 滑动冲突的处理规则 / 156 3.5.3 滑动冲突的解决方式 / 157 第4章 View的工作原理 / 174 4.1 初识View Root和Decor View / 174 4.2 理解Measure Spec / 177 4.2.1 Measure Spec / 177 4.2.2 Measure Spec和Layout Params的对应关系 / 178 4.3 View的工作流程 / 183 4.3.1 measure过程 / 183 4.3.2 layout过程 / 193 4.3.3 draw过程 / 197 4.4 自定义View / 199 4.4.1 自定义View的分类 / 200 4.4.2 自定义View须知 / 201 4.4.3 自定义View示例 / 202 4.4.4 自定义View的思想 / 217 第5章 理解Remote Views / 218 5.1 Remote Views的应用 / 218 5.1.1 Remote Views在通知栏上的应用 / 219 5.1.2 Remote Views在桌面小部件上的应用 / 221 5.1.3 Pending Intent概述 / 228 5.2 Remote Views的内部机制 / 230 5.3 Remote Views的意义 / 239 第6章 Android的Drawable / 243 6.1 Drawable简介 / 243 6.2 Drawable的分类 / 244 6.2.1 Bitmap Drawable / 244 6.2.2 Shape Drawable / 247 6.2.3 Layer Drawable / 251 6.2.4 State List Drawable / 253 6.2.5 Level List Drawable / 255 6.2.6 Transition Drawable / 256 6.2.7 Inset Drawable / 257 6.2.8 Scale Drawable / 258 6.2.9 Clip Drawable / 260 6.3 自定义Drawable / 262 第7章 Android动画深入分析 / 265 7.1 View动画 / 265 7.1.1 View动画的种类 / 265 7.1.2 自定义View动画 / 270 7.1.3 帧动画 / 272 7.2 View动画的特殊使用场景 / 273 7.2.1 LayoutAnimation / 273 7.2.2 Activity的切换效果 / 275 7.3 属性动画 / 276 7.3.1 使用属性动画 / 276 7.3.2 理解插值器和估值器 / 280 7.3.3 属性动画的监听器 / 282 7.3.4 对任意属性做动画 / 282 7.3.5 属性动画的工作原理 / 288 7.4 使用动画的注意事项 / 292 第8章 理解Window和Window Manager / 294 8.1 Window和Window Manager / 294 8.2 Window的内部机制 / 297 8.2.1 Window的添加过程 / 298 8.2.2 Window的删除过程 / 301 8.2.3 Window的更新过程 / 303 8.3 Window的创建过程 / 304 8.3.1 Activity的Window创建过程 / 304 8.3.2 Dialog的Window创建过程 / 308 8.3.3 Toast的Window创建过程 / 311 第9章 大组件的工作过程 / 316 9.1 大组件的运行状态 / 316 9.2 Activity的工作过程 / 318 9.3 Service的工作过程 / 336 9.3.1 Service的启动过程 / 336 9.3.2 Service的绑定过程 / 344 9.4 Broadcast Receiver的工作过程 / 352 9.4.1 广播的注册过程 / 353 9.4.2 广播的发送和接收过程 / 356 9.5 Content Provider的工作过程 / 362 第10章 Android的消息机制 / 372 10.1 Android的消息机制概述 / 373 10.2 Android的消息机制分析 / 375 10.2.1 Thread Local的工作原理 / 375 10.2.2 消息队列的工作原理 / 380 10.2.3 Looper的工作原理 / 383 10.2.4 Handler的工作原理 / 385 10.3 主线程的消息循环 / 389 第11章 Android的线程和线程池 / 391 11.1 主线程和子线程 / 392 11.2 Android中的线程形态 / 392 11.2.1 Async Task / 392 11.2.2 Async Task的工作原理 / 395 11.2.3 Handler Thread / 402 11.2.4 Intent Service / 403 11.3 Android中的线程池 / 406 11.3.1 Thread Pool Executor / 407 11.3.2 线程池的分类 / 410 第12章 Bitmap的加载和Cache / 413 12.1 Bitmap的高效加载 / 414 12.2 Android中的缓存策略 / 417 12.2.1 Lru Cache / 418 12.2.2 Disk Lru Cache / 419 12.2.3 Image Loader的实现 / 424 12.3 Image Loader的使用 / 441 12.3.1 照片墙效果 / 441 12.3.2 优化列表的卡顿现象 / 446 第13章 综合技术 / 448 13.1 使用Crash Handler来获取应用的crash信息 / 449 13.2 使用multidex来解决方法数越界 / 455 13.3 Android的动态加载技术 / 463 13.4 反编译初步 / 469 13.4.1 使用dex2jar和jd—gui反编译apk / 470 13.4.2 使用apktool对apk进行二次打包 / 470 第14章 JNI和NDK编程 / 473 14.1 JNI的开发流程 / 474 14.2 NDK的开发流程 / 478 14.3 JNI的数据类型和类型签名 / 484 14.4 JNI调用Java方法的流程 / 486 第15章 Android性能优化 / 489 15.1 Android的性能优化方法 / 490 15.1.1 布局优化 / 490 15.1.2 绘制优化 / 493 15.1.3 内存泄露优化 / 493 15.1.4 响应速度优化和ANR日志分析 / 496 15.1.5 List View和Bitmap优化 / 501 15.1.6 线程优化 / 501 15.1.7 一些性能优化建议 / 501 15.2 内存泄露分析之MAT工具 / 502 15.3 提高程序的可维护性 / 506

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值