Android中View绘制各种状态的背景图片原理深入分析以及StateListDrawable使用

本文详细解释了Android中View如何通过Drawable的setState()方法动态切换背景,涉及Drawable类的onStateChange()、selectDrawable()方法,以及Drawable.Callback接口的运用。还介绍了View绘制背景的过程和自定义View的示例,展示了如何利用StateListDrawable实现不同状态下的背景切换。
摘要由CSDN通过智能技术生成

mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;

return mDrawableState;

}

}

通过这段代码我们可以明白View内部是如何获取更细后的状态值以及动态获取对应的背景Drawable对象----setState()方法

去完成的。这儿我简单的分析下Drawable类里的setState()方法的功能,把流程给走一下:

Step 1 、 setState()函数原型 ,

函数位于:frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 类中

//如果状态态值发生了改变,就回调onStateChange()方法。

public boolean setState(final int[] stateSet) {

if (!Arrays.equals(mStateSet, stateSet)) {

mStateSet = stateSet;

return onStateChange(stateSet);

}

return false;

}

该函数的主要功能: 判断状态值是否发生了变化,如果发生了变化,就调用onStateChange()方法进一步处理。

Step 2 、onStateChange()函数原型:

该函数位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 类中

//状态值发生了改变,我们需要找出第一个吻合的当前状态的Drawable对象

protected boolean onStateChange(int[] stateSet) {

//要找出第一个吻合的当前状态的Drawable对象所在的索引位置, 具体匹配算法请自己深入源码看看

int idx = mStateListState.indexOfStateSet(stateSet);

//获取对应索引位置的Drawable对象

if (selectDrawable(idx)) {

return true;

}

}

该函数的主要功能: 根据新的状态值,从StateListDrawable实例对象中,找到第一个完全吻合该新状态值的索引下标处 ;

继而,调用selectDrawable()方法去获取索引下标的当前Drawable对象。

具体查找算法在 mStateListState.indexOfStateSet(stateSet) 里实现了。基本思路是:查找第一个能完全吻合该新状态值

的索引下标,如果找到了,则立即返回。 具体实现过程,只好看看源码咯。

Step 3 、selectDrawable()函数原型:

该函数位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 类中

public boolean selectDrawable(int idx)

{

if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {

//获取对应索引位置的Drawable对象

Drawable d = mDrawableContainerState.mDrawables[idx];

mCurrDrawable = d; //mCurrDrawable即使当前Drawable对象

mCurIndex = idx;

} else {

}

//请求该View刷新自己,这个方法我们稍后讲解。

invalidateSelf();

return true;

}

该函数的主要功能是选择当前索引下标处的Drawable对象,并保存在mCurrDrawable中。

知识点三: 关于Drawable.Callback接口

该接口定义了如下三个函数:

//该函数位于 frameworks\base\graphics\java\android\graphics\drawable\Drawable.java 类中

public static interface Callback {

//如果Drawable对象的状态发生了变化,会请求View重新绘制,

//因此我们对应于该View的背景Drawable对象能够”绘制出来”.

public void invalidateDrawable(Drawable who);

//

public void scheduleDrawable(Drawable who, Runnable what, long when);

//

public void unscheduleDrawable(Drawable who, Runnable what);

}

其中比较重要的函数为:

public voidinvalidateDrawable(Drawable who)

函数功能:如果Drawable对象的状态发生了变化,会请求View重新绘制,因此我们对应于该View的背景Drawable对象

能够重新”绘制“出来。

Android框架View类继承了该接口,同时实现了这三个函数的默认处理方式,其中invalidateDrawable()方法如下:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource

{

//Invalidates the specified Drawable.

//默认实现,重新绘制该视图本身

public void invalidateDrawable(Drawable drawable) {

if (verifyDrawable(drawable)) { //是否是同一个Drawable对象,通常为真

final Rect dirty = drawable.getBounds();

final int scrollX = mScrollX;

final int scrollY = mScrollY;

//重新请求绘制该View,即重新调用该View的draw()方法 …

invalidate(dirty.left + scrollX, dirty.top + scrollY,

dirty.right + scrollX, dirty.bottom + scrollY);

}

}

}

因此,我们的Drawable类对象必须将View设置为回调对象,否则,即使改变了状态,也不会显示对应的背景图。 如下:

Drawable d  ;                // 图片资源

d.setCallback(View v) ;  // 视图v的背景资源为 d 对象

知识点四:View绘制背景图片过程

在前面的博客中《Android中View绘制流程以及invalidate()等相关方法分析》,我们知道了一个视图的背景绘制过程时在

View类里的draw()方法里完成的,我们这儿在回顾下draw()的流程,同时重点讲解下绘制背景的操作。

//方法所在路径:frameworks\base\core\java\android\view\View.java

//draw()绘制过程

