2012年3月13日 星期二

Android學習_客製化ListView_加上Button

正覺得iOS寫的順手時,又很快樂的轉換回Android,最不習慣的莫過於介面上的配置,iOS幫開發者很貼心的加上許多美工部分,我們可以較容易地做出好看又顯專業的介面,但Android就必須要花很多時間在介面的配置上,其中包含許多設定上的小技巧,才能讓美感不佳如我的人做出差強人意的畫面。而在Android裡ListView可以說是一個倘若做的好,程式先有60分的一個重要控制項,所以這次花了很多時間在ListView上面,先看效果:

如標題所說,本篇主要介紹如何做出含有按鈕之ListView。

正文-------------------------------------------------
在iOS的UITableView中,有一種預設的模式會有文字與一個小圖示:

若是直接點擊文字或是空白處,會觸發didSelectRowAtIndexPath,若點擊旁邊的小圖示,則會觸發accessoryButtonTappendForRowWithIndexPath,所以在GIS系統開發上,就可以很容易的將定位與顯示屬性分別在這兩個事件上實現,提供使用者一個很不錯的操作方式。

但反觀Android,這樣的需求就必須要經過自己重新撰寫,才能達到類似的效果;在之前曾經有發過相關文章,這篇剛好可以補足完整客製化時的教學。
一維陣列:Android學習_ArrayAdapter的使用
自行處理的二維陣列:Android學習_SimpleAdapter的使用
Cursor的處理:Android學習_SimpleCursorAdapter的使用
光是單純的變化都需要自行實作,更何況在ListView上面加個與事件綁定的小圖示。

--------------------------實作-------------------------
所以本篇的目標,就是客製化一個帶有按鈕的ListView,目標像下面這張:

可以顯示相關小圖示,主要標題、副標與一個帶有事件的按鈕。

觀念:觀察要將資料放入ListView的方法是setAdapter(adapter),所以唯一方法即是客製化adapter。

步驟:
1. 一個依照需求設計好的adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="3dip"
android:descendantFocusability="blocksDescendants" >

<ImageView
android:id="@+id/ItemImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dip"
android:contentDescription="@string/adapter_btnText"
/>

<LinearLayout android:layout_toRightOf="@id/ItemImage"
android:layout_toLeftOf="@+id/ItemButton"
android:layout_alignTop="@+id/ItemImage"
android:layout_alignBottom="@+id/ItemImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:id="@+id/ItemName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textSize="20dip"
/>

<TextView
android:id="@+id/ItemInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textSize="20dip"
/>
</LinearLayout>

<Button android:id="@+id/ItemButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_launcher"
android:contentDescription="@string/adapter_btnText"
/>

</RelativeLayout>

2. 建立一個繼承於Adapter的類別(本篇使用BaseAdapter)


可以看到若是繼承於BaseAdapter,預設會有四個方法:
A. public int getCount()
B. public Object getItem(int position)
C. public long getItemId(int position)
D. public View getView(int position, View convertView, ViewGroup parent)
當中最重要的方法是getView,該方法會在每次重新繪製ListView任一個Item時被呼叫,也就是說我們要客製化每一列,就必須在這個方法中描繪我們需要的內容,並返回一個處理好的View。

除了上述四個方法必須要複寫外,通常還會增加一個建構函式,在建立該Adapter時將所需要的外部變數丟入,也就是丟入我們要顯示的值與圖案。

最後,因為我們希望除了點擊ListView可以有反應外,點擊自己加上的按鈕也可以有相對應的事件觸發,所以需要增加一個OnClickListener來監聽。

程式碼:

package tw.com.wangshifu.ola;

import java.util.ArrayList;
import java.util.HashMap;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

public class lv_BtnAdapter extends BaseAdapter {

private ArrayList<HashMap<String, Object>> mAppList;
private LayoutInflater mInflater;
private Context mContext;
private String[] keyString;
private int[] valueViewID;

private ItemView itemView;

private class ItemView {
ImageView ItemImage;
TextView ItemName;
TextView ItemInfo;
Button ItemButton;
}

public lv_BtnAdapter(Context c, ArrayList<HashMap<String, Object>> appList, int resource, String[] from, int[] to) {
mAppList = appList;
mContext = c;
mInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
keyString = new String[from.length];
valueViewID = new int[to.length];
System.arraycopy(from, 0, keyString, 0, from.length);
System.arraycopy(to, 0, valueViewID, 0, to.length);
}

@Override
public int getCount() {
// TODO Auto-generated method stub
//return 0;
return mAppList.size();
}

@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
//return null;
return mAppList.get(position);
}

