Android进阶三:自定义View
2017-09-21 22:28
267 查看
自定义view步骤:
一.自定义View的属性
二.在View的构造方法中获取自定义属性
[三.重写onMesure]
四.重写onDraw
我把四用[]标出了,所以说四不一定是必须的,当然了大部分情况下还是需要重写的。
我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型,一共有:string,color,dimension,integer,enum,reference,float,boolean,fraction,flag
详见 Android应用资源总结七:attrs中format详解
然后在布局中声明我们的自定义View:
一定要引入
我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。
onDraw:绘制自定义view
此时的效果是:
是不是觉得还不错,基本已经实现了自定义View。但是此时如果我们把布局文件的宽和高写成wrap_content,会发现效果并不是我们的预期:
系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。
所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法。
重写之前先了解MeasureSpec的specMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是我们重写onMeasure代码:
现在我们修改下布局文件:
现在的效果是:
二.在布局文件中使用View:
然后运行(屏幕分辨率为:720×1280)
输出结果如下:
1.此时layout_width和layout_height都设置成wrap_content
2.当将layout_width和layout_height都设置成match_parent
3.当将layout_width和layout_height都设置成100px
由此可知
a.当控件的layout_width或layout_height指定为wrap_content时,为AT_MOST
b.当控件的layout_width或layout_height指定为match_parent或具体数值时,为EXACTLY
关于上述1,可能会有些疑问,为什么宽高设置成wrap_content,父控件给该子控件分配了整个屏幕的大小?因为子控件的“android:text”值可能很长,长到占据整个屏幕,此时只要控件的尺寸不超过父控件允许的最大尺寸即可。子控件会在onMeasure方法中判断它的所需尺寸,当所需尺寸 < 屏幕尺寸时,就使用所需尺寸;当所需尺寸 >= 屏幕尺寸时,则使用屏幕尺寸
1.设置drawable在canvas中位置的Rect:
2.在onDraw中:
setBounds是设置drawable在canvas的位置信息。
3.自定义view什么时候知道宽高:
http://blog.csdn.net/mchenys/article/details/50408819
一.自定义View的属性
二.在View的构造方法中获取自定义属性
[三.重写onMesure]
四.重写onDraw
我把四用[]标出了,所以说四不一定是必须的,当然了大部分情况下还是需要重写的。
一、自定义View的属性
在res/values/底下建立一个attrs.xml,在里面定义属性和声明我们的整体样式<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="titleText" format="string" /> <attr name="titleTextColor" format="color" /> <attr name="titleTextSize" format="dimension" /> <declare-styleable name="CustomTitleView"> <attr name="titleText" /> <attr name="titleTextColor" /> <attr name="titleTextSize" /> </declare-styleable> </resources>
我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型,一共有:string,color,dimension,integer,enum,reference,float,boolean,fraction,flag
详见 Android应用资源总结七:attrs中format详解
然后在布局中声明我们的自定义View:
<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:layout_width="match_parent" android:layout_height="match_parent" > <com.example.customview01.view.CustomTitleView android:layout_width="200dp" android:layout_height="100dp" custom:titleText="3712" custom:titleTextColor="#ff0000" custom:titleTextSize="40sp" /> </RelativeLayout>
一定要引入
xmlns:custom="http://schemas.android.com/apk/res-auto"我们的命名空间,关于命名空间,请参考:Android应用资源总结三:Android命名空间
二、获取自定义属性
/** * 文本 */ private String mTitleText; /** * 文本的颜色 */ private int mTitleTextColor; /** * 文本的大小 */ private int mTitleTextSize; /** * 绘制时控制文本绘制的范围 */ private Rect mBound; private Paint mPaint; public CustomTitleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomTitleView(Context context) { this(context, null); } /** * 获得我自定义的样式属性 * * @param context * @param attrs * @param defStyle */ public CustomTitleView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); /** * 获得我们所定义的自定义样式属性 */ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.CustomTitleView_titleText: mTitleText = a.getString(attr); break; case R.styleable.CustomTitleView_titleTextColor: // 默认颜色设置为黑色 mTitleTextColor = a.getColor(attr, Color.BLACK); break; case R.styleable.CustomTitleView_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); // mPaint.setColor(mTitleTextColor); mBound = new Rect(); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound); }
我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。
三、重写onMesure,onDraw方法
onMesure:计算自定义view的宽度和高度onDraw:绘制自定义view
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { 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); }
此时的效果是:
是不是觉得还不错,基本已经实现了自定义View。但是此时如果我们把布局文件的宽和高写成wrap_content,会发现效果并不是我们的预期:
系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。
所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法。
重写之前先了解MeasureSpec的specMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是我们重写onMeasure代码:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 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(mTitle, 0, mTitle.length(), mBounds); float textWidth = mBounds.width(); int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight()); width = desired; } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else { mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds); float textHeight = mBounds.height(); int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom()); height = desired; } setMeasuredDimension(width, height); }
现在我们修改下布局文件:
<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/com.example.customview01" android:layout_width="match_parent" android:layout_height="match_parent" > <com.example.customview01.view.CustomTitleView android:layout_width="wrap_content" android:layout_height="wrap_content" custom:titleText="3712" android:padding="10dp" custom:titleTextColor="#ff0000" android:layout_centerInParent="true" custom:titleTextSize="40sp" /> </RelativeLayout>
现在的效果是:
补充:关于View测量模式:EXACTLY、AT_MOST、UNSPECIFIED
一.自定义一个View:public class CustomView extends TextView { public CustomView(Context context) { super(context); } public CustomView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //宽度 String specMode_width = ""; int specModeWidth = MeasureSpec.getMode(widthMeasureSpec); switch (specModeWidth) { case MeasureSpec.EXACTLY: specMode_width = "EXACTLY"; break; case MeasureSpec.AT_MOST: specMode_width = "AT_MOST"; break; case MeasureSpec.UNSPECIFIED: specMode_width = "UNSPECIFIED"; break; } //高度度 String specMode_height = ""; int specModeHeight = MeasureSpec.getMode(heightMeasureSpec); switch (specModeHeight) { case MeasureSpec.UNSPECIFIED: specMode_height = "UNSPECIFIED"; break; case MeasureSpec.AT_MOST: specMode_height = "AT_MOST"; break; case MeasureSpec.EXACTLY: specMode_height = "EXACTLY"; break; } Log.e("TAG", "specMode_width = " + specMode_width + " , specMode_height = " + specMode_height); Log.e("TAG", "specSize_width = " + MeasureSpec.getSize(widthMeasureSpec) + " , specSize_height = " + MeasureSpec.getSize(heightMeasureSpec)); } }
二.在布局文件中使用View:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.customerviewapp.CustomView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/holo_blue_light" android:gravity="center" android:text="hello world"/> </LinearLayout>
然后运行(屏幕分辨率为:720×1280)
输出结果如下:
1.此时layout_width和layout_height都设置成wrap_content
TAG: specMode_width = AT_MOST , specMode_height = AT_MOST TAG: specSize_width = 720 , specSize_height = 1280
2.当将layout_width和layout_height都设置成match_parent
TAG: specMode_width = EXACTLY , specMode_height = EXACTLY TAG: specSize_width = 720 , specSize_height = 1280
3.当将layout_width和layout_height都设置成100px
TAG: specMode_width = EXACTLY , specMode_height = EXACTLY TAG: specSize_width = 100 , specSize_height = 100
由此可知
a.当控件的layout_width或layout_height指定为wrap_content时,为AT_MOST
b.当控件的layout_width或layout_height指定为match_parent或具体数值时,为EXACTLY
关于上述1,可能会有些疑问,为什么宽高设置成wrap_content,父控件给该子控件分配了整个屏幕的大小?因为子控件的“android:text”值可能很长,长到占据整个屏幕,此时只要控件的尺寸不超过父控件允许的最大尺寸即可。子控件会在onMeasure方法中判断它的所需尺寸,当所需尺寸 < 屏幕尺寸时,就使用所需尺寸;当所需尺寸 >= 屏幕尺寸时,则使用屏幕尺寸
四、使用总结
画drawable:mBaseLineDrawable1.设置drawable在canvas中位置的Rect:
mBaseLineRect.set(left,top,left + mBaseLineDrawable.getIntrinsicWidth(),top + mBaseLineDrawable.getIntrinsicHeight());
2.在onDraw中:
mBaseLineDrawable.setBounds(mBaseLineRect); mBaseLineDrawable.draw(canvas);
setBounds是设置drawable在canvas的位置信息。
3.自定义view什么时候知道宽高:
http://blog.csdn.net/mchenys/article/details/50408819
相关文章推荐
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 自定义View (二) 进阶
- 我的Android进阶之旅------>Android自定义View来实现解析lrc歌词并同步滚动、上下拖动、缩放歌词的功能
- Android进阶学习-使用Canvas自定义ProgressView(3)
- Android进阶之自定义View实战(三)贝塞尔曲线应用
- Android高手进阶教程(三)之----Android 中自定义View的应用.
- Android 自定义View (二) 进阶
- Android高手进阶教程(三)之----Android 中自定义View的应用
- Android高手进阶教程(三)之----Android 中自定义View的应用.
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android进阶之自定义view(一)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 自定义View (二) 进阶
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android进阶学习-使用Canvas自定义ImageTextView(2)
- Android 自定义View (二) 进阶
- Android高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android自定义View进阶 - 贝塞尔曲线