2024年最全Android 应用层开发 Drawable 的一些叨叨絮,应届毕业生硬件工程师面试题

结语

看到这篇文章的人不知道有多少是和我一样的Android程序员。

35岁,这是我们这个行业普遍的失业高发阶段,这种情况下如果还不提升自己的技能,进阶发展,我想,很可能就是本行业的职业生涯的终点了。

我们要有危机意识,切莫等到一切都成定局时才开始追悔莫及。只要有规划的,有系统地学习,进阶提升自己并不难,给自己多充一点电,你才能走的更远。

千里之行始于足下。这是上小学时,那种一元钱一个的日记本上每一页下面都印刷有的一句话,当时只觉得这句话很短,后来渐渐长大才慢慢明白这句话的真正的含义。

有了学习的想法就赶快行动起来吧,不要被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。

给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

public int getAlpha() {return 0xFF;}

/**

  • @hide 被匿了,给将来准备的货,这就尴尬了,类比Paint的Xfermode吧。

*/

public void setXfermode(@Nullable Xfermode mode) {…}

/**

  • 设置滤镜效果,类似Paint的setColorFilter()方法,譬如我们常干的一件事:

  • textview.getBackground().setColorFilter(new LightingColorFilter(0xAAFFCCEE, 0xFF00BBAA));

  • 不懂的去前面翻自定义博客文章吧,取消滤镜效果直接传null即可。

  • 有时候我们自定义Drawable时会在内部定义一个Paint,所以setAlpha实现也可以直接为mPaint.setColorFilter(colorFilter);

*/

public abstract void setColorFilter(@Nullable ColorFilter colorFilter);

/**

  • 设置滤镜效果,同上setColorFilter(@Nullable ColorFilter colorFilter);

  • 其实就是setColorFilter(new PorterDuffColorFilter(color, mode));的封装。

*/

public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {…}

public @Nullable ColorFilter getColorFilter() {…}

public void clearColorFilter() {…}

/**

  • Drawable的Tint变色处理,setTint为setTintList的封装而已,注意API兼容问题,一般可用DrawableCompat的。

  • 实际用处譬如我们自定义的selector drawable,getResources().getColorStateList(R.color.selector_XXX));

  • 注意:如果设置了setColorFilter,则setTint和setTintList将无效。

*/

public void setTint(@ColorInt int tintColor) {…}

public void setTintList(@Nullable ColorStateList tint) {…}

/**

  • 设置上面Tint变色时的PorterDuff.Mode模式,默认为PorterDuff.Mode.SRC_IN。

  • 注意:如果设置了setColorFilter,则setTintMode将无效。

*/

public void setTintMode(@NonNull PorterDuff.Mode tintMode) {}

/**

  • 设置热点坐标,5.0加入,坑爹。

  • RippleDrawable就是一个以波纹效果来显示状态变化的Drawable,其中波纹的位置就是这玩意设置的。

*/

public void setHotspot(float x, float y) {}

public void setHotspotBounds(int left, int top, int right, int bottom) {}

public void getHotspotBounds(@NonNull Rect outRect) {…}

/**

  • 设置和获取状态,有变化时会触发onStateChange。譬如:

  • {@link android.R.attr#state_focused}

  • {@link android.R.attr#state_pressed}

  • 在View状态改变的时候,会调用Drawable的setState函数。

*/

public boolean setState(@NonNull final int[] stateSet) {…}

protected boolean onStateChange(int[] state) {…}

public @NonNull int[] getState() {…}

/**

  • 上面setState只能定义有限的几种状态,如果需要更多的状态,就可以使用图像级别资源。

  • 图像级别资源文件中可以定义任意多个图像级别,可以通过该方法来切换不同状态的图像。

*/

public final boolean setLevel(@IntRange(from=0,to=10000) int level) {…}

public final @IntRange(from=0,to=10000) int getLevel() {…}

protected boolean onLevelChange(int level) {…}