@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
//return 0;
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
//return null;

if (convertView != null) {
itemView = (ItemView) convertView.getTag();
} else {
convertView = mInflater.inflate(R.layout.adapter_button, null);
itemView = new ItemView();
itemView.ItemImage = (ImageView)convertView.findViewById(valueViewID[0]);
itemView.ItemName = (TextView)convertView.findViewById(valueViewID[1]);
itemView.ItemInfo = (TextView)convertView.findViewById(valueViewID[2]);
itemView.ItemButton = (Button)convertView.findViewById(valueViewID[3]);
convertView.setTag(itemView);
}

HashMap<String, Object> appInfo = mAppList.get(position);
if (appInfo != null) {

int mid = (Integer)appInfo.get(keyString[0]);
String name = (String) appInfo.get(keyString[1]);
String info = (String) appInfo.get(keyString[2]);
int bid = (Integer)appInfo.get(keyString[3]);
itemView.ItemName.setText(name);
itemView.ItemInfo.setText(info);
itemView.ItemImage.setImageDrawable(itemView.ItemImage.getResources().getDrawable(mid));
itemView.ItemButton.setBackgroundDrawable(itemView.ItemButton.getResources().getDrawable(bid));
itemView.ItemButton.setOnClickListener(new ItemButton_Click(position));
}

return convertView;
}

class ItemButton_Click implements OnClickListener {
private int position;

ItemButton_Click(int pos) {
position = pos;
}

@Override
public void onClick(View v) {
int vid=v.getId();
if (vid == itemView.ItemButton.getId())
Log.v("ola_log",String.valueOf(position) );
}
}
}

3. 使用步驟二所建立的adapter,置入ListView中
我們用簡單的迴圈模擬外部要放入的資料,並且將剛剛所建立的adapter new起來:

protected void putDataToListView()
{
ArrayList<HashMap<String, Object>> Item = new ArrayList<HashMap<String, Object>>();
for(int i=0; i<20; i++)
{
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("ItemImage", R.drawable.ic_launcher);
map.put("ItemName", "Name");
map.put("ItemInfo", "Info");
map.put("ItemButton", R.drawable.ic_launcher);
Item.add(map);
}

lv_BtnAdapter Btnadapter = new lv_BtnAdapter(
this,
Item,
R.layout.adapter_button,
new String[] {"ItemImage","ItemName", "ItemInfo","ItemButton"},
new int[] {R.id.ItemImage,R.id.ItemName,R.id.ItemInfo,R.id.ItemButton}
);
lv.setAdapter(Btnadapter);
}

效果:

稍微對於輸入的內容做些變更,就可以看出客製化的效果:

最後的列表效果可以說是非常之讚。

51 則留言:

jenny6932003 提到...

可以請問一下

lv.setAdapter(Btnadapter);

的lv是指甚麼嗎???

有點不懂

ola的家 提到...

就是layout檔內ListView的id對應名稱。

匿名 提到...

不好意思

請問image 如果要改是在哪做修改

是在 lv_BtnAdapter

還是在 HashMap map

ola的家 提到...

在HashMap put的時候換,也就是上面文章的"map.put("ItemImage", R.drawable.ic_launcher);"這句,跑迴圈時滿足某條件就put圖1,另一個條件就put進圖2,以此類推。

匿名 提到...

謝謝

sky 提到...

請問一下客製化的那張圖
按一下去會連結網址嗎??
那這樣功能要怎麼達成??

ola的家 提到...

在按鈕的onClick事件撰寫置換右邊webView的網址即可。

sky 提到...

抱歉...不是很懂
那這樣每個按鈕都要不一樣的網址要怎麼去對應??
有code可以提供參考嗎?
謝謝~

ola的家 提到...

在第二步驟的最後:
@Override
public void onClick(View v) {
int vid=v.getId();
if (vid == itemView.ItemButton.getId())
Log.v("ola_log",String.valueOf(position) );
}

這句可以取得你按下哪一列,也就是position這個變數,有了這個變數就可以對應回你在ArrayList> appList這個裡面的網址。

