Android Drawable 那些鮮爲人知的高效用法

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:https://blog.csdn.net/lmj623565791/article/details/43752383
轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/43752383,本文出自:【張鴻洋的博客】
一、概述
Drawable在咱們平時的開發中,基本都會用到,並且給你們很是的有用。那麼什麼是Drawable呢?可以在canvas上繪製的一個玩意,並且相比於View,並不須要去考慮measure、layout,僅僅只要去考慮如何draw(canavs)。固然了,對於Drawable傳統的用法,你們確定不陌生 ,今天主要給你們帶來如下幾個Drawable的用法:android

一、自定義Drawable,相比View來講,Drawable屬於輕量級的、使用也很簡單。之後自定義實現一個效果的時候,能夠改變View first的思想,嘗試下Drawable first。git

二、自定義狀態,相信你們對於State Drawable都不陌生,可是有沒有嘗試過去自定義一個狀態呢?github

三、利用Drawable提高咱們的UI Perfermance , 如何利用Drawable去提高咱們的UI的性能。canvas

二、Drawable基本概念
通常狀況下,除了直接使用放在Drawable下的圖片,其實的Drawable的用法都和xml相關,咱們可使用shape、layer-list等標籤繪製一些背景,還能夠經過selector標籤訂義View的狀態的效果等。固然了基本每一個標籤都對應於一個真正的實體類,關係以下:(圖片來自:Cyril Mottier :master_android_drawables)app

常見的用法這裏就不舉例了,下面開始看本文的重點。ide

二、自定義Drawable
關於自定義Drawable,能夠經過寫一個類,而後繼承自Drawable , 相似於自定義View,固然了自定義Drawable的核心方法只有一個,那就是draw。那麼自定義Drawable到底有什麼實際的做用呢?能幹什麼呢?佈局

相信你們對於圓角、圓形圖片都不陌生,而且我曾經寫過經過自定義View實現的方式,具體可參考:性能

Android BitmapShader 實戰 實現圓形、圓角圖片this

Android Xfermode 實戰 實現圓形、圓角圖片.net

那我今天要告訴你,不須要自定義View,自定義Drawable也能實現,並且更加簡單、高效、使用範圍更廣(你能夠做爲任何View的背景)。

一、RoundImageDrawable
代碼比較簡單,下面看下RoundImageDrawable

package com.zhy.view;
 
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.Drawable;
 
public class RoundImageDrawable extends Drawable
{
 
    private Paint mPaint;
    private Bitmap mBitmap;
 
    private RectF rectF;
 
    public RoundImageDrawable(Bitmap bitmap)
    {
        mBitmap = bitmap;
        BitmapShader bitmapShader = new BitmapShader(bitmap, TileMode.CLAMP,
                TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setShader(bitmapShader);
    }
 
    @Override
    public void setBounds(int left, int top, int right, int bottom)
    {
        super.setBounds(left, top, right, bottom);
        rectF = new RectF(left, top, right, bottom);
    }
 
    @Override
    public void draw(Canvas canvas)
    {
        canvas.drawRoundRect(rectF, 30, 30, mPaint);
    }
 
    @Override
    public int getIntrinsicWidth()
    {
        return mBitmap.getWidth();
    }
 
    @Override
    public int getIntrinsicHeight()
    {
        return mBitmap.getHeight();
    }
 
    @Override
    public void setAlpha(int alpha)
    {
        mPaint.setAlpha(alpha);
    }
 
    @Override
    public void setColorFilter(ColorFilter cf)
    {
        mPaint.setColorFilter(cf);
    }
 
    @Override
    public int getOpacity()
    {
        return PixelFormat.TRANSLUCENT;
    }
 
}
核心代碼就是draw了,but,咱們只須要一行~~~~setAlpha、setColorFilter、getOpacity、draw這幾個方法是必須實現的,不過除了draw覺得,其餘都很簡單。getIntrinsicWidth、getIntrinsicHeight主要是爲了在View使用wrap_content的時候,提供一下尺寸,默認爲-1可不是咱們但願的。setBounds就是去設置下繪製的範圍。

ok,圓角圖片就這麼實現了,easy 不~~

看下用法:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
                R.drawable.mv);
        ImageView iv = (ImageView) findViewById(R.id.id_one);
        iv.setImageDrawable(new RoundImageDrawable(bitmap));

