Android View 绘制刷新流程分析

摘要:
在Android中有很多方法可以更新View,在使用它时应该区分不同的应用程序。强制访问将报告:android。看法ViewRoot$CalledFromWrongThreadException:仅创建视图层次结构的原始线程访问视图。此时,您需要创建继承android.os的子类。处理程序并重写handleMessage方法。android.os。处理程序可以发送和处理消息。您需要发送一条消息来更新“活动”中的UI,然后在处理程序中处理该消息。自从SurfaceHolder。回调接口已实现,新线程不需要android.os.Handler的帮助。

Android中对View的更新有很多种方式,使用时要区分不同的应用场合。
1.不使用多线程和双缓冲
这种情况最简单,一般只是希望在View发生改变时对UI进行重绘。你只需显式地调用View对象中的invalidate(){关于invalidate的解释:当调用线程处于空闲状态时,会调用onDraw,刷新界面,也就是说,该函数仅是标记当前界面过期,并不直接负责刷新界面;}方法即可。系统会自动调用View的onDraw()方法。
2.使用多线程但不使用双缓冲
这种情况需要开启新的线程,新开的线程就不好访问View对象了。强行访问的话会报:android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
这时候你需要创建一个继承了android.os.Handler的子类,并重写handleMessage(Message msg)方法。android.os.Handler是能发送和处理消息的,你需要在Activity中发出更新UI的消息,然后在Handler(可以使用匿名内部类)中处理消息(因为匿名内部类可以访问父类变量, 你可以直接调用View对象中的invalidate()方法 )。也就是说:在新线程创建并发送一个Message,然后再主线程中捕获、处理该消息。
3.使用多线程和双缓冲
Android中SurfaceView是View的子类,她同时也实现了双缓冲。可以定义一个她的子类并实现SurfaceHolder.Callback接口。由于实现SurfaceHolder.Callback接口,新线程就不需要android.os.Handler帮忙了。SurfaceHolder中lockCanvas()方法可以锁定画布,绘制完新的图像后调用unlockCanvasAndPost(canvas)解锁(显示)
先看看源代码对SurfaceHolder接口的描述

/*** 允许你控制surface view的大小、样式,编辑像素或监视surface的改变,典型的运用于SurfaceView中,需要注意
* lockCanvas方法和Callback.surfaceCreated方法
*/

再看SurfaceHolder.Callback的描述

    /*** A client may implement this interface to receive information about
     * changes to the surface.  When used with a {@linkSurfaceView}, the
     * Surface being held is only available between calls to
     * {@link#surfaceCreated(SurfaceHolder)} and
     * {@link#surfaceDestroyed(SurfaceHolder)}.  The Callback is set with
     * {@linkSurfaceHolder#addCallback SurfaceHolder.addCallback} method.
     */

下面是一个继承自SurfaceView并实现SurfaceHolder.Callback接口的类

public class MySurfaceView extends SurfaceView implementsSurfaceHolder.Callback {
    privateSurfaceHolder holder;

    publicMySurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    publicMySurfaceView(Context context) {
        super(context);
        holder = this.getHolder();
        holder.addCallback(this);
        this.setLongClickable(true);//不设置将无法捕捉onFling()事件
        setFocusable(true);//设置键盘焦点
        setFocusableInTouchMode(true);//设置触摸屏焦点
}

    protected voidpaintView(Canvas canvas) { // 自定义方法,类似于onDraw
    }public voidrePaint() { // 自定义类似于invalidate方法,调用此方法刷新View
        Canvas c = null;
        try{
            c =holder.lockCanvas();
            paintView(c);
        } finally{
            if (c != null) {
                holder.unlockCanvasAndPost(c);
            }
        }
    }

    @Override
    public voidsurfaceCreated(SurfaceHolder holder) {
        Canvas canvas = holder.lockCanvas(null);//获取画布
        canvas.drawColor(Color.WHITE);//设置画布背景
        holder.unlockCanvasAndPost(canvas);//解锁画布,提交画好的图像
}

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, intwidth,
            intheight) {
    }

    @Override
    public voidsurfaceDestroyed(SurfaceHolder holder) {
    }
}

------------------------------------------------------View的绘制流程-----------------------------------------------------

View的绘制绘制流程:父View负责刷新、布局、显示子View;而当子View需要刷新时,则是通知父View来完成。下面通过查看原代码来验证
1.子类调用invalidate方法()

    /*** 使当前View无效. 如果View可见,onDraw方法将会在之后某个时间点被调用,这个方法的调用必须在UI线程中,如果在非UI线程中调用需要使用postInvalidate()方法*/
    public voidinvalidate() {
        invalidate(true);
    }

    /*** invalidate实际上是调用这个方法.drawing的缓存被设置为无效之后一个完整的invalidate将会发生.但是这个功能可以通过设置invalidateCachefalse来跳过无效的步骤当并不需要重新绘制View的时候(例如,一个组件保持着同样的尺寸和内容)
     * @paraminvalidateCache 这个View的缓存是否应该被设置为无效,通常是false表示要进行全部绘制,但是可能设置为true当View的Content和dimension都没有改变时.
     */
    void invalidate(booleaninvalidateCache) {
        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 || isOpaque() !=mLastIsOpaque) {
            // ......final AttachInfo ai =mAttachInfo;// 获取匹配
            final ViewParent p =mParent; // 获取父类对象
            // noinspection PointlessBooleanExpression,ConstantConditions
            if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
                if (p != null && ai != null &&ai.mHardwareAccelerated) {
                    p.invalidateChild(this, null);
                    return;
                }
            }

            if (p != null && ai != null) {
                final Rect r =ai.mTmpInvalRect;
                r.set(0, 0, mRight - mLeft, mBottom -mTop); // 设置View的尺寸
                p.invalidateChild(this, r); // 调用parent对象让parent对象重绘制child
            }
        }
    }