當然,你必須要在組出ArrayList> item的地方就給他每一個項目的對應網址。

所以流程就是:
1. 按下按鈕取得按到哪一列。
2. 由哪一列去陣列裡面換出網址字串。
3. 將網址塞到webview當中。

sky 提到...

謝謝~~
我嘗試看看!!
感謝你的分享^^

匿名 提到...

想請問您一下
如果是在Fragment底下,要製作出這樣的功能
有什麼方法可以達到呢?
謝謝~

ola的家 提到...

我沒有實際做過,但是你應該仍然可以將ListView放進你所使用的框架,然後完成一樣的效果,這要嘗試才知道。

匿名 提到...

感謝~ 我在嘗試看看!!

匿名 提到...

adapter_button檔在哪裡 是哪一個?

ola的家 提到...

是一個檔名為adapter_button的layout,裡面有1個ImageView、2個TextView跟1個Button。

ginnyhuang 提到...

請問一下,要如何在adapter內調用database呢?
想要在每一個listitem都加個按鈕來刪除那一個item。
因為我是從SQL取出資料填到list內,所以刪除時想打開SQLiteDatabase並對其動作,但在adapter內沒有出現openOrCreateDatabase這個function。
是否在adapter內無法調用database?我該如何解決呢?
謝謝:)
(因為點擊listitem還有別的工作,希望只有在點擊listitem內的按鈕才做調用DB的動作)

ola的家 提到...

這邊就單純的在按下按鈕的事件中使用刪除該列的方法。

理論上我們會把操作SQLite的方法都寫在另一個類別裡面,你要做的只是把那個類別new起來,然後把id丟給刪除的方法。

