Android知識(shí)點(diǎn)總結(jié)(二)

什么是Fragment

對(duì)于Fragment的一些理解

前言

Fragment想必大家不陌生吧,在日常開發(fā)中,對(duì)于Fragment的使用也很頻繁,現(xiàn)在主流的APP中,基本的架構(gòu)也都是一個(gè)主頁,然后每個(gè)Tab項(xiàng)用Fragment做布局,不同選項(xiàng)做切換,使用起來也方便。但是否對(duì)它有足夠的認(rèn)識(shí)嗎,谷歌推薦用Fragment來代替Activity,但又沒有明確說為什么要用Fragment來代替Activity,這里就引發(fā)爭(zhēng)議了,那到底是要不要用,是否使用Fragment完全替換Activity真的比常規(guī)開發(fā)模式更好嗎?如果要用的話,那需要了解為何要使用Fragment,F(xiàn)ragment是什么,它的生命周期如何,如何使用,通信又是怎樣,有什么缺點(diǎn)嗎?帶著這些問題,我們一一去解讀。

目錄

  • Fragment為何要用
  • Fragment是什么
  • Fragment生命周期
  • Fragment怎么用
  • Fragment通信
  • Fragment是否很完美

Fragment為何要用

Fragment是Android 3.0 (Honeycomb)被引入的。主要目的是為了給大屏幕(如平板電腦)上更加動(dòng)態(tài)和靈活的UI設(shè)計(jì)提供支持。由于平板電腦的屏幕比手機(jī)的屏幕大很多,因此可用于組合和交換的UI組件的空間更大,利用Fragment實(shí)現(xiàn)此類設(shè)計(jì)的時(shí),就無需管理對(duì)視圖層次結(jié)構(gòu)的復(fù)雜更改。

通過將 Activity 布局分成片段,您可以在運(yùn)行時(shí)修改 Activity 的外觀,并在由 Activity 管理的返回棧中保留這些更改。如果僅僅只有Activity布局,那是不夠的,不僅在手機(jī)上有一套布局,同時(shí)在平板上還需要設(shè)計(jì)一套布局,那樣維護(hù)起來也麻煩,代碼上也有一定的冗余,對(duì)于APP包的大小也有一定的壓力。Fragment的優(yōu)勢(shì)是布局在不同設(shè)備上的適配。

比如:

平板和手機(jī)

從圖中我們可以看到,在平板中,一個(gè)Activity A包含了兩個(gè)Fragment,分別是Fragment A和Fragment B,但在手機(jī)中呢,就需要兩個(gè)Activity,分別是Activity A包含F(xiàn)ragment A和Activity B包含F(xiàn)ragment B。同時(shí)每個(gè)Fragment都具有自己的一套生命周期回調(diào)方法,并各自處理自己的用戶輸入事件。 因此,在平板中使用一個(gè)Activity 就可以了,左側(cè)是列表,右邊是內(nèi)容詳情。

除此之外,使用Fragment還有這么幾個(gè)方面優(yōu)勢(shì):

  • 代碼復(fù)用。特別適用于模塊化的開發(fā),因?yàn)橐粋€(gè)Fragment可以被多個(gè)Activity嵌套,有個(gè)共同的業(yè)務(wù)模塊就可以復(fù)用了,是模塊化UI的良好組件。
  • Activity用來管理Fragment。Fragment的生命周期是寄托到Activity中,F(xiàn)ragment可以被Attach添加和Detach釋放。
  • 可控性。Fragment可以像普通對(duì)象那樣自由的創(chuàng)建和控制,傳遞參數(shù)更加容易和方便,也不用處理系統(tǒng)相關(guān)的事情,顯示方式、替換、不管是整體還是部分,都可以做到相應(yīng)的更改。
  • Fragments是view controllers,它們包含可測(cè)試的,解耦的業(yè)務(wù)邏輯塊,由于Fragments是構(gòu)建在views之上的,而views很容易實(shí)現(xiàn)動(dòng)畫效果,因此Fragments在屏幕切換時(shí)具有更好的控制。

Fragment是什么

說了半天的Fragment,也看到這么多次Fragment這個(gè)名詞出現(xiàn),那么Fragment到底是什么東東呢?定義又是如何?

Fragment也可以叫為“片段”,但我覺得“片段”中文叫法有點(diǎn)生硬,還是保持叫Fragment比較好,它可以表示Activity中的行為或用戶界面部分。我們可以在一個(gè)Activity中用多個(gè)Fragment組合來構(gòu)建多窗格的UI,以及在多個(gè)Activity中重復(fù)使用某個(gè)Fragment。它有自己的生命周期,能接受自己的輸入,并且可以在 Activity 運(yùn)行時(shí)添加或刪除Fragment(有點(diǎn)像在不同 Activity 中重復(fù)使用的“子 Activity”)。

簡(jiǎn)單來說,F(xiàn)ragment其實(shí)可以理解為一個(gè)具有自己生命周期的控件,只不過這個(gè)控件又有點(diǎn)特殊,它有自己的處理輸入事件的能力,有自己的生命周期,又必須依賴于Activity,能互相通信和托管。

Fragment生命周期

如圖:

Fragment生命周期

這張圖是Fragment生命周期和Activity生命周期對(duì)比圖,可以看到兩者還是有很多相似的地方,比如都有onCreate(),onStart(),onPause(),onDestroy()等等,因?yàn)镕ragment是被托管到Activity中的,所以多了兩個(gè)onAttach()和onDetach()。這里講講與Activity生命周期不一樣的方法。

onAttach()

Fragment和Activity建立關(guān)聯(lián)的時(shí)候調(diào)用,被附加到Activity中去。

onCreate()

系統(tǒng)會(huì)在創(chuàng)建Fragment時(shí)調(diào)用此方法??梢猿跏蓟欢钨Y源文件等等。

onCreateView()

系統(tǒng)會(huì)在Fragment首次繪制其用戶界面時(shí)調(diào)用此方法。 要想為Fragment繪制 UI,從該方法中返回的 View 必須是Fragment布局的根視圖。如果Fragment未提供 UI,您可以返回 null。

