Android自定义View学习笔记04

来源:互联网 发布:手机吉他软件下载 编辑:程序博客网 时间:2024/05/17 07:54

Android自定义View学习笔记04

好长时间没有写相关的博客了,前几周在帮学姐做毕设,所以博客方面有些耽误。过程中写了一个类似wp的磁贴的view,想再写个配套的layout,所以昨天看了一下自定义viewGroup的相关知识…晚上睡觉想了一下可行性不是很高…代码量还不如直接自己在xml上写来得快,速度上也是个问题。今天看了一下张鸿洋老师的 Android 自定义View (三) 圆环交替 等待效果这篇博文,再加上前一段时间看到的一幅图,结合之前写的一个圆形imageView的实现博文Android自定义View学习笔记03,于是有了将二者相结合一下的念头,下午和晚上动手实践了一下,效果还不错。

给我灵感的图片:
这里写图片描述

分析

由图看出,只需要在原来圆形imageView的基础上,将图片外的圆环改为有一定宽度和弧度的圆弧,在圆弧下加上两行文本,就可以实现上图的效果。
但是光是这样,和上一篇博客就没有什么大的区别了。所以,参考 Android 自定义View (三) 圆环交替 等待效果这篇博客,做了一个简单的动画效果:手指按下,外边框变长,直到将内部图片完全包裹为止,手指移开,外框变短,恢复原状。
另外,过多的使用类似的view,即使将原图做了缩放处理,仍然有可能出现OOM的情况,所以在使用图片时,可以使用软引用来缓存图片。有关缓存图片的代码见一次OOM引起的优化。

实现

先放上相关代码再分析
自定义view代码如下:

