美文网首页自定义控件
自定义View之CustomImageView

自定义View之CustomImageView

作者: rockman_ | 来源:发表于2016-04-26 13:05 被阅读0次

    自定义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错,这时由于系统中已经有这个属性,我们尽量要避免与之重名。

    相关文章

      网友评论

        本文标题:自定义View之CustomImageView

        本文链接:https://www.haomeiwen.com/subject/mjbqrttx.html