一.Android的onLayout、layout方法
onLayout方法是ViewGroup中子View的布局方法,用于放置子View的位置。放置子View很简单,只需在重写onLayout方法,然后获取子View的实例,调用子View的layout方法实现布局。在实际开发中,一般要配合onMeasure测量方法一起使用。
onLayout方法:
1
2
3
|
@Override
protected
abstract
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b);
|
layout方法:
1
|
public
void
layout(
int
l,
int
t,
int
r,
int
b);
|
平常开发所用到RelativeLayout、LinearLayout、FrameLayout...这些都是继承ViewGroup的布局。这些布局的实现都是通过都实现ViewGroup的onLayout方法,只是实现方法不一样而已。
下面是一个自定义ViewGroup的Demo,用onLayout和layout实现子View的水平放置,间隔是20px
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
class
MyViewGroup
extends
ViewGroup {
// 子View的水平间隔
private
final
static
int
padding =
20
;
public
MyViewGroup(Context context, AttributeSet attrs) {
super
(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
// TODO Auto-generated method stub
// 动态获取子View实例
for
(
int
i =
0
, size = getChildCount(); i < size; i++) {
View view = getChildAt(i);
// 放置子View,宽高都是100
view.layout(l, t, l +
100
, t +
100
);
l +=
100
+ padding;
}
}
}
|
Activity的XML布局:
1
2
3
4
5
6
7
8
9
10
|
<relativelayout xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:padding=
"10dp"
>
<com.example.layout.myviewgroup android:layout_width=
"match_parent"
android:layout_height=
"100dp"
android:background=
"#0000ff"
>
<view android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:background=
"#ff0000"
>
<view android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:background=
"#00ff00"
>
</view></view></com.example.layout.myviewgroup>
</relativelayout>
|
在自定义View中,onLayout配合onMeasure方法一起使用,可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,然后调用onLayout方法,动态获取子View和子View的测量大小,然后进行layout布局。
二.Canvas的save和restore
在创建新的控件或修改现有的控件时,我们都会涉及到重写控件或View的onDraw方法。
onDraw方法会传入一个Canvas对象,它是你用来绘制控件视觉界面的画布。
在onDraw方法里,我们经常会看到调用save和restore方法,它们到底是干什么用的呢?
❑ save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
❑ restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
save和restore要配对使用(restore可以比save少,但不能多),如果restore调用次数比save多,会引发Error。
例如:我们先想在画布上绘制一个右向的三角箭头,当然,我们可以直接绘制,另外,我们也可以先把画布旋转90°,画一个向上的箭头,然后再旋转回来(这种旋转操作对于画圆周上的标记非常有用)。然后,我们想在右下角有个20像素的圆,那么,onDraw中的核心代码是:
int px = getMeasuredWidth();
int py = getMeasuredWidth();
// Draw background
canvas.drawRect(0, 0, px, py, backgroundPaint);
canvas.save();
canvas.rotate(90, px/2, py/2);
// Draw up arrow
canvas.drawLine(px / 2, 0, 0, py / 2, linePaint);
canvas.drawLine(px / 2, 0, px, py / 2, linePaint);
canvas.drawLine(px / 2, 0, px / 2, py, linePaint);
canvas.restore();
// Draw circle
canvas.drawCircle(px - 10, py - 10, 10, linePaint);
效果如图1所示:
图1
如果我们不调用save和restore会是什么样子呢?如图2所示:
图2
从这两个图中,我们就能看到圆圈位置的明显差异。不进行Canvas的save和restore操作的话,所有的图像都是在画布旋转90°后的画布上绘制的。当执行完onDraw方法,系统自动将画布恢复回来。save和restore操作执行的时机不同,就能造成绘制的图形不同。
所以,save和restore之间,往往夹杂的是对Canvas的特殊操作。
三.ondraw() 和dispatchdraw()的区别绘制VIew本身的内容,通过调用View.onDraw(canvas)函数实现
绘制自己的孩子通过dispatchDraw(canvas)实现
View组件的绘制会调用draw(Canvas canvas)方法,draw过程中主要是先画Drawable背景,对 drawable调用setBounds()然后是draw
(Canvas c)方法.有点注意的是背景drawable的实际大小会影响view组件的大小,drawable的实际大小通过getIntrinsicWidth()和
getIntrinsicHeight()获取,当背景比较大时view组件大小等于背景drawable的大小
画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分发
给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的
是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。
因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw
(Canvas c)和 getIntrinsicWidth(),
getIntrinsicHeight()方法,然后设为背景。
四.paint.ascent()和paint.descent()各自指的是什么意思
1.基准点是baseline
2.ascent:是baseline之上至字符最高处的距离
3.descent:是baseline之下至字符最低处的距离
4.leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离
5.top:是指的是最高字符到baseline的值,即ascent的最大值
6.bottom:是指最低字符到baseline的值,即descent的最大值
五.getWidth和getMeasuredWidth的区别
1. 在一个类初始化时,即在构造函数当中我们是得不到View的实际大小的。感兴趣的朋友可以试一下,getWidth()和getMeasuredWidth()得到的结果都是0.但是我们可以从onDraw()方法裡面得到控件的大小。
2. 这两个方法所得到的结果的单位是像素即pixel.
对两个方法做介绍:
getWidth(): 得到的是view在父Layout中布局好后的宽度值,如果没有父布局,那麼默认的父布局是整个屏幕。也许不好理解。通过一个例子来说明一下。
例1 :
public class Test extends Activity {
private LinearLayout mBackgroundLayout;
private TextViewTest mTextViewTest;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBackgroundLayout = new MyLayout(this);
mBackgroundLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT));
mTextViewTest = new TextViewTest(this);
mBackgroundLayout.addView(mTextViewTest);
setContentView(mBackgroundLayout);
}
public class MyLayout extends LinearLayout{
public MyLayout(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
super.onLayout(changed, l, t, r, b);
Log.i("Tag", "--------------");
View mView=getChildAt(0);
mView.measure(0, 0);
}
}
public class TextViewTest extends TextView {
public TextViewTest(Context context) {
super(context);
// TODO Auto-generated constructor stub
setText("test test ");
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
// measure(0, 0);
Log.i("Tag", "width: " + getWidth() + ",height: " + getHeight());
Log.i("Tag", "MeasuredWidth: " + getMeasuredWidth()
+ ",MeasuredHeight: " + getMeasuredHeight());
}
}
}
这裡是在LinearLayout裡添加一个TextView控件,如果此时要得到对TextView获取getWidth(),那麼是在TextView添加到Layout后再去获取值,并不单单的是对TextView本身宽度的获取。
getMeasuredWidth():先看一下API裡面怎麼说的
The width of this view as measured in the most recent call to measure(). This should be used during measurement and layout calculations only.
得到的是在最近一次调用measure()方法测量后得到的view的宽度,它仅仅用在测量和layout的计算中。
所以此方法得到的是view的内容佔据的实际宽度。
你如果想从一个最简单的例子中的到它们的不同,下面将对上面的例子做一下修改:
public class Test extends Activity {
private TextViewTest mTextViewTest;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextViewTest = new TextViewTest(this);
setContentView(mTextViewTest);
}
public class TextViewTest extends TextView {
public TextViewTest(Context context) {
super(context);
// TODO Auto-generated constructor stub
setText("test test ");
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
measure(0, 0);
Log.i("Tag", "width: " + getWidth() + ",height: " + getHeight());
Log.i("Tag", "MeasuredWidth: " + getMeasuredWidth()
+ ",MeasuredHeight: " + getMeasuredHeight());
}
}
}
总结(正解):
getWidth(): View在设定好布局后整个View的宽度。
getMeasuredWidth(): 对View上的内容进行测量后得到的View内容佔据的宽度,前提是你必须在父布局的onLayout()方法或者此View的onDraw()方法裡调 用measure(0,0);(measure 参数的值你可以自己定义),否则你得到的结果和getWidth()得到的结果一样。
关於这两个方法的区别就是看你有没有用measure()方法,当然measure()的位置也是很重要的。