Android Drawable 那些不为人知的高效用法

分类:【Android 自定义控件实战】【Android 源码解析】 22935人阅读 评论(49) 收藏 举报 Android Drawable Custom State RoundImageView

目录(?)[+]

转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/43752383,本文出自: 【张鸿洋的博客】

1、概述

Drawable在我们平时的开发中,基本都会用到,而且给大家非常的有用。那么什么是Drawable呢?能够在canvas上绘制的一个玩意,而且相比于View,并不需要去考虑measure、layout,仅仅只要去考虑如何draw(canavs)。当然了,对于Drawable传统的用法,大家肯定不陌生 ,今天主要给大家带来以下几个Drawable的用法:

1、自定义Drawable,相比View来说,Drawable属于轻量级的、使用也很简单。以后自定义实现一个效果的时候,可以改变View first的思想,尝试下Drawable first。

2、自定义状态,相信大家对于State Drawable都不陌生,但是有没有尝试过去自定义一个状态呢?

3、利用Drawable提升我们的UI Perfermance , 如何利用Drawable去提升我们的UI的性能。


2、Drawable基本概念

一般情况下,除了直接使用放在Drawable下的图片,其实的Drawable的用法都和xml相关,我们可以使用shape、layer-list等标签绘制一些背景,还可以通过selector标签定义View的状态的效果等。当然了基本每个标签都对应于一个真正的实体类,关系如下:(图片来自:Cyril Mottier :master_android_drawables)

Android Drawable 那些不为人知的高效用法_第1张图片

常见的用法这里就不举例了,下面开始看本文的重点。

2、自定义Drawable

关于自定义Drawable,可以通过写一个类,然后继承自Drawable , 类似于自定义View,当然了自定义Drawable的核心方法只有一个,那就是draw。那么自定义Drawable到底有什么实际的作用呢?能干什么呢?

相信大家对于圆角、圆形图片都不陌生,并且我曾经写过通过自定义View实现的方式,具体可参考:

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

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

那我今天要告诉你,不需要自定义View,自定义Drawable也能实现,而且更加简单、高效、使用范围更广(你可以作为任何View的背景)。

1、RoundImageDrawable

代码比较简单,下面看下RoundImageDrawable

[java] view plain copy
  1. packagecom.zhy.view;
  2. importandroid.graphics.Bitmap;
  3. importandroid.graphics.BitmapShader;
  4. importandroid.graphics.Canvas;
  5. importandroid.graphics.ColorFilter;
  6. importandroid.graphics.Paint;
  7. importandroid.graphics.PixelFormat;
  8. importandroid.graphics.RectF;
  9. importandroid.graphics.Shader.TileMode;
  10. importandroid.graphics.drawable.Drawable;
  11. publicclassRoundImageDrawableextendsDrawable
  12. {
  13. privatePaintmPaint;
  14. privateBitmapmBitmap;
  15. privateRectFrectF;
  16. publicRoundImageDrawable(Bitmapbitmap)
  17. {
  18. mBitmap=bitmap;
  19. BitmapShaderbitmapShader=newBitmapShader(bitmap,TileMode.CLAMP,
  20. TileMode.CLAMP);
  21. mPaint=newPaint();
  22. mPaint.setAntiAlias(true);
  23. mPaint.setShader(bitmapShader);
  24. }
  25. @Override
  26. publicvoidsetBounds(intleft,inttop,intright,intbottom)
  27. {
  28. super.setBounds(left,top,right,bottom);
  29. rectF=newRectF(left,top,right,bottom);
  30. }
  31. @Override
  32. publicvoiddraw(Canvascanvas)
  33. {
  34. canvas.drawRoundRect(rectF,30,30,mPaint);
  35. }
  36. @Override
  37. publicintgetIntrinsicWidth()
  38. {
  39. returnmBitmap.getWidth();
  40. }
  41. @Override
  42. publicintgetIntrinsicHeight()
  43. {
  44. returnmBitmap.getHeight();
  45. }
  46. @Override
  47. publicvoidsetAlpha(intalpha)
  48. {
  49. mPaint.setAlpha(alpha);
  50. }
  51. @Override
  52. publicvoidsetColorFilter(ColorFiltercf)
  53. {
  54. mPaint.setColorFilter(cf);
  55. }
  56. @Override
  57. publicintgetOpacity()
  58. {
  59. returnPixelFormat.TRANSLUCENT;
  60. }
  61. }

核心代码就是draw了,but,我们只需要一行~~~~setAlpha、setColorFilter、getOpacity、draw这几个方法是必须实现的,不过除了draw以为,其他都很简单。getIntrinsicWidth、getIntrinsicHeight主要是为了在View使用wrap_content的时候,提供一下尺寸,默认为-1可不是我们希望的。setBounds就是去设置下绘制的范围。

