android view第一次执行动画无效

文章详细描述了一个在Android应用中遇到的视图动画执行异常问题,原因在于ListView布局解析的延迟导致动画未能正确执行。通过调整ListView初始状态,解决了动画执行的顺序问题。

今天遇到一个特别奇怪的问题,就是view执行动画的时候,我使用下面的这个,第一次动画竟然没有了,第二次,第三。。。就有动画。

先这个代码是我现实执行view动画时候的代码,也没有什么特别,但是那个view就是一个组合控件(2个listview在里面)

public void show(int id){
    View item = this.map.get(id);
    if (item==null)return;
    //先隐藏之前显示的
    if (this.lastShow!=null){
        this.lastShow.clearAnimation();
        this.lastShow.setVisibility(View.GONE);       //设置消失
    }
    this.lastShow = item;
    item.setVisibility(View.VISIBLE);     //可见
    item.clearAnimation();
    item.startAnimation(dropdown_in);     //设置进入动画
    bgview.setVisibility(View.VISIBLE);     //背景
    bgview.startAnimation(alphaAnimation);
}


	我百度了一下,也很少有类似我这种情况。然后我又换了台手机测试,我的lg的lu6200测试。
	发现这个老手机可以,当我的荣耀7不行。(lu6200是4.0系统,荣耀7是5.0的)
	
	我想了想,会不会是因为这2个listview的原因?让后我把其中一个listview先屏蔽了。
	再打印一下日志,打印了listview执行getVeiw的日志。
	
	发现终于可以。
	
	原来我的2个listview,一开始是设置了gone的,所以就算设置的adapter,他也没有马上getview生成item。
	然后到我执行动画的时候,getview的日志就打印了。因为getview有解析item的布局的操作,而且我有2个listview,
	item的数量我测试放了50个,所以还没有来得及那几个getview,动画的时间已经结束了。
	
	然后第二次执行动画,因为那些item已经有了,也不解析布局了,这样动画可以在以后完美执行。
	问题找到了,那么解决的办法自然也有了
	
	

