Android(一)

原文地址:http://www.android100.org/html/201606/06/241682.html
為多屏設(shè)計(一) - 支持多個屏幕尺寸
參考地址:http://developer.android.com/training/multiscreen/index.htmlAndroid UI設(shè)計提供了一個靈活的框架,允許應(yīng)用程序為不同設(shè)備顯示不同的布局,創(chuàng)建自定義UI部件云茸,在App外部控制系統(tǒng)的Window腌零。Android的設(shè)備尺寸參差不齊党瓮,從幾寸的小手機到幾十寸的TV設(shè)備,我們需要學(xué)會為這么多的設(shè)備做出適配讓盡可能多的人有更好的體驗精偿。支持多個屏幕尺寸有以下幾種方式:- 確保你的布局可以充分調(diào)整大小以適應(yīng)屏幕- 根據(jù)屏幕配置提供適當(dāng)?shù)腢I布局- 確保正確的布局應(yīng)用到正確的屏幕- 提供可縮放的Bitmap
使用”wrap_content” 和”match_parent”
一句話:少用固定的dp來設(shè)置寬高鹤树,不利于屏幕適配。

![](http://upload-images.jianshu.io/upload_images/2761423-baf2c23129b8c802.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
使用相對布局(RelativeLayout)
雖然你可以使用LinearLayout和”wrap_content” 烹困、”match_parent”來創(chuàng)建一個相當(dāng)復(fù)雜的布局,但不能精確地控制子View之間以及子View和父View之間的關(guān)系乾吻。比如屏幕方向變化時髓梅,為保證子View能隨著變化而保持對父View的相對位置不變,這時绎签,就必須使用RelativeLayout了枯饿。

![](http://upload-images.jianshu.io/upload_images/2761423-5ab2bb8426e18ef2.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)![](http://upload-images.jianshu.io/upload_images/2761423-e0ded543fe435b3a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)以上兩張圖顯示,橫豎屏切換時诡必,Cancel和OK按鈕的相對位置以及相對屏幕的位置都沒變奢方。
使用Size限定符
上面兩種布局雖然可以適配一定的屏幕,但無法適配一些特定的屏幕尺寸爸舒。比如蟋字,對于“列表”和“詳細(xì)”,一些屏幕實現(xiàn)“兩屏”的模式扭勉,特別是在平板和TV上鹊奖,但在手機上,必須將兩屏分開在兩個界面顯示涂炎。
res/layout/main.xml

res/layout-large/main.xml

注意:large限定符嫉入,它修飾的布局焰盗,在大屏幕上顯示(如在7寸及以上尺寸的平板上),而沒有任何限定符的咒林,則在一些較小的設(shè)備上顯示。

使用最小寬度限定符( Smallest-width Qualifier)
很多App希望不同的大屏上顯示不同的布局(比如在5寸和7寸的大屏上)爷光,這就是為什么在Android 3.2上出現(xiàn)最小寬度限定符的原因垫竞。Smallest-width限定符允許你使用一個確定的最小的dp單位的寬度應(yīng)用到目標(biāo)設(shè)備上。如果你想在大屏上使用左右窗格顯示蛀序,可以像上面那種模式一樣欢瞪,寫多個相同的布局,這次不用large限定符了徐裸,用sw600dp遣鼓,表示在寬度600dp及以上的設(shè)備上將使用我們定義的布局:
res/layout/main.xml

res/layout-sw600dp/main.xml

這意味著寬度大于等于600dp的設(shè)備將使用layout-sw600dp/main.xml(雙窗格模式)的布局,小于這個寬度的設(shè)備使用layout/main.xml(單窗格模式)重贺。然而骑祟,在Android3.2以前的設(shè)備,是不能識別sw600dp這種限定符的气笙,所以為了兼容你必須使用large限定符次企。再增加res/layout-large/main.xml,讓里面的內(nèi)容和res/layout-sw600dp/main.xml一模一樣潜圃。在下一部分缸棵,你將看到一種技術(shù),它允許你避免重復(fù)定義這樣的布局文件谭期。
使用布局別名(Layout Alias)
在使用smallest-width限定符時堵第,由于它是3.2才有的,所以在兼容以前老版本時隧出,需要再重復(fù)定義large限定符的布局文件踏志,這樣會對以后的開發(fā)維護帶來麻煩。為了避免這種情況鸳劳,我們使用布局別名狰贯,比如:
res/layout/main.xml, 單窗格res/layout/main_twopanes.xml,多窗格然后加下面兩個文件:
res/values-large/layout.xml:
@layout/main_twopanes

res/values-sw600dp/layout.xml:@layout/main_twopanes

這樣解決了維護多個相同布局文件的麻煩。
使用方向限定符
一些布局可以在橫屏和豎屏自動調(diào)整的很好赏廓,但大部分還是需要手工調(diào)整的涵紊。比如在NewsReader例子中,不同的屏幕尺寸不同的方向幔摸,顯示的Bar是不一樣的:
small screen, portrait: single pane, with logo small screen, landscape: single pane, with logo 7” tablet, portrait: single pane, with action bar 7” tablet, landscape: dual pane, wide, with action bar 10” tablet, portrait: dual pane, narrow, with action bar 10” tablet, landscape: dual pane, wide, with action bar TV, landscape: dual pane, wide, with action bar所以每一個布局都定義在res/layout/目錄下摸柄,每一個布局都對應(yīng)一個屏幕配置(大小和方向)。app使用布局別名來匹配它們到各自對應(yīng)的設(shè)備:res/layout/onepane.xml:

res/layout/onepane_with_bar.xml:

res/layout/twopanes.xml:

res/layout/twopanes_narrow.xml:

既然所有的布局都定義好了既忆,現(xiàn)在就只需要將正確的布局對應(yīng)到每個配置的文件中驱负。如下使用布局別名技術(shù):res/values/layouts.xml:
@layout/onepane_with_bar
false

res/values-sw600dp-land/layouts.xml:
@layout/twopanes
true

res/values-sw600dp-port/layouts.xml:
@layout/onepane
false

res/values-large-land/layouts.xml:
@layout/twopanes
true

res/values-large-port/layouts.xml:
@layout/twopanes_narrow
true

使用Nine-patch圖片
要支持不同的屏幕分辨率嗦玖,那么圖片也需要做成多個尺寸的,這樣勢必增加美工的工作量跃脊。如果應(yīng)用圖片較多宇挫,那么可想而知這時一件多么可怕的事情。這時酪术,9-patch圖片可以很好的解決這個問題器瘪,它可以隨著屏幕的變化而伸展而不變形。9-patch圖片是.9.png為后綴的圖片绘雁,它使用SDK目錄下tools文件夾下的draw9patch.bat的工具來制作橡疼。
樣例代碼:NewsReader
為多屏設(shè)計(二) - 支持不同的屏幕密度
參考地址:http://developer.android.com/training/multiscreen/screendensities.html本文向你展示如何通過提供不同的資源和使用分辨率無關(guān)的測量單位支持不同的屏幕密度。
使用像素密度
一句話庐舟,使用dp(尺寸欣除、距離等)、sp(文本)單位挪略,盡量不用px單位历帚。例如:

指定文本大小,使用sp:

提供可選擇的Bitmap
因為Android運行在各個屏幕密度不同的設(shè)備中瘟檩,所以你需要為不同的密度設(shè)備提供不同的圖片資源 low, medium, high and xhigh 等等抹缕。
xhdpi: 2.0 hdpi: 1.5 mdpi: 1.0 (baseline) ldpi: 0.75意思是說,如果你為一個密度是2 的設(shè)備準(zhǔn)備了200x200的圖片墨辛,那么同時需要為密度為1.5的設(shè)備準(zhǔn)備150x150的圖片卓研,為密度為1的設(shè)備準(zhǔn)備100x100的圖片,為密度為0.75的設(shè)備準(zhǔn)備75x75的圖片睹簇。然后在res目錄下生成多個drawable文件夾:MyProject/ res/ drawable-xhdpi/ awesomeimage.png drawable-hdpi/ awesomeimage.png drawable-mdpi/ awesomeimage.png drawable-ldpi/ awesomeimage.png

你通過引用@drawable/awesomeimage奏赘,系統(tǒng)將通過屏幕的dpi找到合適的圖片。
將app啟動logo放在mipmap/文件夾下:
res/... mipmap-ldpi/... finished_launcher_asset.png mipmap-mdpi/... finished_launcher_asset.png mipmap-hdpi/... finished_launcher_asset.png mipmap-xhdpi/... finished_launcher_asset.png mipmap-xxhdpi/... finished_launcher_asset.png mipmap-xxxhdpi/... finished_launcher_asset.png

你應(yīng)該將app啟動圖片放在res/mipmap-[density]/文件夾太惠,而不是drawable/下面磨淌,以確保使用最佳的分辨率

為多屏設(shè)計(三) - 實現(xiàn)適配的UI流
參考地址:http://developer.android.com/training/multiscreen/adaptui.html根據(jù)應(yīng)用程序目前顯示的布局,界面流可能會有所不同凿渊。例如梁只,如果你的App是雙窗格模式,點擊左側(cè)窗格上的一個item埃脏,將在右邊的面板中顯示對應(yīng)的內(nèi)容贯底;如果是在單窗格模式下旭斥,顯示的內(nèi)容應(yīng)該在一個新的Activity里四康。
確定當(dāng)前的布局
判斷當(dāng)前布局是單窗格模式還是多窗格模式(如在NewsReader App中):
public class NewsReaderActivity extends FragmentActivity { boolean mIsDualPane; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); View articleView = findViewById(R.id.article); mIsDualPane = articleView != null && articleView.getVisibility() == View.VISIBLE; }}

