本篇參考來源
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的特效就不會岀現,
這邊可能要跟使用這個功能的開發者告知一聲。
附上本篇教學的原始碼