ok,圆角图片就这么实现了,easy 不~~

看下用法:

[java] view plain copy
  1. Bitmapbitmap=BitmapFactory.decodeResource(getResources(),
  2. R.drawable.mv);
  3. ImageViewiv=(ImageView)findViewById(R.id.id_one);
  4. iv.setImageDrawable(newRoundImageDrawable(bitmap));

ok,贴一下我们的效果图,两个ImageView和一个TextView

Android Drawable 那些不为人知的高效用法_第2张图片

可以看到,不仅仅用于ImageView去实现圆角图片,并且可以作为任何View的背景,在ImageView中的拉伸的情况,配下ScaleType即可。在其他View作为背景时,如果出现拉伸情况,请参考:Android BitmapShader 实战 实现圆形、圆角图片。 足够详细了。

2、CircleImageDrawable

那么下来,我们再看看自定义圆形Drawable的写法:

[java] view plain copy
  1. packagecom.zhy.view;
  2. importandroid.graphics.Bitmap;
  3. importandroid.graphics.BitmapShader;
  4. importandroid.graphics.Canvas;
  5. importandroid.graphics.ColorFilter;
  6. importandroid.graphics.Paint;
  7. importandroid.graphics.PixelFormat;
  8. importandroid.graphics.RectF;
  9. importandroid.graphics.Shader.TileMode;
  10. importandroid.graphics.drawable.Drawable;
  11. publicclassCircleImageDrawableextendsDrawable
  12. {
  13. privatePaintmPaint;
  14. privateintmWidth;
  15. privateBitmapmBitmap;
  16. publicCircleImageDrawable(Bitmapbitmap)
  17. {
  18. mBitmap=bitmap;
  19. BitmapShaderbitmapShader=newBitmapShader(bitmap,TileMode.CLAMP,
  20. TileMode.CLAMP);
  21. mPaint=newPaint();
  22. mPaint.setAntiAlias(true);
  23. mPaint.setShader(bitmapShader);
  24. mWidth=Math.min(mBitmap.getWidth(),mBitmap.getHeight());
  25. }
  26. @Override
  27. publicvoiddraw(Canvascanvas)
  28. {
  29. canvas.drawCircle(mWidth/2,mWidth/2,mWidth/2,mPaint);
  30. }
  31. @Override
  32. publicintgetIntrinsicWidth()
  33. {
  34. returnmWidth;
  35. }
  36. @Override
  37. publicintgetIntrinsicHeight()
  38. {
  39. returnmWidth;
  40. }
  41. @Override
  42. publicvoidsetAlpha(intalpha)
  43. {
  44. mPaint.setAlpha(alpha);
  45. }
  46. @Override
  47. publicvoidsetColorFilter(ColorFiltercf)
  48. {
  49. mPaint.setColorFilter(cf);
  50. }
  51. @Override
  52. publicintgetOpacity()
  53. {
  54. returnPixelFormat.TRANSLUCENT;
  55. }
  56. }

一样出奇的简单,再看一眼效果图:

Android Drawable 那些不为人知的高效用法_第3张图片

ok,关于自定义Drawable的例子over~~~接下来看自定义状态的。

上述参考了:Romain Guy's Blog

3、自定义Drawable State

关于Drawable State,state_pressed神马的,相信大家都掌握的特别熟练了。

那么接下来,我们有个需求,类似于邮箱,邮件以ListView形式展示,但是我们需要一个状态去标识出未读和已读:so,我们自定义一个状态state_message_readed。

效果图:

Android Drawable 那些不为人知的高效用法_第4张图片

可以看到,如果是已读的邮件,我们的图标是打开状态,且有个淡红色的背景。那么如何通过自定义drawable state 实现呢?

自定义drawable state 需要分为以下几个步骤:

1、res/values/新建一个xml文件:drawable_status.xml

[html] view plain copy
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <resources>
  3. <declare-styleablename="MessageStatus">
  4. <attrname="state_message_readed"format="boolean"/>
  5. </declare-styleable>
  6. </resources>


2、继承Item的容器

我们这里Item选择RelativeLayout实现,我们需要继承它,然后复写它的onCreateDrawableState方法,把我们自定义的状态在合适的时候添加进去。