在使用一個組件之前需要檢測是否為null枫匾。比如,在NewsReader的例子App中堵幽,有一個按鈕只在Android 3.0 一下的版本下運行時才出現(xiàn)狗超,3.0以上的版本顯示Actionbar(API11+)弹澎,所以在操作這個按鈕時,應(yīng)該這樣做:
Button catButton = (Button) findViewById(R.id.categorybutton);OnClickListener listener = /* create your listener here */;if (catButton != null) { catButton.setOnClickListener(listener);}

根據(jù)當(dāng)前的布局React
當(dāng)前布局不同努咐,那么點擊同樣的item苦蒿,會產(chǎn)生不同的效果。比如麦撵,在NewsReader中刽肠,單窗格模式,點擊item免胃,會進入一個新的Activity,雙窗格模式下惫撰,點擊item(左側(cè))羔沙,右側(cè)則顯示相應(yīng)的內(nèi)容:
@Overridepublic void onHeadlineSelected(int index) { mArtIndex = index; if (mIsDualPane) { /* display article on the right pane / mArticleFragment.displayArticle(mCurrentCat.getArticle(index)); } else { / start a separate activity */ Intent intent = new Intent(this, ArticleActivity.class); intent.putExtra("catIndex", mCatIndex); intent.putExtra("artIndex", index); startActivity(intent); }}

同樣,在雙窗格模式下厨钻,你應(yīng)該在ActionBar上創(chuàng)建Tabs導(dǎo)航扼雏,反而言之,如果app是單窗格模式夯膀,你應(yīng)該使用Spinner組件進行導(dǎo)航诗充。所以代碼如下:
final String CATEGORIES[] = { "Top Stories", "Politics", "Economy", "Technology" };public void onCreate(Bundle savedInstanceState) { .... if (mIsDualPane) { /* use tabs for navigation / actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_TABS); int i; for (i = 0; i < CATEGORIES.length; i++) { actionBar.addTab(actionBar.newTab().setText( CATEGORIES[i]).setTabListener(handler)); } actionBar.setSelectedNavigationItem(selTab); } else { / use list navigation (spinner) */ actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_LIST); SpinnerAdapter adap = new ArrayAdapter(this, R.layout.headline_item, CATEGORIES); actionBar.setListNavigationCallbacks(adap, handler); }}

在其他Activities重用Fragments
復(fù)用模式在多屏狀態(tài)下比較常用。比如在NewsReader App中诱建,News詳情采用一個Fragment蝴蜓,那么它既可以用在雙窗格模式的右邊,又可以用在單窗格模式下的詳情Activity中俺猿。ArticleFragment在雙窗格模式下:

在單窗格模式下茎匠,不需要為ArticleActivity創(chuàng)建布局,直接使用ArticleFragment:
ArticleFragment frag = new ArticleFragment();getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();