onViewCreated()

在Fragment被繪制后,調(diào)用此方法,可以初始化控件資源。

onActivityCreated()

當(dāng)onCreate(),onCreateView(),onViewCreated()方法執(zhí)行完后調(diào)用,也就是Activity被渲染繪制出來后。

onPause()

系統(tǒng)將此方法作為用戶離開Fragment的第一個(gè)信號(hào)(但并不總是意味著此Fragment會(huì)被銷毀)進(jìn)行調(diào)用。 通??梢栽诖朔椒▋?nèi)確認(rèn)在當(dāng)前用戶會(huì)話結(jié)束后仍然有效的任何更改(因?yàn)橛脩艨赡懿粫?huì)返回)。

onDestroyView()

Fragment中的布局被移除時(shí)調(diào)用。

onDetach()

Fragment和Activity解除關(guān)聯(lián)的時(shí)候調(diào)用。

但需要注一點(diǎn)是:除了onCreateView,其他的所有方法如果你重寫了,必須調(diào)用父類對(duì)于該方法的實(shí)現(xiàn)。

還有一般在啟動(dòng)Fragment的時(shí)候,它的生命周期就會(huì)執(zhí)行這幾個(gè)方法。

Test

Fragment怎么用

前面介紹了半天,不耐煩的人會(huì)說,這么多廢話,也不見的到底是如何使用,畢竟我們是開發(fā)者,需要的使用方式,那么現(xiàn)在就來說說用法如何吧。兩種方式:靜態(tài)用法和動(dòng)態(tài)用法。

靜態(tài)用法

1、繼承Fragment,重寫onCreateView決定Fragemnt的布局

2、在Activity中聲明此Fragment,就當(dāng)和普通的View一樣

首先是布局文件:fragment1.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="#00ff00" >  

    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="This is fragment 1"  
        android:textColor="#000000"  
        android:textSize="25sp" />  

</LinearLayout>  

可以看到,這個(gè)布局文件非常簡(jiǎn)單,只有一個(gè)LinearLayout,里面加入了一個(gè)TextView。我們?cè)傩陆ㄒ粋€(gè)fragment2.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="#ffff00" >  

    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="This is fragment 2"  
        android:textColor="#000000"  
        android:textSize="25sp" />  

</LinearLayout>  

然后新建一個(gè)類Fragment1,這個(gè)類是繼承自Fragment的:

 public class Fragment1 extends Fragment {  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
        return inflater.inflate(R.layout.fragment1, container, false);  
    }  
} 

可以看到,在onCreateView()方法中加載了fragment1.xml的布局。同樣fragment2.xml也是一樣的做法,新建一個(gè)Fragment2類:

public class Fragment2 extends Fragment {  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
        return inflater.inflate(R.layout.fragment2, container, false);  
    }  
}  

然后打開或新建activity_main.xml作為主Activity的布局文件,在里面加入兩個(gè)Fragment的引用,使用android:name前綴來引用具體的Fragment:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:baselineAligned="false" >  

    <fragment  
        android:id="@+id/fragment1"  
        android:name="com.example.fragmentdemo.Fragment1"  
        android:layout_width="0dip"  
        android:layout_height="match_parent"  
        android:layout_weight="1" />  

    <fragment  
        android:id="@+id/fragment2"  
        android:name="com.example.fragmentdemo.Fragment2"  
        android:layout_width="0dip"  
        android:layout_height="match_parent"  
        android:layout_weight="1" />  

</LinearLayout>  

最后新建MainActivity作為程序的主Activity,里面的代碼非常簡(jiǎn)單,都是自動(dòng)生成的:

public class MainActivity extends Activity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
    }  
}  

現(xiàn)在我們來運(yùn)行一次程序,就會(huì)看到,一個(gè)Activity很融洽地包含了兩個(gè)Fragment,這兩個(gè)Fragment平分了整個(gè)屏幕,效果圖如下:


運(yùn)行結(jié)果

動(dòng)態(tài)用法

上面僅僅是Fragment簡(jiǎn)單用法,它真正強(qiáng)大部分是在動(dòng)態(tài)地添加到Activity中,那么動(dòng)態(tài)用法又是如何呢?

還是在靜態(tài)用法代碼的基礎(chǔ)上修改,打開activity_main.xml,將其中對(duì)Fragment的引用都刪除,只保留最外層的LinearLayout,并給它添加一個(gè)id,因?yàn)槲覀円獎(jiǎng)討B(tài)添加Fragment,不用在XML里添加了,刪除后代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@+id/main_layout"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:baselineAligned="false" >  

</LinearLayout>  

然后打開MainActivity,修改其中的代碼如下所示:

public class MainActivity extends Activity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        Display display = getWindowManager().getDefaultDisplay();  
        if (display.getWidth() > display.getHeight()) {  
            Fragment1 fragment1 = new Fragment1();  
            getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit();  
        } else {  
            Fragment2 fragment2 = new Fragment2();  
            getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit();  
        }  
    }  

}  

看到了沒,首先,我們要獲取屏幕的寬度和高度,然后進(jìn)行判斷,如果屏幕寬度大于高度就添加fragment1,如果高度大于寬度就添加fragment2。動(dòng)態(tài)添加Fragment主要分為4步:

1.獲取到FragmentManager,在Activity中可以直接通過getFragmentManager得到。

2.開啟一個(gè)事務(wù),通過調(diào)用beginTransaction方法開啟。

3.向容器內(nèi)加入Fragment,一般使用replace方法實(shí)現(xiàn),需要傳入容器的id和Fragment的實(shí)例。

4.提交事務(wù),調(diào)用commit方法提交。

現(xiàn)在運(yùn)行一下程序,效果如下圖所示:

橫屏結(jié)果
豎屏結(jié)果

要想管理 Activity 中的片段,需要使用 FragmentManager。要想獲取它,需要 Activity 調(diào)用 getFragmentManager()。

