# 仿9GAG制作过程(一)

# 有话要说

准备开始学习Android应用程序的一个完整的设计过程。准备做一个仿9GAG的APP,前端界面设计+后台数据爬虫+后台接口设计,整个流程体验一遍。今天准备先把前端界面的框架给完成了。

# 成果图

# 布局代码

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/activity_main_appbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="false"
        app:headerLayout="@layout/activity_main_drawer_head"
        app:menu="@menu/activity_main_drawer_menu"
        android:theme="@style/MenuTextStyle"
         />

</android.support.v4.widget.DrawerLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

主活动用了DrawerLayout的布局方式,通过设置DrawerLayoutopenDrawer属性以及NavigationViewgravity属性来实现左侧的测拉区域。

下面来看看NavigationView的头部布局以及menu的布局:

<?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="?attr/actionBarSize"
    android:background="@color/background">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/circleImageView"
            android:layout_width="28dp"
            android:layout_height="28dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="14dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="懒星人"
            android:layout_centerVertical="true"
            android:layout_marginLeft="14dp"
            android:layout_toRightOf="@id/circleImageView"
            android:textColor="@color/colorPrimary"/>
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_settings_gray_24dp"
            android:layout_centerVertical="true"
            android:layout_marginRight="14dp"
            android:layout_alignParentRight="true"/>
    </RelativeLayout>

    <View
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="?android:listDivider"
        />
</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

这里用到了CircleImageView组件来实现图片缩放裁剪成圆形,作为左上角头像的布局。并且由于头部的布局与menu的布局之间没有直接的分割线,就用View来实现了一个分割线。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group
        android:id="@+id/group1"
        android:checkableBehavior="single">
        <item
            android:id="@+id/nav_home"
            android:icon="@drawable/ic_home_gray_24dp"
            android:title="@string/home" />
        <item
            android:id="@+id/nav_notifications"
            android:icon="@drawable/ic_notifications_gray_24dp"
            android:title="@string/notifications" />
    </group>

    <group android:id="@+id/group2">
        <item
            android:id="@+id/nav_share"
            android:icon="@drawable/ic_share_gray_24dp"
            android:title="@string/share" />
        <item
            android:id="@+id/nav_send"
            android:icon="@drawable/ic_send_gray_24dp"
            android:title="@string/send" />
    </group>

</menu>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

左侧menu的布局和主menu实现方式一致,通过menu的配置文件来实现。

在这里遇到了两个问题:

  • 左侧menu字体不是粗体,但是需要粗体
  • 左侧menu布局的图标和文字之间的间隔太大

第一个问题通过给NavigationView设置了主题,主题的主要意义就是加粗字体,如下代码:

<style name="MenuTextStyle">
        <item name="android:textStyle">bold</item>
</style>
1
2
3

第二个问题,通过阅读NavigationView的源码逐步找到了item的布局文件,布局文件为design_navigation_menu_item.xml,于是将布局文件复制到layout下,并将drawablePadding改成了20dp,如下代码:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <CheckedTextView
        android:id="@+id/design_menu_item_text"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:drawablePadding="20dp"
        android:gravity="center_vertical|start"
        android:maxLines="1"
        android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>

    <ViewStub
        android:id="@+id/design_menu_item_action_area_stub"
        android:inflatedId="@+id/design_menu_item_action_area"
        android:layout="@layout/design_menu_item_action_area"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"/>

</merge>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 插一个知识点

可以直接通过Android Studio来生成需要用到的图标,这里的图标我都是直接通过Android Studio生成的,生成步骤如下:

接下来说一下主页面的实现,是通过TabLayout+ViewPage的方式来实现的,先来看代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabBackground="@color/background"
            app:tabIndicatorColor="@color/colorPrimary"
            app:tabTextColor="@color/defaultColor"
            app:tabSelectedTextColor="@color/colorPrimary"
            app:tabTextAppearance="@style/TabText"/>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPage"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

这里需要注意一下几个知识点:

  • 通过给Toolbarlayout_scrollFlags属性设置scroll|enterAlways并且给ViewPager设置layout_behavior属性来实现滑动的时候Toolbar消失。即当设置layout_behavior的组件滑动时设置layout_scrollFlags的组件会移出屏幕
  • TabLayouttabTextAppearance设置一个字体样式来实现Tab页加粗效果