要深深記住的一點是押袍,不要讓Fragment和你的Activity有強的耦合性诵冒。你可以在Fragment中定義接口,由Activity來實現(xiàn)谊惭。比如News Reader app的HeadlinesFragment :
public class HeadlinesFragment extends ListFragment { ... OnHeadlineSelectedListener mHeadlineSelectedListener = null; /* Must be implemented by host activity */ public interface OnHeadlineSelectedListener { public void onHeadlineSelected(int index); } ... public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) { mHeadlineSelectedListener = listener; }}

當(dāng)用戶選擇了一個標(biāo)題汽馋,fragment就會通知綁定到指定Activity的監(jiān)聽器:
public class HeadlinesFragment extends ListFragment { ... @Override public void onItemClick(AdapterView parent, View view, int position, long id) { if (null != mHeadlineSelectedListener) { mHeadlineSelectedListener.onHeadlineSelected(position); } } ...}

處理屏幕配置更改(Configuration Changes)
如果你使用單獨的Activity實現(xiàn)界面的一些部分,那么就應(yīng)該記住當(dāng)屏幕變化時(比如旋轉(zhuǎn)屏幕)需要重構(gòu)界面圈盔。比如豹芯,在7寸運行Android3.0及以上版本的平板上,NewsReader App在豎屏?xí)r新聞詳情界面在單獨的一個Activity中药磺,但是在橫屏?xí)r告组,它使用雙窗格模式,左右各一個Fragment癌佩。這意味著木缝,當(dāng)用戶豎屏切換到橫屏來看一個新聞詳情便锨,你需要監(jiān)測到屏幕的變化,來進行適當(dāng)?shù)闹貥?gòu):結(jié)束當(dāng)前的Activity我碟,顯示左右兩個窗格放案。
public class ArticleActivity extends FragmentActivity { int mCatIndex, mArtIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCatIndex = getIntent().getExtras().getInt("catIndex", 0); mArtIndex = getIntent().getExtras().getInt("artIndex", 0); // If should be in two-pane mode, finish to return to main activity if (getResources().getBoolean(R.bool.has_two_panes)) { finish(); return; } ...}

示例代碼:NewsReader
AppBar(ActionBar->ToolBar)
參考地址:http://developer.android.com/training/appbar/index.html
創(chuàng)建應(yīng)用程序欄(AppBar)
在大多數(shù)的App中都會有標(biāo)題欄這個組件,這個組件能讓App有統(tǒng)一的風(fēng)格矫俺,它一般由標(biāo)題和溢出菜單(overflow menu)組成吱殉。![](http://upload-images.jianshu.io/upload_images/2761423-6a6b9a26bfc69d9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)從Android3.0開始,使用默認(rèn)主題的Activity都有一個ActionBar作為標(biāo)題欄厘托。然而友雳,隨著版本的不斷升級,不斷有新的特性加到ActionBar中铅匹,導(dǎo)致不同的版本ActionBar的行為不太一樣押赊。相比之下,支持庫(support library)中的ToolBar不斷集成了最新的特性包斑,而且可以無差異的運行到任意的設(shè)備上流礁。基于此罗丰,我們使用支持庫中的Toolbar作為AppBar神帅,他提供各種設(shè)備一致的行為。
在Activity中添加Toolbar
添加v7 appcompat support library到工程中 確保Activity繼承AppCompatActivity 在manifest文件的 元素中使用 NoActionBar主題

4.在Activity的布局中添加Toolbar

.support.v7.widget.toolbar>

Material Design specification推薦App Bar有一個4dp的elevation萌抵。
5.在Activity的onCreate()方法中找御,調(diào)用Activity的setSupportActionBar(),使ToolBar作為Activity的App bar谜嫉。
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar); setSupportActionBar(myToolbar); }

這樣你就創(chuàng)建了一個基本的actionbar萎坷,上面默認(rèn)是app的名稱和一個溢出菜單。菜單中只有Settings選項沐兰。
使用 App Bar的實用方法
在Activity的標(biāo)題欄創(chuàng)建了toolbar 之后哆档,你可以使用v7 appcompat support library提供的ActionBar類的很多實用方法,比如顯示和隱藏App bar住闯。通過getSupportActionBar()得到兼容的ActionBar瓜浸,如果要隱藏它,可以調(diào)用ActionBar.hide()比原。
添加和處理Actions
Appbar是有限的插佛,所以在它上面添加action,就會溢出(overflow)量窘,可以選擇將其放到menu中雇寇。![](http://upload-images.jianshu.io/upload_images/2761423-32297b5176786578.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)圖:添加了一個按鈕的AppBar
添加Action 按鈕
在menu資源中添加item來添加菜單選項:

app:showAsAction屬性表示哪個action可以顯示到appbar上。如果設(shè)置為app:showAsAction=”ifRoom”,則appbar上有空間顯示在appbar上锨侯,沒有空間就藏在菜單中嫩海;如果設(shè)置app:showAsAction=”never”,則這個action永遠(yuǎn)都在菜單中囚痴,不會顯示在appbar上叁怪。
響應(yīng)Actions
在Activity中的onOptionsItemSelected()中處理菜單的點擊事件:
@Overridepublic boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_settings: // User chose the "Settings" item, show the app settings UI... return true; case R.id.action_favorite: // User chose the "Favorite" action, mark the current item // as a favorite... return true; default: // If we got here, the user's action was not recognized. // Invoke the superclass to handle it. return super.onOptionsItemSelected(item); }}

添加一個Up Action
為了用戶方便的返回主界面,我們需要在Appbar上提供一個Up 按鈕深滚。當(dāng)用戶點擊Up按鈕奕谭,app則返回到父Activity。
聲明父Activity
例如:
......

讓Up按鈕可用(Enable)
要讓Up按鈕可用痴荐,需要在onCreate()方法中調(diào)用appbar的setDisplayHomeAsUpEnabled()方法血柳。
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_child); // my_child_toolbar is defined in the layout file Toolbar myChildToolbar = (Toolbar) findViewById(R.id.my_child_toolbar); setSupportActionBar(myChildToolbar); // Get a support ActionBar corresponding to this toolbar ActionBar ab = getSupportActionBar(); // Enable the Up button ab.setDisplayHomeAsUpEnabled(true);}

