標籤:
原文連結:http://www.orlion.ga/453/
一、單位與尺寸
布局檔案中一共有以下單位供選擇:px,pt,dp,sp
px:是像素,螢幕中可見的最小元素單位。
pt:是磅,1磅等於1/72英寸,一般pt都會作為字型的單位來使用。
同樣px數的控制項在不同解析度上的手機螢幕上的效果是不同的,pt與px的情況差不多
dp:是密度無關像素,也稱為dip,與px相比,它在不同密度的螢幕中的顯示比例保持一致
sp:是可伸縮像素,採用了與dp同樣的設計理念,解決了文字大小的適配問題
android中的密度就是螢幕沒英寸所包含的像素數,通常以dpi為單位,比如一個手機螢幕的寬是2英寸長是3英寸,如果它的解析度是320*480像素,那這個手機的螢幕就是160dpi,如果分辨是640*960,那麼這個螢幕的密度就是320dpi。
根據Android的規定,在160dpi的螢幕上1dp等於1px,而在320dpi的螢幕上1dp就等於2px。
二、ListView
1、ListView的簡單用法
首先建立一個 ListViewTest 項目,並讓 ADT 自動幫我們建立好活動。然後修改
activity_main.xml中的代碼,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView></LinearLayout>
接下來修改 MainActivity中的代碼,如下所示:
public class MainActivity extends Activity { private String[] data = { "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ArrayAdapter<String> adapter = new ArrayAdapter<String>( MainActivity.this, android.R.layout.simple_list_item_1, data); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); }}
既然ListView是用於展示大量資料的,那我們就應該先將資料提供好。這些資料可以是從網上下載的,也可以是從資料庫中讀取的,應該視具體的應用程式情境來決定。這裡我們就簡單使用了一個 data數組來測試,裡麵包含了很多水果的名稱。
不過,數組中的資料是無法直接傳遞給 ListView的,我們還需要藉助適配器來完成。Android中提供了很多適配器的實作類別,其中我認為最好用的就是 ArrayAdapter。它可以通過泛型來指定要適配的資料類型, 然後在建構函式中把要適配的資料傳入即可。 ArrayAdapter有多個建構函式的重載,你應該根據實際情況選擇最合適的一種。這裡由於我們提供的資料都是字串,因此將 ArrayAdapter的泛型指定為 String,然後在 ArrayAdapter的建構函式中依次傳入當前上下文、ListView 子項布局的 id,以及要適配的資料。注意我們使用了android.R.layout.simple_list_item_1作為 ListView子項布局的 id,這是一個 Android內建的布局檔案, 裡面只有一個 TextView, 可用於簡單地顯示一段文本。 這樣配接器物件就構建好了。最後,還需要調用 ListView的 setAdapter()方法,將構建好的配接器物件傳遞進去,這樣 ListView和資料之間的關聯就建立完成了。現在運行一下程式,
2、給ListView加圖片
只能顯示一段文本的 ListView實在是太單調了,我們現在就來對 ListView的介面進行定製,讓它可以顯示更加豐富的內容。首先需要準備好一組圖片,分別對應上面提供的每一種水果,待會我們要讓這些水果名稱的旁邊都有一個圖樣。接著定義一個實體類,作為 ListView適配器的適配類型。建立類 Fruit,代碼如下所示:
public class Fruit { private String name; private int imageId; public Fruit(String name, int imageId) { this.name = name; this.imageId = imageId; } public String getName() { return name; } public int getImageId() { return imageId; }}
Fruit類中只有兩個欄位,name表示水果的名字,imageId表示水果對應圖片的資源 id。然後需要為 ListView 的子項指定一個我們自訂的布局,在 layout 目錄下建立fruit_item.xml,代碼如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/fruit_image" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/fruit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="10dip" /></LinearLayout>
在這個布局中,我們定義了一個 ImageView 用於顯示水果的圖片,又定義了一個TextView用於顯示水果的名稱。
接下來需要建立一個自訂的適配器,這個適配器繼承自 ArrayAdapter,並將泛型指定為 Fruit類。建立類 FruitAdapter,代碼如下所示:
public class FruitAdapter extends ArrayAdapter<Fruit> { private int resourceId; public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) { super(context, textViewResourceId, objects); resourceId = textViewResourceId; } @Override public View getView(int position, View convertView, ViewGroup parent) { Fruit fruit = getItem(position); // 擷取當前項的Fruit執行個體 View view = LayoutInflater.from(getContext()).inflate(resourceId, null); ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image); TextView fruitName = (TextView) view.findViewById(R.id.fruit_name); fruitImage.setImageResource(fruit.getImageId()); fruitName.setText(fruit.getName()); return view; }}
FruitAdapter重寫了父類的一組建構函式,用於將上下文、ListView子項布局的 id和資料都傳遞進來。另外又重寫了 getView()方法,這個方法在每個子項被滾動到螢幕內的時候會被調用。在 getView方法中,首先通過 getItem()方法得到當前項的 Fruit執行個體,然後使用LayoutInflater來為這個子項載入我們傳入的布局, 接著調用 View的 findViewById()方法分別擷取到 ImageView和 TextView的執行個體,並分別調用它們的 setImageResource()和 setText()方法來設定顯示的圖片和文字,最後將布局返回,這樣我們自訂的適配器就完成了。下面修改 MainActivity中的代碼,如下所示:
public class MainActivity extends Activity { private List<Fruit> fruitList = new ArrayList<Fruit>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initFruits(); // 初始化水果資料 FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); } private void initFruits() { Fruit apple = new Fruit("Apple", R.drawable.apple_pic); fruitList.add(apple); Fruit banana = new Fruit("Banana", R.drawable.banana_pic); fruitList.add(banana); Fruit orange = new Fruit("Orange", R.drawable.orange_pic); fruitList.add(orange); Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic); fruitList.add(watermelon); Fruit pear = new Fruit("Pear", R.drawable.pear_pic); fruitList.add(pear); Fruit grape = new Fruit("Grape", R.drawable.grape_pic); fruitList.add(grape); Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic); fruitList.add(pineapple); Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic); fruitList.add(strawberry); Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic); fruitList.add(cherry); Fruit mango = new Fruit("Mango", R.drawable.mango_pic); fruitList.add(mango); }}
可以看到,這裡添加了一個 initFruits()方法,用於初始化所有的水果資料。在 Fruit類的建構函式中將水果的名字和對應的圖片 id傳入,然後把建立好的對象添加到水果列表中。接著我們在 onCreate()方法中建立了 FruitAdapter對象,並將 FruitAdapter作為適配器傳遞給了 ListView。這樣定製 ListView介面的任務就完成了。現在重新運行程式
,
3、提升ListView的運行效率
之所以說 ListView這個控制項很難用,就是因為它有很多的細節可以最佳化,其中運行效率就是很重要的一點。 目前我們ListView的運行效率是很低的, 因為在FruitAdapter的getView()方法中每次都將布局重新載入了一遍,當 ListView快速滾動的時候這就會成為效能的瓶頸。仔細觀察,getView()方法中還有一個 convertView參數,這個參數用於將之前載入好的
布局進行緩衝,以便之後可以進行重用。修改 FruitAdapter中的代碼,如下所示:
public class FruitAdapter extends ArrayAdapter<Fruit> { …… @Override public View getView(int position, View convertView, ViewGroup parent) { Fruit fruit = getItem(position); View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(resourceId, null); } else { view = convertView; } ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image); TextView fruitName = (TextView) view.findViewById(R.id.fruit_name); fruitImage.setImageResource(fruit.getImageId()); fruitName.setText(fruit.getName()); return view; }}
可以看到,現在我們在 getView()方法中進行了判斷,如果 convertView為空白,則使用LayoutInflater去載入布局,如果不為空白則直接對 convertView進行重用。這樣就大大提高了ListView的運行效率,在快速滾動的時候也可以表現出更好的效能。
不過, 目前我們的這份代碼還是可以繼續最佳化的, 雖然現在已經不會再重複去載入布局,但是每次在getView()方法中還是會調用View的findViewById()方法來擷取一次控制項的執行個體。我們可以藉助一個 ViewHolder來對這部分效能進行最佳化,修改 FruitAdapter中的代碼,如下所示:
public class FruitAdapter extends ArrayAdapter<Fruit> { …… @Override public View getView(int position, View convertView, ViewGroup parent) { Fruit fruit = getItem(position); View view; ViewHolder viewHolder; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(resourceId, null); viewHolder = new ViewHolder(); viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image); viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name); view.setTag(viewHolder); // 將ViewHolder儲存在View中 } else { view = convertView; viewHolder = (ViewHolder) view.getTag(); // 重新擷取ViewHolder } viewHolder.fruitImage.setImageResource(fruit.getImageId()); viewHolder.fruitName.setText(fruit.getName()); return view; } class ViewHolder { ImageView fruitImage; TextView fruitName; }}
我們新增了一個內部類 ViewHolder,用於對控制項的執行個體進行緩衝。當 convertView為空白的時候, 建立一個 ViewHolder對象, 並將控制項的執行個體都存放在 ViewHolder裡, 然後調用 View的 setTag()方法,將 ViewHolderObject Storage Service在 View中。當 convertView不為空白的時候則調用View的 getTag()方法, 把 ViewHolder重新取出。 這樣所有控制項的執行個體都緩衝在了 ViewHolder裡,就沒有必要每次都通過 findViewById()方法來擷取控制項執行個體了。通過這兩步的最佳化之後,我們 ListView的運行效率就已經非常不錯了。
4、ListView的點擊事件
ListView的滾動畢竟只是滿足了我們視覺上的效果,可是如果 ListView中的子項不能點擊的話,這個控制項就沒有什麼實際的用途了。因此,本小節中我們就來學習一下ListView如何才能響應使用者的點擊事件。修改 MainActivity中的代碼,如下所示:
public class MainActivity extends Activity { private List<Fruit> fruitList = new ArrayList<Fruit>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initFruits(); FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Fruit fruit = fruitList.get(position); Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show(); } }); } ……}
可以看到,我們使用了 setOnItemClickListener()方法來為 ListView註冊了一個監聽器,當使用者點擊了 ListView中的任何一個子項時就會回調 onItemClick()方法, 在這個方法中可以通過 position參數判斷出使用者點擊的是哪一個子項,然後擷取到相應的水果,並通過 Toast將水果的名字顯示出來。重新運行程式,並點擊一下西瓜
Android入門(五)UI-單位與尺寸、ListView