[java] view plain copy
  1. packagecom.zhy.view;
  2. importcom.zhy.sample.drawable.R;
  3. importandroid.content.Context;
  4. importandroid.util.AttributeSet;
  5. importandroid.widget.RelativeLayout;
  6. publicclassMessageListItemextendsRelativeLayout
  7. {
  8. privatestaticfinalint[]STATE_MESSAGE_READED={R.attr.state_message_readed};
  9. privatebooleanmMessgeReaded=false;
  10. publicMessageListItem(Contextcontext,AttributeSetattrs)
  11. {
  12. super(context,attrs);
  13. }
  14. publicvoidsetMessageReaded(booleanreaded)
  15. {
  16. if(this.mMessgeReaded!=readed)
  17. {
  18. mMessgeReaded=readed;
  19. refreshDrawableState();
  20. }
  21. }
  22. @Override
  23. protectedint[]onCreateDrawableState(intextraSpace)
  24. {
  25. if(mMessgeReaded)
  26. {
  27. finalint[]drawableState=super
  28. .onCreateDrawableState(extraSpace+1);
  29. mergeDrawableStates(drawableState,STATE_MESSAGE_READED);
  30. returndrawableState;
  31. }
  32. returnsuper.onCreateDrawableState(extraSpace);
  33. }
  34. }

代码不复杂,声明了一个STATE_MESSAGE_READED,然后在mMessgeReaded=true的情况下,通过onCreateDrawableState方法,加入我们自定义的状态。

类似的代码,大家可以看看CompoundButton(CheckBox父类)的源码,它有个checked状态:

[java] view plain copy
  1. @Override
  2. protectedint[]onCreateDrawableState(intextraSpace){
  3. finalint[]drawableState=super.onCreateDrawableState(extraSpace+1);
  4. if(isChecked()){
  5. mergeDrawableStates(drawableState,CHECKED_STATE_SET);
  6. }
  7. returndrawableState;
  8. }

3、使用

布局文件:

[html] view plain copy
  1. <com.zhy.view.MessageListItemxmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="50dp"
  5. android:background="@drawable/message_item_bg">
  6. <ImageView
  7. android:id="@+id/id_msg_item_icon"
  8. android:layout_width="30dp"
  9. android:src="@drawable/message_item_icon_bg"
  10. android:layout_height="wrap_content"
  11. android:duplicateParentState="true"
  12. android:layout_alignParentLeft="true"
  13. android:layout_centerVertical="true"
  14. />
  15. <TextView
  16. android:id="@+id/id_msg_item_text"
  17. android:layout_width="match_parent"
  18. android:layout_height="wrap_content"
  19. android:layout_centerVertical="true"
  20. android:layout_toRightOf="@id/id_msg_item_icon"/>
  21. </com.zhy.view.MessageListItem>

很简单,一个图标,一个文本;

Activity

[java] view plain copy
  1. packagecom.zhy.sample.drawable;
  2. importcom.zhy.view.MessageListItem;
  3. importandroid.app.ListActivity;
  4. importandroid.os.Bundle;
  5. importandroid.view.LayoutInflater;
  6. importandroid.view.View;
  7. importandroid.view.ViewGroup;
  8. importandroid.widget.ArrayAdapter;
  9. importandroid.widget.TextView;
  10. publicclassCustomStateActivityextendsListActivity
  11. {
  12. privateMessage[]messages=newMessage[]{
  13. newMessage("Gasbilloverdue",true),
  14. newMessage("Congratulations,you'vewon!",true),
  15. newMessage("Iloveyou!",false),
  16. newMessage("Pleasereply!",false),
  17. newMessage("Youignoringme?",false),
  18. newMessage("Notheardfromyou",false),
  19. newMessage("Electricitybill",true),
  20. newMessage("Gasbill",true),newMessage("Holidayplans",false),
  21. newMessage("Marketingstuff",false),};
  22. @Override
  23. protectedvoidonCreate(BundlesavedInstanceState)
  24. {
  25. super.onCreate(savedInstanceState);
  26. getListView().setAdapter(newArrayAdapter<Message>(this,-1,messages)
  27. {
  28. privateLayoutInflatermInflater=LayoutInflater
  29. .from(getContext());
  30. @Override
  31. publicViewgetView(intposition,ViewconvertView,ViewGroupparent)
  32. {
  33. if(convertView==null)
  34. {
  35. convertView=mInflater.inflate(R.layout.item_msg_list,
  36. parent,false);
  37. }
  38. MessageListItemmessageListItem=(MessageListItem)convertView;
  39. TextViewtv=(TextView)convertView
  40. .findViewById(R.id.id_msg_item_text);
  41. tv.setText(getItem(position).message);
  42. messageListItem.setMessageReaded(getItem(position).readed);
  43. returnconvertView;
  44. }
  45. });
  46. }
  47. }