我們不需要在onOptionsItemSelected()處理Up按鈕的事件,我們只需要調(diào)用這個方法的父類方法即可生兆,即super.onOptionsItemSelected()混驰。因為系統(tǒng)已經(jīng)通過manifest中的定義自動處理了這個事件。
Action 視圖(View)和Action 提供者(Provider)
Action View 是在Appbar上提供功能豐富的action皂贩。比如一個搜索action,它可以在appbar輸入搜索內(nèi)容昆汹,而不改變Activity或Fragment的樣式 Action Provider是一個有自定義布局的action明刷。這個action一開始是一個button或menu,但用戶點擊了action后满粗,你可以通過action provider任意控制你定義的action的行為辈末。添加一個Action View
在toolbar的菜單資源文件中添加一個item來添加一個ActionView,比如搜索框SearchView的定義:

![](http://upload-images.jianshu.io/upload_images/2761423-6a848235e76af26f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)圖:Toolbar上的SearchView運行效果映皆。
也可以配置action挤聘,通過getActionView()得到actionview的對象,然后進行操作捅彻。例如SearchView:
@Overridepublic boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_activity_actions, menu); MenuItem searchItem = menu.findItem(R.id.action_search); SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); // Configure the search info and add any event listeners... return super.onCreateOptionsMenu(menu);}

響應(yīng)action view的伸展行為
如果菜單的item中設(shè)置了collapseActionView 標(biāo)記组去,則這個action View會在appbar上顯示一個icon,那么點擊這個icon步淹,這個actionview就會展開从隆,同樣也可以縮回來,展開和縮回的行為我們可以為它設(shè)置監(jiān)聽缭裆。我們使用MenuItem.OnActionExpandListener來監(jiān)聽键闺,我們可以在里面處理展開和縮回來時改變Activity的UI:
@Overridepublic boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.options, menu); // ... // Define the listener OnActionExpandListener expandListener = new OnActionExpandListener() { @Override public boolean onMenuItemActionCollapse(MenuItem item) { // Do something when action item collapses return true; // Return true to collapse action view } @Override public boolean onMenuItemActionExpand(MenuItem item) { // Do something when expanded return true; // Return true to expand action view } }; // Get the MenuItem for the action item MenuItem actionMenuItem = menu.findItem(R.id.myActionItem); // Assign the listener to that action item MenuItemCompat.setOnActionExpandListener(actionMenuItem, expandListener); // Any other things you have to do when creating the options menu… return true;}

添加一個Action Provider
在菜單的item中添加actionProviderClass屬性來添加一個action provider。例如澈驼,我們定義一個ShareActionProvider如下:

這里不需要為ShareActionProvider提供一個icon因為系統(tǒng)已經(jīng)定義了辛燥,不過可以自定義一個icon,Just do it!
使用Snackbar代替Toast
參考地址:http://developer.android.com/training/snackbar/index.html很多時候我們都需要短暫的彈出一個消息提示用戶挎塌,然后自動消失徘六。Android以前是用Toast類來實現(xiàn)的,現(xiàn)在我們偏向于首選使用Snackbar組件來代替Toast實現(xiàn)這樣的需求勃蜘,當(dāng)然硕噩,Toast依然是支持的。
創(chuàng)建和顯示一個Pop-Up 消息
Snackbar組件是彈出消息的理想選擇缭贡。
使用CoordinatorLayout
Snackbar是綁定在CoordinatorLayout上的炉擅,而且增加了一些新特性:
Snackbar可以通過手勢滑動dismiss掉 Snackbar顯示時將移動在它上面的布局。CoordinatorLayout類提供FrameLayout功能的超集阳惹,所以如果你使用了FrameLayout谍失,Just將其換成CoordinatorLayout,因為CoordinatorLayout提供了Snackbar的功能莹汤。如果你的布局采用了其他的布局方式快鱼,下面展示將你的布局包在CoordinatorLayout之中:

.support.design.widget.coordinatorlayout>

注意:必須為CoordinatorLayout設(shè)置android:id屬性,因為Snackbar顯示pop消息需要CoordinatorLayout的id纲岭。
顯示一個消息
2步:1抹竹、創(chuàng)建Snackbar對象;2止潮、調(diào)用show方法
Snackbar mySnackbar = Snackbar.make(viewId, stringId, duration);

viewId一般參入與之綁定的CoordinatorLayout的layoutId窃判。
mySnackbar.show();

系統(tǒng)在同一時間不能顯示多個Snackbar。所以要顯示第二個Snackbar喇闸,要等第一個Snackbar過期或被dismiss袄琳。如果不用調(diào)用Snackbar其他實用方法,僅僅是顯示一條消息而不用持有Snackbar的引用燃乍,你也可以將創(chuàng)建和顯示一起寫:
Snackbar.make(findViewById(R.id.myCoordinatorLayout), R.string.email_sent, Snackbar.LENGTH_SHORT) .show();

