Android自定义view学习笔记02
来源:互联网 发布:ios11降级数据丢失 编辑:程序博客网 时间:2024/05/29 17:05
Android自定义view学习笔记02
本文代码来自于张鸿洋老师的博客之Android 自定义View (二) 进阶 学习笔记,对代码进行些许修改,并补充一些在coding过程中遇到的问题、学习的新东西。
相关代码
//CustomImageView.javapackage mmrx.com.myuserdefinedview.textview;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.text.TextPaint;import android.text.TextUtils;import android.util.AttributeSet;import android.util.Log;import android.util.TypedValue;import android.view.View;import mmrx.com.myuserdefinedview.R;/** * Created by mmrx on 2015/4/6. */public class CustomImageView extends View { //文本内容 private String mTitleText; //文本的颜色 private int mTitleColor; //文本大小 private int mTitleTextSize; //图片资源 private Bitmap mImage; //图片缩放 private int mImageScale; //文本时候的文字显示范围 private Rect mBound; private Paint mPaint; //图片和字外面的矩形 private Rect rect; //高度和宽度 int mWidth,mHeight; //在代码中调用 public CustomImageView(Context context){ this(context,null); } //在布局文件中调用 public CustomImageView(Context context,AttributeSet set){ this(context,set,R.attr.CustomImageView02Style); } //显示被调用 public CustomImageView(Context context,AttributeSet set,int defStyle){ super(context,set,defStyle); TypedArray ta = context.obtainStyledAttributes(set, R.styleable.CustomImageView, defStyle,R.style.CustomizeStyle02); int count = ta.getIndexCount(); for(int i=0;i<count;i++){ int index = ta.getIndex(i); switch (index){ case R.styleable.CustomImageView_image: /* * decodeResource(Resources res, int id) * res 是一个Resources类对象,用来读取res文件夹下的资源 * 这个res是的来源是该view创建时传入的Context对象, context.getResources() * */ mImage = BitmapFactory.decodeResource(getResources(),ta.getResourceId(index,0));// assert mImage!=null; break; case R.styleable.CustomImageView_imageScaleType: mImageScale = ta.getInt(index,0); break; case R.styleable.CustomImageView_titleColor: mTitleColor = ta.getColor(index, Color.BLACK); break; case R.styleable.CustomImageView_titleSize: /* * TypedValue.applyDimension是转变为标准尺寸的函数 * 第一个参数是 单位,第二个参数是要数值,第三个是DisplayMetircs 对象,用于获取屏幕分辨率 * */ mTitleTextSize = ta.getDimensionPixelSize(index, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,getResources().getDisplayMetrics())); break; case R.styleable.CustomImageView_titleText: mTitleText = ta.getString(index); break; default: break; }//end switch }//end for ta.recycle(); rect = new Rect(); mPaint = new Paint(); mBound = new Rect(); mPaint.setTextSize(mTitleTextSize); //计算字体所需范围 mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound); }//end method @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// super.onMeasure(widthMeasureSpec, heightMeasureSpec); int hightMod = MeasureSpec.getMode(heightMeasureSpec); int hightSize = MeasureSpec.getSize(heightMeasureSpec); int widthMod = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); //高度有具体数值 if(hightMod == MeasureSpec.EXACTLY){ mHeight = hightSize; } //wrap_content & others else{ //获得图片的高度 int imageHeight = mImage.getHeight(); //获得文字高度 int textHeight = mBound.height(); //总体高度 int totalHeight = imageHeight + textHeight + getPaddingTop() + getPaddingBottom(); if(hightMod == MeasureSpec.AT_MOST){ //warp_content //获得相对较小的高度 mHeight = Math.min(hightSize,totalHeight); } else{ //多个自定义CustomImageView放入ScrollView中,只会显示出最后一个处理办法 mHeight = hightSize; } } //宽度有具体数值 if(widthMod == MeasureSpec.EXACTLY){ mWidth = widthSize; } //wrap_content & others else{ // 由图片决定的宽 int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth(); // 由字体决定的宽 int desireByTitle = getPaddingLeft() + getPaddingRight() + mBound.width(); Log.w("CustomImageView","desireByTitle:"+desireByTitle+" desireByImg:"+desireByImg+" widthSize: "+widthSize); if(widthMod == MeasureSpec.AT_MOST){ //warp_content Log.w("CustomImageView","MeasureSpec.AT_MOST"); //获得相对较大的宽度 int desire = Math.max(desireByImg,desireByTitle); mWidth = Math.min(widthSize,desire); } else{ Log.w("CustomImageView","NOT MeasureSpec.AT_MOST"); //多个自定义CustomImageView放入ScrollView中,只会显示出最后一个处理办法 mWidth = widthSize; } } //将计算好的高度宽度放入,一定要记得这句话不要丢了... setMeasuredDimension(mWidth, mHeight); } @Override protected void onDraw(Canvas canvas) {// super.onDraw(canvas); //边框 //设置线宽 mPaint.setStrokeWidth(4); //设置样式为空心 mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(Color.RED); /*开画,前四个参数是 左 上 右 下 边的位置(注意是边,不是点) 画笔 * left 矩形左边的x坐标 right 矩形右边的x坐标 * top 矩形上边的y坐标 bottom 矩形下边的y坐标 * */ canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint); //获得绘制图片的矩形框 rect.left = getPaddingLeft(); rect.right = mWidth - getPaddingRight(); rect.top = getPaddingTop(); rect.bottom = mHeight - getPaddingBottom(); //设置画笔风格为 实心 mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mTitleColor); //当当前字体的宽度大于边框的宽度,截取字符串的,改为xxxx... if(mBound.width()>mWidth){ //TextPaint 是 Paint 的一个子类 TextPaint paint = new TextPaint(mPaint); /*TextUtils是处理字符串的工具类,ellipsize是用来截断给定长度的字符串的方法 * 参数 需要截断的字符串 显示的字符串的宽度 省略号的位置 * */ String msg = TextUtils.ellipsize(mTitleText,paint, (float)mWidth-getPaddingLeft()-getPaddingRight(),TextUtils.TruncateAt.END).toString(); /* * 坐标参数 x,y x默认是字符串左边在屏幕的位置,如果设置了paint.setTextAlign(Paint.Align.CENTER); * 那就是字符的中心,y是指定这个字符baseline在屏幕上的位置 * */ canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint); } //正常情况,字体居中 else{ canvas.drawText(mTitleText,mWidth/2-mBound.width()*1.0f/2, mHeight-getPaddingBottom(),mPaint); } //这句话是什么意思?如果图片缩放选择的是FITXY,则需要将rect的bottom边向上提一下,防止把 //字覆盖掉,如果选择的是CENTER,rect的位置会重新定义就不用这句话了。我感觉这句话放到 //if(mImageScale == 0)语句块里比较好懂一些。 rect.bottom -= mBound.height(); //如果缩放为 FITXY if(mImageScale == 0){ canvas.drawBitmap(mImage,null,rect,mPaint); } //如果为居中 CENTER else{ //计算居中的矩形范围 rect.left = mWidth/2 - mImage.getWidth()/2; rect.right = mWidth/2 + mImage.getWidth()/2; rect.bottom = (mHeight-mBound.height())/2 + mImage.getHeight()/2; rect.top = (mHeight-mBound.height())/2 - mImage.getHeight()/2; canvas.drawBitmap(mImage,null,rect,mPaint); } }}
xml文件的相关内容
<!--attrs.xml--> <attr name="titleText" format="string"/> <attr name="titleColor" format="color"/> <attr name="titleSize" format="dimension"/> <attr name="CustomImageView02Style" format="reference"/> <attr name="image" format="reference"/> <attr name="imageScaleType"> <enum name="fillXY" value="0"/> <enum name="center" value="1"/> </attr> <declare-styleable name="CustomImageView"> <attr name="titleText" /> <attr name="titleColor" /> <attr name="titleSize" /> <attr name="imageScaleType"/> <attr name="image"/> </declare-styleable>
<!-- style.xml--><resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="CustomView01Style">@style/CustomizeStyle01</item> <item name="CustomImageView02Style">@style/CustomizeStyle02</item> </style> <style name="CustomizeStyle01"> <item name="titleText">@string/hello_world</item> <item name="titleColor">#ff0000</item> <item name="titleSize">14dp</item> </style> <style name="CustomizeStyle02"> <item name="titleText">@string/hello_world</item> <item name="titleColor">#ff0000</item> <item name="titleSize">14dp</item> <item name="imageScaleType">center</item> <item name="image">@drawable/ic_launcher</item> </style></resources>
<!--activity_main.xml--> <mmrx.com.myuserdefinedview.textview.CustomImageView android:layout_marginTop="20dp" android:layout_width="wrap_content" android:layout_height="wrap_content" customview:imageScaleType="center" customview:titleText="androidandroidandroidandroid" customview:image="@drawable/ic_launcher" android:padding="20dp"/> <mmrx.com.myuserdefinedview.textview.CustomImageView android:layout_marginTop="20dp" android:layout_width="150dp" android:layout_height="200dp" customview:imageScaleType="fillXY" customview:titleText="板娘" customview:image="@drawable/hello" android:padding="5dp"/>
遇到的问题
- 启动过程中logcat打印以下错误后程序崩溃
02-06 14:25:08.383 30804-30804/mmrx.com.myuserdefinedview E/AndroidRuntime﹕ FATAL EXCEPTION: main java.lang.IllegalStateException: onMeasure() did not set the measured dimension by calling setMeasuredDimension() at android.view.View.measure(View.java:15561) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5109) at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396) at android.widget.LinearLayout.measureVertical(LinearLayout.java:681) at android.widget.LinearLayout.onMeasure(LinearLayout.java:574) at android.view.View.measure(View.java:15556) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5109) at android.widget.FrameLayout.onMeasure(FrameLayout.java:310) at android.view.View.measure(View.java:15556) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5109) at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396) at android.widget.LinearLayout.measureVertical(LinearLayout.java:681) at android.widget.LinearLayout.onMeasure(LinearLayout.java:574) at android.view.View.measure(View.java:15556) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5109) at android.widget.FrameLayout.onMeasure(FrameLayout.java:310) at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2397) at android.view.View.measure(View.java:15556) at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1987) at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1228) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1401) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1121) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4598) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725) at android.view.Choreographer.doCallbacks(Choreographer.java:555) at android.view.Choreographer.doFrame(Choreographer.java:525) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711) at android.os.Handler.handleCallback(Handler.java:615) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4921) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1038) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805) at dalvik.system.NativeStart.main(Native Method)
一长串的错误信息。。。看了下代码,是onMeasure
方法中没有调用setMeasuredDimension(int, int)
方法。
- 类似的还有一些比如说变量名用错…case后的标签用错…导致上次coding的时候心烦意乱的,事隔半周后重新拿起来再看,错误都是一些及其低级的错。
补充的知识点
Bitmap decodeResource(Resources res, int id)
这个方法属于类BitmapFactory
。在示例代码中的调用语句为mImage = BitmapFactory.decodeResource(getResources(),ta.getResourceId(index,0));
这第一个参数,当时有点不理解…查询到的资料和通过看源码的收获如下:res 是一个
Resources
类对象,用来读取res文件夹下的资源。
每一个View类型的对象都拥有一个Resources
实例mResources
,而这个实例又是通过实例化View
类型对象时传入的Context
对象获取的。
说明一个Activity
的View共有一个Resources
对象的引用。TypedValue.applyDimension
是转变为标准尺寸的函数,在示例代码中有相关的注释。关于
canves.drawRect()
方法的坐标参数问题,我找到了一篇写的很不错的博客android Draw Rect 坐标图示,里面有一张很生动的图来表示坐标的位置,清晰易懂。需要注意的是坐标原点在左上方,x轴(横轴)向右递增,y轴(纵轴)向下递增。于是也解决了代码中这些运算的具体含义了。
rect.left = mWidth/2 - mImage.getWidth()/2;rect.right = mWidth/2 + mImage.getWidth()/2;rect.bottom = (mHeight-mBound.height())/2 + mImage.getHeight()/2;rect.top = (mHeight-mBound.height())/2 - mImage.getHeight()/2;
canvas.drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
这个方法里面的坐标参数也是很奇怪…坐标参数 x,y x默认是字符串左边在屏幕的位置,如果设置了paint.setTextAlign(Paint.Align.CENTER);
那就是字符的中心,y是指定这个字符baseline在屏幕上的位置。这里有一篇博文讲的很不错android中canvas.drawText参数的介绍以及绘制一个文本居中的案例偶然找到几篇很棒的介绍
canves
绘图的博文,这是第四篇android Graphics(四):canvas变换与操作,有空得好好学习一下。
Android自定义view学习笔记01
Android自定义view学习笔记03
Android自定义view学习笔记04
源码同步到gitHub
- Android自定义view学习笔记02
- Android自定义view学习笔记02
- 【Android学习笔记】自定义View
- Android自定义view学习笔记
- Android学习笔记-自定义view
- android 学习笔记(1) ExpandableListActivity 自定义view
- android学习笔记3:自定义view
- Android自定义view学习笔记01
- Android自定义View学习笔记03
- Android自定义View学习笔记04
- Android自定义view学习笔记01
- Android自定义View学习笔记03
- Android自定义View学习笔记04
- android学习笔记-自定义View的属性
- Android学习笔记-自定义view保存状态
- android自定义view学习笔记1
- 自定义view学习笔记
- android自定义view笔记
- centos网络配置方法(手动设置,自动获取)
- java:生产者消费者问题
- iOS UIScrollView的代理的使用及缩放时代理监听的几个方法
- Find Minimum in Rotated Sorted Array—LeetCode
- ImageThumbnailMode
- Android自定义view学习笔记02
- 数据结构与算法之排序:堆排序、归并排序及快速排序
- 第六届南桥杯JavaA组 第四题 循环节长度
- 【KMP】杭电2549(第一次用java写kmp算法)
- Cocos2dx 3.0 -- 有freeType做靠山的Label
- ios时间的转换
- Swing threads
- 神经网络概论
- Android之手机闹钟