使用 FragmentManager 執(zhí)行的操作包括:

  • 通過 findFragmentById()(對(duì)于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(對(duì)于提供或不提供 UI 的片段)獲取 Activity 中存在的片段
  • 通過 popBackStack()將片段從返回棧中彈出
  • 通過 addOnBackStackChangedListener() 注冊(cè)一個(gè)偵聽返回棧變化的偵聽器

也可以使用 FragmentManager 打開一個(gè) FragmentTransaction,通過它來執(zhí)行某些事務(wù),如添加和刪除片段。

Fragment通信

盡管 Fragment 是作為獨(dú)立于 Activity的對(duì)象實(shí)現(xiàn),并且可在多個(gè) Activity 內(nèi)使用,但Fragment 的給定實(shí)例會(huì)直接綁定到包含它的 Activity。具體地說,F(xiàn)ragment 可以通過 getActivity() 訪問 Activity實(shí)例,并輕松地執(zhí)行在 Activity 布局中查找視圖等任務(wù)。如:

View listView = getActivity().findViewById(R.id.list);

同樣地,Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通過從 FragmentManager 獲取對(duì) Fragment 的引用來調(diào)用Fragment中的方法。例如:

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

創(chuàng)建對(duì) Activity 的事件回調(diào)

在某些情況下,可能需要通過與 Activity 共享事件。執(zhí)行此操作的一個(gè)好方法是,在Fragment 內(nèi)定義一個(gè)回調(diào)接口,并要求宿主 Activity 實(shí)現(xiàn)它。 當(dāng) Activity 通過該接口收到回調(diào)時(shí),可以根據(jù)需要與布局中的其他Fragment共享這些信息。

例如,如果一個(gè)新聞應(yīng)用的 Activity 有兩個(gè)Fragment ,一個(gè)用于顯示文章列表(Fragment A),另一個(gè)用于顯示文章(Fragment B)—,那么Fragment A必須在列表項(xiàng)被選定后告知 Activity,以便它告知Fragment B 顯示該文章。 在本例中,OnArticleSelectedListener 接口在片段 A 內(nèi)聲明:

public static class FragmentA extends ListFragment { 
    public interface OnArticleSelectedListener { 
        public void onArticleSelected(Uri articleUri);
    } 
} 

然后,該Fragment的宿主 Activity 會(huì)實(shí)現(xiàn) OnArticleSelectedListener 接口并替代 onArticleSelected(),將來自Fragment A 的事件通知Fragment B。為確保宿主 Activity 實(shí)現(xiàn)此界面,F(xiàn)ragment A 的 onAttach() 回調(diào)方法(系統(tǒng)在向 Activity 添加Fragment時(shí)調(diào)用的方法)會(huì)通過轉(zhuǎn)換傳遞到 onAttach() 中的 Activity 來實(shí)例化 OnArticleSelectedListener 的實(shí)例:

public static class FragmentA extends ListFragment { 
    OnArticleSelectedListener mListener;
    @Override 
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try { 
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        } 
    } 
} 

如果 Activity 未實(shí)現(xiàn)界面,則片段會(huì)引發(fā) ClassCastException。實(shí)現(xiàn)時(shí),mListener 成員會(huì)保留對(duì) Activity 的 OnArticleSelectedListener 實(shí)現(xiàn)的引用,以便Fragment A 可以通過調(diào)用 OnArticleSelectedListener 界面定義的方法與 Activity 共享事件。例如,如果Fragment A 是 ListFragment 的一個(gè)擴(kuò)展,則用戶每次點(diǎn)擊列表項(xiàng)時(shí),系統(tǒng)都會(huì)調(diào)用Fragment中的 onListItemClick(),然后該方法會(huì)調(diào)用 onArticleSelected() 以與 Activity 共享事件:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        mListener.onArticleSelected(noteUri);
    }
}

Fragment是否很完美

