延續昨天建立的RecyclerView,我們實作幾個功能來發掘它厲害的地方。
實務上,列表要顯示的項目經常不只一行字,還會放更多圖文訊息甚至可互動的元件,我們就放一個Button到我們項目中。
修改list_item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="72dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/txtItem"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="@+id/btnRemove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="刪除"/>
</LinearLayout>
第6行我們把android:orientation
改成horizontal讓文字和按鈕水平並排。
可以特別注意的是12行我們把TextView加上android:layout_weight="1"
這個屬性,這是LinearLayout專用的屬性,表示元件的空間比重。
此處TextView的weight是1而Button沒有設置所以是0,效果為TextView能獲得Button之外的全部空間,如圖所示藍色框都是TextView的可用空間,Button就只有符合本身大小的空間。
對weight不清楚的話可以嘗試把Button的weight也改為1,兩個元件就會變一樣大,改成2的話就會是2:1的大小。
在MyAdapter的ViewHolder中連結Button,而Button的點擊事件待會一起介紹
RecyclerView並沒有內建的點擊Listener可以用,但沒關係,它的用法是在ViewHolder裡寫點擊事件,其實寫起來很簡單而且彈性,可以為一列中的每個元件個別設置
class ViewHolder extends RecyclerView.ViewHolder{
// 宣告元件
private TextView txtItem;
private Button btnRemove;
ViewHolder(View itemView) {
super(itemView);
txtItem = (TextView) itemView.findViewById(R.id.txtItem);
btnRemove = (Button) itemView.findViewById(R.id.btnRemove);
// 點擊項目時
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(view.getContext(),
"click " +getAdapterPosition(),Toast.LENGTH_SHORT).show();
}
});
// 點擊項目中的Button時
btnRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 按下Button要做的事
}
});
}
}
itemView表示整個項目,我們令點擊時出現Toast。這邊也可以為元件個別設置點擊事件,當按下btnRemove時要做的事可以跟按下itemView不同。
起初RecyclerView沒有內建點擊Listener令我相當錯愕,覺得這元件什麼都沒有,一貧如洗。不過瞭解以後其實這樣超方便的對吧!比用ListView和onItemSelectListener彈性很多,很容易擴充元件且程式碼也很簡單。
沒錯,RecyclerView也沒有內建的點擊視覺回饋,目前點下去看起來完全沒反應,要有視覺回饋的話只要加一行程式到list_item裡就可以了。
android:background="?android:attr/selectableItemBackground"
執行結果
RecyclerView在增減項目時有內建動畫效果,個人覺得這點RecyclerView大優於ListView,雖然也不是什麼驚天地泣鬼神的動畫,但我是設計白癡,覺得內建這樣的效果已經很好了。
先在activity_main加上一個按鈕用來新增項目
<Button
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="新增"/>
新增和刪除項目的程式則寫在MyAdapter中
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
// 未更動的程式省略..
// 新增項目
public void addItem(String text) {
// 為了示範效果,固定新增在位置3。若要新增在最前面就把3改成0
mData.add(3,text);
notifyItemInserted(3);
}
// 刪除項目
public void removeItem(int position){
mData.remove(position);
notifyItemRemoved(position);
}
}
將ViewHolder中的btnRemove加上刪除功能
btnRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 移除項目,getAdapterPosition為點擊的項目位置
removeItem(getAdapterPosition());
}
});
MainActivity加上新增按鈕的部分
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 新增一個項目
adapter.addItem("New Item");
}
});
執行結果
RecyclerView可以用LayoutManager來改變樣式,除了一般的List樣式,也可以用成Grid的方格,只要一行程式就可以了哦!
我們可以從這兩種選用一種樣式
// Linear型態,第二個參數控制垂直或水平,第三個參數為是否reverse順序
recycler_view.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
// Grid型態,第二個參數控制一列顯示幾項
recycler_view.setLayoutManager(new GridLayoutManager(this, 2));
(其實還有第三種StaggeredGridLayout(瀑布型),但在此例看不出效果,教材準備失敗真是抱歉)
格線也可以選用要垂直的或水平的
// 第二個參數VERTICAL或HORIZONTAL控制垂直或水平
recycler_view.addItemDecoration(
new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
而且不管你用哪種樣式,增減項目的動畫都能運作哦!用Grid樣式的效果如下:
總結來說,RecyclerView一開始建立需要多一點點步驟,但往後的延展性非常強,可以輕易的自訂項目佈局和整體樣式,而且效能更好,Reddit討論串也一面倒的建議入門的夥伴使用RecyclerView作為列表元件。
希望這兩天的文章能幫助大家順利的使用它,明天再補充關於點擊事件進一步的用法。
點擊事件請優先考慮interface而非明天的EventBus,因為EventBus在專案擴大時不好維護。建立interface方式:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
// 1. 建立interface,命名為OnItemClickHandler,並在裡面寫好我們要發生的事件
interface OnItemClickHandler {
// 提供onItemClick方法作為點擊事件,括號內為接受的參數
void onItemClick(String text);
// 提供onItemRemove做為移除項目的事件
void onItemRemove(int position, String text);
}
private List<String> mData;
// 2. 宣告interface
private OnItemClickHandler mClickHandler;
// 3. 修改Constructor
MyAdapter(List<String> data, OnItemClickHandler clickHandler) {
mData = data;
mClickHandler = clickHandler;
}
class ViewHolder extends RecyclerView.ViewHolder{
...
ViewHolder(final View itemView) {
super(itemView);
...
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String msg = mData.get(getAdapterPosition());
// 4. 呼叫interface的method
mClickHandler.onItemClick(msg);
}
});
btnRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = getAdapterPosition();
String text = mData.get(position);
// 4. 呼叫interface的method
mClickHandler.onItemRemove(position, text);
}
});
}
}
...
}
修改MainActivity,接收MyAdapter的OnItemClickHandler
// 1. implements要接收的interface
public class MainActivity extends AppCompatActivity implements MyAdapter.OnItemClickHandler {
...
private MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
// 2. 修改Adapter建立方式,this指此Activity,因為我們已經implement所以此Activity本身就是一個OnItemClickHandler
adapter = new MyAdapter(mData, this);
recycler_view.setAdapter(adapter);
...
}
// 3. 點擊事件
@Override
public void onItemClick(String text) {
// text即為點擊的內容,可在此顯示Toast或其他處理
}
// 3. 移除事件
@Override
public void onItemRemove(int position, String text) {
// do something...
}
}
在要接收點擊事件的Activity/Fragment中implement OnItemClickHandler,之後Android Studio會有紅燈泡提醒你要建立method,即OnItemClickHandler中宣告的那兩個,就可以在method中寫點擊後要做的事了。