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
最后推荐一部美剧《疑犯追踪》,最近正在追,很棒~图片就是男主角。
- Android自定义View学习笔记04
- Android自定义View学习笔记04
- 【Android学习笔记】自定义View
- Android自定义view学习笔记
- Android学习笔记-自定义view
- android 学习笔记(1) ExpandableListActivity 自定义view
- android学习笔记3:自定义view
- Android自定义view学习笔记01
- Android自定义view学习笔记02
- Android自定义View学习笔记03
- Android自定义view学习笔记01
- Android自定义view学习笔记02
- Android自定义View学习笔记03
- android学习笔记-自定义View的属性
- Android学习笔记-自定义view保存状态
- android自定义view学习笔记1
- 自定义view学习笔记
- android自定义view笔记
- FragmentPagerAdapter
- 程序员的自我修养:(1)目标文件
- 矩阵-向量求导法则
- ViewPager,Bundle,类的解析
- Two Substrings
- Android自定义View学习笔记04
- poj 3468 A Simple Problem with Integers(线段树区间更新)
- 新手报到
- 设置linux静态IP
- Preparing Olympiad
- Linux 压缩与解压缩相关
- php本地文件上传到远程服务器
- 线程间通讯和等待唤醒机制
- java内置数据结构--Map典型应用