>>2.child View调用invalidate时,首先找到自己父View(View的成员变量mParent记录自己的父View),然后将AttachInfo中保存的信息告诉父View刷新自己,父View调用invalidateChild函数刷新child View
下面查看ViewGroup中的invalidateChild方法的实现

    /*** 不要调用或重写此方法,这个方法是用于实现View的绘制层次
     */
    public final void invalidateChild(View child, finalRect dirty) {
        ViewParent parent = this;

        final AttachInfo attachInfo =mAttachInfo;
        if (attachInfo != null) {
            //如果child view绘制的是动画,我们希望child的mPrivateFlags拷贝到ViewGroup之上
            //并且让parent确保无效的请求通过
            final boolean drawAnimation = (child.mPrivateFlags &PFLAG_DRAW_ANIMATION)
                    ==PFLAG_DRAW_ANIMATION;

            // ...final int[] location =attachInfo.mInvalidateChildLocation;
            location[CHILD_LEFT_INDEX] =child.mLeft;
            location[CHILD_TOP_INDEX] =child.mTop;
            // ...do{
                View view = null;
                if (parent instanceofView) {
                    view =(View) parent;
                }

                if(drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |=PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceofViewRootImpl) {
                        ((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); // 转到第三步,调用此方法层层刷新View
                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) (boundingRect.left - 0.5f),
                                (int) (boundingRect.top - 0.5f),
                                (int) (boundingRect.right + 0.5f),
                                (int) (boundingRect.bottom + 0.5f));
                    }
                }
            } while (parent != null);
        }
    }

3>>.调用invalidateChildInParent函数依次层层刷新

    /*** 这个方法返回null如果ViewGroup已经没有父View了,
     * 或者如果这个ViewGrop已经全部被设置为无效,或者当前View的需要刷新的rectangle区域与ViewGroup不相交
     */
    public ViewParent invalidateChildInParent(final int[] location, finalRect dirty) {
        if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) ==PFLAG_DRAWING_CACHE_VALID) {
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=FLAG_OPTIMIZE_INVALIDATE) {
                dirty.offset(location[CHILD_LEFT_INDEX] -mScrollX,
                        location[CHILD_TOP_INDEX] -mScrollY); // 根据父View的位置,偏移刷新区域

                final int left =mLeft;
                final int top =mTop;

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) ==FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom -top)) { // 计算实际可刷新区域
                        dirty.setEmpty();
                    }
                }
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

                location[CHILD_LEFT_INDEX] =left;
                location[CHILD_TOP_INDEX] =top;

                if (mLayerType !=LAYER_TYPE_NONE) {
                    mPrivateFlags |=PFLAG_INVALIDATED;
                    mLocalDirtyRect.union(dirty);
                }

                returnmParent;

            } else{
                mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;

                location[CHILD_LEFT_INDEX] =mLeft;
                location[CHILD_TOP_INDEX] =mTop;
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) ==FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom -mTop);
                } else{
                    //in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom -mTop);
                }

                if (mLayerType !=LAYER_TYPE_NONE) {
                    mPrivateFlags |=PFLAG_INVALIDATED;
                    mLocalDirtyRect.union(dirty);
                }
                returnmParent;
            }
        }
        return null;
    }

免责声明:文章转载自《Android View 绘制刷新流程分析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Oracle服务无法启动,报:Windows无法启动OracleOraDb10g_home1TNSListener服务,错误 1067:进程意外终止。java环境添加chrome驱动下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

小程序自定义头部导航栏

第一步:在app.json中设置 目前微信小程序不支持单个页面设置,一旦决定要使用自定义导航栏后,那么每个页面都需要设置 导航栏组件目录: index.wxml 文件 <view class='nav-wrap' style='height: {{height*2 + 20}}px;'> <!--导航栏 中间的标题 --&g...

序列化器关系 (Serializer relations)

糟糕的程序员担心代码。好的程序员担心数据结构和它们的关系。—— Linus Torvalds 关系字段用于表示模型关系。它们可以应用于 ForeignKey,ManyToManyField 和 OneToOneField 关系,以及反向关系和自定义关系 (例如:GenericForeignKey)。 注意:关系字段在 relations.py 中声明,但...

[微信小程序]实现一个自定义遮罩层

正文: 先上效果图: 点击按钮Show显示遮罩层,再次点击屏幕任何地方隐藏遮罩层; <button bindtap="showview">Show</button> <view class="bg" bindtap='hideview' style='display:{{display}}'></view>...

小程序自定义轮播图

话不多说,上图上代码。 wxml <view bindtouchstart="touchstart" bindtouchmove="touchmove" bindtouchend="touchend"> <view animation="{{animation1}}" bindtap="scrollLeft"> <imag...

Android Xfermode 实战 实现圆形、圆角图片

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/42094215,本文出自:【张鸿洋的博客】 1、概述 其实这篇本来准备Android BitmapShader 实战 实现圆形、圆角图片放到一篇里面,结果由于篇幅原因就独立出来了~在很久以前也写过一个利用Xfermode 实现圆形、圆角图片...

微信小程序地图控件篇 自定义图标被地图覆盖的问题

今天在做微信小程序的时候遇到这个这样的问题  需要在地图上加个一个自定义的图标控件 类似这样的    刚开始的时候怎图片一直会被地图组件覆盖  ,要怎么解决这个问题  我去翻了下小程序的文档 有个cover-view以及cover-image组件是可以覆盖在map组件之上  只要把view换成 cover-view就解决啦   希望对各位开发的同学有帮助...