# 仿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>
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的布局方式,通过设置DrawerLayout的openDrawer属性以及NavigationView的gravity属性来实现左侧的测拉区域。
下面来看看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>
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>
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>
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>
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>
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
这里需要注意一下几个知识点:
- 通过给
Toolbar的layout_scrollFlags属性设置scroll|enterAlways并且给ViewPager设置layout_behavior属性来实现滑动的时候Toolbar消失。即当设置layout_behavior的组件滑动时设置layout_scrollFlags的组件会移出屏幕 - 给
TabLayout的tabTextAppearance设置一个字体样式来实现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;
}
}
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);
}
}
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);
}
}
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>
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,使得卡片为正矩形。
ImageView的scaleType的意思是图片如何填充,其中fitCenter为居中填充,fitStart为左对齐填充,fitEnd为右对齐填充。
写的比较仓促,如有疑问或者错误的地方欢迎留言指正。