自定義View之CustomImageView

自定義View的步驟:

  1. 自定義VIew的屬性
  2. 在VIew的構(gòu)造方法中獲得我們的屬性
  3. 重寫(xiě)OnMeasure方法
  4. 重寫(xiě)OnDraw方法

效果如下:

device-2016-04-26-124048.png

自定義屬性

res/attr.xml

<resources>
    <attr name="titleText" format="string"/>
    <attr name="titleTextSizes" format="dimension"/>
    <attr name="titleTextColors" format="reference"/>
    <attr name="image" format="reference"/>
    <attr name="imageScaleType" format="reference">
        <enum name="fillXY" value="0"/>
        <enum name="center" value="1"/>
    </attr>

    <declare-styleable name="CustomImageView">
        <attr name="titleText"/>
        <attr name="titleTextSizes"/>
        <attr name="titleTextColors"/>
        <attr name="image"/>
        <attr name="imageScaleType"/>
    </declare-styleable>

</resources>

構(gòu)造我們的自定義View

public class CustomImageView extends View{
    private static int IMAGE_SCALE_FITXY = 0;
    private Bitmap mImage;
    private int mImageScale;
    private String mTitle;
    private int mTextColor;
    private int mTextSize;

    private Rect rect;
    private Rect mTextBound;
    private Paint mPaint;

    private int mWidth;
    private int mHeight;

