Android Drawable, which is unknown for Efficient Usage
1. Overview
Drawable is basically used in our usual development and is very useful to everyone. So what is Drawable? A thing that can be drawn on a canvas. Compared with a View, you do not need to consider measure and layout. You only need to consider how to draw a drawing (canavs ). Of course, there are certainly no strangers to the traditional usage of Drawable. Today we will mainly introduce the following usage of Drawable:
1. Custom Drawable. Compared with View, Drawable is lightweight and easy to use. In the future, you can change the idea of View first and try Drawable first.
2. I believe everyone is familiar with State Drawable, but have you ever tried to customize a State?
3. Use Drawable to improve our UI Perfermance and how to use Drawable to improve our UI performance.
2. Basic concepts of Drawable
In general, in addition to directly using images placed under Drawable, the actual usage of Drawable is related to xml. We can use labels such as shape and layer-list to draw background information, you can also use the selector label to define the effect of the View status. Of course, each tag corresponds to a real object class. The relationship is as follows: (picture from: Cyril Mottier: master_android_drawables)
Common usage here is not an example. The focus of this article is as follows.
2. Custom Drawable
For custom Drawable, you can write a class and inherit from the Drawable, similar to the custom View. Of course, there is only one core method for custom Drawable, that is, draw. So what is the actual function of custom Drawable? What can we do?
I believe that everyone is familiar with rounded corners and circular images. I have written about the implementation of custom views. For details, refer:
AndroidBitmapShader implements circular and rounded image
Android Xfermode implements circular and rounded image
Today, I want to tell you that custom views are also implemented without the need for custom Drawable, Which is simpler, more efficient, and more widely used (you can be the background of any View ).
1. RoundImageDrawable
The code is relatively simple. Let's take a look at RoundImageDrawable.
[Java]View plaincopy
- Packagecom. zhy. view;
-
- Importandroid. graphics. Bitmap;
- Importandroid. graphics. BitmapShader;
- Importandroid. graphics. Canvas;
- Importandroid. graphics. ColorFilter;
- Importandroid. graphics. Paint;
- Importandroid. graphics. PixelFormat;
- Importandroid. graphics. RectF;
- Importandroid. graphics. Shader. TileMode;
- Importandroid. graphics. drawable. Drawable;
-
- PublicclassRoundImageDrawableextendsDrawable
- {
-
- PrivatePaintmPaint;
- PrivateBitmapmBitmap;
-
- PrivateRectFrectF;
-
- PublicRoundImageDrawable (Bitmapbitmap)
- {
- MBitmap = bitmap;
- BitmapShaderbitmapShader = newBitmapShader (bitmap, TileMode. CLAMP,
- TileMode. CLAMP );
- MPaint = newPaint ();
- MPaint. setAntiAlias (true );
- MPaint. setShader (bitmapShader );
- }
-
- @ Override
- PublicvoidsetBounds (intleft, inttop, intright, intbottom)
- {
- Super. setBounds (left, top, right, bottom );
- RectF = newRectF (left, top, right, bottom );
- }
-
- @ Override
- Publicvoiddraw (Canvascanvas)
- {
- Canvas. drawRoundRect (rectF, 30,30, mPaint );
- }
-
- @ Override
- PublicintgetIntrinsicWidth ()
- {
- ReturnmBitmap. getWidth ();
- }
-
- @ Override
- PublicintgetIntrinsicHeight ()
- {
- ReturnmBitmap. getHeight ();
- }
-
- @ Override
- PublicvoidsetAlpha (intalpha)
- {
- MPaint. setAlpha (alpha );
- }
-
- @ Override
- PublicvoidsetColorFilter (ColorFiltercf)
- {
- MPaint. setColorFilter (cf );
- }
-
- @ Override
- PublicintgetOpacity ()
- {
- ReturnPixelFormat. TRANSLUCENT;
- }
-
- }
The core code is draw, but we only need a line ~~~~ The setAlpha, setColorFilter, getOpacity, and draw methods must be implemented. However, except for draw, the other methods are simple. GetIntrinsicWidth and getIntrinsicHeight are mainly used to provide the following dimensions when the View uses wrap_content. The default value is-1, which is not what we want. SetBounds is used to set the drawn range.
OK. This is how the rounded corner image is implemented. It's easy ~~
Check the usage:
[Java]View plaincopy
- Bitmapbitmap = BitmapFactory. decodeResource (getResources (),
- R. drawable. mv );
- ImageViewiv = (ImageView) findViewById (R. id. id_one );
- Iv. setImageDrawable (newRoundImageDrawable (bitmap ));
OK. paste our image, two imageviews, and one TextView.
As you can see, it is not only used for ImageView to achieve the rounded corner of the image, but also can be used as the background of any View. In the case of stretching in ImageView, configure ScaleType. If other views are used as the background, see: Android BitmapShader for circular and rounded images. Detailed enough.
2. CircleImageDrawableNext, let's look at the writing method of the custom circular Drawable:
[Java]View plaincopy
- Packagecom. zhy. view;
-
- Importandroid. graphics. Bitmap;
- Importandroid. graphics. BitmapShader;
- Importandroid. graphics. Canvas;
- Importandroid. graphics. ColorFilter;
- Importandroid. graphics. Paint;
- Importandroid. graphics. PixelFormat;
- Importandroid. graphics. RectF;
- Importandroid. graphics. Shader. TileMode;
- Importandroid. graphics. drawable. Drawable;
-
- PublicclassCircleImageDrawableextendsDrawable
- {
-
- PrivatePaintmPaint;
- PrivateintmWidth;
- PrivateBitmapmBitmap;
-
- PublicCircleImageDrawable (Bitmapbitmap)
- {
- MBitmap = bitmap;
- BitmapShaderbitmapShader = newBitmapShader (bitmap, TileMode. CLAMP,
- TileMode. CLAMP );
- MPaint = newPaint ();
- MPaint. setAntiAlias (true );
- MPaint. setShader (bitmapShader );
- MWidth = Math. min (mBitmap. getWidth (), mBitmap. getHeight ());
- }
-
- @ Override
- Publicvoiddraw (Canvascanvas)
- {
- Canvas. drawCircle (mWidth/2, mWidth/2, mWidth/2, mPaint );
- }
-
- @ Override
- PublicintgetIntrinsicWidth ()
- {
- ReturnmWidth;
- }
-
- @ Override
- PublicintgetIntrinsicHeight ()
- {
- ReturnmWidth;
- }
-
- @ Override
- PublicvoidsetAlpha (intalpha)
- {
- MPaint. setAlpha (alpha );
- }
-
- @ Override
- PublicvoidsetColorFilter (ColorFiltercf)
- {
- MPaint. setColorFilter (cf );
- }
-
- @ Override
- PublicintgetOpacity ()
- {
- ReturnPixelFormat. TRANSLUCENT;
- }
-
- }
This is surprisingly simple. Let's take a look at it again:
OK. For the custom Drawable example, over ~~~ Next we will look at the custom status.
For more information, see Romain Guy's Blog.
3. Customize the Drawable StateI believe everyone is familiar with Drawable State and state_pressed.
Next, we have a requirement, similar to the mailbox, that the mail is displayed in ListView form, but we need a status to identify unread and read: so, And We customize a status state_message_readed.
:
As you can see, if it is a read email, our icon is open and has a light red background. So how can we implement it through a custom drawable state?
The custom drawable state can be divided into the following steps:
1. res/values/create an xml file: drawable_status.xml
[Html]View plaincopy
-
-
-
-
-
-
2. inherit the container of Item
Here we select the RelativeLayout implementation for Item, and we need to inherit it, And then rewrite its onCreateDrawableState method to add our custom state when appropriate.
[Java]View plaincopy
- Packagecom. zhy. view;
-
- Importcom. zhy. sample. drawable. R;
-
- Importandroid. content. Context;
- Importandroid. util. AttributeSet;
- Importandroid. widget. RelativeLayout;
-
- PublicclassMessageListItemextendsRelativeLayout
- {
-
- Privatestaticfinalint [] STATE_MESSAGE_READED = {R. attr. state_message_readed };
- PrivatebooleanmMessgeReaded = false;
-
- PublicMessageListItem (Contextcontext, AttributeSetattrs)
- {
- Super (context, attrs );
- }
-
- PublicvoidsetMessageReaded (booleanreaded)
- {
- If (this. mMessgeReaded! = Readed)
- {
- MMessgeReaded = readed;
- RefreshDrawableState ();
- }
- }
-
- @ Override
- Protectedint [] onCreateDrawableState (intextraSpace)
- {
- If (mMessgeReaded)
- {
- Finalint [] drawableState = super
- . OnCreateDrawableState (extraSpace + 1 );
- MergeDrawableStates (drawableState, STATE_MESSAGE_READED );
- ReturndrawableState;
- }
- Returnsuper. onCreateDrawableState (extraSpace );
- }
-
- }
The code is not complex. It declares a STATE_MESSAGE_READED and adds the custom state through the onCreateDrawableState method when mMessgeReaded = true.
For similar code, you can look at the source code of CompoundButton (CheckBox parent class). It has a checked status:
[Java]View plaincopy
- @ Override
- Protectedint [] onCreateDrawableState (intextraSpace ){
- Finalint [] drawableState = super. onCreateDrawableState (extraSpace + 1 );
- If (isChecked ()){
- MergeDrawableStates (drawableState, CHECKED_STATE_SET );
- }
- ReturndrawableState;
- }
3. Use
Layout file:
[Html]View plaincopy
- Xmlns: tools = "http://schemas.android.com/tools"
- Android: layout_width = "match_parent"
- Android: layout_height = "50dp"
- Android: background = "@ drawable/message_item_bg">
-
- Android: id = "@ + id/id_msg_item_icon"
- Android: layout_width = "30dp"
- Android: src = "@ drawable/message_item_icon_bg"
- Android: layout_height = "wrap_content"
- Android: duplicateParentState = "true"
- Android: layout_alignParentLeft = "true"
- Android: layout_centerVertical = "true"
- />
-
- Android: id = "@ + id/id_msg_item_text"
- Android: layout_width = "match_parent"
- Android: layout_height = "wrap_content"
- Android: layout_centerVertical = "true"
- Android: layout_toRightOf = "@ id/id_msg_item_icon"/>
-
-
Simple: an icon and text;
Activity
[Java]View plaincopy
- Packagecom. zhy. sample. drawable;
-
- Importcom. zhy. view. MessageListItem;
-
- Importandroid. app. ListActivity;
- Importandroid. OS. Bundle;
- Importandroid. view. LayoutInflater;
- Importandroid. view. View;
- Importandroid. view. ViewGroup;
- Importandroid. widget. ArrayAdapter;
- Importandroid. widget. TextView;
-
- PublicclassCustomStateActivityextendsListActivity
- {
- PrivateMessage [] messages = newMessage [] {
- NewMessage ("Gasbilloverdue", true ),
- NewMessage ("Congratulations, you 'vewon! ", True ),
- NewMessage ("Iloveyou! ", False ),
- NewMessage ("Pleasereply! ", False ),
- NewMessage ("Youignoringme? ", False ),
- NewMessage ("Notheardfromyou", false ),
- NewMessage ("maid", true ),
- NewMessage ("Gasbill", true), newMessage ("Holidayplans", false ),
- NewMessage ("Marketingstuff", false ),};
-
- @ Override
- ProtectedvoidonCreate (BundlesavedInstanceState)
- {
- Super. onCreate (savedInstanceState );
-
- GetListView (). setAdapter (newArrayAdapter (This,-1, messages)
- {
- PrivateLayoutInflatermInflater = LayoutInflater
- . From (getContext ());
-
- @ Override
- PublicViewgetView (intposition, ViewconvertView, ViewGroupparent)
- {
- If (convertView = null)
- {
- ConvertView = mInflater. inflate (R. layout. item_msg_list,
- Parent, false );
- }
- MessageListItemmessageListItem = (MessageListItem) convertView;
- TextViewtv = (TextView) convertView
- . FindViewById (R. id. id_msg_item_text );
- TV. setText (getItem (position). message );
- MessageListItem. setMessageReaded (getItem (position). readed );
- ReturnconvertView;
- }
-
- });
-
- }
- }
The code is very simple, but we can see that we need to call the setMessageReaded method in getView. Of course, some other States must also be manually triggered, such as triggering pressed in ACTION_DOWN. Do not worry about not using ViewHolder or anything. Just add it yourself.
4. Improve Our UI PerfermanceNow we are paying more and more attention to performance issues. In fact, we don't need to care so much, but now that everyone cares, here we use Cyril Mottier: an example in master_android_drawables ppt shows how to use Drawable to improve the UI performance.
We can see this:
Layout file:
[Html]View plaincopy
-
- Android: layout_width = "match_parent"
- Android: layout_height = "match_parent"
- Android: background = "@ color/app_background"
- Android: padding = "8dp">
-
- Android: layout_width = "wrap_content"
- Android: layout_height = "wrap_content"
- Android: layout_gravity = "center"
- Android: layout_marginBottom = "24dp"
- Android: src = "@ drawable/logo"/>
-
- Android: layout_width = "match_parent"
- Android: layout_height = "48dp"
- Android: layout_gravity = "bottom"
- Android: orientation = "horizontal">
-
- Android: layout_width = "0dp"
- Android: layout_height = "fill_parent"
- Android: layout_weight = "1"
- Android: text = "@ string/sign_up"/>
-
- Android: layout_width = "0dp"
- Android: layout_height = "fill_parent"
- Android: layout_weight = "1"
- Android: text = "@ string/sign_in"/>
-
-
-
We can see that the outermost layer is FrameLayout, just to set the background image and padding. This layout is believed to have been written by many people.
Let's take a look at the intuitive effect of this layout when the APP is started:
The user first sees a whiteboard and then shows our page. Next, we will use Drawable to improve our UI performance and user experience.
1. First, we remove the FrameLayout Of Our outermost layer, and then customize a drawable xml, called logo. xml.
[Html]View plaincopy
-
-
-
-
-
-
-
-
-
-
- Android: gravity = "center"
- Android: src = "@ drawable/logo"/>
-
-
OK. This drawable sets our background and logo;
2. Use it as the windowBackground of our current Activity
[Html]View plaincopy
-
-
- Name = "Theme. Default. NoActionBar"
- Parent = "@ android: style/Theme. Holo. Light. NoActionBar">
- @ Drawable/login
-
-
3. Set it to Activity:
[Html]View plaincopy
- Android: name = "LoginActivity"
- Android: theme = "@ style/Theme. Default. NoActionBar">
Okay, this not only minimizes our layout, but now there is only one LinearLayout and two buttons in our layout, and enhances the user experience. Now, when the user's visual effect is:
Is it a good experience? I personally like this example ~~
OK. Now our article is over ~~~ For most of the content, refer to examples written by some cool people. The example is great. You can also explore some things while reading this article ~~
Download source code