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:卡片的背景色。