    public CustomImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs,defStyle);
        //獲取自定義的屬性
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView,defStyle,0);
        int n = a.getIndexCount();

        for (int i = 0; i < n; i++){
            int attr = a.getIndex(i);

            switch (attr){
                case R.styleable.CustomImageView_image:
                    mImage = BitmapFactory.decodeResource(getResources(),a.getResourceId(attr,0));
                    break;
                case R.styleable.CustomImageView_imageScaleType:
                    mImageScale = a.getInt(attr,0);
                    break;
                case R.styleable.CustomImageView_titleText:
                    mTitle = a.getString(attr);
                    break;
                case R.styleable.CustomImageView_titleTextColors:
                    mTextColor = a.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CustomImageView_titleTextSizes:
                    //轉(zhuǎn)換sp為dp
                    mTextSize = a.getDimensionPixelSize(attr, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                            16,getResources().getDisplayMetrics()));
                    break;
            }
        }
        /*
        * 在TypedArray后調(diào)用recycle主要是為了緩存剔应。當(dāng)recycle被調(diào)用后要出,這就說(shuō)明這個(gè)對(duì)象從現(xiàn)在可以被重用了允蜈。TypedArray 內(nèi)部持有部分?jǐn)?shù)組,它們緩存在Resources類中的靜態(tài)字段中,這樣就不用每次使用前都需要分配內(nèi)存供屉。
        * */
        a.recycle();
        mPaint = new Paint();
        rect = new Rect();
        mTextBound = new Rect();
        mPaint.setTextSize(mTextSize);
        //獲得TextView的寬度和高度
        //計(jì)算文字所在矩形往产,可以得到寬高
        mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /*
        * 顧名思義,通過(guò)measureSpec這個(gè)參數(shù)撒犀,獲取size 福压,兩個(gè)都是int類型,怎么通過(guò)一個(gè)int類型的數(shù)獲取另一個(gè)int類型的數(shù)或舞。
        * 我們?cè)趯W(xué)習(xí)java的時(shí)候知道荆姆,一個(gè)int類型是32位,任何int類型的數(shù)都是有32位映凳,比如一個(gè)int類型的數(shù)值3胆筒,它也是占有32位,只是高30位全部為0诈豌。
        * google 也是利用這一點(diǎn)仆救,讓這個(gè)int類型的measureSpec數(shù)存了兩個(gè)信息,一個(gè)就是size矫渔,保存在int類型的低30位彤蔽,另一個(gè)就是mode,保存在int類型的高2位庙洼。
        * 前面我們看到了有幾個(gè)成員變量顿痪,UNSPECIFIED,EXACTLY油够,AT_MOST
          者就是mode的三種選擇蚁袭,目前也只有這三種選擇,所以只需要2位就能實(shí)現(xiàn)叠聋。
        * */
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        /*
        * 這也好理解撕阎,獲取模式,但這些模式有啥用處呢碌补?
        * 1)虏束、EXACTLY 模式: 準(zhǔn)確的、精確的厦章;這種模式镇匀,是最容易理解和處理的,可以理解為大小固定袜啃,比如在定義layout_width的時(shí)候汗侵,定義為固定大小 10dp,20dp,或者match_parent(此時(shí)父控件是固定的)這時(shí)候晰韵,獲取出來(lái)的mode就是EXACTLY
        * 2)发乔、AT_MOST 模式: 最大的;這種模式稍微難處理些雪猪,不過(guò)也好理解栏尚,就是View的大小最大不能超過(guò)父控件,超過(guò)了只恨,取父控件的大小译仗,沒(méi)有,則取自身大小官觅,這種情況一般都是在layout_width設(shè)為warp_content時(shí)纵菌。
        * 3)、UNSPECIFIED 模式:不指定大小休涤,這種情況咱圆,我們幾乎用不上,它是什么意思呢滑绒,就是View的大小想要多大闷堡,就給多大,不受父View的限制疑故,幾個(gè)例子就好理解了杠览,ScrollView控件就是。
        * */

        ////重點(diǎn)來(lái)了纵势,判斷模式踱阿,這個(gè)模式哪里來(lái)的呢,就是在編寫(xiě)xml的時(shí)候钦铁,設(shè)置的layout_width
        //如果是精確的软舌,好說(shuō),是多少牛曹,就給多少佛点;
        if (specMode == MeasureSpec.EXACTLY){
            mWidth = specSize;
        } else {
            //由圖片決定的寬
            int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();
            //由字體決定的寬
            int desireByTitle = getPaddingLeft() + getPaddingRight() + mTextBound.width();
            //如果是AT_MOST,不能超過(guò)父View的寬度
            if (specMode == MeasureSpec.AT_MOST){   //WRAP_CONTENT
                int desire = Math.max(desireByImg,desireByTitle);
                mWidth = Math.min(desire,specSize);
            }
        }

        specMode = MeasureSpec.getMode(heightMeasureSpec);
        specSize = MeasureSpec.getSize(heightMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY){
            mHeight = specSize;
        } else {
            int desire = getPaddingTop() + getPaddingBottom() + mImage.getHeight() + mTextBound.height();
            if (specMode == MeasureSpec.AT_MOST){
                mHeight = Math.min(desire,specSize);
            }
        }
        setMeasuredDimension(mWidth,mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);

        //設(shè)置邊框
        mPaint.setStrokeWidth(4);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.CYAN);
        //畫(huà)布
        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);

        //獲取坐標(biāo)
        rect.left = getPaddingLeft();
        rect.right = mWidth - getPaddingRight();
        rect.top = getPaddingTop();
        rect.bottom = mHeight - getPaddingBottom();

        mPaint.setColor(mTextColor);
        mPaint.setStyle(Paint.Style.FILL);

        if (mTextBound.width() > mWidth){
            TextPaint paint = new TextPaint(mPaint);
            // 根據(jù)長(zhǎng)度截取出剪裁后的文字
            String msg = TextUtils.ellipsize(mTitle, paint, (float)mWidth - getPaddingLeft()
            ,TextUtils.TruncateAt.END).toString();
            canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint);
        } else {
            //正常情況黎比,將字體居中
            canvas.drawText(mTitle, mWidth / 2 - mTextBound.width() * 1.0f / 2, mHeight - getPaddingBottom(), mPaint);
        }

        rect.bottom -= mTextBound.height();

        if (mImageScale == IMAGE_SCALE_FITXY){
            canvas.drawBitmap(mImage, null, rect, mPaint);
        } else {
            //計(jì)算居中的矩形范圍
            rect.left = mWidth / 2 - mImage.getWidth() / 2;
            rect.right = mWidth / 2 + mImage.getWidth() / 2;
            rect.top = (mHeight - mTextBound.height()) / 2 - mImage.getHeight() / 2;
            rect.bottom = (mHeight - mTextBound.height()) / 2 + mImage.getHeight() / 2;

            canvas.drawBitmap(mImage, null, rect, mPaint);
        }
    }

}

在布局中引用