/**

  • 设置或者获取Drawable是否可见。

*/

public boolean setVisible(boolean visible, boolean restart) {…}

public final boolean isVisible() {}

/**

  • 返回当前Drawable透明或者半透明或者不透明等,默认不清楚时直接返回TRANSLUCENT是最好的选择。

  • {@link android.graphics.PixelFormat}:

  • {@link android.graphics.PixelFormat#UNKNOWN},

  • {@link android.graphics.PixelFormat#TRANSLUCENT},

  • {@link android.graphics.PixelFormat#TRANSPARENT}, or

  • {@link android.graphics.PixelFormat#OPAQUE}.

*/

public abstract @PixelFormat.Opacity int getOpacity();

/**

  • 返回Drawable的真实宽高,包含padding等,如果没有宽高(譬如纯Color)则返回-1即可。

*/

public int getIntrinsicWidth() {…}

public int getIntrinsicHeight() {…}

/**

  • 返回建议的最小宽高,一般这个宽高决定了自定义View在有些情况下的宽高。

*/

public int getMinimumWidth() {…}

public int getMinimumHeight() {…}

/**

  • 返回Drawable的padding,没有padding时return false,反之。

*/

public boolean getPadding(@NonNull Rect padding) {…}

/**

  • 如果有多个控件同时使用某一资源且要改变该资源的状态,我们就需要用mutate方法。

  • 使用mutate方法是为了更改一个资源状态时其它引用该资源的控件不被改变,因为默认情况下,所有从同一资源(R.drawable.XXX)

  • 加载的Drawable实例都共享一个共用的状态ConstantState,如果我们更改了一个实例的状态,其余的实例都会接收到相同的更改变化。

  • @see ConstantState

  • @see #getConstantState()

*/

public @NonNull Drawable mutate() {return this;}

/**

  • 通过inputstream、xml、FilePath创建一个Drawable。

*/

public static Drawable createFromStream(InputStream is, String srcName) {…}

public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName) {…}

public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) {…}

public static Drawable createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException {…}

public static Drawable createFromXml(Resources r, XmlPullParser parser, Theme theme) throws XmlPullParserException, IOException {…}

public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {…}

public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException {…}

public static Drawable createFromPath(String pathName) {…}

public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {…}

public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException {…}

void inflateWithAttributes(@NonNull @SuppressWarnings(“unused”) Resources r, @NonNull @SuppressWarnings(“unused”) XmlPullParser parser, @NonNull TypedArray attrs, @AttrRes int visibleAttr) throws XmlPullParserException, IOException {…}

/**

  • 每一个Drawable对象都关联一个ConstantState对象,目的是为了保存Drawable对象的一些恒定不变数据,

  • 为了节约内存,Android系统对于从同一个res创建的Drawable对象会共享同一个ConstantState对象。

*/

public static abstract class ConstantState {

public abstract @NonNull Drawable newDrawable();

public @NonNull Drawable newDrawable(@Nullable Resources res) {return newDrawable();}

public @NonNull Drawable newDrawable(@Nullable Resources res, @Nullable @SuppressWarnings(“unused”) Theme theme) {return newDrawable(res);}

public abstract @Config int getChangingConfigurations();

public int addAtlasableBitmaps(@NonNull Collection atlasList) {return 0;}

protected final boolean isAtlasable(@Nullable Bitmap bitmap) {…}

public boolean canApplyTheme() {return false;}

}

/**

  • 返回一个当前Drawable的ConstantState,参见mutate方法。

*/

public @Nullable ConstantState getConstantState() {return null;}

}

确实闷逼!上面涉及 Paint 和 Canvas 的注释不懂的可以参考我这篇博文《Android应用自定义View绘制方法手册》。不过这和自定义 View 一样,为了强大,所以提供的方法都很基础专一,只有这样才能自由组合为所欲为;不过对于我们大多数自定义需求来说使用 Drawable 的方法是十分有限的,所以不要被那么多方法蒙蔽了双眼。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

