教你轻松实现Material Design风格的知乎主页(详解多种控件的综合使用)

本文讲主要来说说Toolbar、RecyclerView、CardView、DrawerLayout、以及SwiperefreshLayout的综合使用,其中Toolbar和RecyclerView在前几篇博客已经详细讲述其用法了,有兴趣的可以去看看。现在利用这几个控件实现Material Design风格的知乎主页(Android v3.3版 知乎),在讲到相关控件的时候,我也会提及一下这个控件的用法。那么,让我们开始控件之旅吧。

示例效果

在动手写代码之前,我们先要看看最终的实现效果是什么:


教你轻松实现Material Design风格的知乎主页(详解多种控件的综合使用)_第1张图片
仿知乎.png

首先,顶部导航栏是Toolbar,下面是RecyclerView,而RecyclerView内部的item view则是一个个CardView。


教你轻松实现Material Design风格的知乎主页(详解多种控件的综合使用)_第2张图片
仿知乎.png

接着,向右滑动,会出现一个侧边菜单栏,这个是用DrawerLayout实现的,下面会详细说明。
仿知乎.gif

最后,还实现了下拉刷新功能,这个是用SwiperefreshLayout实现的。
以上效果大致和手机上的知乎效果一样(v3.3版),但最新的知乎已经更新到了4.0,改动很多,就不是以上的效果了。但无碍我们接下来的学习。

动手实践

part 1.侧滑菜单的布局:DrawerLayout

根据以上的效果,我们首先要创建一个DrawerLayout布局,它是一个ViewGroup,因此能放下其他的View,显然左边滑出来的部分放我们的侧滑菜单选项,而右边则是我们的主页部分,新建activity_main.xml文件:


    
    

    
    

这里简单介绍一下DrawerLayout的用法:在布局内部放两个子布局,分别是content_main和drawermenu,放在上面的布局会默认显示为主页,而下面的布局则会隐藏,需要滑动才能显示。因此,主页的view一般放在drawerlayout的第一个子View位置,同时宽高设置应为match_parent。而侧滑菜单的高度为match_parent,但宽度应该设置为一个固定的值(一般侧滑菜单不会完全覆盖主内容),同时它的layout_gravity要指定一个值,比如本例,是从左滑出的,因此要设置为android:layout_gravity="left",表示在父布局的左侧,否则会失去侧滑的效果。更多DrawerLayout的具体使用方法可以参考安卓官方指南:Creating a Navigation Drawer。

Part 2.侧滑菜单:ListView

为了方便起见,侧滑菜单采用了listView的列表呈现,新建drawermenu.xml文件,代码如下:




    
        
        
    
    
    
    
    


同时,需要为listview的每一个Item创建一个item view,因此,新建list_item.xml:



    
    


既然有了listView,那么就需要一个适配器,因此我们新建MenuAdapter.java:

public class MenuAdapter extends BaseAdapter {
    private LayoutInflater mInflater;
    private List mData;
    private List mDataIcon;

    public MenuAdapter(Context context, List data,List 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设置一个图标和相应的文字
        imageView.setImageResource(mDataIcon.get(position));
        textView.setText(mData.get(position));
        return view;
    }
}

该adapter继承自BaseAdapter,相信熟悉ListView的都对适配器比较熟悉了,因此这里不再展开来讲了。

Part 3.主页布局

1、实现下拉刷新:SwiperefreshLayout

先新建主页布局文件:content_main.xml,我们看一下上面的图,主页布局主要由如下三者组成:一个toolbar导航栏,一个RecyclerView用于展示数据,以及一个刷新的小圆圈。那么我们的实现思路如下:首先toolbar应该是位于最顶层的,接着利用swiperefreshlayout布局,装载一个RecycleView。代码如下所示:


    
    
    
        
    

关于Toolbar的使用,如果还没用过的可以参考我之前的一篇文章Android ToolBar 使用完全解析,这里就省略了关于Toolbar中的menu的xml文件,可以参考完整的源码。接下来我们看看SwiperefreshLayout这个组件的使用:
这个组件是在v4包里面的,它实际上是一个ViewGroup,但是它内部只能有一个子View,因此,我们可以把RecyclerView放在里面,在布局文件里面的写法很简单,那么我们在Activity内怎么初始化这个控件呢?
与一般控件一样,它都是使用如下方法来获取实例:

swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swiperefreshlayout);