因?yàn)镕ragment是由FragmentManager來管理,每一個(gè)Activity有一個(gè)FragmentManager,管理著一個(gè)Fragment的棧,Activity是系統(tǒng)級(jí)別的,由系統(tǒng)來管理ActivityManager,棧也是系統(tǒng)范圍的。而Fragment則是每個(gè)Activity范圍內(nèi)的,所以在使用Fragment的時(shí)候也有幾點(diǎn)要注意。

  • 同一個(gè)Activity中,只能有一個(gè)ID或TAG標(biāo)識(shí)的Fragment實(shí)例。
    這很容易理解,同一個(gè)范圍內(nèi),有標(biāo)識(shí)的實(shí)例肯定是要唯一才行(否則還要標(biāo)識(shí)干嘛)這個(gè)在布局中經(jīng)常犯錯(cuò),在布局中寫Fragment最好不要加ID或者TAG,否則很容易出現(xiàn)不允許創(chuàng)建的錯(cuò)誤。我的原則是如果放在布局中,就不要加ID和TAG,如果需要ID和TAG就全用代碼控制。創(chuàng)建新實(shí)例前先到FragmentManager中查找一番,這也正是有標(biāo)識(shí)的意義所在。
  • 一個(gè)Activity中有一個(gè)Fragment池,實(shí)例不一定會(huì)被銷毀,可能會(huì)保存在池中。
    這個(gè)跟第一點(diǎn)差不多。就好比系統(tǒng)會(huì)緩存Activity的實(shí)例一樣,F(xiàn)ragmentManager也會(huì)緩存Fragment實(shí)例,以方便和加速再次顯示。
  • FragmentManager的作用范圍是整個(gè)Activity,所以,某一個(gè)布局ID,不能重復(fù)被Fragment替換。
    通常顯示Fragment有二種方式,一種是層疊到某個(gè)布局上,或者把某個(gè)布局上面的Fragment替換掉,但是這個(gè)布局不能出現(xiàn)二次,比如布局A中有ID為id的區(qū)域,要顯示為Fragment,此布局A,只能在一個(gè)Activity中顯示一個(gè),否則第二個(gè)id區(qū)域不能被Fragment成功替換。因?yàn)殡m有二個(gè)ID布局的實(shí)例,但I(xiàn)D是相同的,對(duì)FragmentManager來說是一樣的,它會(huì)認(rèn)為只有一個(gè),因?yàn)樗吹氖遣季值腎D,而不是布局的實(shí)例。
  • Fragment的生命周期反應(yīng)Activity的生命周期。
    Fragment在顯示和退出時(shí)會(huì)走一遍完整的生命周期。此外,正在顯示時(shí),就跟Activity的一樣,Activity被onPause,里面的Fragment就onPause,以此類推,由此帶來的問題就是,比如你在onStart()里面做了一些事情,那么,當(dāng)宿主Activity被擋住,又出現(xiàn)時(shí)(比如接了個(gè)電話),F(xiàn)ragment的onStart也會(huì)被高到,所以你要想到,這些生命周期不單單在顯示和退出時(shí)會(huì)走到。
  • Fragment的可見性。
    這個(gè)問題出現(xiàn)在有Fragment棧的時(shí)候,也就是說每個(gè)Fragment不知道自己是否真的對(duì)用戶可見。比如現(xiàn)在是Fragment A,又在其上面顯示了Fragment B,當(dāng)B顯示后,A并不知道自己上面還有一個(gè),也不知道自己對(duì)用戶不可見了,同樣再有一個(gè)C,B也不知。C退出后,B依然不知自己已在棧頂,對(duì)用戶可見,B退后,A也不知。也就是說Fragment顯示或者退出,棧里的其他Fragment無法感知。這點(diǎn)就不如Activity,a被b蓋住后,a會(huì)走到onStop(),同樣c顯示后,b也能通過onStop()感知。Fragment可以從FragmentManager監(jiān)聽BackStackState的變化,但它只告訴你Stack變了,不告訴你是多了,還是少,還有你處的位置。有一個(gè)解決方案就是,記錄頁面的Path深度,再跟Fragment所在的Stack深度來比較,如果一致,那么這個(gè)Fragment就在棧頂。因?yàn)槊總€(gè)頁面的Path深度是固定的,而Stack深度是不變化的,所以這個(gè)能準(zhǔn)確的判斷Fragment是否對(duì)用戶可見,當(dāng)然,這個(gè)僅針對(duì)整個(gè)頁面有效,對(duì)于布局中的一個(gè)區(qū)域是無效的。
  • Fragment的事件傳遞。
    對(duì)于層疊的Fragment,其實(shí)就相當(dāng)于在一個(gè)FrameLayout里面加上一堆的View,所以,如果處于頂層的Fragment沒處理點(diǎn)擊事件,那么事件就會(huì)向下層傳遞,直到事件被處理。比如有二個(gè)Fragment A和B,B在A上面,B只有TextView且沒處理事件,那么點(diǎn)擊B時(shí),會(huì)發(fā)現(xiàn)A里的View處理了事件。這個(gè)對(duì)于Activity也不會(huì)發(fā)生,因?yàn)槭录荒芸绱绑w傳播,上面的Activity沒處理事件,也不會(huì)傳給下面的Activity,即使它可見。解決之法,就是讓上面的Fragment的根布局吃掉事件,為每個(gè)根ViewGroup添加onClick=“true”。
  • 與第三方Activity交互。與第三方交互,仍要采用Android的標(biāo)準(zhǔn)startActivityForResult()和onActivityResult()這二個(gè)方法來進(jìn)行。但對(duì)于Fragment有些事情需要注意,F(xiàn)ragment也有這二個(gè)方法,但是為了能正確的讓Fragment收到onActivityResult(),需要:
    1. 宿主Activity要實(shí)現(xiàn)一個(gè)空的onActivityResult(),里面調(diào)用super.onActivityResult()
    2. 調(diào)用Fragment#startActivityForResult()而不是用Activity的 當(dāng)然,也可以直接使用Activity的startActivityForResult(),那樣的話,就只能在宿主Activity里處理返回的結(jié)果了。

android自定義View

1、概述

  Android****自定義View / ViewGroup****的步驟大致如下:

  1. 自定義屬性;
  2. 選擇和設(shè)置構(gòu)造方法;
  3. 重寫onMeasure()方法;
  4. 重寫onDraw()方法;
  5. 重寫onLayout()方法;
  6. 重寫其他事件的方法(滑動(dòng)監(jiān)聽等)。</pre>

2、自定義屬性

Android自定義屬性主要有定義、使用和獲取三個(gè)步驟。

2.1、定義自定義屬性

我們通常將自定義屬性定義在/values/attr.xml文件中(attr.xml文件需要自己創(chuàng)建)。

先來看一段示例代碼:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="rightPadding" format="dimension" />

    <declare-styleable name="CustomMenu">
        <attr name="rightPadding" />
    </declare-styleable>
</resources>

可以看到,我們先是定義了一個(gè)屬性rightPadding,然后又在CustomMenu中引用了這個(gè)屬性。下面說明一下:

  • 首先,我們可以在declare-stylable標(biāo)簽中直接定義屬性而不需要引用外部定義好的屬性,但是為了屬性的重用,我們可以選擇上面的這種方法:先定義,后引用;
  • declare-stylable標(biāo)簽只是為了給自定義屬性分類。一個(gè)項(xiàng)目中可能又多個(gè)自定義控件,但只能又一個(gè)attr.xml文件,因此我們需要對(duì)不同自定義控件中的自定義屬性進(jìn)行分類,這也是為什么declare-stylable標(biāo)簽中的name屬性往往定義成自定義控件的名稱;
  • 所謂的在declare-stylable標(biāo)簽中的引用,就是去掉了外部定義的format屬性,如果沒有去掉format,則會(huì)報(bào)錯(cuò);如果外部定義中沒有format而在內(nèi)部引用中又format,也一樣會(huì)報(bào)錯(cuò)。

  常用的format類型:

  1. string:字符串類型;
  2. integer:整數(shù)類型;
  3. float:浮點(diǎn)型;
  4. dimension:尺寸,后面必須跟dp、dip、px、sp等單位;
  5. Boolean:布爾值;
  6. reference:引用類型,傳入的是某一資源的ID,必須以“@”符號(hào)開頭;
  7. color:顏色,必須是“#”符號(hào)開頭;
  8. fraction:百分比,必須是“%”符號(hào)結(jié)尾;
  9. enum:枚舉類型</pre>

