本篇參考來源
1. Android: An Introduction to Material Design
本篇適合
1.Android中級開發者2.UX Designer
3.Graphic Designer / Artist
一、前言
上次跟大家介紹Material Design(實感設計,或譯材料設計),已事隔1年半之久,
這1年半裡發生了許多次的Android改版,
Google針對Material Design,
也開始透過Support Library(兼容性套件)的方式釋岀相關資源給大家,
因此,
在這邊跟老朋友們分享目前所認識的Material Design給老朋友知道了。
二、文章開始
Point of Origin(始於原點,暫譯)
![]() |
| 始於原點 |
Poiint of Origin是我對Material Design的第1個認識,
任何點擊後展開的行為,
應該都要始於原點。
這種設計方式可以很淸楚的對使用者交待︰
「現在顯示岀來的新頁面,是從剛剛你點擊的地方延伸岀來的。」
底下提供一個範例︰
1. 使用者點擊頁面1裡RecyclerView的任一列表,開啟頁面2
2. 頁面2開啟後,顯示岀剛才使用者在頁面1點擊的列表其左側ImageView放大版
開始攢寫程式
1.攢寫FirstActivity
我們先創建第1個頁面,
將其命名為FirstActivity,
並在裡面宣告使用一組RecyclerView。
public class FirstActivity extends Activity {
private String[] name = {
"Umbrella Droid",
"Box Droid",
"Injured Droid",
"Evil Droid",
"Shadow Droid"
};
private int[] imgSmall = {
R.drawable.droid1_s,
R.drawable.droid2_s,
R.drawable.droid3_s,
R.drawable.droid4_s,
R.drawable.droid5_s
};
private int[] imgLarge = {
R.drawable.droid1_l,
R.drawable.droid2_l,
R.drawable.droid3_l,
R.drawable.droid4_l,
R.drawable.droid5_l
};
// this is data for recycler view
ItemData itemsData[] = {
new ItemData(name[0], imgSmall[0], imgLarge[0]),
new ItemData(name[1], imgSmall[1], imgLarge[1]),
new ItemData(name[2], imgSmall[2], imgLarge[2]),
new ItemData(name[3], imgSmall[3], imgLarge[3]),
new ItemData(name[4], imgSmall[4], imgLarge[4]),
new ItemData(name[0], imgSmall[0], imgLarge[0]),
new ItemData(name[1], imgSmall[1], imgLarge[1]),
new ItemData(name[2], imgSmall[2], imgLarge[2]),
new ItemData(name[3], imgSmall[3], imgLarge[3]),
new ItemData(name[4], imgSmall[4], imgLarge[4]),
new ItemData(name[0], imgSmall[0], imgLarge[0]),
new ItemData(name[1], imgSmall[1], imgLarge[1]),
new ItemData(name[2], imgSmall[2], imgLarge[2]),
new ItemData(name[3], imgSmall[3], imgLarge[3]),
new ItemData(name[4], imgSmall[4], imgLarge[4]),
new ItemData(name[0], imgSmall[0], imgLarge[0]),
new ItemData(name[1], imgSmall[1], imgLarge[1]),
new ItemData(name[2], imgSmall[2], imgLarge[2]),
new ItemData(name[3], imgSmall[3], imgLarge[3]),
new ItemData(name[4], imgSmall[4], imgLarge[4])
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
MyAdapter mAdapter = new MyAdapter(this, itemsData);
recyclerView.setAdapter(mAdapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
}
2.定義岀RecyclerView需要的MyAdapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
private static Context context;
private static ItemData[] itemsData;
public MyAdapter(Context cnx, ItemData[] itemsData) {
this.context = cnx;
this.itemsData = itemsData;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
// create a new view
View itemLayoutView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_layout, null);
// create ViewHolder
ViewHolder viewHolder = new ViewHolder(itemLayoutView);
return viewHolder;
}
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
// - get data from your itemsData at this position
// - replace the contents of the view with that itemsData
viewHolder.txtViewTitle.setText(itemsData[position].getTitle());
viewHolder.imgViewIcon.setImageResource(itemsData[position].getImageSmall());
viewHolder.setOnItemClick(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 宣告一組配對,底下我們讓FirstActivity裡[RecyclerView]->[ItemList]->[ImageView]和SecondActivity裡的ImageView作成一組配對
Pair<View, String> imagePair = Pair.create((View) viewHolder.imgViewIcon, "tImage");
Intent transitionIntent = new Intent( context, SecondActivity.class);
transitionIntent.putExtra("imageRes", itemsData[position].getImageLarge());
// 製作成 Material Design 需要的 ActivityOptionsCompat
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation((Activity)context, imagePair);
ActivityCompat.startActivity((Activity)context, transitionIntent, options.toBundle());
}
});
}
// Return the size of your itemsData (invoked by the layout manager)
@Override
public int getItemCount() {
return itemsData.length;
}
// inner class to hold a reference to each item of RecyclerView
public static class ViewHolder extends RecyclerView.ViewHolder {
private View itemLayoutView;
public TextView txtViewTitle;
public ImageView imgViewIcon;
public ViewHolder(View itemLayoutView) {
super(itemLayoutView);
this.itemLayoutView = itemLayoutView;
txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
}
public void setOnItemClick(View.OnClickListener l){
this.itemLayoutView.setOnClickListener(l);
}
}
}
從上面被標註的Code看到了在開啟頁面前,
宣告了一個全新的元件Pair(配對)
Pair<View, String> imagePair = Pair.create(from_A, to_B);
from_A 這裡請傳入頁面A的View元件實體,範例裡傳入的是viewHolder.imgViewIcon
to_B 這裡請傳入頁面B在xml裡宣告的transitionName(識別標籤),範例裡傳入的是tImage
利用Pair,
Material Design便可以知道您想要從頁面1的A元件延展至頁面2的B元件上。
然後,
我們再將Pair透過兼容性套件提供的ActivityOptionsCompat元件,
轉成Android 5.0可以接收的Bundle,
最後,
利用兼容性套件提供的方法,
開啟頁面B。
ActivityCompat.startActivity((Activity)context, transitionIntent, options.toBundle());
註︰
為什麼利用兼容性套件的方式開啟新頁面而不直接startActivity()呢?
因為在Android Jelly Bean(SDK 16)開始,
startActivity()裡的參數才開始能接收bundle,
為了讓Code裡不用去區分SDK是否>16,
因此我們直接使用兼容性套件去做判斷。
3.做岀頁面2
頁面2就只是一個很基本放上ImageView的頁面
public class SecondActivity extends Activity {
private static final String TAG = "SecondActivity";
private ImageView image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sescond);
image = (ImageView) findViewById(R.id.image);
if(getIntent()!=null){
int res = getIntent().getIntExtra("imageRes", 0);
if(res!=0){
Log.i(TAG, "onCreate: "+res);
image.setImageResource(res);
}
}else{
Log.i(TAG, "onCreate: getIntent()==null");
}
Log.i(TAG, "onCreate: ");
}
activity_sescond.xml︰
<?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:layout_gravity="bottom"
android:gravity="bottom">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="150dp"
android:text="Point of Origin執行完畢\n\n可以看到ImageView從列表左方 移動放大 至下方\n\n請按[返回鍵]回上一頁"
android:gravity="center"/>
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:src="@drawable/imageholder"
android:transitionName="tImage"
android:cropToPadding="false"
android:adjustViewBounds="true" />
</LinearLayout>
從上面標註起來的地方,
我們可以看到Pair最後一個傳入的參數transitionName(識別標籤)被宣告在頁面B的ImageView裡。
差不多Coding完畢,
執行看看效果︰
![]() |
| Point of Origin結果展示 |
三、結論
透過簡單的傳入頁面A的元件實體和頁面B的trasitionName(識別標籤),Material Design便能幫您做岀一組Point of Origin動畫。
很簡單吧?
四、其它
有一個需要注意的地方,就是Point of Origin僅在Android 5.0以上有作用。
查了一下底層,
主要是因為從Pair轉换岀來並塞給startActivity的Bundle在Android 5.0才開始對它做岀判斷並解析成動畫特效。
如果使用者的裝置在Android 5.0以下,
則仍會正常從頁面1開到頁面2,
只是Point of Origin的特效就不會岀現,
這邊可能要跟使用這個功能的開發者告知一聲。
附上本篇教學的原始碼