ok,貼一下咱們的效果圖,兩個ImageView和一個TextView


能夠看到,不只僅用於ImageView去實現圓角圖片,而且能夠做爲任何View的背景,在ImageView中的拉伸的狀況,配下ScaleType便可。在其餘View做爲背景時,若是出現拉伸狀況,請參考:Android BitmapShader 實戰 實現圓形、圓角圖片 。 足夠詳細了。

二、CircleImageDrawable
那麼下來,咱們再看看自定義圓形Drawable的寫法:

package com.zhy.view;
 
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.Drawable;
 
public class CircleImageDrawable extends Drawable
{
 
    private Paint mPaint;
    private int mWidth;
    private Bitmap mBitmap ; 
 
    public CircleImageDrawable(Bitmap bitmap)
    {
        mBitmap = bitmap ; 
        BitmapShader bitmapShader = new BitmapShader(bitmap, TileMode.CLAMP,
                TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setShader(bitmapShader);
        mWidth = Math.min(mBitmap.getWidth(), mBitmap.getHeight());
    }
 
    @Override
    public void draw(Canvas canvas)
    {
        canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2, mPaint);
    }
 
    @Override
    public int getIntrinsicWidth()
    {
        return mWidth;
    }
 
    @Override
    public int getIntrinsicHeight()
    {
        return mWidth;
    }
 
    @Override
    public void setAlpha(int alpha)
    {
        mPaint.setAlpha(alpha);
    }
 
    @Override
    public void setColorFilter(ColorFilter cf)
    {
        mPaint.setColorFilter(cf);
    }
 
    @Override
    public int getOpacity()
    {
        return PixelFormat.TRANSLUCENT;
    }
 
}

同樣出奇的簡單,再看一眼效果圖:


ok,關於自定義Drawable的例子over~~~接下來看自定義狀態的。

上述參考了:Romain Guy's Blog

三、自定義Drawable State
關於Drawable State,state_pressed神馬的,相信你們都掌握的特別熟練了。

那麼接下來,咱們有個需求,相似於郵箱,郵件以ListView形式展現,可是咱們須要一個狀態去標識出未讀和已讀:so,咱們自定義一個狀態state_message_readed。

效果圖:

能夠看到,若是是已讀的郵件,咱們的圖標是打開狀態,且有個淡紅色的背景。那麼如何經過自定義drawable state 實現呢?

自定義drawable state 須要分爲如下幾個步驟:

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

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MessageStatus">
        <attr name="state_message_readed" format="boolean" />
    </declare-styleable>
</resources>


二、繼承Item的容器

咱們這裏Item選擇RelativeLayout實現,咱們須要繼承它,而後複寫它的onCreateDrawableState方法,把咱們自定義的狀態在合適的時候添加進去。

package com.zhy.view;
 
import com.zhy.sample.drawable.R;
 
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
 
public class MessageListItem extends RelativeLayout
{
 
    private static final int[] STATE_MESSAGE_READED = { R.attr.state_message_readed };
    private boolean mMessgeReaded = false;
 
    public MessageListItem(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
 
    public void setMessageReaded(boolean readed)
    {
        if (this.mMessgeReaded != readed)
        {
            mMessgeReaded = readed;
            refreshDrawableState();
        }
    }
 
    @Override
    protected int[] onCreateDrawableState(int extraSpace)
    {
        if (mMessgeReaded)
        {
            final int[] drawableState = super
                    .onCreateDrawableState(extraSpace + 1);
            mergeDrawableStates(drawableState, STATE_MESSAGE_READED);
            return drawableState;
        }
        return super.onCreateDrawableState(extraSpace);
    }
 
}

代碼不復雜,聲明瞭一個STATE_MESSAGE_READED,而後在mMessgeReaded=true的狀況下,經過onCreateDrawableState方法,加入咱們自定義的狀態。
相似的代碼,你們能夠看看CompoundButton(CheckBox父類)的源碼,它有個checked狀態:

 @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }

三、使用
佈局文件:

<com.zhy.view.MessageListItem xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@drawable/message_item_bg" >
 
    <ImageView
        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"
      />
 
    <TextView
        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" />
 
