Android 自定义View基础(二)
来源:互联网 发布:自闭倾向 知乎 编辑:程序博客网 时间:2024/06/04 18:05
接着上一篇《Android 自定义View基础(一)》,这一篇我们真正的来实现一个文字自定义控件:
自定义控件的步骤:
自定义属性在自定义View的构造函数获取属性onMesure()onDraw()
1.自定义属性
在res/values/下创建一个attrs.xml文件,然后在创建需要的属性
代码如下:
<?xml version="1.0" encoding="utf-8"?><resources> <attr name="titleText" format="string"></attr> <attr name="titleTextColor" format="color"></attr> <attr name="titleTextSize" format="dimension"></attr> <declare-styleable name="CustomView01" > <attr name="titleText"/> <attr name="titleTextColor"/> <attr name="titleTextSize"/> </declare-styleable></resources>
上面的属性中,我们创建了文字,文字颜色,文字大小三个属性
<declare-styleable name="CustomView01" >
名字也可以是你随便起,但是建议一般与你创建的自定义类名相同。方便查看和管理。
2.创建CustomView01
先创建一个CustomView01继承自View
public class CustomView01 extends View { public CustomView01(Context context) { super(context); } public CustomView01(Context context, AttributeSet attrs) { super(context, attrs); }}
上面的两个构造方法中,一个是我们动态创建控件时使用,一个是从xml布局文件中加载时调用。
3.将创建的控件写入到布局中
在activity_main.xml中加入我们创建的属性和控件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:custom="http://schemas.android.com/apk/res-auto" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.aofei.myview.MainActivity"> <com.aofei.myview.CustomView01 android:layout_width="200dp" android:layout_height="100dp" custom:titleText="这是测试文字" custom:titleTextColor="#ff0000" custom:titleTextSize="40sp" /></RelativeLayout>
此行代码就是将我们创建的自定义属性导入进当前布局,该代码不必记忆,只要输入app然后就会出现一个appNs的提示,确认后就是
xmlns:app="http://schemas.android.com/apk/res-auto"
然后可以更改名字,为custom,我们的属性就是以custom 开头,
xmlns:custom="http://schemas.android.com/apk/res-auto"
到这里我们运行,后发现,界面上并没有我们的创建的控件,为什么呢?这是因为我们的自定Custom中,并没有饮用我们的属性和调用onDraw()进行绘画。
然后我们实现我们调用属性和绘画
public class CustomView01 extends View { private String mTitleText;//文本 private int mTitleTextColor;//文本的颜色 private int mTitleTextSize;//文本的大小 private Rect mBound;//绘制时控制文本绘制的范围 private Paint mPaint; //绘制文本时的画笔 public CustomView01(Context context) { this(context, null); } public CustomView01(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 获取我们的自定义样式 * * @param context * @param attrs * @param defStyleAttr */ public CustomView01(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //获取我们所定义的自定义样式属性 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView01, defStyleAttr, 0); int n = a.getIndexCount();//获取多少个属性 //遍历所有属性 for (int i = 0; i < n; i++) { int attr = a.getIndex(i); //根据从xml对应的值对属性赋值 switch (attr) { case R.styleable.CustomView01_titleText: mTitleText = a.getString(attr); break; case R.styleable.CustomView01_titleTextColor: mTitleTextColor = a.getColor(attr, Color.BLACK);//默认颜色为黑色 break; case R.styleable.CustomView01_titleTextSize: //給文字尺寸赋值,默认设置为16sp,TypeValue也可以把sp转化为px mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; } } a.recycle();//释放资源,这个一定不要忘记! //获取绘制文本的宽和高 mPaint=new Paint(); mPaint.setTextSize(mTitleTextSize); mBound= new Rect(); mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound); }}
我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。
4.我们重写onDraw,onMesure
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) {// super.onDraw(canvas); //我们要重写,不必调用继承的View的方法 mPaint.setColor(Color.YELLOW); //背景矩形 canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint); mPaint.setColor(mTitleTextColor);//画文字 canvas.drawText(mTitleText,getWidth()/2-mBound.width()/2,getHeight()/2+mBound.height()/2,mPaint); }
运行后的结果如下:
到这里,我们就实现一个类似于TextView的控件
我们在onDraw()中打印一下几个测量的值
@Override protected void onDraw(Canvas canvas) {// super.onDraw(canvas); //我们要重写,不必调用继承的View的方法 mPaint.setColor(Color.YELLOW); canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint); mPaint.setColor(mTitleTextColor); mPaint.setAntiAlias(true);//抗锯齿 canvas.drawText(mTitleText,getWidth()/2-mBound.width()/2,getHeight()/2+mBound.height()/2,mPaint); Log.e(TAG,"getMeasuredWidth()="+getMeasuredWidth()+", getMeasuredHeight()="+getMeasuredHeight()); Log.e(TAG,"getWidth()="+getWidth()+" ,getHeight="+getHeight()); Log.e(TAG,"mBound.width()="+mBound.width()+" ,mBound.height()="+mBound.height()); }
打印结果如下
03-09 14:37:47.172 22448-22448/com.aofei.myview E/CustomView01: getMeasuredWidth()=600, getMeasuredHeight()=30003-09 14:37:47.172 22448-22448/com.aofei.myview E/CustomView01: getWidth()=600 ,getHeight=30003-09 14:37:47.172 22448-22448/com.aofei.myview E/CustomView01: mBound.width()=354 ,mBound.height()=58
这个就是我们控件的大小。
如果我们将我们的布局文件中的属性更改为wrap_content,
<com.aofei.myview.CustomView01 android:layout_width="wrap_content" android:layout_height="wrap_content" custom:titleText="这是测试文字" custom:titleTextColor="#ff0000" custom:titleTextSize="20sp" />
运行后的结果如下:
为什么会出现这样的结果呢,看下我们打印的测量尺寸。
03-09 14:44:32.548 22448-22448/com.aofei.myview E/CustomView01: getMeasuredWidth()=1080, getMeasuredHeight()=153603-09 14:44:32.548 22448-22448/com.aofei.myview E/CustomView01: getWidth()=1080 ,getHeight=153603-09 14:44:32.548 22448-22448/com.aofei.myview E/CustomView01: mBound.width()=354 ,mBound.height()=58
此时的宽高发生了变化,这是系统帮我们测量的尺寸,都是match_parent,是手机的屏幕尺寸了,getWidth()=1080 ,getHeight=1536
getMeasuredWidth()=1080, getMeasuredHeight()=1536,显然,这不是我们想要的效果。所以当我们设置了wrap_content时,我们需要自己进行测量,即重写onMeasure.
MeasureSpec的SpecMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为wrap_content
UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是我们重写onMeasure代码:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// super.onMeasure(widthMeasureSpec, heightMeasureSpec); //重写onMeasure int widthMode=MeasureSpec.getMode(widthMeasureSpec); int widthSize=MeasureSpec.getSize(widthMeasureSpec); int heightMode=MeasureSpec.getMode(heightMeasureSpec); int heightSize=MeasureSpec.getSize(heightMeasureSpec); int width; int height; if (widthMode==MeasureSpec.EXACTLY){ width=widthSize; }else { //如果不是给出精确的值,我们通过计算文字的长度和距离控件左右的值来确定,我们画多大 mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound); float textWidth=mBound.width(); int desired = (int) (getPaddingLeft()+textWidth+getPaddingRight()); width=desired; } if (heightMode==MeasureSpec.EXACTLY){ height=heightSize; }else { mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound); float textHeight=mBound.height(); int desired = (int) (getPaddingTop()+textHeight+getPaddingBottom()); height=desired; } setMeasuredDimension(width,height);//设置计算后的宽高 }
我们修改一下布局文件,如下
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:custom="http://schemas.android.com/apk/res-auto" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.aofei.myview.MainActivity"> <com.aofei.myview.CustomView01 android:layout_width="wrap_content" android:layout_height="wrap_content" custom:titleText="3388" custom:titleTextColor="#ff0000" custom:titleTextSize="20sp" android:padding="10dp" /></RelativeLayout>
运行后的结果是:
这样的效果,就是我们想要的,我们可以对高度,宽度随便设置,基本可以满足我们的需求。
接下来我们稍微扩展一下,給这个View添加一个点击事件。在onDraw()最后面添加一个点击事件。
//再画完后我们給这个控件添加一个点击事件 this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mTitleText=randomText(); postInvalidate();//重写绘制 } });
/** * 获取一个随机的四个数字的字符串 * @return */ private String randomText(){ Random random = new Random(); Set<Integer> set=new HashSet<>(); while (set.size()<4){ int randomInt=random.nextInt(10); set.add(randomInt); } StringBuffer sb=new StringBuffer(); for (Integer i:set) { sb.append(""+i); } return sb.toString(); }
运行效果如下:
,这是不是很像我们登陆一些网站的时候验证码的更换。写到这里,就简单实现了一个类似于TextView的自定义控件,不管看博客还是视频,都需要自己动手,永远不要认为自己看懂了,不用写了。
- Android 自定义View基础(二)
- Android自定义View基础篇(二)
- Android 自定义view基础(二)
- Android 自定义View (二)
- Android 自定义View (二)
- Android 自定义View (二)
- Android 自定义View (二)
- android 自定义view二
- Android自定义View(二)
- Android 自定义View 二
- Android自定义View基础
- android--View自定义基础
- android自定义View基础
- Android 自定义View(基础)
- Android自定义View基础
- Android自定义view基础
- Android自定义View基础(二)-角度与弧度
- android自定义View基础系列二(贝塞尔曲线)
- C#笔记整理(三)
- Docker入门系列1:简介
- 析构函数
- 2D效果(近大远小)-----手掌代码
- web.xml中openEntityManagerInViewFilter的作用
- Android 自定义View基础(二)
- Docker入门系列2 安装
- 今天吐槽一下yershop
- Spring源码学习--ConfigurableWebApplicationContext(九)
- 串口小工具KKCOM-Python
- Docker入门系列3:使用
- 第一次
- Greenplum和数据库内核概念
- git初识