天天看點

Android自定義View的實作方法,帶你一步步深入了解View

轉載請注明出處:http://blog.csdn.net/guolin_blog

如果說要按類型來劃分的話,自定義view的實作方式大概可以分為三種,自繪控件、組合控件、以及繼承控件。那麼下面我們就來依次學習一下,每種方式分别是如何自定義view的。

自繪控件的意思就是,這個view上所展現的内容全部都是我們自己繪制出來的。繪制的代碼是寫在ondraw()方法中的,而這部分内容我們已經在 android視圖繪制流程完全解析,帶你一步步深入了解view(二) 中學習過了。

下面我們準備來自定義一個計數器view,這個view可以響應使用者的點選事件,并自動記錄一共點選了多少次。建立一個counterview繼承自view,代碼如下所示:

[java] view

plaincopy

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

public class counterview extends view implements onclicklistener {  

    private paint mpaint;  

    private rect mbounds;  

    private int mcount;  

    public counterview(context context, attributeset attrs) {  

        super(context, attrs);  

        mpaint = new paint(paint.anti_alias_flag);  

        mbounds = new rect();  

        setonclicklistener(this);  

    }  

    @override  

    protected void ondraw(canvas canvas) {  

        super.ondraw(canvas);  

        mpaint.setcolor(color.blue);  

        canvas.drawrect(0, 0, getwidth(), getheight(), mpaint);  

        mpaint.setcolor(color.yellow);  

        mpaint.settextsize(30);  

        string text = string.valueof(mcount);  

        mpaint.gettextbounds(text, 0, text.length(), mbounds);  

        float textwidth = mbounds.width();  

        float textheight = mbounds.height();  

        canvas.drawtext(text, getwidth() / 2 - textwidth / 2, getheight() / 2  

                + textheight / 2, mpaint);  

    public void onclick(view v) {  

        mcount++;  

        invalidate();  

}  

可以看到,首先我們在counterview的構造函數中初始化了一些資料,并給這個view的本身注冊了點選事件,這樣當counterview被點選的時候,onclick()方法就會得到調用。而onclick()方法中的邏輯就更加簡單了,隻是對mcount這個計數器加1,然後調用invalidate()方法。通過 android視圖狀态及重繪流程分析,帶你一步步深入了解view(三) 這篇文章的學習我們都已經知道,調用invalidate()方法會導緻視圖進行重繪,是以ondraw()方法在稍後就将會得到調用。

既然counterview是一個自繪視圖,那麼最主要的邏輯當然就是寫在ondraw()方法裡的了,下面我們就來仔細看一下。這裡首先是将paint畫筆設定為藍色,然後調用canvas的drawrect()方法繪制了一個矩形,這個矩形也就可以當作是counterview的背景圖吧。接着将畫筆設定為黃色,準備在背景上面繪制目前的計數,注意這裡先是調用了gettextbounds()方法來擷取到文字的寬度和高度,然後調用了drawtext()方法去進行繪制就可以了。

這樣,一個自定義的view就已經完成了,并且目前這個counterview是具備自動計數功能的。那麼剩下的問題就是如何讓這個view在界面上顯示出來了,其實這也非常簡單,我們隻需要像使用普通的控件一樣來使用counterview就可以了。比如在布局檔案中加入如下代碼:

[html] view

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"  

    android:layout_width="match_parent"  

    android:layout_height="match_parent" >  

    <com.example.customview.counterview  

        android:layout_width="100dp"  

        android:layout_height="100dp"  

        android:layout_centerinparent="true" />  

</relativelayout>  

可以看到,這裡我們将counterview放入了一個relativelayout中,然後可以像使用普通控件來給counterview指定各種屬性,比如通過layout_width和layout_height來指定counterview的寬高,通過android:layout_centerinparent來指定它在布局裡居中顯示。隻不過需要注意,自定義的view在使用的時候一定要寫出完整的包名,不然系統将無法找到這個view。

好了,就是這麼簡單,接下來我們可以運作一下程式,并不停地點選counterview,效果如下圖所示。

Android自定義View的實作方法,帶你一步步深入了解View

怎麼樣?是不是感覺自定義view也并不是什麼進階的技術,簡單幾行代碼就可以實作了。當然了,這個counterview功能非常簡陋,隻有一個計數功能,是以隻需幾行代碼就足夠了,當你需要繪制比較複雜的view時,還是需要很多技巧的。

組合控件的意思就是,我們并不需要自己去繪制視圖上顯示的内容,而隻是用系統原生的控件就好了,但我們可以将幾個系統原生的控件組合到一起,這樣建立出的控件就被稱為組合控件。

舉個例子來說,标題欄就是個很常見的組合控件,很多界面的頭部都會放置一個标題欄,标題欄上會有個傳回按鈕和标題,點選按鈕後就可以傳回到上一個界面。那麼下面我們就來嘗試去實作這樣一個标題欄控件。

建立一個title.xml布局檔案,代碼如下所示:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

<?xml version="1.0" encoding="utf-8"?>  

    android:layout_height="50dp"  

    android:background="#ffcb05" >  

    <button  

        android:id="@+id/button_left"  

        android:layout_width="60dp"  

        android:layout_height="40dp"  

        android:layout_centervertical="true"  

        android:layout_marginleft="5dp"  

        android:background="@drawable/back_button"  

        android:text="back"  

        android:textcolor="#fff" />  

    <textview  

        android:id="@+id/title_text"  

        android:layout_width="wrap_content"  

        android:layout_height="wrap_content"  

        android:layout_centerinparent="true"  

        android:text="this is title"  

        android:textcolor="#fff"  

        android:textsize="20sp" />  

在這個布局檔案中,我們首先定義了一個relativelayout作為背景布局,然後在這個布局裡定義了一個button和一個textview,button就是标題欄中的傳回按鈕,textview就是标題欄中的顯示的文字。

接下來建立一個titleview繼承自framelayout,代碼如下所示:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

public class titleview extends framelayout {  

    private button leftbutton;  

    private textview titletext;  

    public titleview(context context, attributeset attrs) {  

        layoutinflater.from(context).inflate(r.layout.title, this);  

        titletext = (textview) findviewbyid(r.id.title_text);  

        leftbutton = (button) findviewbyid(r.id.button_left);  

        leftbutton.setonclicklistener(new onclicklistener() {  

            @override  

            public void onclick(view v) {  

                ((activity) getcontext()).finish();  

            }  

        });  

    public void settitletext(string text) {  

        titletext.settext(text);  

    public void setleftbuttontext(string text) {  

        leftbutton.settext(text);  

    public void setleftbuttonlistener(onclicklistener l) {  

        leftbutton.setonclicklistener(l);  

titleview中的代碼非常簡單,在titleview的建構方法中,我們調用了layoutinflater的inflate()方法來加載剛剛定義的title.xml布局,這部分内容我們已經在 android

layoutinflater原理分析,帶你一步步深入了解view(一) 這篇文章中學習過了。

接下來調用findviewbyid()方法擷取到了傳回按鈕的執行個體,然後在它的onclick事件中調用finish()方法來關閉目前的activity,也就相當于實作傳回功能了。

另外,為了讓titleview有更強地擴充性,我們還提供了settitletext()、setleftbuttontext()、setleftbuttonlistener()等方法,分别用于設定标題欄上的文字、傳回按鈕上的文字、以及傳回按鈕的點選事件。

到了這裡,一個自定義的标題欄就完成了,那麼下面又到了如何引用這個自定義view的部分,其實方法基本都是相同的,在布局檔案中添加如下代碼:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

    xmlns:tools="http://schemas.android.com/tools"  

    <com.example.customview.titleview  

        android:id="@+id/title_view"  

        android:layout_width="match_parent"  

        android:layout_height="wrap_content" >  

    </com.example.customview.titleview>  

這樣就成功将一個标題欄控件引入到布局檔案中了,運作一下程式,效果如下圖所示:

Android自定義View的實作方法,帶你一步步深入了解View

現在點選一下back按鈕,就可以關閉目前的activity了。如果你想要修改标題欄上顯示的内容,或者傳回按鈕的預設事件,隻需要在activity中通過findviewbyid()方法得到titleview的執行個體,然後調用settitletext()、setleftbuttontext()、setleftbuttonlistener()等方法進行設定就ok了。

繼承控件的意思就是,我們并不需要自己重頭去實作一個控件,隻需要去繼承一個現有的控件,然後在這個控件上增加一些新的功能,就可以形成一個自定義的控件了。這種自定義控件的特點就是不僅能夠按照我們的需求加入相應的功能,還可以保留原生控件的所有功能,比如 android

powerimageview實作,可以播放動畫的強大imageview 這篇文章中介紹的powerimageview就是一個典型的繼承控件。

為了能夠加深大家對這種自定義view方式的了解,下面我們再來編寫一個新的繼承控件。listview相信每一個android程式員都一定使用過,這次我們準備對listview進行擴充,加入在listview上滑動就可以顯示出一個删除按鈕,點選按鈕就會删除相應資料的功能。

首先需要準備一個删除按鈕的布局,建立delete_button.xml檔案,代碼如下所示:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

<button xmlns:android="http://schemas.android.com/apk/res/android"  

    android:id="@+id/delete_button"  

    android:layout_width="wrap_content"  

    android:layout_height="wrap_content"  

    android:background="@drawable/delete_button" >  

</button>  

這個布局檔案很簡單,隻有一個按鈕而已,并且我們給這個按鈕指定了一張删除背景圖。

接着建立mylistview繼承自listview,這就是我們自定義的view了,代碼如下所示:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

public class mylistview extends listview implements ontouchlistener,  

        ongesturelistener {  

    private gesturedetector gesturedetector;  

    private ondeletelistener listener;  

    private view deletebutton;  

    private viewgroup itemlayout;  

    private int selecteditem;  

    private boolean isdeleteshown;  

    public mylistview(context context, attributeset attrs) {  

        gesturedetector = new gesturedetector(getcontext(), this);  

        setontouchlistener(this);  

    public void setondeletelistener(ondeletelistener l) {  

        listener = l;  

    public boolean ontouch(view v, motionevent event) {  

        if (isdeleteshown) {  

            itemlayout.removeview(deletebutton);  

            deletebutton = null;  

            isdeleteshown = false;  

            return false;  

        } else {  

            return gesturedetector.ontouchevent(event);  

        }  

    public boolean ondown(motionevent e) {  

        if (!isdeleteshown) {  

            selecteditem = pointtoposition((int) e.getx(), (int) e.gety());  

        return false;  

    public boolean onfling(motionevent e1, motionevent e2, float velocityx,  

            float velocityy) {  

        if (!isdeleteshown && math.abs(velocityx) > math.abs(velocityy)) {  

            deletebutton = layoutinflater.from(getcontext()).inflate(  

                    r.layout.delete_button, null);  

            deletebutton.setonclicklistener(new onclicklistener() {  

                @override  

                public void onclick(view v) {  

                    itemlayout.removeview(deletebutton);  

                    deletebutton = null;  

                    isdeleteshown = false;  

                    listener.ondelete(selecteditem);  

                }  

            });  

            itemlayout = (viewgroup) getchildat(selecteditem  

                    - getfirstvisibleposition());  

            relativelayout.layoutparams params = new relativelayout.layoutparams(  

                    layoutparams.wrap_content, layoutparams.wrap_content);  

            params.addrule(relativelayout.align_parent_right);  

            params.addrule(relativelayout.center_vertical);  

            itemlayout.addview(deletebutton, params);  

            isdeleteshown = true;  

    public boolean onsingletapup(motionevent e) {  

    public void onshowpress(motionevent e) {  

    public boolean onscroll(motionevent e1, motionevent e2, float distancex,  

            float distancey) {  

    public void onlongpress(motionevent e) {  

    public interface ondeletelistener {  

        void ondelete(int index);  

由于代碼邏輯比較簡單,我就沒有加注釋。這裡在mylistview的構造方法中建立了一個gesturedetector的執行個體用于監聽手勢,然後給mylistview注冊了touch監聽事件。然後在ontouch()方法中進行判斷,如果删除按鈕已經顯示了,就将它移除掉,如果删除按鈕沒有顯示,就使用gesturedetector來處理目前手勢。

當手指按下時,會調用ongesturelistener的ondown()方法,在這裡通過pointtoposition()方法來判斷出目前選中的是listview的哪一行。當手指快速滑動時,會調用onfling()方法,在這裡會去加載delete_button.xml這個布局,然後将删除按鈕添加到目前選中的那一行item上。注意,我們還給删除按鈕添加了一個點選事件,當點選了删除按鈕時就會回調ondeletelistener的ondelete()方法,在回調方法中應該去處理具體的删除操作。

好了,自定義view的功能到此就完成了,接下來我們需要看一下如何才能使用這個自定義view。首先需要建立一個listview子項的布局檔案,建立my_list_view_item.xml,代碼如下所示:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

    android:layout_height="match_parent"  

    android:descendantfocusability="blocksdescendants"  

    android:orientation="vertical" >  

        android:id="@+id/text_view"  

        android:layout_height="50dp"  

        android:gravity="left|center_vertical"  

        android:textcolor="#000" />  

然後建立一個擴充卡myadapter,在這個擴充卡中去加載my_list_view_item布局,代碼如下所示:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

public class myadapter extends arrayadapter<string> {  

    public myadapter(context context, int textviewresourceid, list<string> objects) {  

        super(context, textviewresourceid, objects);  

    public view getview(int position, view convertview, viewgroup parent) {  

        view view;  

        if (convertview == null) {  

            view = layoutinflater.from(getcontext()).inflate(r.layout.my_list_view_item, null);  

            view = convertview;  

        textview textview = (textview) view.findviewbyid(r.id.text_view);  

        textview.settext(getitem(position));  

        return view;  

到這裡就基本已經完工了,下面在程式的主布局檔案裡面引入mylistview這個控件,如下所示:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

    <com.example.customview.mylistview  

        android:id="@+id/my_list_view"  

    </com.example.customview.mylistview>  

最後在activity中初始化mylistview中的資料,并處理了ondelete()方法的删除邏輯,代碼如下所示:

Android自定義View的實作方法,帶你一步步深入了解View
Android自定義View的實作方法,帶你一步步深入了解View

public class mainactivity extends activity {  

    private mylistview mylistview;  

    private myadapter adapter;  

    private list<string> contentlist = new arraylist<string>();  

    protected void oncreate(bundle savedinstancestate) {  

        super.oncreate(savedinstancestate);  

        requestwindowfeature(window.feature_no_title);  

        setcontentview(r.layout.activity_main);  

        initlist();  

        mylistview = (mylistview) findviewbyid(r.id.my_list_view);  

        mylistview.setondeletelistener(new ondeletelistener() {  

            public void ondelete(int index) {  

                contentlist.remove(index);  

                adapter.notifydatasetchanged();  

        adapter = new myadapter(this, 0, contentlist);  

        mylistview.setadapter(adapter);  

    private void initlist() {  

        contentlist.add("content item 1");  

        contentlist.add("content item 2");  

        contentlist.add("content item 3");  

        contentlist.add("content item 4");  

        contentlist.add("content item 5");  

        contentlist.add("content item 6");  

        contentlist.add("content item 7");  

        contentlist.add("content item 8");  

        contentlist.add("content item 9");  

        contentlist.add("content item 10");  

        contentlist.add("content item 11");  

        contentlist.add("content item 12");  

        contentlist.add("content item 13");  

        contentlist.add("content item 14");  

        contentlist.add("content item 15");  

        contentlist.add("content item 16");  

        contentlist.add("content item 17");  

        contentlist.add("content item 18");  

        contentlist.add("content item 19");  

        contentlist.add("content item 20");  

這樣就把整個例子的代碼都完成了,現在運作一下程式,會看到mylistview可以像listview一樣,正常顯示所有的資料,但是當你用手指在mylistview的某一行上快速滑動時,就會有一個删除按鈕顯示出來,如下圖所示:

Android自定義View的實作方法,帶你一步步深入了解View

點選一下删除按鈕就可以将第6行的資料删除了。此時的mylistview不僅保留了listview原生的所有功能,還增加了一個滑動進行删除的功能,确實是一個不折不扣的繼承控件。

到了這裡,我們就把自定義view的幾種實作方法全部講完了,雖然每個例子都很簡單,但是萬變不離其宗,複雜的view也是由這些簡單的原理堆積出來的。經過了四篇文章的學習,相信每個人對view的了解都已經較為深入了,那麼帶你一步步深入了解view系列的文章就到此結束,感謝大家有耐心看到最後。

繼續閱讀