接着我们看它几个常用的方法:

  //该方法用于指定刷新时进度条的颜色变化,第一个颜色表示进度条出现的初始颜色,这里引用的是资源文件
  public void setColorSchemeResources (int... colorResIds)

  /**该方法用于设置刷新监听器,用于监听用户下拉刷新的操作
   * 接收的参数是SwipeRefreshLayout.OnRefreshListener,并需要重写该接口的onRefresh()方法。
   * 设置该监听器后,可以使得用户在下拉刷新的时候回调该方法
   */
  public void setOnRefreshListener (SwipeRefreshLayout.OnRefreshListener listener)

  //该方法用于设置刷新状态,比如设置为false,那么是停止刷新圆圈的转动
  public void setRefreshing (boolean refreshing)

2、数据内容: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'
}

这个控件的优点在于,它是一个卡片式的布局,能轻松实现卡片式效果,并且能实现阴影、圆角效果。我们先看它的几个重要的xml属性:

app:cardElevation 阴影的大小
app:cardMaxElevation 阴影最大高度
app:cardBackgroundColor 卡片的背景色
app:cardCornerRadius 卡片的圆角大小
app:contentPadding 卡片内容于边距的间隔

以上app是自定义的命名空间。通过设置以上的属性,就能配置好一个漂亮的卡片了,接下来为卡片添加各种子View内容,新建item_cardview.xml:




    

        

            

            

            

            
        

        

        
    


3、数据列表:RecyclerView

接着,我们继续完善主页,我们需要一个数据呈现的控件,RecyclerView,该控件的详细使用方法可以参考我的一篇文章:揭开RecyclerView的神秘面纱(一):RecyclerView的基本使用,上面所说的CardView正是这里RecyclerView的一个子Item view,我们新建一个MyAdapter.java,即RecyclerView的数据适配器:

public class MyAdapter extends RecyclerView.Adapter {

    private List 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 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的内容都是通过网络加载或者本地缓存都获得的,因此,我们新建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实现了数据列表的呈现,通过SwiperefreshLayout实现了下拉刷新的功能,通过Toolbar实现了导航的功能,通过DrawerLayout实现了侧滑菜单的功能。那么最后,我们需要在Activity中,对以上一系列控件初始化以及准备数据的提供。

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 mData;
    private Toolbar toolbar;
    private List choices;
    private List 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("首页");
        //设置导航图标、添加菜单点击事件要在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();
            }
        });

        /**
         *  初始化侧滑菜单 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 obj1 = new MessageObj("神盾局",R.mipmap.shield,"5.6K","神盾局是怎么样的一个组织?",
                "神盾局,全称为国土战略防御攻击与后勤保障局,由斯坦·李与杰克·科比联合创造。神盾局是国际安全理事会专门用于处理各种奇异事件的特殊部队");
        mData.add(obj1);
        MessageObj obj2 = new MessageObj("Stark",R.mipmap.stark,"7.8K","钢铁侠是谁?",
                "托尼·斯塔克(小罗伯特·唐尼饰)是“斯塔克工业”的董事长,作为钢铁侠 官方剧照钢铁侠军火商他毁誉不一,但还是过着上流生活。此时,");
        mData.add(obj2);
        MessageObj obj3 = new MessageObj("索尔",R.mipmap.thor,"7.8K","雷神索尔的能力如何?",
                "北欧神话里挥舞着大铁锤、掌控着风暴和闪电的天神,还能用铁锤打开时空之门。暴脾气的他因为自大鲁莽的行为重新点燃了一场古老战争的战火,之后被贬到凡间被迫与人类一起生活。");
        mData.add(obj3);
        MessageObj obj4 = new MessageObj("罗杰斯",R.mipmap.steven,"7.8K","怎么评价美国队长3?",
                "该片根据漫威2006年出版的漫画大事件《内战》改编,背景故事承接于《复仇者联盟2:奥创纪元》事件的余波中,讲述了奥创事件后引发的");
        mData.add(obj4);
        MessageObj obj5 = new MessageObj("黑寡妇",R.mipmap.widow,"7.8K","黑寡妇是一个怎么样的角色?",
                "1928年出生于前苏联的斯大林格勒,自幼被前苏联特工人员训练成特工,身体经前苏联政府基因改造后大大延缓了其衰老速度,并增强其免疫系统以及抗击打能力,加上本身多年的各种体能及精神上的训练");
        mData.add(obj5);
        choices = new ArrayList();
        choiceIcon = new ArrayList<>();
        choices.add("首页");
        choices.add("发现");
        choices.add("关注");
        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;
    }

}

最后,可以运行一下,就会发现结果和一开始的效果是一样的了。

源码地址:https://github.com/chenyua1995/androiddemo

你可能感兴趣的:(教你轻松实现Material Design风格的知乎主页(详解多种控件的综合使用))