3 Drawable 调用流程浅析

=====================

能耐心看完上面的是真爱,有了上面的解释我想你此刻一定在想,我自定义 View 好歹也有个调用流程、好歹也有个规律可循的结构啊,这自定义 Drawable 怎么是完全闷逼的,这就对了,这一小节就是打算通过源码给你揭开 Drawable 的调用流程,以此让你在自定义 Drawable 时做到胸有成竹。

为了让大家浅显易懂的知道 Drawable 的作用和用法,有了上面 Drawable 的分析外我们还需要给一个使用样例分析;其实关于 Drawable 最好的使用样例是 ImageView,鉴于 ImageView 源码的庞大复杂性,我们这里选择之前有铺垫分析的 View 为样例,看过《Android应用层View绘制流程与源码分析》的同学都知道,在 Android 中每个 View 都至少有一个 Drawable 成员变量,尤其在基类 View 中有一个背景 Drawable。我们来看看这段暧昧的代码:

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

private Drawable mBackground;

//View构造方法

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {

//获取View的属性

final TypedArray a = context.obtainStyledAttributes(

attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);

//定义一个局部背景变量Drawable

Drawable background = null;

//获取到设置的background属性,也就是我们平时给各个控件在xml中设置的android:background属性值

case com.android.internal.R.styleable.View_background:

background = a.getDrawable(attr);

break;

//当设置有背景Drawable属性时调用setBackground方法

if (background != null) {

setBackground(background);

}

}

}

首先我们会发现在 Android 任何 View 控件中都有一个 Background 属性,框架会在 View 构造方法中获取这个属性 Drawable,然后传入 setBackground(background) 方法,这方法大家一定都很熟悉吧,我们通常总是通过各种 setBackgroundXXX 方法来给控件设置背景,用的就是这个系列方法,所以我们就直接去看看这个系列方法最终归处 setBackgroundDrawable(Drawable background) 方法,如下:

//几种设置背景的方法终归都是这个

public void setBackgroundDrawable(Drawable background) {

//每次设置新的background后就进行复位操作

if (mBackground != null) {

if (isAttachedToWindow()) {

//Drawable设置为不可见(这部就用上上面Drawable的分析了么)

mBackground.setVisible(false, false);

}

//Drawable回调断开(这部就用上上面Drawable的分析了么)

mBackground.setCallback(null);

//移除Drawable绘制队列,实质触发回调的该方法(这部就用上上面Drawable的分析了么)

unscheduleDrawable(mBackground);

}

if (background != null) {

//当有设置背景Drawable

//给Drawable设置View当前的布局方向(这部就用上上面Drawable的分析了么)

background.setLayoutDirection(getLayoutDirection());

//判断当前Drawable是否设置有padding(这部就用上上面Drawable的分析了么,有padding则返回true)

if (background.getPadding(padding)) {

//依据Drawable的这个padding去给当前View相关padding属性建议修改

}

//比较上次旧的(可能没有)和现在设置的Drawable的最小宽高,发现不一样就预示着我们接下来需要对View再次layout,做标记

if (mBackground == null

|| mBackground.getMinimumHeight() != background.getMinimumHeight()

|| mBackground.getMinimumWidth() != background.getMinimumWidth()) {

requestLayout = true;

}

//把要新设置的Drawable正式赋值给View的mBackground成员

mBackground = background;

//判断当状态改变时当前Drawable是否需要切换图片,一般在StateListDrawable实现中为true(这部就用上上面Drawable的分析了么)

if (background.isStateful()) {

//设置Drawable状态(这部就用上上面Drawable的分析了么)

background.setState(getDrawableState());

}

//如果View已经attached to window了就把Drawable设置为可见(这部就用上上面Drawable的分析了么)

if (isAttachedToWindow()) {

background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);

}

//设置Drawable的callback,在View继承关系中有实现Drawable的callback

background.setCallback(this);

} else {

//当没有设置背景Drawable时清空背景Drawable,然后设置View的重新layout标志

mBackground = null;

requestLayout = true;

}