在消息中添加一個Action
可以在Snackbar中添加一個Action唆樊。比如加一個undo按鈕,那么在刪除一個郵件之后刻蟹,可以點擊這個undo按鈕恢復(fù)剛剛刪除的郵件逗旁。![](http://upload-images.jianshu.io/upload_images/2761423-f7245952b9079f54.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)為Snackbar中的button設(shè)置事件監(jiān)聽,使用Snackbar的setAction()方法:
public class MyUndoListener implements View.OnClickListener{ &Override public void onClick(View v) { // Code to undo the user's last action }}Snackbar mySnackbar = Snackbar.make(findViewById(R.id.myCoordinatorLayout), R.string.email_archived, Snackbar.LENGTH_SHORT);mySnackbar.setAction(R.string.undo_string, new MyUndoListener());mySnackbar.show();

注意:Snackbar 只是短暫的顯示一個消息座咆,你不能指望用戶看到消息時還有機會按到這個按鈕痢艺。所以,你得考慮另一種方法去執(zhí)行這個action介陶。

自定義View(一) - 定義自己的View類
參考地址:http://developer.android.com/training/custom-views/create-view.html自定義View的第一種方式是定義自己的View類堤舒,并為它設(shè)置自定義的屬性。
創(chuàng)建View的子類
繼承View必須至少有一個里面有Context和AttributeSet的構(gòu)造方法哺呜。
class PieChart extends View { public PieChart(Context context, AttributeSet attrs) { super(context, attrs); }}

定義自定義屬性
給自定義的View力添加自定義的屬性舌缤,從而在XML布局時使用,有如下步驟:1、在資源文件中添加自定義屬性2国撵、在XML布局中為自定義屬性賦值3陵吸、在運行時提取出屬性的值4、將屬性的值應(yīng)用到自定義的View上
例如介牙,下面是res/values/attrs.xml的例子:

上面的代碼定義了showText 和labelPosition的屬性壮虫,他倆屬于名為PieChart的styleable。 styleable實體的名稱一般和自定義View的名稱相同环础,但這不是必須的囚似。定義好了自定義屬性,就要去自己的布局文件中使用了线得,必須聲明命名空間饶唤。http://schemas.android.com/apk/res/[your package name]。比如:

.example.customviews.charting.piechart>

注意:自定義View在XML布局中使用贯钩,必須使用完整的包名+類名募狂;如果自定義的View是一個類的內(nèi)部類,那么需要從它的外部類訪問它了角雷。比如祸穷,這個例子中的自定義View是PieView,是PieChart的內(nèi)部類勺三,則這樣使用它:com.example.customviews.charting.PieChart$PieView粱哼。

應(yīng)用自定義屬性
雖然我們可以從AttributeSet 中讀出屬性值,但有兩個不好的地方:
資源強引用屬性值 不支持Style所以檩咱,我們不使用AttributeSet而使用obtainStyledAttributes()得到一個TypedArray。TypedArray里面的屬性值解除了引用胯舷,而且被樣式化(styled)了刻蚯。例如:public PieChart(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.PieChart, 0, 0); try { mShowText = a.getBoolean(R.styleable.PieChart_showText, false); mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0); } finally { a.recycle(); }}

注意:TypedArray不是共享的資源,在使用完后必須手動釋放桑嘶。

增加屬性和事件
為自定義View的類中的屬性增加get和set方法炊汹,以動態(tài)的改變View的外觀和行為。例如:
public boolean isShowText() { return mShowText;}public void setShowText(boolean showText) { mShowText = showText; invalidate(); requestLayout();}

注意:setShowText調(diào)用了invalidate()和requestLayout()逃顶,這個確保能讓View正確的展示讨便。改變View的屬性后需要調(diào)用invalidate以及時刷新,同樣如果View屬性改變影響尺寸和形狀的話也要調(diào)用requestLayout以政,否則會產(chǎn)生一些難以捕捉的bug霸褒。
輔助性設(shè)計
我們的App需要為一些有殘障的人士使用,那么我們需要做一些額外的工作:
在輸入控件上添加android:contentDescription屬性盈蛮。(視力障礙者使用Google的一些服務(wù)可以通過聲音讀出來) 在合適的地方調(diào)用sendAccessibilityEvent()發(fā)送輔助的事件 支持備用控制器,方向鍵和軌跡球等自定義View(二) - 自定義繪制
參考地址:http://developer.android.com/training/custom-views/custom-drawing.html自定義View的最重要的部分就是繪制外觀(重寫onDraw())废菱,而根據(jù)應(yīng)用程序的不同需求,繪制可易可難。
創(chuàng)建繪制對象
canvas殊轴,表示畫什么衰倦;paint,表示怎么畫旁理。
private void init() { mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setColor(mTextColor); if (mTextHeight == 0) { mTextHeight = mTextPaint.getTextSize(); } else { mTextPaint.setTextSize(mTextHeight); } mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPiePaint.setStyle(Paint.Style.FILL); mPiePaint.setTextSize(mTextHeight); mShadowPaint = new Paint(0); mShadowPaint.setColor(0xff101010); mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL)); ...

提前創(chuàng)建Paint對象是一個重要的優(yōu)化樊零。View經(jīng)常需要不斷的重繪(reDraw),在onDraw中創(chuàng)建Paint對象將嚴(yán)重降低性能孽文,有可能使界面卡頓驻襟。
處理布局事件
為了正確地畫出你的自定義View,您需要知道它的大小叛溢。復(fù)雜的自定義View通常需要根據(jù)自己在屏幕上的區(qū)域大小和形狀執(zhí)行多次布局計算塑悼。對于View大小你不能猜測要進行明確的計算。即使只有一個應(yīng)用程序使用你的View楷掉,應(yīng)用程序需也要處理不同的屏幕尺寸厢蒜,多個屏幕密度以及不同寬高比、橫屏豎屏等情況烹植。盡管View有很多方法處理測量行為斑鸦,其中大部分是不需要覆蓋的。如果你認(rèn)為不需要特殊控制其大小草雕,你只需要覆蓋一個方法:onSizeChanged()巷屿。當(dāng)你的View初次分配一個size時會調(diào)用onSizeChanged(),當(dāng)因為任何理由改變了size都會再次調(diào)用墩虹。在onSizeChanged()中它會計算位置嘱巾、尺寸、和任何與View有關(guān)的值诫钓,而不是在onDraw()中進行重新計算旬昭。在PieChart的例子中,onSizeChanged()就是PieChart的View用來計算的邊界矩形餅圖和文本標(biāo)簽以及其他可見UI元素的相對位置的菌湃。當(dāng)你的View被分配一個size问拘,布局管理器會假定size里包含了包括了所有View的Padding。你必須處理Padding的值來計算你的View的大小惧所。在例子中PieChart.onSizeChanged()是這樣做的:
// Account for padding float xpad = (float)(getPaddingLeft() + getPaddingRight()); float ypad = (float)(getPaddingTop() + getPaddingBottom()); // Account for the label if (mShowText) xpad += mTextWidth; float ww = (float)w - xpad; float hh = (float)h - ypad; // Figure out how big we can make the pie. float diameter = Math.min(ww, hh);

如果你要更好的控制你的布局參數(shù)(Layout Parameters)骤坐,需要實現(xiàn)onMeasure()方法。這個方法的參數(shù)是View.MeasureSpec的值下愈,這個值告訴你的View的父視圖希望你的View多大纽绍,或者允許你的View最大是多少甚至建議是多少。作為一個優(yōu)化势似,這些值通過包裝的int整型數(shù)值存儲顶岸,然后使用View.MeasureSpec的靜態(tài)方法拆包出存儲在每個int值里的信息腔彰。下面是onMeasure()的一個例子,PieChart 試圖讓自己的view區(qū)域足夠大以至讓餅圖和文本一樣大辖佣。
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on our minimum int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); // Whatever the width ends up being, ask for a height that would let the pie // get as big as it can int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0); setMeasuredDimension(w, h);}