但是如果你是把全部的東西都寫在你的Activity裡面,那你有兩個選擇,把SQLite相關的東西傳進adapter內來操作,第二個就是使用委派的方法來操作(可參考:http://wangshifuola.blogspot.tw/2012/03/androidimitate-ioss-delegate-callback.html),但我還是建議將SQLite相關操作獨立出來,因為當程式變複雜的時候,操作SQLite還是一樣單純。

匿名 提到...

您好:
請問是否能做到只有某部份加按鈕,另外一部份不加
例如,我現在有6個項目,上面3個項目要加按鈕另外3個項目不加,如果能做到可否大概說明一下要如何做,或是在何處可參考,謝謝您

ola的家 提到...

在getView的方法內將itemView.ItemButton隱藏即可。

-----------------------------------------
getView就是畫出一個一個cell的方法,所以一每一個cell要畫的時候都會去執行,在該方法利用判斷式則可做到有些有按鈕有些沒有按鈕的效果。(或是你可以將圖換成不同的來告知使用者也可以。)

匿名 提到...

不知道大哥你有沒有一個專案檔可供下載??

ola的家 提到...

目前沒有提供相關的下載空間。 :D

匿名 提到...

你好,想請問一下在這一段程式碼中 "3. 使用步驟二所建立的adapter,置入ListView中" 請問要加入在哪一個,是在lv_BtnAdapter當中嗎? 還是在MainActivity裡面,如果在lv_BtnAdapter那邊,那麼MainActivity裡應該也需要呼應的程式碼吧???抱歉,小弟才疏學淺,但是對於你的這個介面非常有興趣!!

ola的家 提到...

在你要呈現列表的Activity內。

匿名 提到...

不知道你這隻程式的架構是不是這個樣子??(不好意思麻煩你點擊一下圖片)
http://i194.photobucket.com/albums/z201/brontosaur16/test/p1.jpg

如果正確,那麼我在看到你下面的回覆有講到說adapter_button的layout,裡面有1個ImageView、2個TextView跟1個Button,因此我在創立了一個layout並且拉了1個ImageView、2個TextView跟1個Button,如此圖片
http://i194.photobucket.com/albums/z201/brontosaur16/test/p2.jpg

最後是你說將最後一段程式碼貼在Activity內,如此圖片
http://i194.photobucket.com/albums/z201/brontosaur16/test/p3.jpg
我都按照你說的去做,可能不知道哪邊有問題,執行起來都是空白一片,想請教你一下,是不是我哪邊出了問題,抱歉,小弟問題很多,真的很不好意思。

匿名 提到...

webview = new WebView(v.getContext());
webview.getSettings().setJavaScriptEnabled(true); webview.loadUrl("http://www.google.com.tw/");

想請問一下,我在
@Override
public void onClick(View v) {
int vid=v.getId();
if (vid == itemView.ItemButton.getId())
上面所述的;
}

但是點選都沒有到網頁去,是我哪邊錯了嗎?

ola的家 提到...

看你的第三張圖片,好像沒看到呼叫PutDataoListView這個方法。

ola的家 提到...

這樣我不能確定耶,可能要先確認:
1. 有沒有進到判斷式內。
2. webview有沒有抓到。

匿名 提到...

你好,我想再加一個textview進入到listview裡面,
請問是否在Mainactivity程式碼那邊的加入protected void putDataToListView()方法裡加入

map.put("Itemadd", "我新加的");
lv_BtnAdapter Btnadapter = new lv_BtnAdapter(
this,
Item,
R.layout.adapter,
new String[] {"ItemImage","ItemName", "ItemInfo","ItemButton","Itemadd"},
new int[] {R.id.ItemImage,R.id.ItemName,R.id.ItemInfo,R.id.ItemButton,R.id.aatextView1}
);

而在lv_BtnAdapter程式碼的getView()方法中那邊修改並加入
itemView.Itemadd = (TextView)convertView.findViewById(valueViewID[4]);
............
String add = (String) appInfo.get(keyString[4]);
itemView.Itemadd.setText(add);

但是我改完後,圖形介面完全沒有我所加入的設定><
請問還有需要修改哪裡嗎?

ola的家 提到...

layout.xml裡面有加入textview物件嗎?

匿名 提到...

有喔!也沒有任何錯誤訊息,所以才不知道錯在哪裡!!
都還是原來的顯示。

順便請教一下,我現在是想要點擊ListView來讓我加在裡面的radiobutton有選項的動作,在網路查了很多,它們說只要將XML的radiobutton裡加入
android:focusable="false"
android:focusableInTouchMode="false"
這兩行即可,在setOnItemClickListener的事件中,也能做出我要的事件 例如 Toast.makeText(),但是我在裡面設定radiobutton.setcheck(true)去沒有跟著動作。請問你有遇過這樣的問題嗎?

ola的家 提到...

印象中在處理listview內有radio時好像沒有碰過,這種細節的問題必須要配合程式碼才好測試,不好意思。

ginnyhuang 提到...

抱歉現在才來留言~
把打開SQL的指令另開一個class真的就可以進行SQLite的指令了~
不過我比較懶XD,只有把create table的指令寫在另一個class
其他的指令在把database開啟後就可以直接接".delete"
".close"等指令了
非常感謝您的幫助~:)
---
另外分享個碰到的小問題,就是如果我想在list的adapter開啟另一個activity的話,要使用自己命名的Context去執行startActivity的指令
ex: context.startActivity(intent)
因為平常指令完整版其實是"this.startActivity(intent)"
不過真正的解決方法是不是這樣就不得而知了(總覺得在程式的架構上會有點小問題...?)
這個問題困擾了我一陣子,找到解答後發現自己對android的架構果然還是不是很熟悉O_Q
---
再次感謝:D

匿名 提到...

想請問一下我listview裡面的按鈕要call電話,那請問要怎第一個按鈕是撥出第一組的號碼,第二個按鈕是撥第二組電話號碼以此類推 請問是要在button 裡面怎寫才能按第幾組撥出第幾組號碼!!感謝~

匿名 提到...

不好意思 請問可否提供 這個範例的完整專案檔 想要更深入的研究 感激不盡

ola的家 提到...

按按鈕打電話跟前面有人問的按按鈕開網頁是一樣的,所以方式就是:


在第二步驟的最後:
@Override
public void onClick(View v) {
int vid=v.getId();
if (vid == itemView.ItemButton.getId())
Log.v("ola_log",String.valueOf(position) );
}

這句可以取得你按下哪一列,也就是position這個變數,有了這個變數就可以對應回你在ArrayList> appList這個裡面的電話。

當然,你必須要在組出ArrayList> item的地方就給他每一個項目的對應電話。

所以流程就是:
1. 按下按鈕取得按到哪一列。
2. 由哪一列去陣列裡面換出電話號碼。
3. 再撥出電話號碼。

