如何自定義View(轉)

對於這個問題並不是一件容易的事,但是如果你掌握了基本的原理其實很簡單。依我的習慣還是先複習一些相關的知識,這樣可以保證你在閱讀過程中沒有阻礙。
【複習或者預習部分 Begin 】
先看看官方文檔,其中Dev Guide -->User Interface -->How Android Draws Views. 
具體文檔內容如下(當然你可以直接在官方上看,剛剛網上找鏈接的時候發現改版了,自己找找吧,我用的是離線的):
When an Activity receives focus, it will be requested to draw its layout. The 
Android framework will handle the procedure for drawing, but the Activity must 
provide the root node of its layout hierarchy.
Drawing begins with the root node of the layout. It is requested to measure 
and draw the layout tree. Drawing is handled by walking the tree and rendering 
each View that intersects the invalid region. In turn, each View group is 
responsible for requesting each of its children to be drawn (with 
the draw() method) 
and each View is responsible for drawing itself. Because the tree is traversed 
in-order, this means that parents will be drawn before (i.e., behind) their 
children, with siblings drawn in the order they appear in the tree.
The framework will not draw Views that are not in the invalid region, and 
also will take care of drawing the Views background for you.
You can force a View to draw, by callinginvalidate().
Drawing the layout is a two pass process: a measure pass and a layout pass. 
The measuring pass is implemented in measure(int, 
int) and is a top-down traversal of the View tree. Each View 
pushes dimension specifications down the tree during the recursion. At the end 
of the measure pass, every View has stored its measurements. The second pass 
happens in layout(int, 
int, int, int) and is also top-down. During this pass each 
parent is responsible for positioning all of its children using the sizes 
computed in the measure pass.
When a View's measure() method returns, its getMeasuredWidth() and getMeasuredHeight() values 
must be set, along with those for all of that View's descendants. A View's 
measured width and measured height values must respect the constraints imposed 
by the View's parents. This guarantees that at the end of the measure pass, all 
parents accept all of their children's measurements. A parent View may 
call measure() more than once on its children. For example, the 
parent may measure each child once with unspecified dimensions to find out how 
big they want to be, then call measure() on them again with actual 
numbers if the sum of all the children's unconstrained sizes is too big or too 
small (i.e., if the children don't agree among themselves as to how much space 
they each get, the parent will intervene and set the rules on the second 
pass).
To initiate a layout, call requestLayout(). 
This method is typically called by a View on itself when it believes that is can 
no longer fit within its current bounds.
The measure pass uses two classes to communicate dimensions. The View.MeasureSpec class 
is used by Views to tell their parents how they want to be measured and 
positioned. The base LayoutParams class just describes how big the View wants to 
be for both width and height. 
【複習或者預習部分 End 】
 
這一部分網上有翻譯過來的,不過這也比較簡單,在此就自己翻譯或者google一下。下面開始切入主題,正式講解。
 
關鍵部分:Drawing the layout is a two pass process: 
a measure pass and a layout pass. 
所以一個view執行OnDraw時最關鍵的是measure和layout。其實這很好理解的,一個view需要繪製出來,那麼必須知道他要佔多大的空間也就是measure,還得知道在哪裏繪製,也就是把view放在哪裏即layout。把這兩部分掌握好也就可以隨意自定義view了。至於viewGroup中如何繪製就參考上面官方文檔,其實就是一個分發繪製,直到child是一個view自己進行繪製。
 
說了這麼多看着挺累的,舉兩個例子給大家。
 
1、重寫一個View。
引用一下網上的一個例子http://labs.ywlx.net/?p=284 ,代碼如下:
public  class  RotateTextView  extends  TextView {private  static  final  String  NAMESPACE = “http://www.ywlx.net/apk/res/easymobi”;private  static  final  String  ATTR_ROTATE = “rotate”;private  static  final  int  DEFAULTVALUE_DEGREES = 0;private  int  degrees ;public  RotateTextView(Context context, AttributeSet attrs) {super(context, attrs);
degrees = attrs.getAttributeIntValue(NAMESPACE, ATTR_ROTATE, DEFAULTVALUE_DEGREES);
}
@Overrideprotected  void  onDraw(Canvas canvas) {
canvas.rotate(degrees,getMeasuredWidth()/2,getMeasuredHeight()/2);super.onDraw(canvas);
}
}
使用自定義RotateTextView如下:
<cn.easymobi.application.memorytest.RotateTextViewandroid:layout_width=”wrap_content”android:layout_height=”wrap_content”android:padding=”8dip”android:gravity=”center”android:id=”@+id/tvBottom_color”android:textSize=”15dip”android:textColor=”@color/black”easymobi:rotate=”10″android:layout_marginTop=”468dip”/>
個人覺得這個例子挺不錯的,簡單易懂,而且涉及的內容比較單一。需要更詳細的講解看文章http://labs.ywlx.net/?p=284 。
 
2、重寫一個ViewGroup。
再次引用網上的一個例子http://blog.csdn.net/arui319/article/details/5868466 ,代碼如下:
public class MyViewGroup extends ViewGroup {  
    public MyViewGroup(Context context) {  
        super(context);  
        this.initOtherComponent(context);  
    }  
    private void initOtherComponent(Context context) {  
        Button aBtn = new Button(context);  
        // set id 1   
        aBtn.setId(1);  
        aBtn.setText("a btn");  
        this.addView(aBtn);  
        Button bBtn = new Button(context);  
        // set id 2   
        bBtn.setId(2);  
        bBtn.setText("b btn");  
        this.addView(bBtn);  
    }  
    @Override  
    protected void onLayout(boolean changed, int l, int t, int r, int b) {  
        int childCount = getChildCount();  
        for (int i = 0; i < childCount; i++) {  
            View child = getChildAt(i);  
            switch (child.getId()) {  
            case 1:  
                // 1 is aBtn   
                Log.d("MyViewGroup", "btn1 setting");  
                child.setVisibility(View.VISIBLE);  
                child.measure(r - l, b - t);  
                child.layout(0, 0, child.getMeasuredWidth(), child  
                        .getMeasuredHeight());  
                break;  
            case 2:  
                // 2 is bBtn   
                Log.d("MyViewGroup", "btn2 setting");  
                child.setVisibility(View.VISIBLE);  
                child.measure(r - l, b - t);  
               child.layout(0, 50, child.getMeasuredWidth(), child  
                        .getMeasuredHeight() + 50);  
                break;  
            default:  
                //   
            }  
        }  
    }  
}  
這個例子主要是說明layout 和Measure的使用。
 
3、兩個例子的總結。
重寫一個view一般情況下只需要重寫OnDraw方法。那麼什麼時候需要重寫OnMeasure、OnLayout、OnDraw方法呢,這個問題只要把這幾個方法的功能弄清楚你就應該知道怎麼做了。在此我也簡單的講一下(描述不正確請拍磚,歡迎交流)。
①如果需要改變View繪製的圖像,那麼需要重寫OnDraw方法。(這也是最常用的重寫方式。)
②如果需要改變view的大小,那麼需要重寫OnMeasure方法。
③如果需要改變View的(在父控件的)位置,那麼需要重寫OnLayout方法。
④根據上面三種不同的需要你可以組合出多種重寫方案,你懂的。
 
說了這麼多如果有沒有講清楚的,請再看看那文檔。希望對你理解view的繪製原理和重寫view有所幫助。歡迎留言交流。


轉自http://www.cnblogs.com/vanezkw/archive/2012/06/27/2565378.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章