package mmrx.com.myuserdefinedview.textview;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.BitmapShader;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.RectF;import android.graphics.Shader;import android.util.AttributeSet;import android.util.Log;import android.util.TypedValue;import android.view.MotionEvent;import android.widget.ImageView;import mmrx.com.myuserdefinedview.R;/** * Created by mmrx on 2015/6/5. */public class CustomRIV2 extends ImageView {    private Context mContext;    private Bitmap mBitmap;    //图片画笔和边框画笔和文字画笔    private Paint mBitMapPaint;    private Paint mBorderPaint;    private Paint mTextPaint;    //边框颜色    private int mBorderColor = Color.WHITE;    //边框宽度 边框宽度为原图半径的1/8    private float mBorderWidth = 5f;    //边框比圆形图片半径大本身的1/2,也就是说边框和图片的距离为 边框宽度的1/2    private int BORDER = 10;    //第一行和第二行文字之间的距离为2    private final float TEXT = 2f;    //边框和文字之间的距离 等于边框的宽度*2    private float BORDER_TEXT = 10f;    //边框角度    private float mBorderAngle = 90f;    private float mBorderAngle_ = 90f;//最初设定的数值    //文字大小    private int mTextSize = 18;    //第一行文//    private String mTextRow1 = "";    //第二行    private String mTextRow2 = "";    //第一行文字颜色    private int mTextColorRow1 = Color.BLACK;    //第二行文字颜色    private int mTextColorRow2 = Color.GRAY;    //渲染器    private BitmapShader mBitMapShader;    //图片拉伸方式 默认为按比例缩放    private int mImageScale = 1;    //控件的长宽    private int mWidth;    private int mHeight;    //边框和圆形图片的半径    private float mBorderRadius;    private float mDrawableRadius;    //矩形    private RectF mDrawableRect;    private RectF mBorderRect;    private RectF mOuterBorderRect;    private Rect mRow1Rect;    private Rect mRow2Rect;    //变换矩形    private Matrix mBitMapMatrix;    //控制圆环变化的标识符    boolean isAdd = true;    boolean isDecreased = true;    //圆环增减的速度    final int mSpeed = 5;    public CustomRIV2(Context mContext){        super(mContext);        this.mContext = mContext;    }    public CustomRIV2(Context mContext,AttributeSet attr){        this(mContext, attr, R.attr.CustomImageView04Style);        this.mContext = mContext;    }    public CustomRIV2(Context mContext,AttributeSet attr,int defSytle){        super(mContext,attr,defSytle);        TypedArray ta = mContext.obtainStyledAttributes(attr,R.styleable.CustomRIV2_style,                defSytle,R.style.CustomizeStyle04);        int count = ta.getIndexCount();        for(int i=0;i<count;i++){            int index = ta.getIndex(i);            switch (index){                case R.styleable.CustomRIV2_style_borderColor:                    mBorderColor = ta.getColor(index,Color.WHITE);                    break;                case R.styleable.CustomRIV2_style_image:                    mBitmap = BitmapFactory.decodeResource(getResources(),ta.getResourceId(index,0));                    break;                case R.styleable.CustomRIV2_style_imageScaleType:                    mImageScale = ta.getInt(index,0);                    break;                case R.styleable.CustomRIV2_style_titleSize:                    mTextSize = ta.getDimensionPixelSize(index,                            (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,                                    18, getResources().getDisplayMetrics()));                    break;                case R.styleable.CustomRIV2_style_borderAngle:                    mBorderAngle = ta.getFloat(index,90);                    mBorderAngle_ = mBorderAngle;                    break;                case R.styleable.CustomRIV2_style_titleText:                    mTextRow1 = ta.getString(index);                    break;                case R.styleable.CustomRIV2_style_contentText:                    mTextRow2 = ta.getString(index);                    break;                case R.styleable.CustomRIV2_style_titleColor:                    mTextColorRow1 = ta.getColor(index,Color.BLACK);                    break;                case R.styleable.CustomRIV2_style_contentColor:                    mTextColorRow2 = ta.getColor(index,Color.GRAY);                    break;                default:                    break;            }        }        ta.recycle();        mBorderPaint = new Paint();        mBitMapPaint = new Paint();        mTextPaint = new Paint();        mDrawableRect = new RectF();        mOuterBorderRect = new RectF();        mBorderRect = new RectF();        mRow1Rect = new Rect();        mRow2Rect = new Rect();        //计算字体所需范围        mTextPaint.setTextSize(mTextSize);        mTextPaint.getTextBounds(mTextRow1,0,mTextRow1.length(),mRow1Rect);        mTextPaint.setTextSize(mTextSize*4/5);        mTextPaint.getTextBounds(mTextRow2,0,mTextRow2.length(),mRow2Rect);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int heightMod = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int widthMod = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int row1width = 0,row2width = 0;        int row1height = 0,row2height = 0;        //march_parent & exactly dimen        if(heightMod == MeasureSpec.EXACTLY){            mHeight = heightSize;        }        //wrap_content & others        else{            //获得图片高度            mHeight = mBitmap.getHeight();        }        if(widthMod == MeasureSpec.EXACTLY){            mWidth = widthSize;        }        //wrap_content & others        else{            //获得图片宽度            mWidth = mBitmap.getWidth();        }        //获得文字的尺寸        row1width = mRow1Rect.width();        row2width = mRow2Rect.width();        row1height = mRow1Rect.height();        row2height = mRow2Rect.height();        //图片的半径        mDrawableRadius = Math.min(mWidth,mHeight);        mBorderWidth = (int)(mDrawableRadius/10);        BORDER = (int)(mBorderWidth/3);        BORDER_TEXT = mBorderWidth*2;        //考虑线条的宽度,如果没有考虑线条宽度,显示会把线条的一部分遮蔽        //外边框的半径        mBorderRadius = mDrawableRadius + BORDER + mBorderWidth;        //计算控件的高度        int height =(int)((mBorderRadius+mBorderWidth)*2 + BORDER_TEXT + row1height + TEXT + row2height + 0.5);        //计算控件的宽度        int textWidth = Math.max(row1width,row2width);        int width = Math.max(textWidth,(int)(mBorderRadius*2+0.5))+(int)mBorderWidth;        setMeasuredDimension(width, height);    }    @Override    protected void onDraw(Canvas canvas) {        if(mBitmap == null)            return;        //设置渲染器        mBitMapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);        //抗锯齿        mBitMapPaint.setAntiAlias(true);        //设置渲染器        mBitMapPaint.setShader(mBitMapShader);        //设置外框的相关参数        mBorderPaint.setStyle(Paint.Style.STROKE);        mBorderPaint.setAntiAlias(true);        mBorderPaint.setColor(mBorderColor);        mBorderPaint.setStrokeWidth(mBorderWidth);        //边框矩形        mBorderRect.set(0,0,getWidth(),getHeight());        //图片外框的矩形,因为图片是在外边框内部,所以位置矩形的坐标要考虑到边框的宽度        final float borderLeft = getWidth()/2-mBorderRadius;        final float borderTop = (getHeight()-mRow1Rect.height()-mRow2Rect.height()-BORDER_TEXT-TEXT)/2-mBorderRadius+mBorderWidth;        final float borderRight = getWidth()/2+mBorderRadius;        final float borderBottom =  getHeight()-mRow1Rect.height()-mRow2Rect.height()-BORDER_TEXT-TEXT;        mOuterBorderRect.set(borderLeft,borderTop,                borderRight,borderBottom);        //图片的矩形        mDrawableRect.set(borderLeft-BORDER,                borderTop+BORDER,                borderRight-BORDER,                borderBottom-BORDER);        //设置图片的缩放        setBitMapScale();        canvas.drawCircle(getWidth()/2,                (getHeight()-mRow1Rect.height()-mRow2Rect.height()-BORDER_TEXT-TEXT)/2+mBorderWidth,                mDrawableRadius,mBitMapPaint);        if(mBorderWidth != 0) {            //设置起始角度 0度位置为三点钟方向            float mBorderAngle_start = 135 - mBorderAngle/2;            canvas.drawArc(mOuterBorderRect, mBorderAngle_start, mBorderAngle, false, mBorderPaint);        }        //绘制文字        if(mTextRow1 != null){            //设置字体画笔相关参数            mTextPaint.setStyle(Paint.Style.FILL);            mTextPaint.setColor(mTextColorRow1);            mTextPaint.setTextSize(mTextSize);            mTextPaint.setTextAlign(Paint.Align.CENTER);            canvas.drawText(mTextRow1,getWidth()/2,borderBottom+BORDER_TEXT+mRow1Rect.height()/2,mTextPaint);        }        if(mTextRow2 != null){            //设置字体画笔相关参数            mTextPaint.setStyle(Paint.Style.FILL);            mTextPaint.setColor(mTextColorRow2);            mTextPaint.setTextSize(mTextSize*4/5);            mTextPaint.setTextAlign(Paint.Align.CENTER);            canvas.drawText(mTextRow2,getWidth()/2,borderBottom+BORDER_TEXT+mRow1Rect.height()+TEXT+mRow2Rect.height()/2,mTextPaint);        }    }    //根据控件的尺寸和设置的图片缩放模式,来对图片进行缩放    private void setBitMapScale(){        float scaleX = 0,scaleY = 0;        //获得圆形的直径和图片的尺寸        float diameter = mDrawableRadius*2;        float mBitMapWidth = mBitmap.getWidth();        float mBitMapHeight = mBitmap.getHeight();        mBitMapMatrix = new Matrix();        mBitMapMatrix.set(null);        //fillXY 宽高单独缩放        if(mImageScale == 0){            scaleX = diameter/mBitMapWidth;            scaleY = diameter/mBitMapHeight;        }        //center 等比例缩放        else{            float scale = 0;            scaleX = diameter/mBitMapWidth;            scaleY = diameter/mBitMapHeight;            //如果宽度和高度至少有一个需要放大            if(scaleX > 1 || scaleY > 1){                scale = Math.max(scaleX,scaleY);            }            else{                scale = Math.min(scaleX, scaleY);            }            scaleX = scale;            scaleY = scale;        }        mBitMapMatrix.setScale(scaleX,scaleY);        mBitMapShader.setLocalMatrix(mBitMapMatrix);    }    //设置图片    public void setImage(Bitmap bm){        this.mBitmap = bm;        invalidate();    }    //设置图片    public void setImage(int rid){        this.mBitmap = BitmapFactory.decodeResource(getResources(),rid);        invalidate();    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()){            case MotionEvent.ACTION_DOWN:                Log.v("CustomRIV2-onTouchEvent","ACTION_DOWN");                isDecreased = false;                isAdd = true;                new Thread(new AddRunnable()).start();                break;            case MotionEvent.ACTION_UP:                Log.v("CustomRIV2-onTouchEvent","ACTION_UP");                isDecreased = true;                isAdd = false;                new Thread(new DecreaseRunnable()).start();                break;            default:                break;        }        return true;    }    private class AddRunnable implements Runnable{        @Override        public void run() {            while (isAdd && mBorderAngle <= 360) {                mBorderAngle += 2;                postInvalidate();                try {                    Thread.sleep(mSpeed);                } catch (InterruptedException e) {                    e.printStackTrace();                    break;                }            }        }    };    private class DecreaseRunnable implements Runnable{        @Override        public void run() {            while (isDecreased && mBorderAngle >mBorderAngle_) {                mBorderAngle -= 2;                postInvalidate();                try {                    Thread.sleep(mSpeed);                } catch (InterruptedException e) {                    e.printStackTrace();                    break;                }            }        }    };}

xml文件中相关代码

<!--attrs.xml-->    <attr name="titleText" format="string"/>    <attr name="titleColor" format="color"/>    <attr name="contentColor" format="color"/>    <attr name="contentText" format="string"/>    <attr name="titleSize" format="dimension"/>    <attr name="image" format="reference"/>    <attr name="imageScaleType">        <enum name="fillXY" value="0"/>        <enum name="center" value="1"/>    </attr>    <!--边框颜色-->    <attr name="borderColor" format="color"/>    <!--边框角度-->    <attr name="borderAngle" format="float"/>    <attr name="CustomImageView04Style" format="reference"/>    <declare-styleable name="CustomRIV2_style">        <attr name="image"/>        <!--<attr name="borderWidth"/>-->        <attr name="borderColor"/>        <attr name="imageScaleType"/>        <attr name="borderAngle"/>        <attr name="titleText"/>        <attr name="titleSize" />        <attr name="contentText"/>        <attr name="titleColor"/>        <attr name="contentColor"/>    </declare-styleable>
<!--styles.xml--><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">        <!-- Customize your theme here. -->        <item name="CustomView01Style">@style/CustomizeStyle01</item>        <item name="CustomImageView02Style">@style/CustomizeStyle02</item>        <item name="CustomImageView03Style">@style/CustomizeStyle03</item>        <item name="CustomImageView04Style">@style/CustomizeStyle04</item>    </style>    <style name="CustomizeStyle04">        <item name="imageScaleType">center</item>        <item name="image">@drawable/hello</item>        <item name="borderWidth">5dp</item>        <item name="borderColor">#ff00</item>        <item name="borderAngle">90</item>    </style>
<!--在布局文件中这样,部分属性没有用到--><mmrx.com.myuserdefinedview.textview.CustomRIV2                android:layout_width="70dp"                android:layout_height="70dp"                customview:borderWidth="5dp"                customview:borderColor="#ff868686"                customview:image="@drawable/hello"                customview:titleText="詹维斯·卡维泽"                customview:titleSize="18dp"                customview:contentText="《疑犯追踪》男主角"                customview:borderAngle="120"/>

然后显示如图
这里写图片描述
不会制作动图…大致的效果看完鸿洋老师的那篇博文后脑补吧~~~

代码分析

相比于上一篇博文Android自定义View学习笔记03,这里增加的东西有:
1. 新增相关的属性(在attrs.xml中)。
2. 在onMeasure方法中增加边框尺寸的计算、内圆半径计算、外圆半径计算,外圆框宽度计算。
3. 在onDraw中增加外圆框圆弧的绘制、文字的绘制。
4. 增加两个实现了Runnable接口的内部类,用于动态改变圆弧角度。

细节说明

  • 外圆框的半径,没有在attrs.xml文件中给出,半径动态设置为内圆图片半径加外圆框宽度加内外圆之间的距离;而外圆框的宽度为内圆半径的1/10,内外圆之间的距离为外圆框的1/3。
mDrawableRadius = Math.min(mWidth,mHeight);//内圆半径mBorderWidth = (int)(mDrawableRadius/10);BORDER = (int)(mBorderWidth/3);mBorderRadius = mDrawableRadius + BORDER + mBorderWidth;
  • 在有关尺寸计算方面,需要特别注意的一点是,外圆框的矩形的尺寸,因为图片是在外边框内部,所以位置矩形的坐标要考虑到边框的宽度,时刻没有拉下’mBorderWidth’这个变量,才能在最后显示的时候不至于外圆框莫名其妙的少一块。自己画个简图就能明白这些变量之间的加减乘除关系。
  • 注意变量
    //控制圆环变化的标识符    boolean isAdd = true;    boolean isDecreased = true;    //圆环增减的速度    final int mSpeed = 5;

前连个标识符分别用于控制圆弧弧度增加和圆弧弧度减小线程的while循环,而mSpeed则是表示速度,在这里为每5ms弧度增加2度。
这两个线程也很简单

    private class AddRunnable implements Runnable{        @Override        public void run() {        //isAdd为真且外圆框没有完全包裹内圆时,每5ms圆弧增加2度            while (isAdd && mBorderAngle <= 360) {                mBorderAngle += 2;                postInvalidate();                try {                    Thread.sleep(mSpeed);                } catch (InterruptedException e) {                    e.printStackTrace();                    break;                }            }        }    };    private class DecreaseRunnable implements Runnable{        @Override        public void run() {        //isDecreased为真且外圆框没有完全包裹内圆时,每5ms圆弧减少2度            while (isDecreased && mBorderAngle >mBorderAngle_) {                mBorderAngle -= 2;                postInvalidate();                try {                    Thread.sleep(mSpeed);                } catch (InterruptedException e) {                    e.printStackTrace();                    break;                }            }        }    };
  • 在调用者两个线程时,注意将对应的标识符置true,相反的标识符置false,防止出现增加线程和减少线程同时运行的情况。
            case MotionEvent.ACTION_DOWN:                Log.v("CustomRIV2-onTouchEvent","ACTION_DOWN");                isDecreased = false;                isAdd = true;                new Thread(new AddRunnable()).start();                break;            case MotionEvent.ACTION_UP:                Log.v("CustomRIV2-onTouchEvent","ACTION_UP");                isDecreased = true;                isAdd = false;                new Thread(new DecreaseRunnable()).start();

总的来说还是比较简单的。

之前的相关博文

Android自定义view学习笔记01
Android自定义view学习笔记02
Android自定义view学习笔记03

源码同步到github

最后推荐一部美剧《疑犯追踪》,最近正在追,很棒~图片就是男主角。

1 0
原创粉丝点击