代码很简单,但是可以看到,我们需要在getView里面中去使用调用setMessageReaded方法,当然了其他的一些状态,肯定也要手动触发,比如在ACTION_DOWN中触发pressed等。请勿纠结咋没有使用ViewHolder什么的,自己添加下就行。

本例参考自:Example from github

4、提升我们的UI Perfermance

现在大家越来越注重性能问题,其实没必要那么在乎,但是既然大家在乎了,这里通过Cyril Mottier :master_android_drawables ppt中的一个例子来说明如果利用Drawable来提升我们的UI的性能。

大家看这样一个效果图:

Android Drawable 那些不为人知的高效用法_第5张图片

布局文件:

[html] view plain copy
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:background="@color/app_background"
  6. android:padding="8dp">
  7. <ImageView
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:layout_gravity="center"
  11. android:layout_marginBottom="24dp"
  12. android:src="@drawable/logo"/>
  13. <LinearLayout
  14. android:layout_width="match_parent"
  15. android:layout_height="48dp"
  16. android:layout_gravity="bottom"
  17. android:orientation="horizontal">
  18. <Button
  19. android:layout_width="0dp"
  20. android:layout_height="fill_parent"
  21. android:layout_weight="1"
  22. android:text="@string/sign_up"/>
  23. <Button
  24. android:layout_width="0dp"
  25. android:layout_height="fill_parent"
  26. android:layout_weight="1"
  27. android:text="@string/sign_in"/>
  28. </LinearLayout>
  29. </FrameLayout>

可以看到最外层是FrameLayout仅仅是为了设置背景图和padding,这样的布局相信很多人也写过。

再看看这个布局作为APP启动时,用户的直观效果:

Android Drawable 那些不为人知的高效用法_第6张图片

用户首先看到一个白板,然后显示出我们的页面。接下来,我们将利用Drawable改善我们的UI性能以及用户体验。

1、首先,我们去除我们最外层的FrameLayout,然后自定义一个drawable的xml,叫做logo.xml

[html] view plain copy
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <layer-listxmlns:android="http://schemas.android.com/apk/res/android">
  3. <item>
  4. <shapeandroid:shape="rectangle">
  5. <solidandroid:color="@color/app_background"/>
  6. </shape>
  7. </item>
  8. <itemandroid:bottom="48dp">
  9. <bitmap
  10. android:gravity="center"
  11. android:src="@drawable/logo"/>
  12. </item>
  13. </layer-list>

ok,这个drawable是设置了我们的背景和logo;

2、将其作为我们当前Activity的windowBackground

[html] view plain copy
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <resources>
  3. <style
  4. name="Theme.Default.NoActionBar"
  5. parent="@android:style/Theme.Holo.Light.NoActionBar">
  6. <itemname="android:windowBackground">@drawable/login</item>
  7. </style>
  8. </resources>

3、设置到Activity上:

[html] view plain copy
  1. <activity
  2. android:name="LoginActivity"
  3. android:theme="@style/Theme.Default.NoActionBar">

Ok,这样不仅最小化了我们的layout,现在我们的layout里面只有一个LinearLayout和两个按钮;并且提升了用户体验,现在用户的直观效果时:

Android Drawable 那些不为人知的高效用法_第7张图片

是不是体验好很多,个人很喜欢这个例子~~


ok,到此我们的文章就over了~~~大多数内容参考自一些牛人写的例子,例子还是棒棒哒,大家看完本文的同时,也可以去挖掘挖掘一些东西~~


源码点击下载


---------------------------------------------------------------------------------

群号:423372824

博主部分视频已经上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):

视频目录地址:本人录制的视频教程

微信公众号请扫描:

Android Drawable 那些不为人知的高效用法_第8张图片

--------------------------------------------------------------------------------------------------------

更多相关文章

  1. Android -- 加载大图片的方法
  2. Android 设备监听网络状态变化
  3. Android WebView 支持H5图片上传
  4. Android 中的高效数据结构
  5. 通過android selector改变界面状态
  6. android 首开机会在数据链接图标的状态栏打开并自行消失主动
  7. Android上传图片(PHP服务器)
  8. android沉浸式状态栏底部背景用图片代替
  9. 浅入浅出Android(015):使用ImageView显示网络图片

随机推荐

  1. [置顶] Android(安卓)多点触摸 multi-tou
  2. Android完美解决多次点击Toast一直提示不
  3. android Uri.Buildr 与string 互转
  4. Android如何进行单元测试
  5. Android短信发送功能
  6. ANDROID 获取SD卡剩余容量
  7. Android 获取联网方式及代理联网
  8. 【Android】图像中Drawable向Bitmap的两
  9. Android 开发图片压缩/缩略图的方法
  10. android使用volley等网络资源请求时注意