下面對(duì)format類型說明幾點(diǎn):

  • format中可以寫多種類型,中間使用“|”符號(hào)分割開,表示這幾種類型都可以傳入這個(gè)屬性;
  • enum類型的定義示例如下代碼所示:
<resources>
    <attr name="orientation">
        <enum name="horizontal" value="0" />
        <enum name="vertical" value="1" />
    </attr>

    <declare-styleable name="CustomView">
        <attr name="orientation" />
    </declare-styleable>
</resources>

使用時(shí)通過getInt()方法獲取到value并判斷,根據(jù)不同的value進(jìn)行不同的操作即可。

2.2、使用自定義屬性

在XML布局文件中使用自定義的屬性時(shí),我們需要先定義一個(gè)namespace。Android中默認(rèn)的namespace是android,因此我們通??梢允褂谩癮ndroid:xxx”的格式去設(shè)置一個(gè)控件的某個(gè)屬性,android這個(gè)namespace的定義是在XML文件的頭標(biāo)簽中定義的,通常是這樣的:

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

我們自定義的屬性不在這個(gè)命名空間下,因此我們需要添加一個(gè)命名空間。

自定義屬性的命名空間如下:

xmlns:app="http://schemas.android.com/apk/res-auto"

可以看出來,除了將命名空間的名稱從android改成app之外,就是將最后的“res/android”改成了“res-auto”。

  注意:自定義namespace的名稱可以自己定義,不一定非得是app。

2.3、獲取自定義屬性

在自定義View / ViewGroup中,我們可以通過TypedArray獲取到自定義的屬性。示例代碼如下:

public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomMenu, defStyleAttr, 0); int indexCount = a.getIndexCount(); for (int i = 0; i < indexCount; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.CustomMenu_rightPadding:
                mMenuRightPadding = a.getDimensionPixelSize(attr, 0); break;
        }
    }
    a.recycle();
}

這里需要說明一下:

  • 獲取自定義屬性的代碼通常是在三個(gè)參數(shù)的構(gòu)造方法中編寫的(具體為什么是三個(gè)參數(shù)的構(gòu)造方法,下面的章節(jié)中會(huì)有解釋);
  • 在獲取TypedArray對(duì)象時(shí)就為其綁定了該自定義View的自定義屬性集(CustomMenu),通過getIndexCount()方法獲取到自定義屬性的數(shù)量,通過getIndex()方法獲取到某一個(gè)屬性,最后通過switch語句判斷屬性并進(jìn)行相應(yīng)的操作;
  • 在TypedArray使用結(jié)束后,需要調(diào)用recycle()方法回收它。

3、構(gòu)造方法

當(dāng)我們定義一個(gè)新的類繼承了View或ViewGroup時(shí),系統(tǒng)都會(huì)提示我們重寫它的構(gòu)造方法。View / ViewGroup中又四個(gè)構(gòu)造方法可以重寫,它們分別有一、二、三、四個(gè)參數(shù)。四個(gè)參數(shù)的構(gòu)造方法我們通常用不到,因此這個(gè)章節(jié)中我們主要介紹一個(gè)參數(shù)、兩個(gè)參數(shù)和三個(gè)參數(shù)的構(gòu)造方法(這里以CustomMenu控件為例)。

3.1、一個(gè)參數(shù)的構(gòu)造方法

構(gòu)造方法的代碼:

 public CustomMenu(Context context) { …… }

這個(gè)構(gòu)造方法只有一個(gè)參數(shù)Context上下文。當(dāng)我們?cè)贘AVA代碼中直接通過new關(guān)鍵在創(chuàng)建這個(gè)控件時(shí),就會(huì)調(diào)用這個(gè)方法。

3.2、兩個(gè)參數(shù)的構(gòu)造方法

public CustomMenu(Context context, AttributeSet attrs) { …… }

這個(gè)構(gòu)造方法有兩個(gè)參數(shù):Context上下文和AttributeSet屬性集。當(dāng)我們需要在自定義控件中獲取屬性時(shí),就默認(rèn)調(diào)用這個(gè)構(gòu)造方法。AttributeSet對(duì)象就是這個(gè)控件中定義的所有屬性。

我們可以通過AttributeSet對(duì)象的getAttributeCount()方法獲取屬性的個(gè)數(shù),通過getAttributeName()方法獲取到某條屬性的名稱,通過getAttributeValue()方法獲取到某條屬性的值。

  注意:不管有沒有使用自定義屬性,都會(huì)默認(rèn)調(diào)用這個(gè)構(gòu)造方法,“使用了自定義屬性就會(huì)默認(rèn)調(diào)用三個(gè)參數(shù)的構(gòu)造方法”的說法是錯(cuò)誤的。

3.3、三個(gè)參數(shù)的構(gòu)造方法

 public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { …… } 

這個(gè)構(gòu)造方法中有三個(gè)參數(shù):Context上下文、AttributeSet屬性集和defStyleAttr自定義屬性的引用。這個(gè)構(gòu)造方法不會(huì)默認(rèn)調(diào)用,必須要手動(dòng)調(diào)用,這個(gè)構(gòu)造方法和兩個(gè)參數(shù)的構(gòu)造方法的唯一區(qū)別就是這個(gè)構(gòu)造方法給我們默認(rèn)傳入了一個(gè)默認(rèn)屬性集。

defStyleAttr指向的是自定義屬性的<declare-styleable>標(biāo)簽中定義的自定義屬性集,我們?cè)趧?chuàng)建TypedArray對(duì)象時(shí)需要用到defStyleAttr。

3.4、三個(gè)構(gòu)造方法的整合

一般情況下,我們會(huì)將這三個(gè)構(gòu)造方法串聯(lián)起來,即層層調(diào)用,讓最終的業(yè)務(wù)處理都集中在三個(gè)參數(shù)的構(gòu)造方法。我們讓一參的構(gòu)造方法引用兩參的構(gòu)造方法,兩參的構(gòu)造方法引用三參的構(gòu)造方法。示例代碼如下:

public CustomMenu(Context context) { this(context, null);
} public CustomMenu(Context context, AttributeSet attrs) { this(context, attrs, 0);
} public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // 業(yè)務(wù)代碼 }

這樣一來,就可以保證無論使用什么方式創(chuàng)建這個(gè)控件,最終都會(huì)到三個(gè)參數(shù)的構(gòu)造方法中處理,減少了重復(fù)代碼。

4、onMeasure()

onMeasure()方法中主要負(fù)責(zé)測(cè)量,決定控件本身或其子控件所占的寬高。我們可以通過onMeasure()方法提供的參數(shù)widthMeasureSpec和heightMeasureSpec來分別獲取控件寬度和高度的測(cè)量模式測(cè)量值(測(cè)量 = 測(cè)量模式 + 測(cè)量值)。

widthMeasureSpec和heightMeasureSpec雖然只是int類型的值,但它們是通過MeasureSpec類進(jìn)行了編碼處理的,其中封裝了測(cè)量模式和測(cè)量值,因此我們可以分別通過MeasureSpec.getMode(xMeasureSpec)和MeasureSpec. getSize(xMeasureSpec)來獲取到控件或其子View的測(cè)量模式和測(cè)量值。

  測(cè)量模式分為以下三種情況:

  1. EXACTLY:當(dāng)寬高值設(shè)置為具體值時(shí)使用,如100DIP、match_parent等,此時(shí)取出的size是精確的尺寸;
  2. AT_MOST:當(dāng)寬高值設(shè)置為wrap_content時(shí)使用,此時(shí)取出的size是控件最大可獲得的空間;
  3. UNSPECIFIED:當(dāng)沒有指定寬高值時(shí)使用(很少見)。</pre>

  onMeasure()方法中常用的方法:

  1. getChildCount():獲取子View的數(shù)量;
  2. getChildAt(i):獲取第i個(gè)子控件;
  3. subView.getLayoutParams().width/height:設(shè)置或獲取子控件的寬或高;
  4. measureChild(child, widthMeasureSpec, heightMeasureSpec):測(cè)量子View的寬高;
  5. child.getMeasuredHeight/width():執(zhí)行完measureChild()方法后就可以通過這種方式獲取子View的寬高值;
  6. getPaddingLeft/Right/Top/Bottom():獲取控件的四周內(nèi)邊距;
  7. setMeasuredDimension(width, height):重新設(shè)置控件的寬高。如果寫了這句代碼,就需要?jiǎng)h除“super. onMeasure(widthMeasureSpec, heightMeasureSpec);”這行代碼。

  注意:onMeasure()方法可能被調(diào)用多次,這是因?yàn)榭丶械膬?nèi)容或子View可能對(duì)分配給自己的空間“不滿意”,因此向父空間申請(qǐng)重新分配空間。

5、onDraw()

onDraw()方法負(fù)責(zé)繪制,即如果我們希望得到的效果在Android原生控件中沒有現(xiàn)成的支持,那么我們就需要自己繪制我們的自定義控件的顯示效果。

要學(xué)習(xí)onDraw()方法,我們就需要學(xué)習(xí)在onDraw()方法中使用最多的兩個(gè)類:Paint和Canvas。

  注意:每次觸摸了自定義View/ViewGroup時(shí)都會(huì)觸發(fā)onDraw()方法。

5.1、Paint類

Paint畫筆對(duì)象,這個(gè)類中包含了如何繪制幾何圖形、文字和位圖的樣式和顏色信息,指定了如何繪制文本和圖形。畫筆對(duì)象右很多設(shè)置方法,大體上可以分為兩類:一類與圖形繪制有關(guān),一類與文本繪制有關(guān)。

  Paint****類中有如下方法:

1、圖形繪制:

  1. setArgb(int a, int r, int g, int b):設(shè)置繪制的顏色,a表示透明度,r、g、b表示顏色值;
  2. setAlpha(int a):設(shè)置繪制的圖形的透明度;
  3. setColor(int color):設(shè)置繪制的顏色;
  4. setAntiAlias(boolean a):設(shè)置是否使用抗鋸齒功能,抗鋸齒功能會(huì)消耗較大資源,繪制圖形的速度會(huì)減慢;
  5. setDither(boolean b):設(shè)置是否使用圖像抖動(dòng)處理,會(huì)使圖像顏色更加平滑飽滿,更加清晰;
  6. setFileterBitmap(Boolean b):設(shè)置是否在動(dòng)畫中濾掉Bitmap的優(yōu)化,可以加快顯示速度;
  7. setMaskFilter(MaskFilter mf):設(shè)置MaskFilter來實(shí)現(xiàn)濾鏡的效果;
  8. setColorFilter(ColorFilter cf):設(shè)置顏色過濾器,可以在繪制顏色時(shí)實(shí)現(xiàn)不同顏色的變換效果;
  9. setPathEffect(PathEffect pe):設(shè)置繪制的路徑的效果;
  10. setShader(Shader s):設(shè)置Shader繪制各種漸變效果;
  11. setShadowLayer(float r, int x, int y, int c):在圖形下面設(shè)置陰影層,r為陰影角度,x和y為陰影在x軸和y軸上的距離,c為陰影的顏色;
  12. setStyle(Paint.Style s):設(shè)置畫筆的樣式:FILL實(shí)心;STROKE空心;FILL_OR_STROKE同時(shí)實(shí)心與空心;
  13. setStrokeCap(Paint.Cap c):當(dāng)設(shè)置畫筆樣式為STROKE或FILL_OR_STROKE時(shí),設(shè)置筆刷的圖形樣式;
  14. setStrokeJoin(Paint.Join j):設(shè)置繪制時(shí)各圖形的結(jié)合方式;
  15. setStrokeWidth(float w):當(dāng)畫筆樣式為STROKE或FILL_OR_STROKE時(shí),設(shè)置筆刷的粗細(xì)度;
  16. setXfermode(Xfermode m):設(shè)置圖形重疊時(shí)的處理方式;