private void draw(Canvas canvas){

//该方法会做如下事情

//1 、绘制该View的背景

//其中背景图片绘制过程如下:

//是否透明, 视图通常是透明的 , 为true

if (!dirtyOpaque) {

//开始绘制视图的背景

final Drawable background = mBGDrawable;

if (background != null) {

final int scrollX = mScrollX; //获取偏移值

final int scrollY = mScrollY;

//视图的布局坐标是否发生了改变, 即是否重新layout了。

if (mBackgroundSizeChanged) {

//如果是,我们的Drawable对象需要重新设置大小了,即填充该View。

background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);

mBackgroundSizeChanged = false;

}

//View没有发生偏移

if ((scrollX | scrollY) == 0) {

background.draw(canvas); //OK, 该方法会绘制当前StateListDrawable的当前背景Drawable

} else {

//View发生偏移,由于背景图片值显示在布局坐标中,即背景图片不会发生偏移,只有视图内容onDraw()会发生偏移

//我们调整canvas对象的绘制区域,绘制完成后对canvas对象属性调整回来

canvas.translate(scrollX, scrollY);

background.draw(canvas); //OK, 该方法会绘制当前StateListDrawable的当前背景Drawable

canvas.translate(-scrollX, -scrollY);

}

}

}

//2、为绘制渐变框做一些准备操作

//3、调用onDraw()方法绘制视图本身

//4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。

//5、绘制渐变框

}

That’s all ! 我们用到的知识点也就这么多吧。 如果大家有丝丝不明白的话,可以去看下源代码,具体去分析下这些流程到底

是怎么走下来的。

我们从宏观的角度分析了View绘制不同状态背景的原理,View框架就是这么做的。为了易于理解性,

下面我们通过一个小Demo来演示前面种种流程。

Demo 说明:

我们参照View框架中绘制不同背景图的实现原理,自定义一个View类,通过给它设定StateListDrawable对象,使其能够在

不同状态时能动态"绘制"背景图片。 基本流程方法和View.java类实现过程一模一样。

截图如下:

            

初始背景图                                                            触摸后显示的背景图(pressed)

一、主文件MainActivity.java如下:

/**

  • @author http://http://blog.csdn.net/qinjuning

*/

public class MainActivity extends Activity

{

@Override

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

LinearLayout ll = new LinearLayout(MainActivity.this);

CustomView customView = new CustomView(MainActivity.this);

//简单设置为 width 200px - height 100px吧

ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(200 , 100);

customView.setLayoutParams(lp);

//需要将该View设置为可点击/触摸状态,否则触摸该View没有效果。

customView.setClickable(true);

ll.addView(customView);

setContentView(ll);

}

}

功能很简单,为Activity设置了视图 。

二、 自定义View如下 , CustomView.java :

/**

  • @author http://http://blog.csdn.net/qinjuning

*/

//自定义View

public class CustomView extends View /extends Button/

{

private static String TAG = “TackTextView”;

private Context mContext = null;

private Drawable mBackground = null;

private boolean mBGSizeChanged = true;; //视图View布局(layout)大小是否发生变化

public CustomView(Context context)

{

super(context);

mContext = context;

initStateListDrawable(); // 初始化图片资源

}

// 初始化图片资源

private void initStateListDrawable()

{

//有两种方式获取我们的StateListDrawable对象:

// 获取方式一、手动构建一个StateListDrawable对象

StateListDrawable statelistDrawable = new StateListDrawable();

int pressed = android.R.attr.state_pressed;

int windowfocused = android.R.attr.state_window_focused;

int enabled = android.R.attr.state_enabled;

int stateFoucesd = android.R.attr.state_focused;

//匹配状态时,是一种优先包含的关系。

// "-"号表示该状态值为false .即不匹配

statelistDrawable.addState(new int[] { pressed, windowfocused },

mContext.getResources().getDrawable(R.drawable.btn_power_on_pressed));

statelistDrawable.addState(new int[]{ -pressed, windowfocused },

mContext.getResources().getDrawable(R.drawable.btn_power_on_nor));

mBackground = statelistDrawable;

//必须设置回调,当改变状态时,会回掉该View进行invalidate()刷新操作.

mBackground.setCallback(this);

//取消默认的背景图片,因为我们设置了自己的背景图片了,否则可能造成背景图片重叠。

this.setBackgroundDrawable(null);

// 获取方式二、、使用XML获取StateListDrawable对象

// mBackground = mContext.getResources().getDrawable(R.drawable.tv_background);

}

protected void drawableStateChanged()

{

Log.i(TAG, “drawableStateChanged”);

Drawable d = mBackground;

if (d != null && d.isStateful())

{

d.setState(getDrawableState());

Log.i(TAG, “drawableStateChanged and is 111”);

}

Log.i(TAG, “drawableStateChanged and is 222”);

super.drawableStateChanged();

}

//验证图片是否相等 , 在invalidateDrawable()会调用此方法,我们需要重写该方法。

protected boolean verifyDrawable(Drawable who)

{

return who == mBackground || super.verifyDrawable(who);

}

//draw()过程,绘制背景图片…

public void draw(Canvas canvas)

{

Log.i(TAG, " draw -----");

if (mBackground != null)

{

if(mBGSizeChanged)

{

//设置边界范围

mBackground.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());

mBGSizeChanged = false ;

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

面试复习笔记:

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

获取!!(备注:Android)**

面试复习笔记:

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

[外链图片转存中…(img-aFqFipuy-1712209212190)]

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

[外链图片转存中…(img-3E41t4Tf-1712209212190)]

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值