Android RecyclerView詳解
介紹
RecyclerView用於在有限的視窗展現大量的資料,其實早已經有了類似的控制元件,如ListView、GridView,那麼相比它們,RecyclerView有什麼樣優勢呢?
RecyclerView標準化了ViewHolder,而且異常的靈活,可以輕鬆實現ListView實現不了的樣式和功能,通過佈局管理器LayoutManager可控制Item的佈局方式,通過設定Item操作動畫自定義Item新增和刪除的動畫,通過設定Item之間的間隔樣式,自定義間隔。
可實現效果
設定佈局管理器以控制Item的佈局方式,橫向、豎向以及瀑布流方式。
可設定Item操作的動畫(刪除或者新增等)
可設定Item的間隔樣式(可繪製)
關於Item的點選和長按事件,需要使用者自己去實現
使用
- 使用RecyclerView時候,必須指定一個介面卡Adapter和一個佈局管理器LayoutManager。
- 介面卡繼承RecyclerView.Adapter類,具體實現類似ListView的介面卡,取決於資料資訊以及展示的UI。
- 佈局管理器用於確定RecyclerView中Item的展示方式以及決定何時複用已經不可見的Item,避免重複建立以及執行高成本的findViewById()方法
用法
示例
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
LinearLayoutManager mLayoutManager=new LinearLayoutManager(this);
// 設定佈局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 設定adapter
mRecyclerView.setAdapter(mAdapter);
// 設定Item新增和移除的動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// 設定Item之間間隔樣式
mRecyclerView.addItemDecoration(mDividerItemDecoration);
基本使用
首先需要在在 build.gradle 檔案中引入 RecyclerView 類
compile 'com.android.support:recyclerview-v7:23.4.0'
Fragment程式碼
package com.demo.fragment;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.demo.R;
import com.demo.adapter.VideoRecyclerViewAdapter;
import com.demo.bean.VideoBean;
import java.util.ArrayList;
import java.util.List;
public class ListViewFragment extends Fragment{
public static ListViewFragment newInstance() {
return new ListViewFragment();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_recycler_view, container, false);
initView(view);
return view;
}
private void initView(View view) {
RecyclerView recyclerView = view.findViewById(R.id.rv);
LinearLayoutManager layoutManager=new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);// 設定佈局管理器
DefaultItemAnimator itemAnimator = new DefaultItemAnimator();
recyclerView .setItemAnimator(itemAnimator);// 設定Item新增和移除的動畫
itemAnimator.setSupportsChangeAnimations(false);
recyclerView.setAdapter(new VideoRecyclerViewAdapter(getVideoList(), getActivity()));
}
public List<VideoBean> getVideoList() {
List<VideoBean> videoList = new ArrayList<>();
//...新增資料
return videoList;
}
}
R.layout.activity_recycler_view
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
RecyclerView介面卡Adapter程式碼
package com.demo.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.demo.R;
import com.demo.bean.VideoBean;
import com.demo.view.CustomVideoView ;
import java.util.List;
public class VideoRecyclerViewAdapter extends RecyclerView.Adapter<VideoRecyclerViewAdapter.VideoHolder> {
private List<VideoBean> videos;
private Context context;
public VideoRecyclerViewAdapter(List<VideoBean> videos, Context context) {
this.videos = videos;
this.context = context;
}
@Override
public VideoHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(R.layout.item_video_auto_play, parent, false);
return new VideoHolder(itemView);
}
@Override
public void onBindViewHolder(final VideoHolder holder, int position) {
VideoBean videoBean = videos.get(position);
holder.title.setText(videoBean.getTitle());
holder.videoView .setPlayUrl(videoBean.getUrl());
}
@Override
public int getItemCount() {
return videos.size();
}
public class VideoHolder extends RecyclerView.ViewHolder {
private CustomVideoView videoView;
private TextView title;
VideoHolder(View itemView) {
super(itemView);
videoView= (CustomVideoView )itemView.findViewById(R.id.video_player);
int widthPixels = context.getResources().getDisplayMetrics().widthPixels;
videoView .setLayoutParams(new LinearLayout.LayoutParams(widthPixels, widthPixels / 16 * 9));
title = itemView.findViewById(R.id.tv_title);
}
}
}
佈局管理器:RecyclerView.LayoutManager
上述程式碼中mLayoutManager 物件是佈局管理器,RecyclerView提供了三種佈局管理器:
- LinerLayoutManager(線性):以垂直或者水平列表方式展示Item
- GridLayoutManager (網格):以網格方式展示Item
- StaggeredGridLayoutManager(瀑布流): 以瀑布流方式展示Item
介面卡:RecyclerView.Adapter
RecyclerView.Adapter的使用方式和ListView的ListAdapter 類似,向RecyclerView提供顯示的資料。
但是RecyclerView.Adapter做了一件了不起的優化,那就是RecyclerView.Adapter的
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
方法能夠保證當前RecyclerView是確實需要你建立一個新的ViewHolder物件。而ListView的ListAdapter 對應的方法
@Override
public View getView(int i, View view, ViewGroup viewGroup)
需要在方法內部判斷是重新建立一個View還是重新整理一個View的資料,而不明所以的客戶可能每次都會返回一個新建立的View造成ListView的卡頓和資源的浪費;建立和重新整理作為兩個不同的功能本來就應該在兩個方法中實現—慶幸的是RecyclerView.Adapter解決了這個問題
檢視容器:RecyclerView.ViewHolder
RecyclerView中強制客戶使用ViewHolder,談及ListView的時候就經常說到使用ViewHolder來進行優化。使用ViewHolder其中一點好處是能夠避免重複呼叫方法findViewById(),對當前item的View中的子View進行管理。
ViewHolder 描述RecylerView中某個位置的itemView和後設資料資訊,屬於Adapter的一部分。其實該類通常用於儲存 findViewById 的結果
ViewHolder的mFlags屬性
- FLAG_BOUND ——ViewHolder已經繫結到某個位置,mPosition、mItemId、mItemViewType都有效
- FLAG_UPDATE ——ViewHolder繫結的View對應的資料過時需要重新繫結,mPosition、mItemId還是一致的
- FLAG_INVALID ——ViewHolder繫結的View對應的資料無效,需要完全重新繫結不同的資料
- FLAG_REMOVED ——ViewHolder對應的資料已經從資料集移除
- FLAG_NOT_RECYCLABLE ——ViewHolder不能複用
- FLAG_RETURNED_FROM_SCRAP ——這個狀態的ViewHolder會加到scrap list被複用。
- FLAG_CHANGED ——ViewHolder內容發生變化,通常用於表明有ItemAnimator動畫
- FLAG_IGNORE ——ViewHolder完全由LayoutManager管理,不能複用
- FLAG_TMP_DETACHED ——ViewHolder從父RecyclerView臨時分離的標誌,便於後續移除或新增回來
- FLAG_ADAPTER_POSITION_UNKNOWN ——ViewHolder不知道對應的Adapter的位置,直到繫結到一個新位置
- FLAG_ADAPTER_FULLUPDATE ——方法 addChangePayload(null) 呼叫時設定
間隔樣式:RecyclerView.ItemDecoration
- 用於繪製itemView之間的一些特殊UI,比如在itemView之前設定空白區域、畫線等。
- RecyclerView 將itemView和裝飾UI分隔開來,裝飾UI即 ItemDecoration ,主要用於繪製item間的分割線、高亮或者margin等
- 通過recyclerView.addItemDecoration(new DividerDecoration(this))對item新增裝飾;對RecyclerView設定多個ItemDecoration,列表展示的時候會遍歷所有的ItemDecoration並呼叫裡面的繪製方法,對Item進行裝飾。
- public void onDraw(Canvas c, RecyclerView parent) 裝飾的繪製在Item條目繪製之前呼叫,所以這有可能被Item的內容所遮擋
- public void onDrawOver(Canvas c, RecyclerView parent) 裝飾的繪製在Item條目繪製之後呼叫,因此裝飾將浮於Item之上
- public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 與padding或margin類似,LayoutManager在測量階段會呼叫該方法,計算出每一個Item的正確尺寸並設定偏移量
展示效果和ListView基本上無差別,但是Item之間並沒有分割線,在xml去找divider屬性的時候,發現RecyclerView沒有divider屬性,當然也可以在Item佈局中加上分割線,但是這樣做並不是很優雅。
其實RecyclerView是支援自定義間隔樣式的。通過
mRecyclerView.addItemDecoration()
來設定我們定義好的間隔樣式
自定義間隔樣式需要繼承RecyclerView.ItemDecoration類,該類是個抽象類,主要有三個方法
- onDraw(Canvas c, RecyclerView parent, State state):在Item繪製之前被呼叫,該方法主要用於繪製間隔樣式
- onDrawOver(Canvas c, RecyclerView parent, State state):在Item繪製之前被呼叫,該方法主要用於繪製間隔樣式
- getItemOffsets(Rect outRect, View view, RecyclerView parent, State state):設定item的偏移量,偏移的部分用於填充間隔樣式,在RecyclerView的onMesure()中會呼叫該方法
onDraw()和onDrawOver()這兩個方法都是用於繪製間隔樣式,我們只需要複寫其中一個方法即可。直接來看一下自定義的間隔樣式的實現,參考官方例項
public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
/**
* 用於繪製間隔樣式
*/
private Drawable mDivider;
/**
* 列表的方向,水平/豎直
*/
private int mOrientation;
public MyDividerItemDecoration(Context context, int orientation) {
// 獲取預設主題的屬性
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// 繪製間隔
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
private void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
/**
* 繪製間隔
*/
private void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin +
Math.round(ViewCompat.getTranslationY(child));
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
/**
* 繪製間隔
*/
private void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin +
Math.round(ViewCompat.getTranslationX(child));
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
然後在程式碼中設定RecyclerView的間隔樣式
mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));
動畫:RecyclerView.ItemAnimator
RecyclerView可以設定列表中Item刪除和新增的動畫,在v7包中給我們提供了一種預設的Item刪除和新增的動畫,如果沒有特殊的需求,預設使用這個動畫即可
// 設定Item新增和移除的動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
設定的動畫用於在 item 項資料變化時的動畫效果
當呼叫 Adapter 的 notifyItemChanged、notifyItemInserted、notifyItemMoved 等方法,會觸發該物件顯示相應的動畫。
RecyclerView 的 ItemAnimator 使得 item 的動畫實現變得簡單而樣式豐富,我們可以自定義 item 項不同操作(如新增,刪除)的動畫效果;
ItemAnimator 觸發於以下三種事件:
- 某條資料被插入到資料集合中 ,對應 public final void notifyItemInserted(int position) 方法
- 從資料集合中移除某條資料 ,對應 public final void notifyItemRemoved(int position) 方法
- 更改資料集合中的某條資料,對應 public final void notifyItemChanged(int position) 方法
注意:notifyDataSetChanged(),會觸發列表的重繪,並不會出現任何動畫效果
使用:Animator使用到的邏輯比較多,因此最方便的就是使用第三方庫:https://github.com/wasabeef/recyclerview-animators
點選事件
RecyclerView並沒有像ListView一樣暴露出Item點選事件或者長按事件處理的api,也就是說使用RecyclerView時候,需要我們自己來實現Item的點選和長按等事件的處理。
實現方法有很多
- 可以監聽RecyclerView的Touch事件然後判斷手勢做相應的處理
- 可以通過在繫結ViewHolder的時候設定監聽,然後通過Apater回撥出去
- 使用點選、長按事件支援類
第二種方法:在繫結ViewHolder的時候設定監聽,通過Apater回撥出去 Adapter 的完整程式碼
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter .ViewHolder>{
/**
* 展示資料
*/
private ArrayList<String> mData;
/**
* 事件回撥監聽
*/
private RecyclerViewAdapter.OnItemClickListener onItemClickListener;
public RecyclerViewAdapter(ArrayList<String> data) {
this.mData = data;
}
public void updateData(ArrayList<String> data) {
this.mData = data;
notifyDataSetChanged();
}
/**
* 新增新的Item
*/
public void addNewItem() {
if(mData == null) {
mData = new ArrayList<>();
}
mData.add(0, "new Item");
notifyItemInserted(0);
}
/**
* 刪除Item
*/
public void deleteItem() {
if(mData == null || mData.isEmpty()) {
return;
}
mData.remove(0);
notifyItemRemoved(0);
}
/**
* 設定回撥監聽
*
* @param listener
*/
public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 例項化展示的view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);
// 例項化viewholder
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
// 繫結資料
holder.mTv.setText(mData.get(position));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemClick(holder.itemView, pos);
}
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemLongClick(holder.itemView, pos);
}
//表示此事件已經消費,不會觸發單擊事件
return true;
}
});
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public ViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(R.id.item_tv);
}
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
}
Activity 設定 Adapter 事件監聽
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MyActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MyActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
}
});
第三種方法:使用點選、長按事件支援類程式碼
參考 Hugo 的文章:Getting your clicks on RecyclerView
- 先要準備一份resources
res -> values -> ids.xml ->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>
- 具體的支援類
public class ItemClickSupport {
private final RecyclerView mRecyclerView;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override public void onClick(View v) {
if (mOnItemClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
}
};
private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
@Override public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
return false;
}
};
private RecyclerView.OnChildAttachStateChangeListener mAttachListener = new RecyclerView.OnChildAttachStateChangeListener() {
@Override public void onChildViewAttachedToWindow(View view) {
if (mOnItemClickListener != null) {
view.setOnClickListener(mOnClickListener);
}
if (mOnItemLongClickListener != null) {
view.setOnLongClickListener(mOnLongClickListener);
}
}
@Override public void onChildViewDetachedFromWindow(View view) {
}
};
private ItemClickSupport(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
mRecyclerView.setTag(R.id.item_click_support, this);
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
}
public static ItemClickSupport addTo(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support == null) {
support = new ItemClickSupport(view);
}
return support;
}
public static ItemClickSupport removeFrom(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support != null) {
support.detach(view);
}
return support;
}
public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
return this;
}
public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
return this;
}
private void detach(RecyclerView view) {
view.removeOnChildAttachStateChangeListener(mAttachListener);
view.setTag(R.id.item_click_support, null);
}
// 點選介面
public interface OnItemClickListener {
void onItemClicked(RecyclerView recyclerView, int position, View v);
}
// 長按介面
public interface OnItemLongClickListener {
boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
}
}
- 在setAdapter()之後呼叫
// 點選
ItemClickSupport.addTo(rv).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override public void onItemClicked(RecyclerView recyclerView, int position, View v) {
Toast.makeText(MainActivity.this, mDatas.get(position), Toast.LENGTH_SHORT).show();
}
});
// 長按
ItemClickSupport.addTo(rv).setOnItemLongClickListener(new ItemClickSupport.OnItemLongClickListener() {
@Override public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
Toast.makeText(MainActivity.this, "長按" + mDatas.get(position) + "已刪除", Toast.LENGTH_SHORT).show();
// 需要自己處理position在集合中的位置(需考慮頭、身、腳佈局數量)
mDatas.remove(position);
if (lastVisible + 1 == mAdapter.getItemCount()) {
addmore();
}
mAdapter.notifyItemRemoved(position);
// 消耗事件
return true;
}
});
總結
目前而言,我們已經知道RecyclerView的一些功能如下
- 水平列表展示,設定LayoutManager的方向性
- 豎直列表展示,設定LayoutManager的方向性
- 自定義間隔,RecyclerView.addItemDecoration()
- Item新增和刪除動畫,RecyclerView.setItemAnimator()
所以在專案中如果再遇見列表類的佈局,就可以優先考慮使用 RecyclerView,更靈活更快捷的使用方式會給編碼帶來不一樣的體驗
相關文章
- Android開發 - RecyclerView 類詳解AndroidView
- 詳解RecyclerView的預佈局View
- Android中的RecyclerViewAndroidView
- Android RecyclerView的使用AndroidView
- 教你玩轉 Android RecyclerView:深入解析 RecyclerView ItemDecoration類AndroidView
- Android RecyclerView多型別佈局卡片解決方案AndroidView多型型別
- Android AsyncTask 詳解Android
- Android:動畫詳解Android動畫
- Android拖拽詳解Android
- Android:Service詳解Android
- Android Notification 詳解Android
- Android WebView 詳解AndroidWebView
- Android – Drawable 詳解Android
- Android Proguard 詳解Android
- android service詳解Android
- Android TV開發——RecyclerView For TVAndroidView
- 【Android進階】RecyclerView之ItemDecoration(一)AndroidView
- Android RecyclerView中Adapter和ViewHoAndroidViewAPT
- Android RecyclerView的簡便寫法AndroidView
- Android RecyclerView 簡介與例項AndroidView
- Android SecureRandom漏洞詳解Androidrandom
- Android Service詳解(二)Android
- Android Service詳解(一)Android
- Android 向量圖詳解Android
- Android元件詳解—TextViewAndroid元件TextView
- Android-Service詳解Android
- Android Gson使用詳解Android
- Android Paint 使用詳解AndroidAI
- Android HttpURLConnection詳解AndroidHTTP
- Android AsyncTask使用詳解Android
- Android webview使用詳解AndroidWebView
- Android混淆(Proguard)詳解Android
- Android Handler原理詳解Android
- Android ViewPager使用詳解AndroidViewpager
- Cordova android框架詳解Android框架
- Android Notification 通知詳解Android
- android StartActivityForResult()方法詳解Android
- Android呼叫WebService詳解AndroidWeb