2、文本繪制:

  1. setTextAlign(Path.Align a):設(shè)置繪制的文本的對(duì)齊方式;
  2. setTextScaleX(float s):設(shè)置文本在X軸的縮放比例,可以實(shí)現(xiàn)文字的拉伸效果;
  3. setTextSize(float s):設(shè)置字號(hào);
  4. setTextSkewX(float s):設(shè)置斜體文字,s是文字傾斜度;
  5. setTypeFace(TypeFace tf):設(shè)置字體風(fēng)格,包括粗體、斜體等;
  6. setUnderlineText(boolean b):設(shè)置繪制的文本是否帶有下劃線效果;
  7. setStrikeThruText(boolean b):設(shè)置繪制的文本是否帶有刪除線效果;
  8. setFakeBoldText(boolean b):模擬實(shí)現(xiàn)粗體文字,如果設(shè)置在小字體上效果會(huì)非常差;
  9. setSubpixelText(boolean b):如果設(shè)置為true則有助于文本在LCD屏幕上顯示效果;
      3、其他方法:
  10. getTextBounds(String t, int s, int e, Rect b):將頁面中t文本從s下標(biāo)開始到e下標(biāo)結(jié)束的所有字符所占的區(qū)域?qū)捀叻庋b到b這個(gè)矩形中;
  11. clearShadowLayer():清除陰影層;
  12. measureText(String t, int s, int e):返回t文本中從s下標(biāo)開始到e下標(biāo)結(jié)束的所有字符所占的寬度;
  13. reset():重置畫筆為默認(rèn)值。

  這里需要就幾個(gè)方法解釋一下:

1、setPathEffect(PathEffect pe):設(shè)置繪制的路徑的效果:

常見的有以下幾種可選方案:

  1. CornerPathEffect:可以用圓角來代替尖銳的角;
  2. DathPathEffect:虛線,由短線和點(diǎn)組成;
  3. DiscretePathEffect:荊棘狀的線條;
  4. PathDashPathEffect:定義一種新的形狀并將其作為原始路徑的輪廓標(biāo)記;
  5. SumPathEffect:在一條路徑中順序添加參數(shù)中的效果;
  6. ComposePathEffect:將兩種效果組合起來,先使用第一種效果,在此基礎(chǔ)上應(yīng)用第二種效果。

2、setXfermode(Xfermode m):設(shè)置圖形重疊時(shí)的處理方式:

關(guān)于Xfermode的多種效果,我們可以參考下面一張圖:

image

在使用的時(shí)候,我們需要通過paint. setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XXX))來設(shè)置,XXX是上圖中的某種模式對(duì)應(yīng)的常量參數(shù),如DST_OUT。

這16中情況的具體解釋如下:

1.PorterDuff.Mode.CLEAR:所繪制不會(huì)提交到畫布上。
2.PorterDuff.Mode.SRC:顯示上層繪制圖片
3.PorterDuff.Mode.DST:顯示下層繪制圖片
4.PorterDuff.Mode.SRC_OVER:正常繪制顯示,上下層繪制疊蓋。
5.PorterDuff.Mode.DST_OVER:上下層都顯示。下層居上顯示。
6.PorterDuff.Mode.SRC_IN:取兩層繪制交集。顯示上層。
7.PorterDuff.Mode.DST_IN:取兩層繪制交集。顯示下層。
8.PorterDuff.Mode.SRC_OUT:上層繪制非交集部分。
9.PorterDuff.Mode.DST_OUT:取下層繪制非交集部分。
10.PorterDuff.Mode.SRC_ATOP:取下層非交集部分與上層交集部分
11.PorterDuff.Mode.DST_ATOP:取上層非交集部分與下層交集部分
12.PorterDuff.Mode.XOR:異或:去除兩圖層交集部分
13.PorterDuff.Mode.DARKEN:取兩圖層全部區(qū)域,交集部分顏色加深
14.PorterDuff.Mode.LIGHTEN:取兩圖層全部,點(diǎn)亮交集部分顏色
15.PorterDuff.Mode.MULTIPLY:取兩圖層交集部分疊加后顏色
16.PorterDuff.Mode.SCREEN:取兩圖層全部區(qū)域,交集部分變?yōu)橥该魃?/p>

5.2、Canvas類

Canvas即畫布,其上可以使用Paint畫筆對(duì)象繪制很多東西。

  Canvas****對(duì)象中可以繪制:

  1. drawArc():繪制圓??;

  2. drawBitmap():繪制Bitmap圖像;

  3. drawCircle():繪制圓圈;

  4. drawLine():繪制線條;

  5. drawOval():繪制橢圓;

  6. drawPath():繪制Path路徑;

  7. drawPicture():繪制Picture圖片;

  8. drawRect():繪制矩形;

  9. drawRoundRect():繪制圓角矩形;

  10. drawText():繪制文本;

  11. drawVertices():繪制頂點(diǎn)。
      Canvas****對(duì)象的其他方法:

  12. canvas.save():把當(dāng)前繪制的圖像保存起來,讓后續(xù)的操作相當(dāng)于是在一個(gè)新圖層上繪制;

  13. canvas.restore():把當(dāng)前畫布調(diào)整到上一個(gè)save()之前的狀態(tài);

  14. canvas.translate(dx, dy):把當(dāng)前畫布的原點(diǎn)移到(dx, dy)點(diǎn),后續(xù)操作都以(dx, dy)點(diǎn)作為參照;

  15. canvas.scale(x, y):將當(dāng)前畫布在水平方向上縮放x倍,豎直方向上縮放y倍;

  16. canvas.rotate(angle):將當(dāng)前畫布順時(shí)針旋轉(zhuǎn)angle度。</pre>

6、onLayout()

onLayout()方法負(fù)責(zé)布局,大多數(shù)情況是在自定義ViewGroup中才會(huì)重寫,主要用來確定子View在這個(gè)布局空間中的擺放位置。