</com.zhy.view.MessageListItem>

很簡單,一個圖標,一個文本;
Activity

package com.zhy.sample.drawable;
 
import com.zhy.view.MessageListItem;
 
import android.app.ListActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
 
public class CustomStateActivity extends ListActivity
{
    private Message[] messages = new Message[] {
            new Message("Gas bill overdue", true),
            new Message("Congratulations, you've won!", true),
            new Message("I love you!", false),
            new Message("Please reply!", false),
            new Message("You ignoring me?", false),
            new Message("Not heard from you", false),
            new Message("Electricity bill", true),
            new Message("Gas bill", true), new Message("Holiday plans", false),
            new Message("Marketing stuff", false), };
 
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
 
        getListView().setAdapter(new ArrayAdapter<Message>(this, -1, messages)
        {
            private LayoutInflater mInflater = LayoutInflater
                    .from(getContext());
 
            @Override
            public View getView(int position, View convertView, ViewGroup parent)
            {
                if (convertView == null)
                {
                    convertView = mInflater.inflate(R.layout.item_msg_list,
                            parent, false);
                }
                MessageListItem messageListItem = (MessageListItem) convertView;
                TextView tv = (TextView) convertView
                        .findViewById(R.id.id_msg_item_text);
                tv.setText(getItem(position).message);
                messageListItem.setMessageReaded(getItem(position).readed);
                return convertView;
            }
 
        });
 
    }
}

代碼很簡單,可是能夠看到,咱們須要在getView裏面中去使用調用setMessageReaded方法,固然了其餘的一些狀態,確定也要手動觸發,好比在ACTION_DOWN中觸發pressed等。請勿糾結咋沒有使用ViewHolder什麼的,本身添加下就行。
本例參考自:Example from github 

四、提高咱們的UI Perfermance 
如今你們愈來愈注重性能問題,其實不必那麼在意,可是既然你們在意了,這裏經過Cyril Mottier :master_android_drawables ppt中的一個例子來講明若是利用Drawable來提高咱們的UI的性能。

你們看這樣一個效果圖:

佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_background"
    android:padding="8dp" >
 
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="24dp"
        android:src="@drawable/logo" />
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_gravity="bottom"
        android:orientation="horizontal" >
 
        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="@string/sign_up" />
 
        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="@string/sign_in" />
    </LinearLayout>
 
</FrameLayout>

能夠看到最外層是FrameLayout僅僅是爲了設置背景圖和padding,這樣的佈局相信不少人也寫過。
再看看這個佈局做爲APP啓動時,用戶的直觀效果:

用戶首先看到一個白板,而後顯示出咱們的頁面。接下來,咱們將利用Drawable改善咱們的UI性能以及用戶體驗。

一、首先,咱們去除咱們最外層的FrameLayout,而後自定義一個drawable的xml,叫作logo.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item>
 
        <shape android:shape="rectangle" >
            <solid android:color="@color/app_background" />
        </shape>
    </item>
 
    <item android:bottom="48dp">
        <bitmap
            android:gravity="center"
            android:src="@drawable/logo" />
    </item>
</layer-list>

ok,這個drawable是設置了咱們的背景和logo;
二、將其做爲咱們當前Activity的windowBackground

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style
        name="Theme.Default.NoActionBar"
        parent="@android:style/Theme.Holo.Light.NoActionBar" >
        <item name="android:windowBackground">@drawable/login</item>
    </style>
</resources>
三、設置到Activity上:

<activity
android:name="LoginActivity"
android:theme="@style/Theme.Default.NoActionBar">

Ok,這樣不只最小化了咱們的layout,如今咱們的layout裏面只有一個LinearLayout和兩個按鈕;而且提高了用戶體驗,如今用戶的直觀效果時:


是否是體驗好不少,我的很喜歡這個例子~~

ok,到此咱們的文章就over了~~~大多數內容參考自一些牛人寫的例子,例子仍是棒棒噠,你們看完本文的同時,也能夠去挖掘挖掘一些東西~~

———————————————— 版權聲明:本文爲CSDN博主「鴻洋_」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/lmj623565791/article/details/43752383

相關文章
相關標籤/搜索