上面的代碼有三個重要的點:
將padding值參與了計算霹抛,前面提到的這是view的責(zé)任。 方法resolveSizeAndState()用于幫助決定最終的寬度和高度值卷谈。這個方法通過比較view期望得到的值和onMeasure規(guī)則指定的值返回一個View.MeasureSpec的值杯拐。 onMeasure()方法沒有返回值。相反世蔗,它通過調(diào)用setMeasuredDimension()傳遞算出來的結(jié)果端逼。setMeasuredDimension方法調(diào)用是必須的,如果你忽略了污淋,系統(tǒng)會報一個運行時異常顶滩。Draw!寸爆!
接下來就是繪制了礁鲁。每個View的繪制方法都不一樣,但有一些共同的操作是一樣的:
使用drawText()繪制文本赁豆。通過setTypeface()設(shè)置字體仅醇,setColor()設(shè)置文本顏色 使用drawRect(), drawOval(), 和drawArc()繪制原始圖形。setStyle()方法控制幾何圖形如何被解析魔种。 使用drawPath()繪制復(fù)雜的圖形析二。 使用LinearGradient對象定義線性填充。 使用drawBitmap()繪制bitmapPieChart中繪制了文本节预、線條以及圖形:protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the shadow canvas.drawOval( mShadowBounds, mShadowPaint ); // Draw the label text canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint); // Draw the pie slices for (int i = 0; i < mData.size(); ++i) { Item it = mData.get(i); mPiePaint.setShader(it.mShader); canvas.drawArc(mBounds, 360 - it.mEndAngle, it.mEndAngle - it.mStartAngle, true, mPiePaint); } // Draw the pointer canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint); canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);}

示例下載:PieChart叶摄。
自定義View(三) - 添加View事件模擬現(xiàn)實
參考地址:http://developer.android.com/training/custom-views/making-interactive.html繪制了View之后,我們需要給View添加一些事件安拟,讓它更接近于現(xiàn)實准谚。比如滑動一個View,當(dāng)快速滑動并突然放手時去扣,View會因為慣性繼續(xù)滑動。本文介紹通過AndroidFramework來給自定義的View添加“現(xiàn)實世界”的事件樊破。
手勢事件
Android中最多最常見的事件就是touch事件愉棱。我們可以在View的onTouchEvent(android.view.MotionEvent)方法中處理:
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }

現(xiàn)代的touch UI事件是由手勢tapping、pulling哲戚、pushing奔滑、flinging和flinging定義的。將原始的觸摸事件轉(zhuǎn)換為手勢顺少,Android中使用GestureDetector朋其。創(chuàng)建GestureDetector需要實現(xiàn)GestureDetector.OnGestureListener接口王浴,如果你只需要處理一些手勢,可以繼承GestureDetector.SimpleOnGestureListener梅猿。
class mListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; }}mDetector = new GestureDetector(PieChart.this.getContext(), new mListener());

不管你是否使用GestureDetector.SimpleOnGestureListener氓辣,你總需要在onDown()中返回true。因為如果返回false袱蚓,系統(tǒng)會認(rèn)為你要忽略其他手勢钞啸,GestureDetector.OnGestureListener的其他方法將都得不到調(diào)用。返回false的情況只有一種喇潘,就是你真的要忽略掉其他手勢(情況極少)体斩。創(chuàng)建完GestureDetector之后就要在onTouchEvent().中攔截觸摸事件了:
@Overridepublic boolean onTouchEvent(MotionEvent event) { boolean result = mDetector.onTouchEvent(event); if (!result) { if (event.getAction() == MotionEvent.ACTION_UP) { stopScrolling(); result = true; } } return result;}

在onTouchEvent()中,如果一些觸摸事件不能識別為手勢事件颖低,則返回false絮吵,我們可以用自己的代碼來檢測手勢。
仿生運動
Android提供Scroller類來處理慣性這種手勢事件忱屑。當(dāng)快速滑動時蹬敲,在fling()中使用啟動速度和最小和最大x和y的值。對于速度想幻,你可以使用GestureDetector計算出來的值粱栖。
@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mScroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY); postInvalidate();}

注意:盡管GestureDetector本身計算速度比較準(zhǔn)確,但許多開發(fā)人員認(rèn)為使用這個值還是太大脏毯,所以一般將x和y速度除以4到8倍闹究。fling()的調(diào)用是建立在快速滑動手勢的物理模型上的。然后你需要每隔一定時間調(diào)用Scroller.computeScrollOffset()來更新Scroller食店。Scroller.computeScrollOffset()通過讀取當(dāng)前時間以及在當(dāng)前時間使用物理模型計算出的x渣淤、y值來更新Scroller的內(nèi)部狀態(tài)。調(diào)用getCurrX()和getCurrY()可以提取到這些值吉嫩。大多數(shù)View直接傳遞Scroller的x价认、y值給scrollTo()。PieChart例子有一點不同自娩,他是用當(dāng)前Scroller的y值來設(shè)置圖表的旋轉(zhuǎn)角用踩。

if (!mScroller.isFinished()) { mScroller.computeScrollOffset(); setPieRotation(mScroller.getCurrY());}

Scroller類幫你計算滾動位置,但它不能自動幫你應(yīng)用這些位置到你的View上忙迁。所以必須由你確保將最新獲得的坐標(biāo)及時應(yīng)用到滾動動畫上脐彩,讓它看起來很平滑。有兩種方式:
在onFling()后調(diào)用postInvalidate()以強制重繪姊扔。這樣會在onDraw中重新計算滾動偏移量 在fling的時候創(chuàng)建一個ValueAnimator進行動畫惠奸,并調(diào)用addUpdateListener()添加一個監(jiān)聽來處理動畫更新PieChart 例子使用的第二種方案。這個技術(shù)使用起來稍微復(fù)雜點恰梢,但它運行更接近于動畫系統(tǒng)佛南,并且不會帶來潛在的View的無效刷新梗掰。缺點是ValueAnimator是Android3.0(API 11)才出來的,以前的老版本用不了嗅回。注意:為系統(tǒng)兼容性及穗,你需要在使用ValueAnimator的地方判斷系統(tǒng)版本號。