onLayout(boolean changed, int l, int t, int r, int b)方法有5個(gè)參數(shù),其中changed表示這個(gè)控件是否有了新的尺寸或位置;l、t、r、b分別表示這個(gè)View相對(duì)于父布局的左/上/右/下方的位置。

  以下是onLayout()方法中常用的方法:

  1. getChildCount():獲取子View的數(shù)量;
  2. getChildAt(i):獲取第i個(gè)子View
  3. getWidth/Height():獲取onMeasure()中返回的寬度和高度的測(cè)量值;
  4. child.getLayoutParams():獲取到子View的LayoutParams對(duì)象;
  5. child.getMeasuredWidth/Height():獲取onMeasure()方法中測(cè)量的子View的寬度和高度值;
  6. getPaddingLeft/Right/Top/Bottom():獲取控件的四周內(nèi)邊距;
  7. child.layout(l, t, r, b):設(shè)置子View布局的上下左右邊的坐標(biāo)

7、其他方法

7.1、generateLayoutParams()

generateLayoutParams()方法用在自定義ViewGroup中,用來指明子控件之間的關(guān)系,即與當(dāng)前的ViewGroup對(duì)應(yīng)的LayoutParams。我們只需要在方法中返回一個(gè)我們想要使用的LayoutParams類型的對(duì)象即可。

在generateLayoutParams()方法中需要傳入一個(gè)AttributeSet對(duì)象作為參數(shù),這個(gè)對(duì)象是這個(gè)ViewGroup的屬性集,系統(tǒng)根據(jù)這個(gè)ViewGroup的屬性集來定義子View的布局規(guī)則,供子View使用。

例如,在自定義流式布局中,我們只需要關(guān)心子控件之間的間隔關(guān)系,因此我們需要在generateLayoutParams()方法中返回一個(gè)new MarginLayoutParams()即可。

7.2、onTouchEvent()

onTouchEvent()方法用來監(jiān)測(cè)用戶手指操作。我們通過方法中MotionEvent參數(shù)對(duì)象的getAction()方法來實(shí)時(shí)獲取用戶的手勢(shì),有UP、DOWN和MOVE三個(gè)枚舉值,分別表示用于手指抬起、按下和滑動(dòng)的動(dòng)作。每當(dāng)用戶有操作時(shí),就會(huì)回掉onTouchEvent()方法。

7.3、onScrollChanged()

如果我們的自定義View / ViewGroup是繼承自ScrollView / HorizontalScrollView等可以滾動(dòng)的控件,就可以通過重寫onScrollChanged()方法來監(jiān)聽控件的滾動(dòng)事件。

這個(gè)方法中有四個(gè)參數(shù):l和t分別表示當(dāng)前滑動(dòng)到的點(diǎn)在水平和豎直方向上的坐標(biāo);oldl和oldt分別表示上次滑動(dòng)到的點(diǎn)在水平和豎直方向上的坐標(biāo)。我們可以通過這四個(gè)值對(duì)滑動(dòng)進(jìn)行處理,如添加屬性動(dòng)畫等。

7.4、invalidate()

invalidate()方法的作用是請(qǐng)求View樹進(jìn)行重繪,即draw()方法,如果視圖的大小發(fā)生了變化,還會(huì)調(diào)用layout()方法。

  一般會(huì)引起invalidate()****操作的函數(shù)如下:

<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; overflow-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">1) 直接調(diào)用invalidate()方法,請(qǐng)求重新draw(),但只會(huì)繪制調(diào)用者本身;

  1. 調(diào)用setSelection()方法,請(qǐng)求重新draw(),但只會(huì)繪制調(diào)用者本身;
  2. 調(diào)用setVisibility()方法,會(huì)間接調(diào)用invalidate()方法,繼而繪制該View;
  3. 調(diào)用setEnabled()方法,請(qǐng)求重新draw(),但不會(huì)重新繪制任何視圖,包括調(diào)用者本身。

7.5、postInvalidate()

功能與invalidate()方法相同,只是postInvalidate()方法是異步請(qǐng)求重繪視圖。

7.6、requestLayout()

requestLayout()方法只是對(duì)View樹進(jìn)行重新布局layout過程(包括measure()過程和layout()過程),不會(huì)調(diào)用draw()過程,即不會(huì)重新繪制任何視圖,包括該調(diào)用者本身。

7.7、requestFocus()

請(qǐng)求View樹的draw()過程,但只會(huì)繪制需要重繪的視圖,即哪個(gè)View或ViewGroup調(diào)用了這個(gè)方法,就重繪哪個(gè)視圖。

8、總結(jié)

最后,讓我們來總覽一下自定義View / ViewGroup時(shí)調(diào)用的各種函數(shù)的順序,如下圖所示:

image

  在這些方法中:

  • onMeasure()會(huì)在初始化之后調(diào)用一到多次來測(cè)量控件或其中的子控件的寬高;
  • onLayout()會(huì)在onMeasure()方法之后被調(diào)用一次,將控件或其子控件進(jìn)行布局;
  • onDraw()會(huì)在onLayout()方法之后調(diào)用一次,也會(huì)在用戶手指觸摸屏幕時(shí)被調(diào)用多次,來繪制控件。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一 Activity 1 Activity 生命周期 1.1 Activity 的四種狀態(tài) running 當(dāng)前...
    _執(zhí)_念__閱讀 10,783評(píng)論 0 91
  • 1: 獲取控件寬高 控件View有g(shù)etHeight()和getwidth()方法可以獲取寬高,但是如果直接在on...
    自由人是工程師閱讀 2,004評(píng)論 0 0
  • 【Android 自定義View】 [TOC] 自定義View基礎(chǔ) 接觸到一個(gè)類,你不太了解他,如果貿(mào)然翻閱源碼只...
    Rtia閱讀 4,134評(píng)論 1 14
  • 1.Android系統(tǒng)的架構(gòu) 1.Android系統(tǒng)架構(gòu)之應(yīng)用程序 Android會(huì)同一系列核心應(yīng)用程序包一起發(fā)布...
    QM閱讀 2,122評(píng)論 0 50
  • 當(dāng)你知道了許多真實(shí)、虛假的東西,就沒有那么多酸情了。你越來越沉默,越來越不想說。 ?? 很多時(shí)候不愿意嘗試新的東西
    太陽當(dāng)空照1閱讀 328評(píng)論 0 0

友情鏈接更多精彩內(nèi)容