Android自定义view时注意




一.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);
该方法在ViewGroup中定义是抽象函数,继承该类必须实现onLayout方法,而ViewGroup的onMeasure并非必须重写的。View的放置都是根据一个矩形空间放置的,onLayout传下来的l,t,r,b分别是放置父控件的矩形可用空间(除去margin和padding的空间)的左上角的left、top以及右下角right、bottom值。


layout方法:

?
1
public void layout( int l, int t, int r, int b);
该方法是View的放置方法,在View类实现。调用该方法需要传入放置View的矩形空间左上角left、top值和右下角right、bottom值。这四个值是相对于父控件而言的。例如传入的是(10, 10, 100, 100),则该View在距离父控件的左上角位置(10, 10)处显示,显示的大小是宽高是90(参数r,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

在创建新的控件或修改现有的控件时,我们都会涉及到重写控件或ViewonDraw方法。

 

onDraw方法会传入一个Canvas对象,它是你用来绘制控件视觉界面的画布。

 

onDraw方法里,我们经常会看到调用saverestore方法,它们到底是干什么用的呢?

 

 save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。

 

 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。

 

saverestore要配对使用(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

 

如果我们不调用saverestore会是什么样子呢?如图2所示:

 

 

2

 

从这两个图中,我们就能看到圆圈位置的明显差异。不进行Canvassaverestore操作的话,所有的图像都是在画布旋转90°后的画布上绘制的。当执行完onDraw方法,系统自动将画布恢复回来。saverestore操作执行的时机不同,就能造成绘制的图形不同。

 

所以,saverestore之间,往往夹杂的是对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()的位置也是很重要的。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值