//需要重新布局,触发重新布局

if (requestLayout) {

requestLayout();

}

//通知重新绘制刷新操作

mBackgroundSizeChanged = true;

invalidate(true);

invalidateOutline();

}

可以看见,每逢我们对控件通过 xml 或者 java 设置 background 后触发的其实就是上面这一堆操作,实质最后触发的就是 layout 或者 draw。我们知道 View 默认的 onLayout 和 onDraw 是空实现,所以我们顺着流程去看看 View 的 draw 方法,如下:

public void draw(Canvas canvas) {

/*

  •  1. Draw the background
    
  •  2. If necessary, save the canvas' layers to prepare for fading
    
  •  3. Draw view's content
    
  •  4. Draw children
    
  •  5. If necessary, draw the fading edges and restore layers
    
  •  6. Draw decorations (scrollbars for instance)
    

*/

drawBackground(canvas);

}

有点意思,再来看看 drawBackground(canvas) 方法,如下:

private void drawBackground(Canvas canvas) {

//实质调用了Drawable的setBounds方法,把当前View测量好的矩形区域顶点赋值给Drawable,说明接下来Drawable绘制区域与View大小相同。

//该方法实质:mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);

setBackgroundBounds();

//有意思!

//简单粗暴理解就是View要是能滑动,化到哪Drawable背景画到哪(其实就是每次滑动都按可见区域绘制Drawable,因为canvas已经被依据滑动平移了)

final int scrollX = mScrollX;

final int scrollY = mScrollY;

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

background.draw(canvas);

} else {

canvas.translate(scrollX, scrollY);

//这不就是Drawable的draw方法么,和前面分析的一样,最后View框架会调用我们Drawable的draw方法,传入的是当前View的canvas而已。

background.draw(canvas);

canvas.translate(-scrollX, -scrollY);

}

}

握草,View 与 Drawable 的暧昧几乎真相大白了,由此更加验证了背景知识简单粗暴的介绍—— Drawable 就是一个框架工具,配合给 View 绘制来用的而已,所以通过上面两大节源码分析,我想你至少脑袋中已经产生了下面这个抽象的不能再抽象的对比图,然后也能恍然大悟吧。

这里写图片描述

这时候机智的人肯定又会问,那我设置完 background 的 Drawable 后 selector 那种状态切换又是咋回事呢?这儿只能回答你,自己动手去 View 中翻翻 state 相关的代码吧,这儿懒得说了,很简单的,核心就是 Android 中 Drawable 的一个特殊实现子类而已。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

4 Drawable 自定义实战

====================

都说了,作为程序员和老大 PK 的真谛是 “Talk is easy, show me the code!”,上面几个小节 BB 了那么多源码,估计很多人都看不到这里就关闭博文了,没事,下面不 BB 了,我们直接来实战,这样你再通过实战的例子回过头去看看上面的源码分析,你会发现你已经完全领悟了。

下面先给出自定义 Drawable 的一般套路模板(通过自定义 Drawable 画一个黑圆为例),大家日后自定义按照这个模板思路来套即可,效果如下:

这里写图片描述

模板套路代码结构如下:

//自定义 Drawable 基本模板(实现一个黑圆Drawable)

public class RoundDrawable extends Drawable {

private Paint mPaint;

Android高级架构师

由于篇幅问题,我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。

  • 330页PDF Android学习核心笔记(内含上面8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT部分大厂面试题(有解析)

好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。

  • 330页PDF Android学习核心笔记(内含上面8大板块)

[外链图片转存中…(img-86YNsSBv-1715115221507)]

[外链图片转存中…(img-EjTcX8sN-1715115221508)]

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

[外链图片转存中…(img-5BrpY22N-1715115221508)]

  • Android BAT部分大厂面试题(有解析)

[外链图片转存中…(img-6g3ESqJJ-1715115221508)]

好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值