Android Material Design 詳解(使用support v7兼容5.0以下系統)
Material Design是Google在2014年的I/O大會上推出的全新設計語言。Material Design是基于Android 5.0(API level 21)的,兼容5.0以下的設備時需要使用版本號v21.0.0以上的support v7包中的appcpmpat,不過遺憾的是support包只支持Material Design的部分特性。使用eclipse或Android Studio進行開發時,直接在Android SDK Manager中將Extras->Android Support Library升級至最新版即可。目前最新版本為:
com.android.support:appcompat-v7:21.0.3
本文中示例程序使用minSdkVersion=14,即屬于使用support包實現Material Design風格。
使用Material Design的步驟:
一、使用Material主題
1.創建一個Android應用,應用主題Theme.AppCompat(或其子主題,如Theme.AppCompat.Light.DarkActionBar)
2.自定義程序所使用的主題的某些屬性,示例:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!--ActionBar的顏色-->
<item name="colorPrimary">@color/primary</item>
<!-- 隨主題而改變的顏色(如CheckBox的顏色)-->
<item name="colorAccent">@color/accent</item>
<!--狀態欄的顏色 (使用support包時似乎無效。)-->
<item name="colorPrimaryDark">@color/primary_dark</item>
<!--ActionBar的樣式-->
<item name="actionBarStyle">@style/AppTheme.ActionBarStyle</item>
</style>
<style name="AppTheme.ActionBarStyle" parent="Widget.AppCompat.ActionBar.Solid">
<item name="android:titleTextStyle">@style/AppTheme.ActionBar.TitleTextStyle</item>
</style>
<style name="AppTheme.ActionBar.TitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title">
<!--ActionBar標題文字顏色-->
<item name="android:textColor">@android:color/white</item>
</style> 3.所有需要使用ActionBar的Activity必須繼承自ActionBarActivity,因為即使使用了類似Theme.AppCompat.Light.DarkActionBar這樣的主題,系統也不會自動添加ActionBar.
效果圖:
相對于普通的ActionBar的變化:
(1)右側三個小點的樣式變了。(這個無所謂。。。)
(2)點擊右側三個小點(更多)時,下拉菜單不是從ActionBar的下面開始展開,而是直接從ActionBar之上開始!也許的確有辦法把它改成舊的樣式,不過查閱官方文檔之后發現,Google對此的解釋是:菜單是一個臨時展現給用戶的組件,因此應該懸浮在上面。也就是說,新的設計規則推薦的就是這種默認的樣式。
二、使用RecyclerView
RecyclerView是Google在support v7包中提供的一個全新的組件。該組件是一個增強版的ListView,新特性:
1.提高了性能;
2.adapter中自動進行item復用,也就是說,以前的這種繁瑣的寫法不需要了:
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.friends_item, parent, false);
holder = new ViewHolder();
holder.nameTV = (TextView) convertView.findViewById(R.id.friends_item_name);
holder.phoneTV = (TextView) convertView.findViewById(R.id.friends_item_phone);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
} 3.預置了item的添加,刪除,移動,修改時的動畫,當且改動畫也可以自定義。
效果圖:
(1)主頁面,獲取到RecyclerView,設置adapter即可。
RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//data
List<CityInfoBean> myDataset = new ArrayList<CityInfoBean>();
for (int i = 0; i < 50; i++) {
CityInfoBean city = new CityInfoBean();
city.setCityName("Tianjin-" + i);
city.setCityPhone("022-" + i);
city.setLocation("Asia_" + i);
myDataset.add(city);
}
RecyclerViewAdapter mAdapter = new RecyclerViewAdapter(this, myDataset);
mRecyclerView.setAdapter(mAdapter);
//RecyclerView doesn't has a 'OnItemClickListener' or 'OnItemLongClickListener' like ListView,
// so you should add the callback in adapter (2)adapter,RecyclerViewAdapter.java:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private Context context;
private List<CityInfoBean> mDataset;
public RecyclerViewAdapter(Context context, List<CityInfoBean> myDataset) {
this.context = context;
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_list_item, parent, false);
// set the view's size, margins, paddings and layout parameters
final ViewHolder vh = new ViewHolder(v);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getPosition();
Toast.makeText(v.getContext(), "Item click. Position:" +
position, Toast.LENGTH_SHORT).show();
}
});
v.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int position = vh.getPosition();
// Toast.makeText(v.getContext(), "Item long click. Position:" +
// position, Toast.LENGTH_SHORT).show();
showDialog(position);
return true;
}
});
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.cityNameTV.setText(mDataset.get(position).getCityName());
holder.phoneTV.setText(mDataset.get(position).getCityPhone());
holder.addrTV.setText(mDataset.get(position).getLocation());
}
@Override
public int getItemCount() {
return mDataset.size();
}
private void showDialog(final int position) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Choose operation");
String[] dialogItems = new String[]{
context.getString(R.string.delete_one_item),
context.getString(R.string.add_one_item),
context.getString(R.string.move_one_item),
context.getString(R.string.change_one_item),
context.getString(R.string.add_many_items),
};
builder.setItems(dialogItems, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
//delete this item
mDataset.remove(position);
notifyItemRemoved(position);
break;
case 1:
//add one item
mDataset.add(position, new CityInfoBean("New City", "010", "Asia"));
notifyItemInserted(position);
break;
case 2:
//TODO remember to change the data set...
//move one item to another position
notifyItemMoved(position, position + 2);
//May cause IndexOutOfBoundsException. This is just a demo!
break;
case 3:
//change one item
mDataset.get(position).setCityName("City name changed");
notifyItemChanged(position);
break;
case 4:
//add many items
List<CityInfoBean> insertList = new ArrayList<CityInfoBean>();
insertList.add(new CityInfoBean("New City 01", "010", "Asia"));
insertList.add(new CityInfoBean("New City 02", "020", "America"));
mDataset.addAll(position, insertList);
notifyItemRangeInserted(position, insertList.size());
break;
default:
break;
}
}
});
builder.create().show();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView cityNameTV, phoneTV, addrTV;
public ViewHolder(View v) {
super(v);
cityNameTV = (TextView) v.findViewById(R.id.city_name);
phoneTV = (TextView) v.findViewById(R.id.city_phone);
addrTV = (TextView) v.findViewById(R.id.city_addr);
}
}
} (3)主頁面布局文件:
recycler_layout.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.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout> 二、使用CardView
CardView是Google在support v7包中提供了另一個全新組件,可以很方便的實現“卡片式布局”(具有投影/圓角 的立體效果)。CardView繼承自FrameLayout,因此如果內部需要互不重疊的放置多個組件時,可能需要再嵌套一個LinearLayout 或RelativeLayout等。
效果圖:
布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:card_view="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.CardView
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="6dp"
card_view:cardCornerRadius="4dp"
card_view:cardBackgroundColor="@color/card_bg"
card_view:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="6dp"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/info_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="18sp"
android:text="@string/example_text" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout> 屬性解釋:
cardCornerRadius:圓角大小;
cardElevation:投影的深度;
cardBackgroundColor:卡片的背景色。