Android 自定义View(一) 介绍和一个简单TextView显示
2016-03-03 19:41
525 查看
转载注明出处:/article/7603128.html
下面介绍几个最普遍的方法:
OnMeasure (测量控件宽高的方法。)
OnDraw (绘制控件内容的方法)
OnLayout (此方法继承ViewGroup用的较多,用于摆放Group里子view的位置)
在View的构造方法中获取属性。
重写onMeasure方法
重写OnDraw方法
这四个步骤是最常见的步骤。
来看一段ImageView的onMeasure方法。
里面两种情况,判断width和height的mode是否为 MeasureSpec.EXACTLY,当width 和 height 其中一个为MeasureSpec.EXACTLY的时候进入情况一也就是
然后就可以在里面定义属于你自己的自定义属性值
example:
关于declare-styleable里的 attr标签里的属性 format可以参考
declare-styleable中format详解 里的内容;
跟你的工具有关,
在布局xml里最外层的控件都会有这个属性值
正常添加自己定义的属性值都是
如eclipse 如下:
不过在Android Studio里这样写,会导致程序crash。需要将获取自定义属性的地址改成
example
注意一下,在构造方法里获得的属性值是布局文件xml里,可以在View类里写入方法修改如
关于View类自带方法的
onMeasure获取到他的MeasureSpec的模式。具体的内容可以查看MeasureSpec介绍 的内容。
看一下简单的重写onMeasure方法
简单的onDraw方法
更多资料参考
Android.View绘制流程 来自 - WPJY
Android View绘制流程及invalidate()等方法分析 来自- qinjuning
Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析 来自 - 老罗(罗升阳)
意义
首先知道自定义View的意义,就是Android自身提供的控件已经满足不了我们的需求的时候就会进行继承View来进行自定义View的代码编写,实现需求。需求
定义需求,是何种,例如 下拉刷新上拉更多的列表 , 看具体展示内容,可继承 GridView 或者 ListView 。步骤
一般来说,只需要了解最基本的自定义方法即可。下面介绍几个最普遍的方法:
OnMeasure (测量控件宽高的方法。)
OnDraw (绘制控件内容的方法)
OnLayout (此方法继承ViewGroup用的较多,用于摆放Group里子view的位置)
正题
定义自定义字节属性,此属性是在布局文件xml里View的属性。在View的构造方法中获取属性。
重写onMeasure方法
重写OnDraw方法
这四个步骤是最常见的步骤。
来看一段ImageView的onMeasure方法。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { resolveUri(); int w; int h; // Desired aspect ratio of the view's contents (not including padding) float desiredAspect = 0.0f; // We are allowed to change the view's width boolean resizeWidth = false; // We are allowed to change the view's height boolean resizeHeight = false; final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (mDrawable == null) { // If no drawable, its intrinsic size is 0. mDrawableWidth = -1; mDrawableHeight = -1; w = h = 0; } else { w = mDrawableWidth; h = mDrawableHeight; if (w <= 0) w = 1; if (h <= 0) h = 1; // We are supposed to adjust view bounds to match the aspect // ratio of our drawable. See if that is possible. if (mAdjustViewBounds) { resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; desiredAspect = (float) w / (float) h; } } int pleft = mPaddingLeft; int pright = mPaddingRight; int ptop = mPaddingTop; int pbottom = mPaddingBottom; int widthSize; int heightSize; if (resizeWidth || resizeHeight) { /* If we get here, it means we want to resize to match the drawables aspect ratio, and we have the freedom to change at least one dimension. */ // Get the max possible width given our constraints widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec); // Get the max possible height given our constraints heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec); if (desiredAspect != 0.0f) { // See what our actual aspect ratio is float actualAspect = (float)(widthSize - pleft - pright) / (heightSize - ptop - pbottom); if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { boolean done = false; // Try adjusting width to be proportional to height if (resizeWidth) { int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright; // Allow the width to outgrow its original estimate if height is fixed. if (!resizeHeight && !mAdjustViewBoundsCompat) { widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); } if (newWidth <= widthSize) { widthSize = newWidth; done = true; } } // Try adjusting height to be proportional to width if (!done && resizeHeight) { int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom; // Allow the height to outgrow its original estimate if width is fixed. if (!resizeWidth && !mAdjustViewBoundsCompat) { heightSize = resolveAdjustedSize(newHeight, mMaxHeight, heightMeasureSpec); } if (newHeight <= heightSize) { heightSize = newHeight; } } } } } else { /* We are either don't want to preserve the drawables aspect ratio, or we are not allowed to change view dimensions. Just measure in the normal way. */ w += pleft + pright; h += ptop + pbottom; w = Math.max(w, getSuggestedMinimumWidth()); h = Math.max(h, getSuggestedMinimumHeight()); widthSize = resolveSizeAndState(w, widthMeasureSpec, 0); heightSize = resolveSizeAndState(h, heightMeasureSpec, 0); } setMeasuredDimension(widthSize, heightSize); }
里面两种情况,判断width和height的mode是否为 MeasureSpec.EXACTLY,当width 和 height 其中一个为MeasureSpec.EXACTLY的时候进入情况一也就是
if (resizeWidth || resizeHeight)如果不是的情况下,进入情况2
else段。最后将得到的width 和 height 值使用 默认方法
setMeasuredDimension传入值。
自定义属性值
在values 文件夹里的 任一xml的<resources>里加上
declare-styleable标签。记得加上属性
name
然后就可以在里面定义属于你自己的自定义属性值
example:
<attr name="customsText" format="string" /> <attr name="customsSize" format="dimension" /> <declare-styleable name="FirstView"> <attr name="customsText" /> <attr name="customsColor" format="color" /> <attr name="customsSize" /> </declare-styleable>
关于declare-styleable里的 attr标签里的属性 format可以参考
declare-styleable中format详解 里的内容;
注意事项
关于自定义View在布局xml里的应用跟你的工具有关,
在布局xml里最外层的控件都会有这个属性值
xmlns:android="http://schemas.android.com/apk/res/android"这个属性值就是获取Android本身自带的xml属性值。
xmlns:android这个android就是属性值开头例如:
android:width等。
正常添加自己定义的属性值都是
xmlns:xxxx="http://schemas.android.com/apk/res/包名
如eclipse 如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/viewdemo.cifer.com.myview" android:layout_width="match_parent" android:layout_height="match_parent"> <viewdemo.cifer.com.myview.view.FirstView android:layout_width="wrap_content" android:layout_height="wrap_content" custom:customsText="3712" android:padding="10dp" custom:customsColor="#ff0000" android:layout_centerInParent="true" custom:customsSize="40sp" /> </RelativeLayout>
不过在Android Studio里这样写,会导致程序crash。需要将获取自定义属性的地址改成
xmlns:custom="http://schemas.android.com/apk/res-auto"即可,所有的自定义属性都会出来再
custom:里。
example
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <viewdemo.cifer.com.myview.view.FirstView android:layout_width="wrap_content" android:layout_height="wrap_content" custom:customsText="3712" android:padding="10dp" custom:customsColor="#ff0000" android:layout_centerInParent="true" custom:customsSize="40sp" /> </RelativeLayout>
构造方法
这里没什么说的,唯一需要注意的是构造方法的前两个的引用不是super而是
this;
public FirstView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FirstView(Context context) { this(context, null); } public FirstView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FirstView, defStyleAttr, 0); //在这里有两种获取方法,我都列出来,我自己用的第二种,可以方便控制是否有此属性,进行获取.注:构造方法只会调用一次。 int n = typedArray.getIndexCount(); for (int i = 0; i < n; i++) { int attr = typedArray.getIndex(i); switch (attr) { case R.styleable.FirstView_customsText: break; case R.styleable.FirstView_customsColor: // 默认颜色设置为黑色 break; case R.styleable.FirstView_customsSize: // 默认设置为16sp,TypeValue也可以把sp转化为px break; } } try { mTitleText = typedArray.getString(R.styleable.FirstView_customsText); mTitleTextColor = typedArray.getColor(R.styleable.FirstView_customsColor, Color.BLACK); mTitleTextSize = typedArray.getDimensionPixelSize(R.styleable.FirstView_customsSize, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); } catch (Exception e) { e.printStackTrace(); } finally { typedArray.recycle(); } /** * 获得绘制文本的宽和高 */ mPaint = new Paint(); mPaint.setTextSize(mTitleTextSize); mPaint.setColor(mTitleTextColor); mBound = new Rect(); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound); }
注意一下,在构造方法里获得的属性值是布局文件xml里,可以在View类里写入方法修改如
public void setText(String text){ this.mTitleText = text; postInvalidate();//invalidate(); }
关于View类自带方法的
postInvalidate() 和 invalidate()的区别可以参考android中Invalidate和postInvalidate的区别
重写onMeasure方法
上面亮出了ImageView的onMeasure方法,觉得很复杂,正常来说一般不过几十行代码,就是计算内容的width和height值,与系统默认的width 和 height值,进行比较,根据布局文件xml里width/height的设置取最小值or最大值。
onMeasure获取到他的MeasureSpec的模式。具体的内容可以查看MeasureSpec介绍 的内容。
看一下简单的重写onMeasure方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = 0; int height = 0; /** * 设置宽度 */ int specMode = MeasureSpec.getMode(widthMeasureSpec); int specSize = MeasureSpec.getSize(widthMeasureSpec); switch (specMode) { case MeasureSpec.EXACTLY:// 明确指定了 width = getPaddingLeft() + getPaddingRight() + specSize; break; case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT width = getPaddingLeft() + getPaddingRight() + mBound.width(); break; } /** * 设置高度 */ specMode = MeasureSpec.getMode(heightMeasureSpec); specSize = MeasureSpec.getSize(heightMeasureSpec); switch (specMode) { case MeasureSpec.EXACTLY:// 明确指定了 height = getPaddingTop() + getPaddingBottom() + specSize; break; case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT height = getPaddingTop() + getPaddingBottom() + mBound.height(); break; } setMeasuredDimension(width, height); }
onDraw
这里较为重要,因为界面的呈现效果,和界面美化,动画全在这里做,也是更新最频繁的方法。简单的onDraw方法
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(Color.BLACK); canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); mPaint.setColor(mTitleTextColor); canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint); }
更多方法
onFinishInflate() 当View中所有的子控件 均被映射成xml后触发 onSizeChanged(int, int, int, int) 改变大小时调用。 参数分别为 width ,height , oldWidth , oldHeight onTrackballEvent(MotionEvent) 轨迹球事件 onTouchEvent(MotionEvent) 触屏事件 onFocusChanged(boolean, int, android.graphics.Rect) 当View获取 或失去焦点时触发 onWindowFocusChanged(boolean) 当view被附着到一个窗口时触发 onDetachedFromWindow() 当view离开附着的窗口时触发,该方法和 onAttachedToWindow() 是相反的。 onWindowVisibilityChanged(int) 当窗口中包含的可见的view发生变化时触发 以及常见的 onKeyDown(int, KeyEvent) onKeyUp(int, KeyEvent)
更多资料参考
Android.View绘制流程 来自 - WPJY
Android View绘制流程及invalidate()等方法分析 来自- qinjuning
Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析 来自 - 老罗(罗升阳)
相关文章推荐
- android:ImageView选择本地图片并显示
- Android——adb工具的使用
- Android学习笔记----SQLite数据库基本用法
- Android Design Support Library初探-更新中
- Android中SharedPreference多进程数据共享出错
- 如何使用Android Studio开发Gradle插件
- 版本扫盲及最新android studio下载
- 将Eclipse代码导入到AndroidStudio的两种方式
- android .9图片使用和一些技巧
- 深入理解Android之AOP
- Android数据持久化技术 — — —SharedPreferences
- android studio 之无法导项目路径包
- Android Intents和Intent过滤器
- Android学习历程20-安卓性能优化简介
- Android截图的两种方法
- android: UriMatcher的用法
- Android Cordova 插件开发之自定义插件生成安装包
- Android Cordova 插件开发之编写自定义插件
- Android Init Language
- Android Cordova 插件开发之创建项目