mScroller = new Scroller(getContext(), null, true); mScrollAnimator = ValueAnimator.ofFloat(0,1); mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { if (!mScroller.isFinished()) { mScroller.computeScrollOffset(); setPieRotation(mScroller.getCurrY()); } else { mScrollAnimator.cancel(); onScrollFinished(); } } });

平滑過渡
用戶希望UI變換時有個平滑的過渡妈拌,而不是突然變化拥坛。在Android3.0以后,Android使用 property animation framework尘分,可以很容易的處理平滑過渡問題猜惋。當(dāng)一個View的屬性改變而導(dǎo)致它的展示發(fā)生改變時,我們可以使用ValueAnimator來做培愁,這樣不會使改變太唐突著摔。例如:
mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0);mAutoCenterAnimator.setIntValues(targetAngle);mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);mAutoCenterAnimator.start();

如果你想要改變View基本屬性,做動畫更容易定续,因為View有一個內(nèi)置的ViewPropertyAnimator谍咆,它對多個屬性同時動畫進行了優(yōu)化。如:
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();

PieChart下載私股。
自定義View(四) - 優(yōu)化View性能
參考地址:http://developer.android.com/training/custom-views/optimizing-view.html來自官網(wǎng)的優(yōu)化View的描述:現(xiàn)在摹察,你已經(jīng)有了一個精心設(shè)計的View,我們要確保使用手勢以及狀態(tài)之間的切換運行流暢倡鲸,避免UI卡頓供嚎;使動畫始終運行在每秒60幀以保證動畫運行不斷斷續(xù)續(xù)。為加速你的View峭状,在onDraw()中不要添加無意義的代碼克滴,因為這個方法執(zhí)行的非常頻繁。在onDraw()中也不要有分配內(nèi)存的操作优床,因為每一次內(nèi)存操作都會引發(fā)潛在的GC操作劝赔,從而導(dǎo)致卡頓。永遠(yuǎn)不要在運行動畫時分配內(nèi)存胆敞。保證onDraw()代碼簡潔着帽,從而讓它盡可能快的執(zhí)行。盡可能消除不必要的invalidate()來減少onDraw()的執(zhí)行移层。
另一個耗時的操作是遍歷布局仍翰。任何時候,view調(diào)用requestLayout幽钢,Android UI系統(tǒng)都要遍歷整個view的層級結(jié)構(gòu)找到每個View到底應(yīng)該多大,如果遇到?jīng)_突的測量值傅是,它可能重復(fù)幾次遍歷整個View層級匪燕。
UI設(shè)計師經(jīng)常設(shè)計的很深的View的層級蕾羊,嵌在ViewGroup里,來方便實現(xiàn)需求帽驯。很深的View層級會帶來性能問題龟再。請保證你的View層級越淺越好。
如果你有一個很復(fù)雜的UI尼变,請自定義一個ViewGroup來實現(xiàn)它利凑。不像內(nèi)置的View,你自定義的View可以特定自己子View的形狀和大小嫌术,而避免遍歷所有的子View并測量其size和shape哀澈。PieChart 例子展示了如何繼承于ViewGroup作為自定義View,PieChart 有子View度气,但從未測量過它們割按,它是通過自定義布局的算法給子View設(shè)置特定的Size。
向下兼容
參考地址:http://developer.android.com/training/backward-compatible-ui/index.html本文用 Android3.0以后才有的Action Bar Tabs的例子磷籍,展示如何用抽象的方法做向下兼容适荣。
抽象出Tab的接口
我們假設(shè)要設(shè)計出一個頂部的Tab選項卡,有如下需求:
Tab選項卡由文本和icon組成 每一個Tab都和一個Fragment對應(yīng) Activity能響應(yīng)Tab切換的事件本文使用Eclair (API level 5) 和 Honeycomb (API Level 11)兩個系統(tǒng)版本來做例子討論如何使用抽象做向下兼容院领。下面這張圖顯示了類的抽象和接口的設(shè)計:![](http://upload-images.jianshu.io/upload_images/2761423-05d0ca33ebe098a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)將ActionBar.Tab抽象
我們模仿ActionBar.Tab類中的方法進行抽象弛矛,相當(dāng)于我們定義了自己的兼容Tab類:
public abstract class CompatTab { ... public abstract CompatTab setText(int resId); public abstract CompatTab setIcon(int resId); public abstract CompatTab setTabListener( CompatTabListener callback); public abstract CompatTab setFragment(Fragment fragment); public abstract CharSequence getText(); public abstract Drawable getIcon(); public abstract CompatTabListener getCallback(); public abstract Fragment getFragment(); ...}

然后創(chuàng)建一個抽象類,允許你創(chuàng)建和添加Tab比然,類似于ActionBar.newTab() 和ActionBar.addTab():
public abstract class TabHelper { ... public CompatTab newTab(String tag) { // This method is implemented in a later lesson. } public abstract void addTab(CompatTab tab); ...}

我們創(chuàng)建的CompatTab 類和TabHelper類是一種代理模式的實現(xiàn)丈氓。你可以在這些具體類中使用更新的API而不會導(dǎo)致設(shè)別crash,因為比如只要你不在Honeycomb (API Level 11)之前的設(shè)備上使用Honeycomb的API谈秫,系統(tǒng)就不會報VerifyError異常扒寄。一種比較好的實現(xiàn)方法是用版本號命名定義這些抽象類的實現(xiàn)類,比如使用CompatTabHoneycomb和TabHelperHoneycomb來實現(xiàn)在Android3.0上的設(shè)備使用Tab的實現(xiàn):![](http://upload-images.jianshu.io/upload_images/2761423-3c4113f5a36aa55f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
實現(xiàn)CompatTabHoneycomb
我們使用新的ActionBar.Tab的API來實現(xiàn)CompatTabHoneycomb:
public class CompatTabHoneycomb extends CompatTab { // The native tab object that this CompatTab acts as a proxy for. ActionBar.Tab mTab; ... protected CompatTabHoneycomb(FragmentActivity activity, String tag) { ... // Proxy to new ActionBar.newTab API mTab = activity.getActionBar().newTab(); } public CompatTab setText(int resId) { // Proxy to new ActionBar.Tab.setText API mTab.setText(resId); return this; } ... // Do the same for other properties (icon, callback, etc.)}

實現(xiàn)TabHelperHoneycomb
直接使用代理調(diào)用ActionBar的API:
public class TabHelperHoneycomb extends TabHelper { ActionBar mActionBar; ... protected void setUp() { if (mActionBar == null) { mActionBar = mActivity.getActionBar(); mActionBar.setNavigationMode( ActionBar.NAVIGATION_MODE_TABS); } } public void addTab(CompatTab tab) { ... // Tab is a CompatTabHoneycomb instance, so its // native tab object is an ActionBar.Tab. mActionBar.addTab((ActionBar.Tab) tab.getTab()); } // The other important method, newTab() is part of // the base implementation.}

接下來我們就要實現(xiàn)在舊的版本設(shè)備上實現(xiàn)一樣的Tab了拟烫,需要尋找一個替代的解決方案:
使用Older APIs實現(xiàn)Tabs
我們使用TabWidget和TabHost 作為替代方案實現(xiàn)TabHelperEclair和CompatTabEclair该编,因為TabWidget和TabHost的API在Android 2.0 (Eclair)以后就有了。![](http://upload-images.jianshu.io/upload_images/2761423-47f46b7bd10f62c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
CompatTabEclair實現(xiàn)了一個文本和icon的存儲硕淑,因為在Eclair版本上沒有ActionBar.Tab的API:
public class CompatTabEclair extends CompatTab { // Store these properties in the instance, // as there is no ActionBar.Tab object. private CharSequence mText; ... public CompatTab setText(int resId) { // Our older implementation simply stores this // information in the object instance. mText = mActivity.getResources().getText(resId); return this; } ... // Do the same for other properties (icon, callback, etc.)}

TabHelperEclair類的實現(xiàn)是使用TabHost組件創(chuàng)建TabHost.TabSpec 實現(xiàn):
public class TabHelperEclair extends TabHelper { private TabHost mTabHost; ... protected void setUp() { if (mTabHost == null) { // Our activity layout for pre-Honeycomb devices // must contain a TabHost. mTabHost = (TabHost) mActivity.findViewById( android.R.id.tabhost); mTabHost.setup(); } } public void addTab(CompatTab tab) { ... TabSpec spec = mTabHost .newTabSpec(tag) .setIndicator(tab.getText()); // And optional icon ... mTabHost.addTab(spec); } // The other important method, newTab() is part of // the base implementation.}

現(xiàn)在有兩個CompatTab和TabHelper的實現(xiàn):一個運行在Android 3.0或更高版本课竣,并使用新的api;另一個運行Android 2.0或更高版本置媳,并使用老的api于樟。下面將討論在應(yīng)用程序中使用這些實現(xiàn):
添加切換邏輯
TabHelper 類扮演了一個工廠,由他創(chuàng)建兼容各種設(shè)備的TabHelper 和CompatTab實例:
public abstract class TabHelper { ... // Usage is TabHelper.createInstance(activity) public static TabHelper createInstance(FragmentActivity activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { return new TabHelperHoneycomb(activity); } else { return new TabHelperEclair(activity); } } // Usage is mTabHelper.newTab("tag") public CompatTab newTab(String tag) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { return new CompatTabHoneycomb(mActivity, tag); } else { return new CompatTabEclair(mActivity, tag); } } ...}

創(chuàng)建版本兼容的布局(layout)
在Android3.0以上我們使用ActionBar拇囊,而在2.0以上我們使用TabHost迂曲,所以在布局上使用老版本時我們需要在XML布局中定義TabHost和TabWidget。res/layout/main.xml:
<framelayout android:id="@android:id/tabcontent" android:layout_height="0dp" android:layout_weight="1" android:layout_width="match_parent"> </framelayout>

在Android3.0以后的布局是這樣的:res/layout-v11/main.xml:
<framelayout android:id="@android:id/tabcontent" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"></framelayout>

系統(tǒng)運行時寥袭,Android會根據(jù)系統(tǒng)本身的版本自動選擇main.xml布局文件路捧。
在Activity中使用TabHelper
@Overridepublic void onCreate(Bundle savedInstanceState) { setContentView(R.layout.main); TabHelper tabHelper = TabHelper.createInstance(this); tabHelper.setUp(); CompatTab photosTab = tabHelper .newTab("photos") .setText(R.string.tab_photos); tabHelper.addTab(photosTab); CompatTab videosTab = tabHelper .newTab("videos") .setText(R.string.tab_videos); tabHelper.addTab(videosTab);}

下面是兩個分別運行在Android 2.3和Android 4.0設(shè)備的截圖:![](http://upload-images.jianshu.io/upload_images/2761423-bc320399e1104d9e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)![](http://upload-images.jianshu.io/upload_images/2761423-24e7ab50fe0f913f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
代碼示例下載:TabCompat.zip

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末关霸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子杰扫,更是在濱河造成了極大的恐慌队寇,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件章姓,死亡現(xiàn)場離奇詭異佳遣,居然都是意外死亡,警方通過查閱死者的電腦和手機凡伊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門零渐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窗声,你說我怎么就攤上這事相恃。” “怎么了笨觅?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵拦耐,是天一觀的道長。 經(jīng)常有香客問我见剩,道長杀糯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任苍苞,我火速辦了婚禮固翰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羹呵。我一直安慰自己骂际,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布冈欢。 她就那樣靜靜地躺著歉铝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凑耻。 梳的紋絲不亂的頭發(fā)上太示,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音香浩,去河邊找鬼类缤。 笑死,一個胖子當(dāng)著我的面吹牛邻吭,可吹牛的內(nèi)容都是我干的餐弱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼膏蚓!你這毒婦竟也來了猖败?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤降允,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后艺糜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剧董,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年破停,在試婚紗的時候發(fā)現(xiàn)自己被綠了翅楼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡真慢,死狀恐怖毅臊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情黑界,我是刑警寧澤管嬉,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站朗鸠,受9級特大地震影響蚯撩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烛占,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一胎挎、第九天 我趴在偏房一處隱蔽的房頂上張望秩铆。 院中可真熱鬧肚菠,春花似錦闻蛀、人聲如沸狂男。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眨唬。三九已至绷落,卻和暖如春蹬竖,著一層夾襖步出監(jiān)牢的瞬間沼沈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工币厕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留列另,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓旦装,卻偏偏與公主長得像页衙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容