首页 > 系统 > Android > 正文

Android自定义view(一) 知识理解

2019-11-09 17:26:48
字体:
来源:转载
供稿:网友

前言:

Android开发的小伙伴都知道自定义view是一个让人又爱又恨的东西,爱的原因是因为自定义view可以实现项目的千变万化的需求,恨是因为自定义view有难么一点难以理解,一副雾里看花的感觉,当然这是我自己的个人感受,所以也是为了让自己对自定义view能够有更深入的理解和体会,写下该博客。

1.View是什么?

View正如它的字义理解,就是视图的意思,android中所有的控件都是继承于View这个类的。无论是viewgroup还是简单的widget控件,比如button,所以如果要真正搞明白自定义view,一定要知道view内部涉及到的主要方法和如何加载的过程。

2.View的加载

一个Activity将view加载到屏幕上,代码实现很简单,只需要调用

setContentView(R.layout.activity_main);

这个方法是android的一个封装,里面的流程大致为: 第一步:

LayoutInflater layoutInflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

获取一个LayoutInflater 对象,然后调用inflate方法:

public View inflate(xmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); mConstructorArgs[0] = mContext; View result = root; try { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("merge can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, attrs); } else { View temp = createViewFromTag(name, attrs); ViewGroup.LayoutParams params = null; if (root != null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { temp.setLayoutParams(params); } } rInflate(parser, temp, attrs); if (root != null && attachToRoot) { root.addView(temp, params); } if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } return result; } }

其中的rInflate的方法为:

PRivate void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { final View view = createViewFromTag(name, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs); viewGroup.addView(view, params); } } parent.onFinishInflate(); }

以上代码的主要作用是将xml布局文件转化为View类,然后加载在页面显示出来。 同时,我们在获取到LayoutInflate对象后有个重载方法:

inflate(int resource, ViewGroup root, boolean attachToRoot)

有些人对这些参数不同值时代表的意义不是很理解,我觉得其实从参数类的名字的意义更好记忆。 resource:表示加载布局的资源id。 root:表示给该resource添加一个父布局。 attachToRoot:true表示root为该resource的父布局,false表示root不为该resource的父布局。 所以:root为null时,resource不主动添加一层父布局,attachToRoot设置的值也就不起作用。 root不为null时,resource主动添加一层父布局,attachToRoot为true时表示添加成功,false表示添加失败。

加载的过程中会涉及到很多重要的方法:

onMeasure();测量控件大小

onMeasure(int widthMeasureSpec, int heightMeasureSpec); 参数是一个MeasureSpec类型,这个类是由一个低2位表示测量模式,低30位表示测量大小组成的。其中,测量类型mode的类型有三种:

EXACTLY 表示父视图希望子视图的大小应该是由specSize的值来决定的,一般对应的是具体的数值或者match_parent。

AT_MOST 表示子视图最多只能是specSize中指定的大小,一般对应的是warp_content。

UNSPECIFIED 表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

在onMeasure方法中,最后调用setMeasuredDimension(widthSize, heightSize); 这个方法也是super中调用的方法,是真正起作用的方法。调用完该方法后,我们使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高才是有值得。对于继承ViewGroup的控件,onMeasure要用一个循环来测量子view的大小,比如像我们熟知的RelativeLayout和LinearLayout都对该方法进行了重写。

onLayout();控件的位置布局

一般继承ViewGroup的控件需要对onLayout方法进行重写来显示子view的位置逻辑控件。调用完该方法后我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了,该宽高是坐标相减计算得到的。

onDraw();控件绘制

对于继承自view的控件,onDraw方法可以用来绘制我们想要的效果的东西。

onFinishInflate();从XML加载完毕后回调 onSizeChange();控件大小发生改变时,这两个方法多在组合控件中使用。

了解完了上面的简单知识点后,下篇文章会以三个具体的例子来说明自定义view的三种方式。 最近搞了个Android技术分享的公众号,欢迎关注投稿。 这里写图片描述


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表