下面主要来说一下activity部分的代码,先上代码:

package com.example.lanxingren.imitating9gag.activity;

import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;

import com.example.lanxingren.imitating9gag.R;
import com.example.lanxingren.imitating9gag.adapter.MyFragmentPagerAdapter;
import com.example.lanxingren.imitating9gag.fragment.HomeFragment;
import com.squareup.picasso.Picasso;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;
import de.hdodenhof.circleimageview.CircleImageView;

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {

    @BindView(R.id.toolbar)
    Toolbar toolbar;
    @BindView(R.id.drawer_layout)
    DrawerLayout drawer;
    @BindView(R.id.nav_view)
    NavigationView navigationView;
    @BindView(R.id.tabLayout)
    TabLayout tabLayout;
    @BindView(R.id.viewPage)
    ViewPager viewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);

        //设置ActionBar
        setSupportActionBar(toolbar);

        //设置DrawerLayout的监听事件,其中后两个参数是给残障人士的语音
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        //设置左上角的三杠图标
        toggle.syncState();
        drawer.addDrawerListener(toggle);

        //设置抽屉的监听事件
        navigationView.setNavigationItemSelectedListener(this);

        //直接findViewById会导致NPE,抽屉head部分的头像
        CircleImageView circleImageView = navigationView.getHeaderView(0)
                .findViewById(R.id.circleImageView);
        Picasso.with(this).load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1527745766743&di=c24134fe5233902ca1a60a8665c30a35&imgtype=0&src=http%3A%2F%2Fimg1.sc115.com%2Fuploads%2Fsc%2Fjpg%2F144%2F18628.jpg")
                .into(circleImageView);

        //定义viewPage的适配器
        List<Fragment> fragments = new ArrayList();
        fragments.add(new HomeFragment());
        fragments.add(new HomeFragment());
        MyFragmentPagerAdapter adapter = new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments);

        viewPager.setAdapter(adapter);
        tabLayout.setupWithViewPager(viewPager);
    }

    @Override
    public void onBackPressed() {
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    /**
     * 右上角按钮图标
     * @param menu
     * @return
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    //右上角按钮点击事件
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return super.onOptionsItemSelected(item);
    }

    //左侧抽屉menu点击事件
    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.nav_home) {

        } else if (id == R.id.nav_notifications) {

        } else if (id == R.id.nav_send) {

        } else if (id == R.id.nav_share) {

        }

        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

知识点:

  • 用了ButterKnife而不是findViewById来获取组件
  • 用了Picasso来加载网络图片,头像以及内部都是通过这种方式来加载的
  • 定义了MyFragmentPagerAdapter适配器来实现ViewPage的布局

MyFragmentPagerAdapter内部的数据实际上为HomeFragment,而该Fragment的布局实际上只是一个简单的RecyclerView,下面上HomeFragment的代码:

package com.example.lanxingren.imitating9gag.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.lanxingren.imitating9gag.R;
import com.example.lanxingren.imitating9gag.adapter.NewsAdapter;
import com.example.lanxingren.imitating9gag.bean.NewsBean;

import java.util.ArrayList;
import java.util.List;

/**
 */
public class HomeFragment extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_home, container, false);
    }

    @Override
    public void onStart() {
        super.onStart();
        List<NewsBean> newsBeans = new ArrayList<NewsBean>();

        for (int i = 0; i < 30; i++) {
            newsBeans.add(new NewsBean("这是第 " + Integer.toString(i+1) + " 条有趣的段子!",
                    "http://ws4.sinaimg.cn/mw600/6c560b83ly1fruncq3z03j20ks0rs41b.jpg", 0));
        }

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());

        RecyclerView recyclerView = getView().findViewById(R.id.recyclerView);
        recyclerView.setAdapter(new NewsAdapter(newsBeans));
        recyclerView.setLayoutManager(linearLayoutManager);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

RecyrView用了NewsAdapter适配器,适配器代码如下:

package com.example.lanxingren.imitating9gag.adapter;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.lanxingren.imitating9gag.R;
import com.example.lanxingren.imitating9gag.bean.NewsBean;
import com.squareup.picasso.Picasso;

import java.util.List;

public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.NewsHolder> {

    private List<NewsBean> myNewsList;
    private Context myContext;

    static class NewsHolder extends RecyclerView.ViewHolder {
        CardView cardView;
        TextView textView;
        ImageView imageView;

        private NewsHolder (View view) {
            super(view);
            cardView = (CardView) view;
            textView = view.findViewById(R.id.item_text);
            imageView = view.findViewById(R.id.item_image);
        }
    }

    public NewsAdapter (List<NewsBean> newsList) {
        this.myNewsList = newsList;
    }

    @Override
    public int getItemCount() {
        int count = 0;
        if (myNewsList != null) {
            count = myNewsList.size();
        }
        return count;
    }

    @NonNull
    @Override
    public NewsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (myContext == null) {
            myContext = parent.getContext();
        }
        View view = LayoutInflater.from(myContext).inflate(R.layout.item_news, parent, false);
        return new NewsHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull NewsHolder holder, int position) {
        NewsBean newsBean = myNewsList.get(position);
        holder.textView.setText(newsBean.getTitle());

        int screenWidth = myContext.getResources()
                .getDisplayMetrics()
                .widthPixels;
        Picasso.with(myContext)
                .load(newsBean.getPicUrl())
                .resize(screenWidth, 0)
                .into(holder.imageView);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

每一项的布局为item_news,一会儿看具体布局。在onBindViewHolder中给布局的textView设置了文字,给imageView设置了图片。

之前看别人的博客,经常会在适配器中定义一个myContext,我一直觉得没什么用。但是在这次实际编写适配器的过程中,发现了myContext还是有很多地方要用到的。

下面来看看item_news的布局,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_marginVertical="10dp"
    app:cardCornerRadius="0dp"
    android:elevation="0dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center">
            <TextView
                android:id="@+id/item_text"
                android:textStyle="bold"
                android:textColor="@color/colorPrimary"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_marginLeft="14dp"/>
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:src="@drawable/ic_expand_more_gray_24dp"
                android:layout_marginRight="14dp"/>
        </RelativeLayout>
        <ImageView
            android:id="@+id/item_image"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scaleType="fitCenter"/>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:orientation="horizontal"
            android:gravity="center">
            <LinearLayout
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <ImageView
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="wrap_content"
                    android:src="@drawable/ic_thumb_up_gray_24dp"
                    android:scaleType="fitEnd"/>
                <TextView
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:text="5k"/>
                <ImageView
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="wrap_content"
                    android:src="@drawable/ic_thumb_down_gray_24dp"
                    android:scaleType="fitStart"/>
            </LinearLayout>

            <View
                android:layout_width="1dp"
                android:layout_height="20dp"
                android:background="?android:listDivider"/>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:gravity="center">
                <ImageView
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="wrap_content"
                    android:src="@drawable/ic_comment_gray_24dp"
                    android:scaleType="fitEnd"
                    android:paddingRight="5dp"/>
                <TextView
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="wrap_content"
                    android:gravity="left"
                    android:text="46"
                    android:paddingLeft="5dp"/>
            </LinearLayout>

            <View
                android:layout_width="1dp"
                android:layout_height="20dp"
                android:background="?android:listDivider"/>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:gravity="center">
                <ImageView
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="wrap_content"
                    android:src="@drawable/ic_share_gray_24dp"
                    android:scaleType="fitEnd"
                    android:paddingRight="5dp"/>
                <TextView
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="wrap_content"
                    android:gravity="left"
                    android:text="分享"
                    android:paddingLeft="5dp"/>
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>

</android.support.v7.widget.CardView>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

每一项使用的是卡片式布局,使用了官方的CardView组件。

通过设置cardCornerRadius来设置圆角弧度为0,使得卡片为正矩形。

ImageViewscaleType的意思是图片如何填充,其中fitCenter为居中填充,fitStart为左对齐填充,fitEnd为右对齐填充。

写的比较仓促,如有疑问或者错误的地方欢迎留言指正。

更新时间: 10/20/2022, 7:04:01 AM