本文講主要來說說Toolbar碉钠、RecyclerView、CardView、DrawerLayout弓坞、以及SwiperefreshLayout的綜合使用,其中Toolbar和RecyclerView在前幾篇博客已經(jīng)詳細(xì)講述其用法了吨岭,有興趣的可以去看看±冢現(xiàn)在利用這幾個控件實現(xiàn)Material Design風(fēng)格的知乎主頁(Android v3.3版 知乎),在講到相關(guān)控件的時候辣辫,我也會提及一下這個控件的用法旦事。那么,讓我們開始控件之旅吧急灭。
示例效果
在動手寫代碼之前姐浮,我們先要看看最終的實現(xiàn)效果是什么:
首先,頂部導(dǎo)航欄是Toolbar葬馋,下面是RecyclerView卖鲤,而RecyclerView內(nèi)部的item view則是一個個CardView。
接著畴嘶,向右滑動蛋逾,會出現(xiàn)一個側(cè)邊菜單欄,這個是用DrawerLayout實現(xiàn)的窗悯,下面會詳細(xì)說明区匣。
最后,還實現(xiàn)了下拉刷新功能蒋院,這個是用SwiperefreshLayout實現(xiàn)的亏钩。
以上效果大致和手機(jī)上的知乎效果一樣(v3.3版),但最新的知乎已經(jīng)更新到了4.0欺旧,改動很多姑丑,就不是以上的效果了。但無礙我們接下來的學(xué)習(xí)辞友。
動手實踐
part 1.側(cè)滑菜單的布局:DrawerLayout
根據(jù)以上的效果彻坛,我們首先要創(chuàng)建一個DrawerLayout布局,它是一個ViewGroup踏枣,因此能放下其他的View昌屉,顯然左邊滑出來的部分放我們的側(cè)滑菜單選項,而右邊則是我們的主頁部分茵瀑,新建activity_main.xml文件:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- main content -->
<include layout="@layout/content_main"></include>
<!-- The navigation drawer-->
<include layout="@layout/drawermenu"></include>
</android.support.v4.widget.DrawerLayout>
這里簡單介紹一下DrawerLayout的用法:在布局內(nèi)部放兩個子布局间驮,分別是content_main和drawermenu,放在上面的布局會默認(rèn)顯示為主頁马昨,而下面的布局則會隱藏竞帽,需要滑動才能顯示扛施。因此,主頁的view一般放在drawerlayout的第一個子View位置屹篓,同時寬高設(shè)置應(yīng)為match_parent疙渣。而側(cè)滑菜單的高度為match_parent,但寬度應(yīng)該設(shè)置為一個固定的值(一般側(cè)滑菜單不會完全覆蓋主內(nèi)容)堆巧,同時它的layout_gravity要指定一個值妄荔,比如本例,是從左滑出的谍肤,因此要設(shè)置為android:layout_gravity="left"啦租,表示在父布局的左側(cè),否則會失去側(cè)滑的效果荒揣。更多DrawerLayout的具體使用方法可以參考安卓官方指南:Creating a Navigation Drawer篷角。
Part 2.側(cè)滑菜單:ListView
為了方便起見,側(cè)滑菜單采用了listView的列表呈現(xiàn)系任,新建drawermenu.xml文件恳蹲,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="left"
android:background="#ffffff">
<LinearLayout
android:id="@+id/admininfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#58a5ff"
android:orientation="vertical">
<ImageView
android:id="@+id/adminicon"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="5dp"
android:src="@mipmap/ic_usericon"/>
<TextView
android:id="@+id/adminname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:paddingLeft="10dp"
android:text="Chen Yu"
android:textSize="23sp"/>
</LinearLayout>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/admininfo"
android:padding="15dp"
android:divider="@null" />
<ImageView
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="?android:attr/listDivider"
android:layout_above="@+id/qiehuanzhuti"
android:layout_alignParentStart="true" />
<TextView
android:id="@+id/qiehuanzhuti"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="切換主題"
android:textSize="20sp"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_below="@id/listview"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="設(shè)置"
android:textSize="20sp"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_below="@id/qiehuanzhuti"/>
</RelativeLayout>
同時,需要為listview的每一個Item創(chuàng)建一個item view俩滥,因此阱缓,新建list_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/actionicon"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@mipmap/ic_format"
android:layout_margin="5dp" />
<TextView
android:id="@+id/choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="選項"
android:textSize="18sp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="15dp"/>
</LinearLayout>
既然有了listView,那么就需要一個適配器举农,因此我們新建MenuAdapter.java:
public class MenuAdapter extends BaseAdapter {
private LayoutInflater mInflater;
private List<String> mData;
private List<Integer> mDataIcon;
public MenuAdapter(Context context, List<String> data,List<Integer> dataicon) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
this.mDataIcon = dataicon;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = mInflater.inflate(R.layout.list_item,null);
TextView textView = (TextView) view.findViewById(R.id.choice);
ImageView imageView = (ImageView) view.findViewById(R.id.actionicon);
//為每一個item設(shè)置一個圖標(biāo)和相應(yīng)的文字
imageView.setImageResource(mDataIcon.get(position));
textView.setText(mData.get(position));
return view;
}
}
該adapter繼承自BaseAdapter,相信熟悉ListView的都對適配器比較熟悉了敞嗡,因此這里不再展開來講了颁糟。
Part 3.主頁布局
1、實現(xiàn)下拉刷新:SwiperefreshLayout
先新建主頁布局文件:content_main.xml喉悴,我們看一下上面的圖棱貌,主頁布局主要由如下三者組成:一個toolbar導(dǎo)航欄,一個RecyclerView用于展示數(shù)據(jù)箕肃,以及一個刷新的小圓圈婚脱。那么我們的實現(xiàn)思路如下:首先toolbar應(yīng)該是位于最頂層的,接著利用swiperefreshlayout布局勺像,裝載一個RecycleView障贸。代碼如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:toolbar="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
toolbar:titleTextColor="@android:color/white"
toolbar:subtitleTextColor="@android:color/white"
toolbar:popupTheme="@style/ToolbarPopupTheme">
</android.support.v7.widget.Toolbar>
<android.support.v4.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/swiperefreshlayout">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
關(guān)于Toolbar的使用,如果還沒用過的可以參考我之前的一篇文章Android ToolBar 使用完全解析吟宦,這里就省略了關(guān)于Toolbar中的menu的xml文件篮洁,可以參考完整的源碼。接下來我們看看SwiperefreshLayout這個組件的使用:
這個組件是在v4包里面的殃姓,它實際上是一個ViewGroup袁波,但是它內(nèi)部只能有一個子View瓦阐,因此,我們可以把RecyclerView放在里面篷牌,在布局文件里面的寫法很簡單睡蟋,那么我們在Activity內(nèi)怎么初始化這個控件呢?
與一般控件一樣枷颊,它都是使用如下方法來獲取實例:
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swiperefreshlayout);
接著我們看它幾個常用的方法:
//該方法用于指定刷新時進(jìn)度條的顏色變化戳杀,第一個顏色表示進(jìn)度條出現(xiàn)的初始顏色,這里引用的是資源文件
public void setColorSchemeResources (int... colorResIds)
/**該方法用于設(shè)置刷新監(jiān)聽器偷卧,用于監(jiān)聽用戶下拉刷新的操作
* 接收的參數(shù)是SwipeRefreshLayout.OnRefreshListener豺瘤,并需要重寫該接口的onRefresh()方法。
* 設(shè)置該監(jiān)聽器后听诸,可以使得用戶在下拉刷新的時候回調(diào)該方法
*/
public void setOnRefreshListener (SwipeRefreshLayout.OnRefreshListener listener)
//該方法用于設(shè)置刷新狀態(tài)坐求,比如設(shè)置為false,那么是停止刷新圓圈的轉(zhuǎn)動
public void setRefreshing (boolean refreshing)
2晌梨、數(shù)據(jù)內(nèi)容:CardView
CardView是v7的一個控件桥嗤,在使用它的時候需要先引入,在build.gradle中修改如下:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:cardview-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
}
這個控件的優(yōu)點(diǎn)在于仔蝌,它是一個卡片式的布局泛领,能輕松實現(xiàn)卡片式效果,并且能實現(xiàn)陰影敛惊、圓角效果渊鞋。我們先看它的幾個重要的xml屬性:
app:cardElevation 陰影的大小
app:cardMaxElevation 陰影最大高度
app:cardBackgroundColor 卡片的背景色
app:cardCornerRadius 卡片的圓角大小
app:contentPadding 卡片內(nèi)容于邊距的間隔
以上app是自定義的命名空間。通過設(shè)置以上的屬性瞧挤,就能配置好一個漂亮的卡片了锡宋,接下來為卡片添加各種子View內(nèi)容,新建item_cardview.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:cardElevation="15dp"
app:contentPaddingTop="2dp"
app:contentPaddingBottom="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/relativelayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/usericon"
android:layout_height="35dp"
android:layout_width="35dp"
android:src="@mipmap/ic_launcher"
android:layout_marginLeft="4dp"
android:layout_centerVertical="true"/>
<TextView
android:id="@+id/username"
android:layout_height="50dp"
android:layout_width="wrap_content"
android:text="羅杰斯"
android:textSize="17sp"
android:textColor="#8cc3ff"
android:gravity="center"
android:layout_toRightOf="@id/usericon"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="贊同了該答案"
android:textSize="17sp"
android:gravity="center_vertical"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/username"/>
<TextView
android:id="@+id/count"
android:layout_width="50dp"
android:layout_height="25dp"
android:text="1.0k"
android:textSize="17sp"
android:textColor="#408aff"
android:background="#a9d3ff"
android:gravity="center"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_marginRight="15dp"
/>
</RelativeLayout>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="Android開發(fā)測試Demo示例"
android:layout_below="@id/relativelayout"
android:padding="4dp"/>
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"
android:lines="3"
android:ellipsize="end"
android:text="測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容測試內(nèi)容"
android:layout_below="@id/title"
android:padding="4dp"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
3特恬、數(shù)據(jù)列表:RecyclerView
接著执俩,我們繼續(xù)完善主頁,我們需要一個數(shù)據(jù)呈現(xiàn)的控件癌刽,RecyclerView役首,該控件的詳細(xì)使用方法可以參考我的一篇文章:揭開RecyclerView的神秘面紗(一):RecyclerView的基本使用张足,上面所說的CardView正是這里RecyclerView的一個子Item view趋艘,我們新建一個MyAdapter.java,即RecyclerView的數(shù)據(jù)適配器:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<MessageObj> mData;
@Override
public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_cardview,viewGroup,false);
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
public MyAdapter(List<MessageObj> data){
mData = data;
}
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, int i) {
myViewHolder.mUsername.setText(mData.get(i).getUsername());
myViewHolder.mUserIcon.setImageResource(mData.get(i).getIcon());
myViewHolder.mCount.setText(mData.get(i).getCount());
myViewHolder.mTitle.setText(mData.get(i).getTitle());
myViewHolder.mContent.setText(mData.get(i).getContent());
}
@Override
public int getItemCount() {
return mData.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView mUsername;
public TextView mCount;
public TextView mTitle;
public TextView mContent;
public ImageView mUserIcon;
public MyViewHolder(View itemView) {
super(itemView);
mUsername = (TextView) itemView.findViewById(R.id.username);
mUserIcon = (ImageView) itemView.findViewById(R.id.usericon);
mCount = (TextView) itemView.findViewById(R.id.count);
mTitle = (TextView) itemView.findViewById(R.id.title);
mContent = (TextView) itemView.findViewById(R.id.content);
}
}
}
接著吕喘,我們要寫一個消息實體類远荠,因為每一個ItemView的內(nèi)容都是通過網(wǎng)絡(luò)加載或者本地緩存都獲得的杰赛,因此,我們新建MessageObj.java:
public class MessageObj {
private String username;
private String count;
private String title;
private String content;
private int icon;
public MessageObj(String username,int icon ,String count, String title, String content) {
this.username = username;
this.count = count;
this.title = title;
this.content = content;
this.icon = icon;
}
public String getUsername() {
return username;
}
public String getContent() {
return content;
}
public String getTitle() {
return title;
}
public String getCount() {
return count;
}
public int getIcon() {
return icon;
}
}
到目前為止矮台,通過使用RecyclerView+CardView實現(xiàn)了數(shù)據(jù)列表的呈現(xiàn)乏屯,通過SwiperefreshLayout實現(xiàn)了下拉刷新的功能根时,通過Toolbar實現(xiàn)了導(dǎo)航的功能,通過DrawerLayout實現(xiàn)了側(cè)滑菜單的功能辰晕。那么最后蛤迎,我們需要在Activity中,對以上一系列控件初始化以及準(zhǔn)備數(shù)據(jù)的提供含友。
Part 4.MainActivity
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private SwipeRefreshLayout swipeRefreshLayout;
private DrawerLayout drawerLayout;
private ActionBarDrawerToggle actionBarDrawerToggle;
private ListView listView;
private List<MessageObj> mData;
private Toolbar toolbar;
private List<String> choices;
private List<Integer> choiceIcon;
private MyAdapter recyclerAdapter;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
swipeRefreshLayout.setRefreshing(false);
recyclerAdapter.notifyDataSetChanged();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initViews();
}
private void initViews() {
/**
* 初始化Toolbar
*/
toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle("首頁");
//設(shè)置導(dǎo)航圖標(biāo)替裆、添加菜單點(diǎn)擊事件要在setSupportActionBar方法之后
setSupportActionBar(toolbar);
toolbar.setNavigationIcon(R.mipmap.ic_drawer_home);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
Toast.makeText(MainActivity.this, "Search !", Toast.LENGTH_LONG).show();
break;
case R.id.action_notifications:
Toast.makeText(MainActivity.this, "Notification !", Toast.LENGTH_LONG).show();
break;
}
return true;
}
});
/**
* 初始化RecyclerView
*/
recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerAdapter = new MyAdapter(mData);
recyclerView.setAdapter(recyclerAdapter);
/**
* 初始化swipeRefreshLayout
*/
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swiperefreshlayout);
swipeRefreshLayout.setColorSchemeResources(R.color.color_blue);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
new Thread(new Runnable() {
@Override
public void run() {
Collections.reverse(mData);
try {
Thread.sleep(1000); //模擬耗時操作
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(1);
}
}).start();
}
});
/**
* 初始化側(cè)滑菜單 DrawerLayout
*/
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
actionBarDrawerToggle = new ActionBarDrawerToggle(
this,
drawerLayout,toolbar,R.string.drawer_open,R.string.drawer_close
);
drawerLayout.setDrawerListener(actionBarDrawerToggle);
listView = (ListView) findViewById(R.id.listview);
ListAdapter adapter = new MenuAdapter(this,choices,choiceIcon);
listView.setAdapter(adapter);
}
private void initData() {
mData = new ArrayList<MessageObj>();
MessageObj obj1 = new MessageObj("神盾局",R.mipmap.shield,"5.6K","神盾局是怎么樣的一個組織?",
"神盾局窘问,全稱為國土戰(zhàn)略防御攻擊與后勤保障局辆童,由斯坦·李與杰克·科比聯(lián)合創(chuàng)造。神盾局是國際安全理事會專門用于處理各種奇異事件的特殊部隊");
mData.add(obj1);
MessageObj obj2 = new MessageObj("Stark",R.mipmap.stark,"7.8K","鋼鐵俠是誰惠赫?",
"托尼·斯塔克(小羅伯特·唐尼飾)是“斯塔克工業(yè)”的董事長把鉴,作為鋼鐵俠 官方劇照鋼鐵俠軍火商他毀譽(yù)不一,但還是過著上流生活儿咱。此時庭砍,");
mData.add(obj2);
MessageObj obj3 = new MessageObj("索爾",R.mipmap.thor,"7.8K","雷神索爾的能力如何?",
"北歐神話里揮舞著大鐵錘混埠、掌控著風(fēng)暴和閃電的天神怠缸,還能用鐵錘打開時空之門。暴脾氣的他因為自大魯莽的行為重新點(diǎn)燃了一場古老戰(zhàn)爭的戰(zhàn)火钳宪,之后被貶到凡間被迫與人類一起生活揭北。");
mData.add(obj3);
MessageObj obj4 = new MessageObj("羅杰斯",R.mipmap.steven,"7.8K","怎么評價美國隊長3?",
"該片根據(jù)漫威2006年出版的漫畫大事件《內(nèi)戰(zhàn)》改編吏颖,背景故事承接于《復(fù)仇者聯(lián)盟2:奧創(chuàng)紀(jì)元》事件的余波中搔体,講述了奧創(chuàng)事件后引發(fā)的");
mData.add(obj4);
MessageObj obj5 = new MessageObj("黑寡婦",R.mipmap.widow,"7.8K","黑寡婦是一個怎么樣的角色?",
"1928年出生于前蘇聯(lián)的斯大林格勒侦高,自幼被前蘇聯(lián)特工人員訓(xùn)練成特工,身體經(jīng)前蘇聯(lián)政府基因改造后大大延緩了其衰老速度厌杜,并增強(qiáng)其免疫系統(tǒng)以及抗擊打能力奉呛,加上本身多年的各種體能及精神上的訓(xùn)練");
mData.add(obj5);
choices = new ArrayList<String>();
choiceIcon = new ArrayList<>();
choices.add("首頁");
choices.add("發(fā)現(xiàn)");
choices.add("關(guān)注");
choices.add("收藏");
choices.add("圓桌");
choices.add("私信");
choiceIcon.add(R.mipmap.ic_main);
choiceIcon.add(R.mipmap.ic_find);
choiceIcon.add(R.mipmap.ic_attention);
choiceIcon.add(R.mipmap.ic_collect);
choiceIcon.add(R.mipmap.ic_circle);
choiceIcon.add(R.mipmap.ic_message);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
}
最后,可以運(yùn)行一下夯尽,就會發(fā)現(xiàn)結(jié)果和一開始的效果是一樣的了瞧壮。