ola的家 提到...

文章裡面已經是完整程式碼了。 @.@

匿名 提到...

請問一下 這個範例 如何用switch case 切換到不同的Activity 或 layout 應該怎麼修改

ola的家 提到...

你已經在onClick事件取得了position,如果你要用switch case判斷,那就是把你的switch case判斷加onClick事件中,再以position判斷執行哪一個Case,用intent轉到各自的Activity。

其實只是單純把判斷寫在onclick內而已。

匿名 提到...

不好意思請問要在onclick裡面取得他按下哪一列
那請問陣列我該怎寫呢去取出他的號碼呢?是用迴圈去跑嗎?該怎給他對應的電話

匿名 提到...

請問此方法可以使用.addFooterView嗎??

陳陳立翰 提到...

如果版主還可以回復我的話~
我想知道的是:如何用一個外在的按鈕,改變item 裡面的 textView
example:我另外設置一個按鈕,(不再listView)裡面,但在同一個layout裡面。
我利用這個按鈕,控制item裡面的值? 可以做得到嗎?
就按下一個按鈕,原本 "info"這字串改成 "123" 這樣。
我目前只有想到 , 按下按鈕,刪除整個 listView 然後在同一的動作下,把他新增回來。

匿名 提到...

可以請問在item裡面的button點擊後如何取到同一個item的textview的值或其他物件嗎
還不太懂那個view怎麼玩...我抓xml裡面那個物件,好像只會固定取第一個item而已

ola的家 提到...

在click事件裡面已經取得了position,也就是點擊到第幾個資料列,就可以利用這個數值跟你塞進List裡面的array進行對應,取得你需要的值。

ola的家 提到...

若是要用外部按鈕變更list內的內容,其實就是按下按鈕後,去改變array裡面的內容, 再重新繪製就可以了。

匿名 提到...

版大您好,想請教一下,
我想要用listview跟startActivity做一個商品列表,點擊蘋果用跑到蘋果的Activity,點擊橘子跑到橘子的Activity,
但這個Activity是同一個,也就是說這個Activity因為點擊的不同而有相對應的呈現,那我該如何讓listview傳值,使傳送過去的Activity知道要呈現甚麼面貌

ola的家 提到...

1. 在click事件內,用Array進行判別,是點到蘋果還是橘子。
2. 要轉換Activity時,利用Bundle將參數代過去即可。

林雋鈜 提到...

請問要怎麼讓使用者按下按鈕後,開啟另外一個activity呢??
我試過用intent,但是intente的setClass方法第一個參數怎麼填都不太對。
謝謝!

匿名 提到...

完整程式作法如下:

01.Studio 建一專案:名字自取 (如 Ola_ListView)

02. Activity: MainActivity, layout: activity_main, Title: MainActivity
Package: tw.com.wangshifu.ola

03. layout 新建 adapter_button.xml (即 Ola 步驟 1. adapter.xml)

04. java 新鍵 lv_BtnAdapter.java (即 Ola 步驟 2. adapter 類別)

05. 加入 protected void putDataToListView() 於
新建的 MainActivity.java 內 onCreate 之後

06. 於 新建的 MainActivity.java
新增 private ListView lv;

07. onCreate 內新增:
lv = (ListView) findViewById(R.id.mlistView);
putDataToListView();

08. onCreate() 後複製
protected void putDataToListView() (即 Ola 步驟 3. putDataToListView() )

09. 於 string.xml 新增
隨便填

10. 於 layout下 拉一個 id 改為 mListView
android:id="@+id/mlistView" (配合 onCreate 內的 lv 變數)

修正(MainActivity)內容
public class MainActivity extends Activity {
private ListView lv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.mlistView); //新增
putDataToListView(); //新增
}

protected void putDataToListView() … (複製 Ola 步驟 3. 的內容)

匿名 提到...

09. 於 string.xml 新增 應該是:
要新建 string name="adapter_btnText" 隨便填命名

匿名 提到...

不好意思有個問題想請教一下
我在listview中的每行設置了一個editText跟Button
由於editText跟Button的值在最初生成的時候就已經設定好了
後來修正HsahMap的內容沒有用
所以想請教一下要怎麼樣去修改ListView中的每行的物件的值

Ian 提到...

It's really helpful for me

tks a lot!!

張貼留言