<think>我们分析了引用中提到的关于`android:drawablePadding`无效的原因,主要归结为以下几点: 1. 当TextView的宽度或高度设置为`match_parent`时,文本和图标有足够的空间,因此padding可能不生效(引用[1][2])。 2. 当空间足够大时,图标和文本不会挤在一起,所以padding也不起作用(引用[3])。 3. 在代码中可以通过`setCompoundDrawablePadding()`方法设置(引用[4])。 在`ShapeDrawableTextView`中,我们在`onDraw`方法中自己绘制了图标,因此原生的`drawablePadding`属性可能不会生效。因为我们没有处理系统自带的`drawablePadding`,而是自己计算位置。 解决方案: 1. 在自定义View中获取`android:drawablePadding`属性的值(即`compoundDrawablePadding`)。 2. 在绘制图标时,将这个padding值考虑进去。 具体步骤: a. 在初始化时获取`drawablePadding`(即`compoundDrawablePadding`)。 b. 在计算图标位置时,在图标和文本之间加入这个padding。 但是注意:我们的`onDraw`方法已经重写,并且自己绘制图标,所以需要调整图标绘制的位置。 在原有代码中,我们计算图标位置时没有考虑padding。现在需要修改: 例如,对于左侧图标: 原计算:文本宽度 + 图标宽度 + drawablePadding(但这里我们没有用系统的padding,而是自己设置了一个padding,实际上应该用系统的`compoundDrawablePadding`) 但是,观察原有代码,它尝试将整个内容(文本+图标+间距)居中,所以它调整了view的padding。这种方式可能和系统的`drawablePadding`机制不同。 为了支持系统的`drawablePadding`,我们应该: 1. 不再调整view的padding(即去掉原有`onDraw`中设置padding的代码)。 2. 按照系统的规则绘制图标:即图标在指定方向(左、上、右、下),并且与文本之间有`compoundDrawablePadding`的间距。 然而,我们自定义的绘制图标的方式是为了实现按下状态切换,所以需要自己绘制。因此,我们需要模仿系统原生的行为。 系统原生行为: 对于`drawableLeft`:图标绘制在文本左侧,中间有`drawablePadding`的间距,整体在View中居中(如果gravity是居中)? 但是,我们的需求是图标和文本整体居中(无论是否设置padding,都要整体居中)。所以,我们可能需要计算整个内容区域(包括图标和文本以及它们之间的padding)的宽度,然后调整绘制起点。 修改思路: 在`onDraw`方法中,我们首先计算整个内容(图标+文本+图标与文本之间的padding)的宽度和高度,然后根据这些值调整绘制起点,使整个内容在View中居中。 步骤: 1. 获取当前图标的尺寸(我们之前已经通过`drawableBounds`保存了)。 2. 获取`compoundDrawablePadding`(通过`getCompoundDrawablePadding()`方法)。 3. 计算整个内容区域的宽度和高度: - 对于水平方向(左或右图标):整个宽度 = 图标宽度 + 内间距 + 文本宽度 - 对于垂直方向(上或下图标):整个高度 = 图标高度 + 内间距 + 文本高度 4. 计算居中的偏移量,然后移动画布。 但是,注意:我们可能同时有多个方向的图标(比如左边和右边都有图标),那么整个内容宽度应该是:左边图标宽度 + 左边图标与文本的间距 + 文本宽度 + 右边图标与文本的间距 + 右边图标宽度。 因此,我们需要分别计算水平和垂直方向上的总尺寸。 然而,由于我们的图标是独立绘制的(并且可以分别设置正常和按下状态),所以我们只考虑单侧图标(即系统默认只支持一个方向一个图标,但我们也可以支持多个,但通常不会同时设置左右)。 为了简化,我们假设一次只有一个方向有图标(左、上、右、下),因为系统原生的`TextView`也只支持一个方向设置一个图标(虽然可以设置多个,但会重叠)。 因此,我们修改绘制逻辑: 首先,计算内容区域的总宽度和总高度: 水平方向(当有左或右图标): 总宽度 = 左侧图标宽度(如果有) + 左侧图标的padding(如果有) + 文本宽度 + 右侧图标的padding(如果有) + 右侧图标宽度(如果有) 垂直方向(当有上或下图标): 总高度 = 顶部图标高度(如果有) + 顶部图标的padding(如果有) + 文本高度 + 底部图标的padding(如果有) + 底部图标高度(如果有) 然后,计算水平偏移量:`(getWidth() - 总宽度) / 2`,垂直偏移量:`(getHeight() - 总高度) / 2`,然后移动画布。 但是,这样计算比较复杂,而且我们原有的`onDraw`方法中已经尝试通过调整padding来居中,但这种方式在`match_parent`时会导致问题。 因此,我们改变策略:不再调整padding,而是通过移动画布来居中整个内容(包括图标和文本以及它们之间的padding)。 具体修改: 1. 在`onDraw`中,先计算内容区域的总大小(包括图标和文本以及间距)。 2. 然后移动画布,使内容区域在View中居中。 3. 然后分别绘制图标和文本(文本的绘制通过`super.onDraw`完成,但我们需要调整文本的位置,使其与图标有间距)。 但是,`super.onDraw`会绘制文本和背景,我们已经在`drawBackground`中绘制了背景,所以这里只需要绘制文本和图标?不对,我们之前已经重写了整个图标的绘制,所以文本还是由`super.onDraw`绘制,但图标由我们自己绘制。 因此,我们需要在调用`super.onDraw`之前移动画布,这样文本也会被移动。但是,我们还需要绘制图标,所以图标的位置也要根据移动后的画布来绘制。 然而,这样会使得整个内容(文本和图标)一起移动,从而达到居中的效果。 修改后的`onDraw`方法: 步骤: 1. 计算内容区域的总宽度和总高度(包括图标、文本以及图标与文本之间的间距)。 2. 计算起始绘制点(x, y),使得内容区域在View中居中。 3. 保存画布状态。 4. 平移画布到(x, y)。 5. 调用`super.onDraw(canvas)`绘制文本(此时文本的位置已经调整,因为画布平移了,所以文本会在平移后的位置绘制,而平移的位置是居中的,所以文本会出现在内容区域的正确位置)。 6. 在平移后的画布上绘制图标(注意图标的位置也要相对于内容区域)。 7. 恢复画布状态。 但是,这样会有一个问题:`super.onDraw`会绘制系统自带的图标,而我们不需要系统绘制图标,因为我们自己绘制。因此,我们需要将系统自带的图标设置为null,然后完全由我们自己绘制。 所以,在初始化时,我们应该将`TextView`的图标设置为null,然后只使用我们自己的图标数组。 因此,我们需要在构造方法中,将`TextView`的图标置空: ```java // 在init方法中 setCompoundDrawables(null, null, null, null); ``` 然后,在`onDraw`中,我们自己绘制图标。 具体实现: 1. 在`onDraw`中,先计算内容区域的大小和起始点。 2. 移动画布到起始点。 3. 调用`super.onDraw(canvas)`绘制文本(此时文本在内容区域的原点(0,0)位置,因为画布被移动了,所以文本会出现在内容区域的正确位置)。 4. 然后我们在内容区域的相应位置绘制图标(比如,左侧图标就画在(0, (内容高度-图标高度)/2)的位置,文本在(图标宽度+padding, ...)的位置,但文本的绘制由`super.onDraw`完成,所以我们需要在设置文本时,让`TextView`知道文本的位置?实际上,我们移动了画布,文本就会被画在移动后的位置,而文本在`TextView`中的位置默认是从(0,0)开始,所以我们需要调整文本的绘制位置?) 这个方案比较复杂,可能需要重新设计。 另一种思路:我们不再自己绘制图标,而是使用系统的`setCompoundDrawables`,但是这样就不能实现按下状态切换图标。因此,我们需要在按下时切换图标,然后调用`setCompoundDrawables`来更新图标。这样,我们就可以利用系统原生的图标绘制和padding处理。 但是,这样修改会使得我们之前自己绘制图标的代码全部废弃,需要重新实现按下状态切换图标的功能(通过设置`setCompoundDrawables`)。 权衡后,我建议采用第二种思路,因为这样可以避免很多自定义绘制的问题,并且能够完美支持`drawablePadding`。 具体步骤: 1. 去掉`onDraw`中自己绘制图标的代码。 2. 在初始化时,根据正常状态设置图标。 3. 在触摸事件中,根据按下状态设置对应的图标,并刷新。 这样,我们就可以利用系统原生支持`drawablePadding`的特性。 修改后的代码结构: 1. 在`init`方法中,获取正常状态和按下状态的图标,并保存。 2. 初始设置正常状态的图标(通过`setCompoundDrawablesWithIntrinsicBounds`)。 3. 在`onTouchEvent`中,当按下时,设置按下状态的图标;当松开时,设置正常状态的图标。 但是,这样就不能使用我们之前设置的图标尺寸(因为`setCompoundDrawablesWithIntrinsicBounds`会使用图标的固有尺寸)。因此,我们需要使用`setCompoundDrawables`并设置边界。 我们可以这样设置边界: ```java Drawable drawable = normalDrawables[0]; if (drawable != null) { drawable.setBounds(0, 0, drawableWidth, drawableHeight); } ``` 然后,在设置时: ```java setCompoundDrawables(drawableLeft, drawableTop, drawableRight, drawableBottom); ``` 因此,我们需要修改为使用`setCompoundDrawables`。 修改方案: 1. 去掉`onDraw`方法中关于图标的全部代码。 2. 添加一个方法`updateDrawables(boolean pressed)`,用于更新当前状态的图标。 3. 在触摸事件中调用这个方法更新图标。 具体代码: 在`init`方法中: 获取属性后,设置初始状态为正常图标,并调用`updateDrawables(false)` 在`updateDrawables`方法中: ```java private void updateDrawables(boolean pressed) { Drawable left = pressed && pressedDrawables[0] != null ? pressedDrawables[0] : normalDrawables[0]; Drawable top = pressed && pressedDrawables[1] != null ? pressedDrawables[1] : normalDrawables[1]; Drawable right = pressed && pressedDrawables[2] != null ? pressedDrawables[2] : normalDrawables[2]; Drawable bottom = pressed && pressedDrawables[3] != null ? pressedDrawables[3] : normalDrawables[3]; // 设置尺寸 if (left != null) { left.setBounds(0, 0, drawableWidth > 0 ? drawableWidth : left.getIntrinsicWidth(), drawableHeight > 0 ? drawableHeight : left.getIntrinsicHeight()); } // 同理设置其他方向 setCompoundDrawables(left, top, right, bottom); } ``` 然后,在`onTouchEvent`中: ```java if (action == MotionEvent.ACTION_DOWN) { updateDrawables(true); } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { updateDrawables(false); } ``` 同时,我们不再需要重写`onDraw`方法来绘制图标,因此可以删除`onDraw`方法。 这样,我们就利用了系统自带的图标绘制和`drawablePadding`支持。 但是,注意:我们之前重写`onDraw`还有一个目的是将整个内容(包括图标和文本)居中。现在,如果我们使用系统自带的`setCompoundDrawables`,那么如何让整个内容居中呢? 我们可以通过设置`gravity`为`center`来实现文本居中,但是图标和文本整体居中?系统默认的行为是:当有左图标时,文本会从图标后面开始,整个内容(图标+文本)在View中默认是左对齐。如果我们想让整个内容居中,我们需要设置`gravity`为`center`,但这样会使得文本居中,而图标在文本的左侧,整体效果是文本居中,图标在文本左侧(整体并没有居中,因为图标在左侧,文本在中间,所以整体看起来是文本居中,图标在居中文本的左侧)。 所以,我们之前自己绘制图标的方式是为了让图标和文本作为一个整体居中。现在,如果我们使用系统自带的图标绘制,那么如何实现整体居中呢? 实际上,系统并没有提供直接的方法让图标和文本作为一个整体居中。因此,我们可能需要自己计算并设置padding。 但是,我们之前自己绘制图标的方式已经可以实现整体居中,只是没有处理`drawablePadding`。那么,我们为什么不修复自己绘制图标的方式,而是改用系统的方式呢? 因此,我们回到第一种思路:自己绘制图标,并支持`drawablePadding`。 重新设计自己绘制图标的方案: 1. 获取`compoundDrawablePadding`(通过`getCompoundDrawablePadding()`)。 2. 计算整个内容区域(包括图标、文本和它们之间的padding)的宽度和高度。 3. 计算起始点,使内容区域在View中居中。 4. 移动画布到起始点。 5. 分别绘制图标和文本。 但是,文本的绘制我们仍然使用`super.onDraw`,所以我们需要调整文本的位置,使其在内容区域中正确显示(即文本在图标后面,中间有padding)。 为了调整文本的位置,我们可以设置`TextView`的padding,但是这样会影响到整个View的布局,而且我们已经在移动画布了,所以文本的位置应该通过移动画布来调整。 具体步骤: 在`onDraw`中: a. 计算内容区域的总宽度和总高度。 b. 计算偏移量:`transX = (getWidth() - totalWidth) / 2`, `transY = (getHeight() - totalHeight) / 2`。 c. 保存画布状态。 d. 平移画布:`canvas.translate(transX, transY)`。 e. 绘制左侧图标(如果有):在(0, (totalHeight - iconHeight)/2)的位置。 f. 绘制文本:但是文本的位置应该在哪里?我们希望文本在图标右侧,并且与图标有padding的距离。所以,我们调用`super.onDraw`时,文本应该被绘制在(iconWidth + padding, ...)的位置。但是,`TextView`并不知道我们移动了画布,它还是会从(0,0)开始绘制文本,所以文本会出现在内容区域的(0,0)位置,这显然不对。 因此,我们需要调整文本的绘制位置。我们可以在绘制文本之前,再移动画布,将画布向右移动(左侧图标的宽度+padding),这样文本就会在正确的位置绘制。 但是,这样就需要分步骤绘制,而且可能会多次移动画布。 另一种做法:我们不再调用`super.onDraw`来绘制文本,而是自己调用`canvas.drawText`来绘制文本。但是这样就需要处理文本的绘制、换行、样式等,非常复杂。 因此,我们折中:将整个内容区域(包括图标和文本)看作一个整体,然后调整TextView的padding,使这个整体在View中居中。但是,这样会涉及到动态计算并设置padding,而且padding会随着图标的变化而变化,可能会影响触摸事件。 考虑到复杂性,我们决定采用以下方案: 1. 在`onDraw`中,我们只绘制图标,文本仍然由`super.onDraw`绘制。 2. 但是,我们通过调整画布的平移,使图标和文本整体居中。 具体做法: - 计算内容区域的总宽度和总高度(包括图标、文本和padding)。 - 计算需要平移的距离(使内容区域居中)。 - 平移画布,然后绘制图标,然后调用`super.onDraw`绘制文本(此时文本会在平移后的画布上绘制,位置相对于平移后的原点)。 但是,文本的绘制位置我们并没有调整,所以文本会从平移后的(0,0)开始绘制,这会导致文本和图标重叠或者错位。 因此,我们需要在绘制文本之前,将画布平移到文本应该出现的位置(即图标绘制完后,再移动padding的距离,然后再绘制文本)。这几乎不可能,因为`super.onDraw`会从(0,0)开始绘制。 综上所述,我们决定放弃移动画布,而是通过动态计算padding,使内容居中。具体做法: - 重写`onLayout`或`onMeasure`,计算内容区域的大小,然后设置padding,使内容区域在View中居中。 但是,这样计算padding会非常复杂,而且可能会递归调用布局。 鉴于时间,我们采用一种简单方案:只支持单方向图标,并且要求用户使用`gravity="center"`,这样文本和图标会作为一个整体居中。但是,系统默认的`gravity="center"`并不能让图标和文本作为一个整体居中,而是让文本居中,图标在文本的旁边。 因此,我们决定不改变,而是修复之前自己绘制图标时没有考虑`drawablePadding`的问题。 在原有的`onDraw`方法中,我们在计算内容宽度时,加入`drawablePadding`: 例如,对于左侧图标: 内容宽度 = 图标宽度 + drawablePadding + 文本宽度 然后,在设置padding时,我们不再设置,而是通过移动画布来实现居中。 我们修改`onDraw`方法: 1. 获取`compoundDrawablePadding`(记为`drawablePadding`)。 2. 计算内容区域的总宽度和总高度。 3. 计算偏移量`transX`和`transY`。 4. 移动画布。 5. 绘制图标。 6. 绘制文本:这里文本的绘制我们仍然调用`super.onDraw`,但是我们需要让文本出现在正确的位置(即图标之后)。为了做到这一点,我们可以在移动画布后,再调整文本的绘制位置,但如前所述,这很困难。 所以,我们改变策略:我们绘制图标,文本的绘制交给系统,但我们通过设置padding来让文本出现在图标的后面。但是,我们之前就是因为设置padding才导致问题。 现在,我们换一种方式设置padding:我们动态计算padding,使文本和图标作为一个整体在View中居中。 在`onLayout`中,我们可以计算并设置padding: 1. 计算内容区域的总宽度和总高度。 2. 计算水平padding:`(getWidth() - totalWidth) / 2`,垂直padding:`(getHeight() - totalHeight) / 2`。 3. 设置这些padding,这样内容就会居中。 但是,这样会递归布局,因为设置padding会导致onLayout重新调用,所以我们需要一个标志位来避免递归。 鉴于这些复杂性,我提供一个简化方案:我们只支持一个方向的图标,并且只处理这个图标和文本的整体居中,同时考虑`drawablePadding`。 以左侧图标为例: 在`onDraw`中: - 获取文本宽度、图标宽度、drawablePadding。 - 总宽度 = 图标宽度 + drawablePadding + 文本宽度 - 总高度 = Math.max(图标高度, textHeight) // 文本高度 - 计算需要平移的X = (getWidth() - totalWidth) / 2 - 计算需要平移的Y = (getHeight() - totalHeight) / 2 - 然后,canvas.translate(X, Y) - 绘制左侧图标:canvas.drawIcon(...) at (0, (totalHeight - iconHeight)/2) - 然后,为了让文本出现在图标的右侧,我们 setPadding(iconWidth + drawablePadding, 0, 0, 0) ? but this will cause layout, so we can't in onDraw. 这似乎走进了死胡同。 经过慎重考虑,我决定采用以下方案: 我们放弃在自定义View中自己绘制图标,而是采用系统绘制,但为了支持按下状态切换图标,我们动态设置compound drawables。至于整体居中,我们通过设置gravity="center"并不能让图标和文本作为整体居中,所以我们要求用户在布局时,将TextView的宽度设置为wrap_content,这样系统就会将图标和文本作为一个整体,然后设置gravity="center"就可以让整体在View中居中。 而引用[1][2][3]中提到,当宽度为match_parent时,drawablePadding may not work,所以我们建议用户使用wrap_content。 因此,我们修改后的方案: 1. 不再自己绘制图标,而是使用系统的setCompoundDrawables。 2. 在触摸事件中更新图标。 3. 在布局文件中,建议用户设置android:layout_width="wrap_content"。 这样,drawablePadding就会生效,因为系统在wrap_content模式下会生效(如引用[2]所说)。 所以,我们删除之前自己绘制图标的代码,改为使用系统方式。 具体代码修改: 1. 删 onDraw 方法. 2. 添加 updateDrawables 方法. 3. 在 init 方法的最后,调用 updateDrawables(false) 设置初始图标. 4. 在 onTouchEvent 中,根据状态 updateDrawables. 5. 在 updateDrawables 方法中,设置图标的边界,然后 setCompoundDrawables. 代码示例: ```java private void updateDrawables(boolean pressed) { Drawable left = null; Drawable top = null; Drawable right = null; Drawable bottom = null; for (int i = 0; i < 4; i++) { Drawable drawable = pressed && pressedDrawables[i] != null ? pressedDrawables[i] : normalDrawables[i]; if (drawable != null) { int width = drawableWidth > 0 ? drawableWidth : drawable.getIntrinsicWidth(); int height = drawableHeight > 0 ? drawableHeight : drawable.getIntrinsicHeight(); drawable.setBounds(0, 0, width, height); } switch (i) { case 0: left = drawable; break; case 1: top = drawable; break; case 2: right = drawable; break; case 3: bottom = drawable; break; } } setCompoundDrawables(left, top, right, bottom); } ``` 6. 在初始化时,获取 compoundDrawablePadding 属性我们不需要了,因为系统已经处理。 7. 在 onTouchEvent 中,除了 updateDrawables, 我们还要// leave the background drawing as is. 8. 删掉 onDraw 方法. 9. 在 init 方法中,将系统的图标设置为null: setCompoundDrawables(null, null, null, null); 然后调用 updateDrawables(false); 注意:我们 setCompoundDrawables 会设置图标,所以不需要再绘制。 10. 背景绘制保持不变。 这样,我们就能利用系统机制实现 drawablePadding,同时支持按下状态切换图标。 因此,最终的解决方案是:改用系统方式设置图标,并动态更新, thus the drawablePadding will work as expected. 我们按照这个方案修改代码。</think>### 解决 ShapeDrawableTextViewandroid:drawablePadding 无效的问题 在自定义 `ShapeDrawableTextView` 中,`android:drawablePadding` 属性无效的主要原因是: 1. **布局尺寸问题**:当 View 设置为 `match_parent` 时,系统无法正确计算间距(引用[1][2]) 2. **自定义绘制逻辑**:重写 `onDraw()` 方法时未正确处理系统自带的 drawablePadding 机制 3. **空间充足时的行为**:当空间足够大时,系统可能忽略 padding(引用[3]) 以下是完整解决方案: ```java package cn.chen.common_base.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatTextView; import cn.chen.common_base.R; public class ShapeDrawableTextView extends AppCompatTextView { // [原有背景相关代码保持不变]... // 新增解决 drawablePadding 的字段 private int compoundDrawablePadding; private boolean customDrawableEnabled = true; public ShapeDrawableTextView(Context context) { super(context); init(context, null); } public ShapeDrawableTextView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context, attrs); } public ShapeDrawableTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { // [原有背景初始化代码保持不变]... // 关键修复:获取系统 drawablePadding if (attrs != null) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ShapeTextView); // 获取系统定义的 drawablePadding compoundDrawablePadding = ta.getDimensionPixelSize( R.styleable.ShapeTextView_android_drawablePadding, 0); ta.recycle(); } // 确保使用正确的 padding 机制 setCompoundDrawablePadding(compoundDrawablePadding); } // 关键修复:重写 drawable 状态更新方法 @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (customDrawableEnabled) { updateDrawableState(); } } private void updateDrawableState() { int[] state = getDrawableState(); boolean pressed = false; for (int s : state) { if (s == android.R.attr.state_pressed) { pressed = true; break; } } updateDrawables(pressed); } private void updateDrawables(boolean pressed) { Drawable left = null, top = null, right = null, bottom = null; if (normalDrawables[0] != null || pressedDrawables[0] != null) { left = pressed ? pressedDrawables[0] : normalDrawables[0]; if (left != null) { left.setBounds(drawableBounds[0]); } } // 同样处理 top/right/bottom... // 关键修复:使用系统方法设置 drawable 并保留 padding setCompoundDrawables(left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { // 关键修复:仅当自定义 drawable 启用时才处理 if (!customDrawableEnabled) { super.onDraw(canvas); return; } // [原有背景绘制代码]... // 使用系统计算的位置,不再自定义定位 super.onDraw(canvas); } // 新增 API:切换自定义 drawable 模式 public void setCustomDrawableEnabled(boolean enabled) { this.customDrawableEnabled = enabled; if (!enabled) { // 恢复系统默认行为 setCompoundDrawables( normalDrawables[0], normalDrawables[1], normalDrawables[2], normalDrawables[3] ); } invalidate(); } } ``` ### 修复要点说明 1. **正确获取 drawablePadding** 通过 `android.R.styleable` 获取系统定义的 padding 值: ```java compoundDrawablePadding = ta.getDimensionPixelSize( R.styleable.ShapeTextView_android_drawablePadding, 0); setCompoundDrawablePadding(compoundDrawablePadding); ``` 2. **使用系统绘制机制** 当 View 设置为 `match_parent` 时(引用[1][2]),启用系统原生绘制: ```java if (!customDrawableEnabled) { super.onDraw(canvas); return; } ``` 3. **动态状态处理** 重写 `drawableStateChanged()` 确保及时更新: ```java @Override protected void drawableStateChanged() { super.drawableStateChanged(); updateDrawableState(); } ``` 4. **API 控制开关** 添加模式切换方法应对不同场景: ```java // 在布局宽度为 match_parent 时调用 textView.setCustomDrawableEnabled(false); ``` ### XML 使用示例 ```xml <cn.chen.common_base.view.ShapeDrawableTextView android:layout_width="wrap_content" <!-- 推荐使用 wrap_content --> android:layout_height="wrap_content" android:drawablePadding="8dp" android:drawableLeft="@drawable/icon" app:solidColor="#4CAF50" ... /> ``` ### 相关优化建议 1. **布局尺寸策略**: - 优先使用 `wrap_content`(引用[2]) - 需要 `match_parent` 时禁用自定义 drawable: ```java if (getLayoutParams().width == MATCH_PARENT) { setCustomDrawableEnabled(false); } ``` 2. **动态设置 padding**: ```java // 代码中动态修改(引用[4]) textView.setCompoundDrawablePadding(20); ``` 3. **防止过度绘制**: ```java @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { setCustomDrawableEnabled(false); } } ``` ### 相关问题 1. 如何在自定义 View 中正确处理 RTL(从右到左)布局的 drawable 位置?[^1][^2] 2. 当 drawable 尺寸大于文本时,如何保持整体居中显示? 3. 如何优化自定义 View 在 RecyclerView 中的滚动性能? 4. 如何在自定义 View 中实现 drawable 的淡入淡出动画效果?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值