<com.riane.customimageview.CustomImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:padding="10dp"
        custom:image = "@mipmap/ic_launcher"
        custom:imageScaleType="center"
        custom:titleText="hello andorid ! "
        custom:titleTextColors="@color/colorPrimary"
        custom:titleTextSizes="30sp"
        />

    <com.riane.customimageview.CustomImageView
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:padding="10dp"
        custom:image="@mipmap/ic_launcher"
        custom:imageScaleType="center"
        custom:titleText="helloworldwelcome"
        custom:titleTextColors="@color/colorPrimary"
        custom:titleTextSizes="20sp"
        />

    <com.riane.customimageview.CustomImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:padding="10dp"
        custom:image="@mipmap/meizi"
        custom:imageScaleType="center"
        custom:titleText="妹子~"
        custom:titleTextColors="@color/colorPrimary"
        custom:titleTextSizes="12sp"
        />

在導(dǎo)入自定義View的時(shí)候遇到一個(gè)坑超营,因?yàn)槿绻以O(shè)置<attr name="titleTextColor" format="reference"/> 的時(shí)候,會(huì)報(bào)Error:(133) Attribute "titleTextColor" has already been defined錯(cuò)阅虫,這時(shí)由于系統(tǒng)中已經(jīng)有這個(gè)屬性演闭,我們盡量要避免與之重名。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颓帝,一起剝皮案震驚了整個(gè)濱河市米碰,隨后出現(xiàn)的幾起案子窝革,更是在濱河造成了極大的恐慌,老刑警劉巖吕座,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虐译,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吴趴,警方通過(guò)查閱死者的電腦和手機(jī)菱蔬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)史侣,“玉大人,你說(shuō)我怎么就攤上這事魏身【鳎” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵箭昵,是天一觀的道長(zhǎng)税朴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)家制,這世上最難降的妖魔是什么正林? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮颤殴,結(jié)果婚禮上觅廓,老公的妹妹穿的比我還像新娘。我一直安慰自己涵但,他們只是感情好杈绸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著矮瘟,像睡著了一般瞳脓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上澈侠,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天劫侧,我揣著相機(jī)與錄音,去河邊找鬼哨啃。 笑死烧栋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的棘催。 我是一名探鬼主播劲弦,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼醇坝!你這毒婦竟也來(lái)了邑跪?” 一聲冷哼從身側(cè)響起次坡,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎画畅,沒(méi)想到半個(gè)月后砸琅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轴踱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年症脂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淫僻。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诱篷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雳灵,到底是詐尸還是另有隱情棕所,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布悯辙,位于F島的核電站琳省,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏躲撰。R本人自食惡果不足惜针贬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拢蛋。 院中可真熱鬧桦他,春花似錦、人聲如沸谆棱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)础锐。三九已至嗓节,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間皆警,已是汗流浹背拦宣。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留信姓,地道東北人鸵隧。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像意推,于是被迫代替她去往敵國(guó)和親豆瘫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 序言:Android自定義View是一個(gè)程序員進(jìn)階的必備技能菊值,也是在日常開(kāi)發(fā)中用得比較多的一個(gè)技能外驱,所以在今后的一...
    轉(zhuǎn)音視頻的老王閱讀 917評(píng)論 0 3
  • 目錄 從0到1Android自定義View(二) 分類和核心函數(shù).png 一育灸、自定義 View 分類 常見(jiàn)的 An...
    justCode_閱讀 641評(píng)論 0 3
  • 1.從本篇文章中我學(xué)到的最重要的概念 愛(ài)就像樹(shù)一樣可以長(zhǎng)大,可以很高昵宇,可以被傳承 學(xué)會(huì)愛(ài)磅崭,盡你所能去照顧...
    無(wú)奈柰閱讀 90評(píng)論 2 0
  • 文/落花聽(tīng)雨 人之初,性本善瓦哎。砸喻。。蒋譬。這首膾炙人口的三字經(jīng)割岛,善的啟示,善的根本犯助。說(shuō)出了人的心聲蜂桶,心的向往...
    落花聽(tīng)雨閱讀 295評(píng)論 5 19
  • 在這個(gè)話題為王的時(shí)代,大佬們隔三差五總要鬧出點(diǎn)動(dòng)靜腰湾,譬如這兩天京東就走在了風(fēng)口浪尖雷恃。6月7日,京東股價(jià)開(kāi)盤(pán)一路下跌...
    張美月閱讀 10,480評(píng)論 0 0