Android自定义View学习笔记04

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值