【移动开发】怎样自己定义ViewGroup
本文翻译自《50 android hacks》
按照惯例。先从一个样例说起。
非常easy,3张扑克牌叠在一起显示。
这个布局效果该怎样实现呢?有的同学该说了,这非常easy啊,用RelativeLayout或FrameLayout,然后为每一个扑克牌设置margin就能实现了。
ok,那就看一下通过这样的方式是怎样实现的。
代码例如以下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" > <View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#FF0000" /> <View
android:layout_width="100dp"
android:layout_height="150dp"
android:layout_marginLeft="30dp"
android:layout_marginTop="20dp"
android:background="#00FF00" /> <View
android:layout_width="100dp"
android:layout_height="150dp"
android:layout_marginLeft="60dp"
android:layout_marginTop="40dp"
android:background="#0000FF" /> </RelativeLayout>
效果图
没错,通过这样的方式是能够实现的。
可是。不认为这样的方式有点low吗?!
让我们用高级一点的方式去实现它,提升一下自己的逼格!
定制ViewGroup之前,我们须要先理解几个概念。
Android绘制视图的方式
这里我不会涉及太多的细节,可是须要理解Android开发文档中的一段话:
“绘制布局由两个遍历过程组成:測量过程和布局过程。
測量过程由measure(int, int)方法完毕,该方法从上到下遍历视图树。
在递归遍历过程中。每一个视图都会向下层传递尺寸和规格。当measure方法遍历结束,每一个视图都保存了各自的尺寸信息。第二个过程由layout(int,int,int,int)方法完毕,该方法也是由上而下遍历视图树。在遍历过程中,每一个父视图通过測量过程的结果定位全部子视图的位置信息。
”
简而言之,第一步是測量ViewGroup的宽度和高度,在onMeasure()方法中完毕,ViewGroup遍历全部子视图计算出它的大小。第二步是依据第一步获取的尺寸去布局全部子视图,在onLayout()中完毕。
创建CascadeLayout
最终到了定制ViewGroup的阶段了。
假设我们已经定制了一个CascadeLayout的容器。我们会这样使用它。
<FrameLayout xmlns:cascade="http://schemas.android.com/apk/res/com.manoel.custom"
<!-- 声明命名空间 -->
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" > <com.manoel.view.CascadeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
<!-- 自己定义属性 -->
cascade:horizontal_spacing="30dp"
cascade:vertical_spacing="20dp" > <View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#FF0000" /> <View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#00FF00" /> <View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#0000FF" />
</com.manoel.view.CascadeLayout> </FrameLayout>
首先,定义属性。在values目录以下创建attrs.xml。代码例如以下:
<resources>
<declare-styleable name="CascadeLayout">
<attr name="horizontal_spacing" format="dimension" />
<attr name="vertical_spacing" format="dimension" />
</declare-styleable>
</resources>
同一时候,为了严谨一些,定义一些默认的垂直距离和水平距离,以防在布局中没有提供这些属性。
在dimens.xml中加入例如以下代码:
<resources>
<dimen name="cascade_horizontal_spacing">10dp</dimen>
<dimen name="cascade_vertical_spacing">10dp</dimen>
</resources>
准备工作已经做好了。接下来看一下CascadeLayout的源代码,稍微有点长,后面帮助大家分析一下。
public class CascadeLayout extends ViewGroup {
private int mHorizontalSpacing;
private int mVerticalSpacing;
public CascadeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout);
try {
mHorizontalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_horizontal_spacing,
getResources().getDimensionPixelSize(
R.dimen.cascade_horizontal_spacing));
mVerticalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_vertical_spacing, getResources()
.getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
} finally {
a.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getPaddingLeft();
int height = getPaddingTop();
int verticalSpacing;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
verticalSpacing = mVerticalSpacing;
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
width = getPaddingLeft() + mHorizontalSpacing * i;
lp.x = width;
lp.y = height;
if (lp.verticalSpacing >= 0) {
verticalSpacing = lp.verticalSpacing;
}
width += child.getMeasuredWidth();
height += verticalSpacing;
}
width += getPaddingRight();
height += getChildAt(getChildCount() - 1).getMeasuredHeight()
+ getPaddingBottom();
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y
+ child.getMeasuredHeight());
}
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p.width, p.height);
}
public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
public int verticalSpacing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LayoutParams(int w, int h) {
super(w, h);
}
}
}
首先,分析构造函数。
public CascadeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout);
try {
mHorizontalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_horizontal_spacing,
getResources().getDimensionPixelSize(
R.dimen.cascade_horizontal_spacing));
mVerticalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_vertical_spacing, getResources()
.getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
} finally {
a.recycle();
}
}
假设在布局中使用CasecadeLayout,系统就会调用这个构造函数,这个大家都应该知道的吧。这里不解释why。有兴趣的能够去看源代码,重点看系统是怎样解析xml布局的。
构造函数非常easy,就是通过布局文件里的属性,获取水平距离和垂直距离。
然后。分析自己定义LayoutParams。
这个类的用途就是保存每一个子视图的x,y轴位置。这里把它定义为静态内部类。ps:提到静态内部类。我又想起来关于多线程内存泄露的问题了,假设有时间再给大家解释一下多线程造成内存泄露的问题。
public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
public int verticalSpacing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LayoutParams(int w, int h) {
super(w, h);
}
}
除此之外。还须要重写一些方法。checkLayoutParams()、generateDefaultLayoutParams()等,这种方法在不同ViewGroup之间往往是同样的。
接下来。分析onMeasure()方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getPaddingLeft();
int height = getPaddingTop();
int verticalSpacing; final int count = getChildCount();
for (int i = 0; i < count; i++) {
verticalSpacing = mVerticalSpacing; View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec); // 令每一个子视图測量自身 LayoutParams lp = (LayoutParams) child.getLayoutParams();
width = getPaddingLeft() + mHorizontalSpacing * i;
// 保存每一个子视图的x。y轴坐标
lp.x = width;
lp.y = height; if (lp.verticalSpacing >= 0) {
verticalSpacing = lp.verticalSpacing;
} width += child.getMeasuredWidth();
height += verticalSpacing;
} width += getPaddingRight();
height += getChildAt(getChildCount() - 1).getMeasuredHeight()
+ getPaddingBottom();
// 使用计算所得的宽和高设置整个布局的測量尺寸
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
}
最后,分析onLayout()方法。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams(); child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y
+ child.getMeasuredHeight());
}
}
逻辑非常easy。用onMeasure()方法计算出的值为參数循环调用子View的layout()方法。
为子视图加入自己定义属性
作为演示样例。以下将加入子视图重写垂直间距的方法。
第一步是向attrs.xml中加入一个新的属性。
<declare-styleable name="CascadeLayout_LayoutParams">
<attr name="layout_vertical_spacing" format="dimension" />
</declare-styleable>
这里的属性名是layout_vertical_spacing,由于该属性名前缀是layout_。同一时候,又不是View固有的属性。所以该属性会被加入到LayoutParams的属性表中。在CascadeLayout类的构造函数中读取这个新属性。
public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
public int verticalSpacing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout_LayoutParams);
try {
verticalSpacing = a
.getDimensionPixelSize(
R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing,
-1);
} finally {
a.recycle();
}
}
public LayoutParams(int w, int h) {
super(w, h);
}
}
那怎么使用这个属性呢?so easy!
<com.manoel.view.CascadeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
cascade:horizontal_spacing="30dp"
cascade:vertical_spacing="20dp" > <!-- 注意:这张“扑克牌”使用了layout_vertical_spacing属性 -->
<View
android:layout_width="100dp"
android:layout_height="150dp"
cascade:layout_vertical_spacing="90dp"
android:background="#FF0000" /> <View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#00FF00" /> <View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#0000FF" />
</com.manoel.view.CascadeLayout>
參考资料
【移动开发】怎样自己定义ViewGroup的更多相关文章
- 50个Android开发技巧(03 自己定义ViewGroup)
问题:怎样创建一个例如以下图所看到的的布局? 图1 (原文地址:http://blog.csdn.net/vector_yi/article/details/244155 ...
- Android 自己定义ViewGroup 实战篇 -> 实现FlowLayout
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38352503 .本文出自[张鸿洋的博客] 1.概述 上一篇已经基本给大家介绍了怎 ...
- 自己定义 ViewGroup 支持无限循环翻页之三(响应回调事件)
大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处,再次感谢 ################################ ...
- Android ViewDragHelper全然解析 自己定义ViewGroup神器
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/46858663. 本文出自:[张鸿洋的博客] 一.概述 在自己定义ViewGro ...
- Android自己定义组件系列【3】——自己定义ViewGroup实现側滑
有关自己定义ViewGroup的文章已经非常多了,我为什么写这篇文章,对于刚開始学习的人或者对自己定义组件比較生疏的朋友尽管能够拿来主义的用了,可是要一步一步的实现和了解当中的过程和原理才干真真脱离别 ...
- Android 自己定义ViewGroup手把手教你实现ArcMenu
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这种UI效果,感觉非常不错,后来知道github上有这 ...
- 自己定义ViewGroup控件(二)----->流式布局进阶(二)
main.xml <?xml version="1.0" encoding="utf-8"? > <com.example.SimpleLay ...
- Android自己定义ViewGroup打造各种风格的SlidingMenu
看鸿洋大大的QQ5.0側滑菜单的视频课程,对于側滑的时的动画效果的实现有了新的认识,似乎打通了任督二脉.眼下能够实现随意效果的側滑菜单了.感谢鸿洋大大!! 鸿洋大大用的是HorizontalScrol ...
- Android自己定义组件系列【4】——自己定义ViewGroup实现双側滑动
在上一篇文章<Android自己定义组件系列[3]--自己定义ViewGroup实现側滑>中实现了仿Facebook和人人网的側滑效果,这一篇我们将接着上一篇来实现双面滑动的效果. 1.布 ...
随机推荐
- POJ 3308 Paratroopers(最小割EK)
题目链接 题意 : 有一个n*m的矩阵,L个伞兵可能落在某些点上,这些点的坐标已知,需要在某些位置安上一些枪,然后每个枪可以将一行或者一列的伞兵击毙.把这种枪安装到不同行的行首.或者不同列的列首,费用 ...
- easyui源码翻译1.32--EasyLoader(简单加载)
前言 扩展自$.fn.datebox.defaults,使用$.fn.datetimebox.defaults重写默认值对象.下载该插件翻译源码 源码 /** * jQuery EasyUI 1.3. ...
- [译]GotW #3: Using the Standard Library (or, Temporaries Revisited)
高效的代码重用是良好的软件工程中重要的一部分.为了演示如何更好地通过使用标准库算法而不是手工编写,我们再次考虑先前的问题.演示通过简单利用标准库中已有的算法来避免的一些问题. Problem JG Q ...
- Oracle Developer Form中Block的重新查询
Form中某些按钮可能调用了Package对表中某些字段进行更新,但是数据库中字段的修改不会马上反映到form的界面上,所以要进行重新查询,但是用户可能使用了查询窗口进行查询之后然后再点击按钮动作,如 ...
- hdu4632Palindrome subsequence
http://acm.hdu.edu.cn/showproblem.php?pid=4632 TLE了N次 原因居然是取模次数太多了..! 这数据卡的好紧 还是我写的太搓..828ms挤过 s[i]= ...
- [Hadoop源码解读](六)MapReduce篇之MapTask类
MapTask类继承于Task类,它最主要的方法就是run(),用来执行这个Map任务. run()首先设置一个TaskReporter并启动,然后调用JobConf的getUseNewAPI()判断 ...
- 【转】Cannot find -ltinfo when compiling android 4.0.3
原文网址:http://stackoverflow.com/questions/9055005/cannot-find-ltinfo-when-compiling-android-4-0-3 up v ...
- Sum It Up
http://acm.hdu.edu.cn/showproblem.php?pid=1258 Sum It Up Time Limit: 2000/1000 MS (Java/Others) M ...
- [codevs4247]奇特的生物
题目描述 Description 科学家们最近发现了一种奇怪的生物,它们每天长大一岁,刚出生的宝宝为1岁,且它们的年龄没有上限.已知年龄为1岁,2岁,3岁,……,k岁的个体具有生育能力,当年龄为i的具 ...
- bzoj 2434 [Noi2011]阿狸的打字机(fail树+离线处理+BIT)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2434 [题意] 按照一定规则生成n个字符串,回答若干个询问:(x,y),问第x个字符串 ...