自定义View之CustomImageView

自定义View的步骤:

  1. 自定义VIew的属性
  2. 在VIew的构造方法中获得我们的属性
  3. 重写OnMeasure方法
  4. 重写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>

构造我们的自定义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:
                    //转换sp为dp
                    mTextSize = a.getDimensionPixelSize(attr, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                            16,getResources().getDisplayMetrics()));
                    break;
            }
        }
        /*
        * 在TypedArray后调用recycle主要是为了缓存。当recycle被调用后,这就说明这个对象从现在可以被重用了。TypedArray 内部持有部分数组,它们缓存在Resources类中的静态字段中,这样就不用每次使用前都需要分配内存。
        * */
        a.recycle();
        mPaint = new Paint();
        rect = new Rect();
        mTextBound = new Rect();
        mPaint.setTextSize(mTextSize);
        //获得TextView的宽度和高度
        //计算文字所在矩形,可以得到宽高
        mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /*
        * 顾名思义,通过measureSpec这个参数,获取size ,两个都是int类型,怎么通过一个int类型的数获取另一个int类型的数。
        * 我们在学习java的时候知道,一个int类型是32位,任何int类型的数都是有32位,比如一个int类型的数值3,它也是占有32位,只是高30位全部为0。
        * google 也是利用这一点,让这个int类型的measureSpec数存了两个信息,一个就是size,保存在int类型的低30位,另一个就是mode,保存在int类型的高2位。
        * 前面我们看到了有几个成员变量,UNSPECIFIED,EXACTLY,AT_MOST
          者就是mode的三种选择,目前也只有这三种选择,所以只需要2位就能实现。
        * */
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        /*
        * 这也好理解,获取模式,但这些模式有啥用处呢?
        * 1)、EXACTLY 模式: 准确的、精确的;这种模式,是最容易理解和处理的,可以理解为大小固定,比如在定义layout_width的时候,定义为固定大小 10dp,20dp,或者match_parent(此时父控件是固定的)这时候,获取出来的mode就是EXACTLY
        * 2)、AT_MOST 模式: 最大的;这种模式稍微难处理些,不过也好理解,就是View的大小最大不能超过父控件,超过了,取父控件的大小,没有,则取自身大小,这种情况一般都是在layout_width设为warp_content时。
        * 3)、UNSPECIFIED 模式:不指定大小,这种情况,我们几乎用不上,它是什么意思呢,就是View的大小想要多大,就给多大,不受父View的限制,几个例子就好理解了,ScrollView控件就是。
        * */

        ////重点来了,判断模式,这个模式哪里来的呢,就是在编写xml的时候,设置的layout_width
        //如果是精确的,好说,是多少,就给多少;
        if (specMode == MeasureSpec.EXACTLY){
            mWidth = specSize;
        } else {
            //由图片决定的宽
            int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();
            //由字体决定的宽
            int desireByTitle = getPaddingLeft() + getPaddingRight() + mTextBound.width();
            //如果是AT_MOST,不能超过父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);

        //设置边框
        mPaint.setStrokeWidth(4);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.CYAN);
        //画布
        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);

        //获取坐标
        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);
            // 根据长度截取出剪裁后的文字
            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 {
            //计算居中的矩形范围
            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"
        />

在导入自定义View的时候遇到一个坑,因为如果我设置<attr name="titleTextColor" format="reference"/> 的时候,会报Error:(133) Attribute "titleTextColor" has already been defined错,这时由于系统中已经有这个属性,我们尽量要避免与之重名。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 序言:Android自定义View是一个程序员进阶的必备技能,也是在日常开发中用得比较多的一个技能,所以在今后的一...
    转音视频的老王阅读 910评论 0 3
  • 目录 从0到1Android自定义View(二) 分类和核心函数.png 一、自定义 View 分类 常见的 An...
    justCode_阅读 637评论 0 3
  • 1.从本篇文章中我学到的最重要的概念 爱就像树一样可以长大,可以很高,可以被传承 学会爱,尽你所能去照顾...
    无奈柰阅读 86评论 2 0
  • 文/落花聽雨 人之初,性本善。。。。这首脍炙人口的三字经,善的启示,善的根本。说出了人的心声,心的向往...
    落花聽雨阅读 295评论 5 19
  • 在这个话题为王的时代,大佬们隔三差五总要闹出点动静,譬如这两天京东就走在了风口浪尖。6月7日,京东股价开盘一路下跌...
    张美月阅读 10,476评论 0 0