添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

问题描述起来很简单,就是在动画结束的时候,调用父view删除子view,出现崩溃,信息如下:

java.lang.NullPointerException
Attempt to read from field 'int android.view.View.mViewFlags' on a null object reference
 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4111)
 android.view.View.updateDisplayListIfDirty(View.java:19073)
 android.view.View.draw(View.java:19935)
 android.view.ViewGroup.drawChild(ViewGroup.java:4333)
 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4112)

下面是问题的核心代码

		//设置动画回调
        animation.setAnimationListener(new Animation.AnimationListener(){
            @Override
            public void onAnimationStart(Animation animation) {
            @Override
            public void onAnimationEnd(Animation animation) {
                //container 是fromView 的父view,是一个viewGroup
                container.removeViewInLayout(fromView);
            @Override
            public void onAnimationRepeat(Animation animation) {
        });
        //执行动画
        fromView.startAnimation(animation);

源码分析:

不想看源码的同学 ,也可以直接去下面看解决方法。

问题出在哪里,就在哪里断点,看看到底是什么问题。

下面先把断点调试的代码,截图出来,方便看到具体的值。两图的代码都是dispatchDraw函数里的,
在这里插入图片描述

下面的代码,是上面代码的文字版本

   @Override
    protected void dispatchDraw(Canvas canvas) {
        ...省略若干代码.....
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
       ...省略若干代码.....
        // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
        // draw reordering internally
        final ArrayList<View> preorderedList = usingRenderNodeProperties
                ? null : buildOrderedChildList();
        final boolean customOrder = preorderedList == null
                && isChildrenDrawingOrderEnabled();
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            //这里发生了空指针异常,child为null
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
        ...省略若干代码.....

我们看到,直接原因是child 为null,导致获取child.mViewFlags 出现NullPointerException

代码再往上找,函数getAndVerifyPreorderedView 来获取child,具体的参数情况,是 children 里的个数是2,但是childIndex是2,得到的结果肯定null。

childIndex 是通过函数getAndVerifyPreorderedIndex(childrenCount, i, customOrder)来获取的,根据当前的参数情况,childIndex 取得是i 的值,i的值是在循环中根据childrenCount来递增的。

继续跟进childrenCount,在dispatchDraw()函数的前面进行赋值

        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;

mChildrenCount 就是mChildren的数量,凡是在mChildren中添加或者删除view,mChildrenCount 都会相应变化。

通过上面的分析,大概知道原因就是开始时mChildrenCount的值是3,赋给了childrenCount ,mChildren里面也是3个view。继续往下执行的时候,出现了mChildren 里面的view被删除了一个,mChildrenCount的值也变成了2。于是就出现了上面的崩溃。

那为什么view 会少了一个呢?
我们接着看代码

            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);

在dispatchDraw中会执行到这个代码,绘制子view,

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);

注意这里调用draw是三个参数的,和平时看的measure,layout,draw的draw函数不是同一个

View.java 中的函数

* This method is called by ViewGroup.drawChild() to have each child view draw itself. * This is where the View specializes rendering behavior based on layer type, * and hardware acceleration. boolean draw(Canvas canvas, ViewGroup parent, long drawingTime){ ...省略若干代码..... //获取是否有动画 final Animation a = getAnimation(); if (a != null) { //若果有动画,需要应用(处理)遗留的动画 more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired); concatMatrix = a.willChangeTransformationMatrix(); if (concatMatrix) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; transformToApply = parent.getChildTransformation(); ...省略若干代码.....

Animation.java 中的函数

* Gets the transformation to apply at a specified point in time. Implementations of this * method should always replace the specified Transformation or document they are doing * otherwise. * @param currentTime Where we are in the animation. This is wall clock time. * @param outTransformation A transformation object that is provided by the * caller and will be filled in by the animation. * @param scale Scaling factor to apply to any inputs to the transform operation, such * pivot points being rotated or scaled around. * @return True if the animation is still running public boolean getTransformation(long currentTime, Transformation outTransformation, float scale) { mScaleFactor = scale; return getTransformation(currentTime, outTransformation);

接着调用getTransformation,这里调用到AnimationSet.java 里的函数

* The transformation of an animation set is the concatenation of all of its * component animations. * @see android.view.animation.Animation#getTransformation @Override public boolean getTransformation(long currentTime, Transformation t) { ...省略若干代码..... boolean ended = true; if (ended != mEnded) { if (mListener != null) { // 这里调用了动画结束的回调 mListener.onAnimationEnd(this); mEnded = ended; ...省略若干代码..... return more;

到这里原因就彻底搞清楚了

解决办法:

知道了原因,再来解决就很简单了。以最开始的核心问题代码,来演示如何解决。

问题出现remove view的时候,在dispatchDraw 中改变了viewGroup已有的子view的数量,导致只有N个view,最大索引是N-1,想要获取第N个view,出现了异常。

那么我们可以考虑不在本次执行中,remove view。在下一次的loop消息中执行remove 操作,那么就通过post 或 handler 发送消息来操作view

提供两种解决方法:

		//设置动画回调
        animation.setAnimationListener(new Animation.AnimationListener(){
            @Override
            public void onAnimationStart(Animation animation) {
            @Override
            public void onAnimationEnd(Animation animation) {
                 //get the parentView...
                 container.post(new Runnable() {
                        public void run () {
                         // it works without the runOnUiThread, but all UI updates must 
                         // be done on the UI thread
                          activity.runOnUiThread(new Runnable() {
                               public void run() {
                                 //container 是fromView 的父view,是一个viewGroup
                                 container.removeViewInLayout(fromView);
                           });
            @Override
            public void onAnimationRepeat(Animation animation) {
        });
        //执行动画
        fromView.startAnimation(animation);
        //执行动画
        fromView.startAnimation(animation);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    container.removeViewInLayout(fromView);
                } catch (Exception ignored) {
         },animation.getDuration());

请点赞、收藏,感谢大家的支持,有任何疑问可在评论区回复

后来分析完源码,在动画结束后,删除view 应该也算是一个合理的需求,于是上网一搜,果然有人也遇到这个问题,第一种解决方法,就是这里的
Android - remove View when its animation finished

问题:问题描述起来很简单,就是在动画结束的时候,调用父view删除子view,出现崩溃,信息如下:java.lang.NullPointerExceptionAttempt to read from field 'int android.view.View.mViewFlags' on a null object reference android.view.ViewGroup.dispa...
本文较为详细的分析了jQuery源码解读之removeClass()方法。分享给大家供大家参考。具体分析如下: removeClass()方法和addClass()差别不大。这就来看看: 代码如下:jQuery.fn.extend({     removeClass: function( value ) {         var classes, elem, cur, clazz, j, finalValue,             i = 0,             len = this.length,             proceed = arguments.length =
本文较为详细的分析了jQuery源码解读之removeAttr()方法。分享给大家供大家参考。具体分析如下: 扩展jQuery原型对象的方法: 代码如下:jQuery.fn.extend({ //name,传入要DOM元素要移除的属性名。     removeAttr: function( name ) { //使用jQuery.fn对象,即jQuery原型对象的each方法遍历当前选择器选择的jQuery对象数组,并返回该jQuery对象以便链式调用。         return this.each(function() { //调用jQuery的全局方法removeAttr,传入遍历出
os.remove不能用来删除文件夹,否则拒绝访问。 # -*- coding:utf-8 -*-import osif __name__ == “__main__”: os.remove(‘D:\\test’) 运行结果: 删除空目录: # -*- coding:utf-8 -*-import osif __name__ == “__main__”: os.rmdir(‘D:\\test’) 如果目录不为空会报错,如下: 删除目录(不论目录是否为空): # -*- coding:utf-8 -*-import shutilif __name__ == “__main__”: shutil
报错 如下 :07-19 10:00:08.398 14724-14724/com.lvche.lvchedingdang E/PushFactory: getPushInstance not found push instance. 07-19 10:00:08.436 14724
本文实例讲述了jQueryremoveData()方法用法。分享给大家供大家参考。具体实现方法如下: 此方法可以移除匹配元素上指定的数据。 removeData()方法与data()方法的作用相反。 语法结构一: 代码如下:$(selector).removeData(name) 参数列表:
最近看了些 View 相关的源码,相比之前,有一些新的认知。争取通过一次整理,能系统了解 Android View 加载和显示的相关过程,记录下来,共勉。接下来的所有源码基于 Android API 27 Platform。 对于 View 创建,通俗说其实就两种方式,一种是直接通过 new 关键词直接创建对象,另外就是通过 xml 填充一个 View。第一种方式写起来最简易,但是,也有一些代价,...
上几篇文章我们分析了Dialog的加载绘制流程,也分析了Acvityi的加载绘制流程,说白了Android系统窗口的展示都是通过Window对象控制,通过ViewRootImpl对象执行绘制操作来完成的,那么窗口的取消绘制流程是怎么样的呢?这篇文章就以Dialog为例说明Window窗口是如何取消绘制的。 有的同学可能会问前几篇文章介绍Activity的加载绘制流程的时候为何没有讲Activ
public V remove(Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; 在这段代码,首先会根据传入的key计算出它的hash值,然后调用removeNode()方法来删除对应的节点。如果删除成功,就返回被删除节点的value值,否则返回null。 removeNode()方法源码如下: final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; p = e; } while ((e = e.next) != null); if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; return null; 在这段代码,首先会根据hash值找到对应的节点p,然后遍历p的链表,找到对应的节点node。如果找到了node,并且它的value值与传入的value值相等,就删除这个节点,并返回它。如果没有找到对应的节点,就返回null。
CSDN-Ada助手: 嗨~好久未见你更新博文了,我们现在上线了AI创作助手哦~可为你的创作提供智能化帮助,快来试试吧~https://editor.csdn.net/md/?not_checkout=1&utmsource=blog_comment_recall,在编辑器页面右侧哦~~限免!! 同时我们为您准备了一份回归奖励,快来看看吧https://activity.csdn.net/creatActivity?id=10430&utmsource=blog_comment_recall Android 图形架构 之二—— SurfaceFlinger 启动和连接 eHackyd: “Android图形架构 之八——硬件VSync、VSync-app、Vsync-sf”这篇文章还没写吗? RxJava之二——Single和Subject 曾幻想仗剑闯天涯: 这图谁能讲讲啥意思呀 Ubuntu 设置系统环境变量和开机自启动 sword_pro: 使用Adb shell dumpsys检测Android的Activity任务栈 Jing.yu: 'sed' 不是内部或外部命令,也不是可运行的程序 这是什么情况啊