仿9GAG制作过程(一)

时间:2022-08-23 17:47:30

有话要说:

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

成果图:

仿9GAG制作过程(一)

布局代码:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:app="http://schemas.android.com/apk/res-auto"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:id="@+id/drawer_layout"
 6     android:layout_width="match_parent"
 7     android:layout_height="match_parent"
 8     android:fitsSystemWindows="true"
 9     tools:openDrawer="start">
10 
11     <include
12         layout="@layout/activity_main_appbar"
13         android:layout_width="match_parent"
14         android:layout_height="match_parent" />
15 
16     <android.support.design.widget.NavigationView
17         android:id="@+id/nav_view"
18         android:layout_width="wrap_content"
19         android:layout_height="match_parent"
20         android:layout_gravity="start"
21         android:fitsSystemWindows="false"
22         app:headerLayout="@layout/activity_main_drawer_head"
23         app:menu="@menu/activity_main_drawer_menu"
24         android:theme="@style/MenuTextStyle"
25          />
26 
27 </android.support.v4.widget.DrawerLayout>

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

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

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     android:layout_width="match_parent"
 5     android:layout_height="?attr/actionBarSize"
 6     android:background="@color/background">
 7 
 8     <RelativeLayout
 9         android:layout_width="match_parent"
10         android:layout_height="match_parent">
11         <de.hdodenhof.circleimageview.CircleImageView
12             android:id="@+id/circleImageView"
13             android:layout_width="28dp"
14             android:layout_height="28dp"
15             android:layout_centerVertical="true"
16             android:layout_marginLeft="14dp"/>
17 
18         <TextView
19             android:layout_width="wrap_content"
20             android:layout_height="wrap_content"
21             android:text="懒星人"
22             android:layout_centerVertical="true"
23             android:layout_marginLeft="14dp"
24             android:layout_toRightOf="@id/circleImageView"
25             android:textColor="@color/colorPrimary"/>
26         <ImageView
27             android:id="@+id/imageView"
28             android:layout_width="wrap_content"
29             android:layout_height="wrap_content"
30             android:src="@drawable/ic_settings_gray_24dp"
31             android:layout_centerVertical="true"
32             android:layout_marginRight="14dp"
33             android:layout_alignParentRight="true"/>
34     </RelativeLayout>
35 
36     <View
37         android:layout_alignParentBottom="true"
38         android:layout_width="match_parent"
39         android:layout_height="1dp"
40         android:background="?android:listDivider"
41         />
42 </RelativeLayout>

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

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <menu xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     tools:showIn="navigation_view">
 5 
 6     <group
 7         android:id="@+id/group1"
 8         android:checkableBehavior="single">
 9         <item
10             android:id="@+id/nav_home"
11             android:icon="@drawable/ic_home_gray_24dp"
12             android:title="@string/home" />
13         <item
14             android:id="@+id/nav_notifications"
15             android:icon="@drawable/ic_notifications_gray_24dp"
16             android:title="@string/notifications" />
17     </group>
18 
19     <group android:id="@+id/group2">
20         <item
21             android:id="@+id/nav_share"
22             android:icon="@drawable/ic_share_gray_24dp"
23             android:title="@string/share" />
24         <item
25             android:id="@+id/nav_send"
26             android:icon="@drawable/ic_send_gray_24dp"
27             android:title="@string/send" />
28     </group>
29 
30 </menu>

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

在这里遇到了两个问题:

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

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

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

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

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 3 
 4     <CheckedTextView
 5         android:id="@+id/design_menu_item_text"
 6         android:layout_width="0dp"
 7         android:layout_height="match_parent"
 8         android:layout_weight="1"
 9         android:drawablePadding="20dp"
10         android:gravity="center_vertical|start"
11         android:maxLines="1"
12         android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
13 
14     <ViewStub
15         android:id="@+id/design_menu_item_action_area_stub"
16         android:inflatedId="@+id/design_menu_item_action_area"
17         android:layout="@layout/design_menu_item_action_area"
18         android:layout_width="wrap_content"
19         android:layout_height="match_parent"/>
20 
21 </merge>

插一个知识点:

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

仿9GAG制作过程(一)

仿9GAG制作过程(一)

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

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:app="http://schemas.android.com/apk/res-auto"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     tools:context=".activity.MainActivity">
 8 
 9     <android.support.design.widget.AppBarLayout
10         android:layout_width="match_parent"
11         android:layout_height="wrap_content"
12         android:theme="@style/AppTheme.AppBarOverlay">
13 
14         <android.support.v7.widget.Toolbar
15             android:id="@+id/toolbar"
16             android:layout_width="match_parent"
17             android:layout_height="?attr/actionBarSize"
18             android:background="?attr/colorPrimary"
19             app:layout_scrollFlags="scroll|enterAlways"
20             app:popupTheme="@style/AppTheme.PopupOverlay" />
21 
22         <android.support.design.widget.TabLayout
23             android:id="@+id/tabLayout"
24             android:layout_width="match_parent"
25             android:layout_height="wrap_content"
26             app:tabBackground="@color/background"
27             app:tabIndicatorColor="@color/colorPrimary"
28             app:tabTextColor="@color/defaultColor"
29             app:tabSelectedTextColor="@color/colorPrimary"
30             app:tabTextAppearance="@style/TabText"/>
31 
32     </android.support.design.widget.AppBarLayout>
33 
34     <android.support.v4.view.ViewPager
35         android:id="@+id/viewPage"
36         android:layout_width="match_parent"
37         android:layout_height="match_parent"
38         app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
39 
40 </android.support.design.widget.CoordinatorLayout>

 

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

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

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

  1 package com.example.lanxingren.imitating9gag.activity;
  2 
  3 import android.os.Bundle;
  4 import android.support.design.widget.NavigationView;
  5 import android.support.design.widget.TabLayout;
  6 import android.support.v4.app.Fragment;
  7 import android.support.v4.view.GravityCompat;
  8 import android.support.v4.view.ViewPager;
  9 import android.support.v4.widget.DrawerLayout;
 10 import android.support.v7.app.ActionBarDrawerToggle;
 11 import android.support.v7.app.AppCompatActivity;
 12 import android.support.v7.widget.Toolbar;
 13 import android.view.Menu;
 14 import android.view.MenuItem;
 15 
 16 import com.example.lanxingren.imitating9gag.R;
 17 import com.example.lanxingren.imitating9gag.adapter.MyFragmentPagerAdapter;
 18 import com.example.lanxingren.imitating9gag.fragment.HomeFragment;
 19 import com.squareup.picasso.Picasso;
 20 
 21 import java.util.ArrayList;
 22 import java.util.List;
 23 
 24 import butterknife.BindView;
 25 import butterknife.ButterKnife;
 26 import de.hdodenhof.circleimageview.CircleImageView;
 27 
 28 public class MainActivity extends AppCompatActivity
 29         implements NavigationView.OnNavigationItemSelectedListener {
 30 
 31     @BindView(R.id.toolbar)
 32     Toolbar toolbar;
 33     @BindView(R.id.drawer_layout)
 34     DrawerLayout drawer;
 35     @BindView(R.id.nav_view)
 36     NavigationView navigationView;
 37     @BindView(R.id.tabLayout)
 38     TabLayout tabLayout;
 39     @BindView(R.id.viewPage)
 40     ViewPager viewPager;
 41 
 42     @Override
 43     protected void onCreate(Bundle savedInstanceState) {
 44         super.onCreate(savedInstanceState);
 45 
 46         setContentView(R.layout.activity_main);
 47 
 48         ButterKnife.bind(this);
 49 
 50         //设置ActionBar
 51         setSupportActionBar(toolbar);
 52 
 53         //设置DrawerLayout的监听事件,其中后两个参数是给残障人士的语音
 54         ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
 55                 this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
 56         //设置左上角的三杠图标
 57         toggle.syncState();
 58         drawer.addDrawerListener(toggle);
 59 
 60         //设置抽屉的监听事件
 61         navigationView.setNavigationItemSelectedListener(this);
 62 
 63         //直接findViewById会导致NPE,抽屉head部分的头像
 64         CircleImageView circleImageView = navigationView.getHeaderView(0)
 65                 .findViewById(R.id.circleImageView);
 66         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")
 67                 .into(circleImageView);
 68 
 69         //定义viewPage的适配器
 70         List<Fragment> fragments = new ArrayList();
 71         fragments.add(new HomeFragment());
 72         fragments.add(new HomeFragment());
 73         MyFragmentPagerAdapter adapter = new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments);
 74 
 75         viewPager.setAdapter(adapter);
 76         tabLayout.setupWithViewPager(viewPager);
 77     }
 78 
 79     @Override
 80     public void onBackPressed() {
 81         if (drawer.isDrawerOpen(GravityCompat.START)) {
 82             drawer.closeDrawer(GravityCompat.START);
 83         } else {
 84             super.onBackPressed();
 85         }
 86     }
 87 
 88     /**
 89      * 右上角按钮图标
 90      * @param menu
 91      * @return
 92      */
 93     @Override
 94     public boolean onCreateOptionsMenu(Menu menu) {
 95         getMenuInflater().inflate(R.menu.main, menu);
 96         return true;
 97     }
 98 
 99     //右上角按钮点击事件
100     @Override
101     public boolean onOptionsItemSelected(MenuItem item) {
102         return super.onOptionsItemSelected(item);
103     }
104 
105     //左侧抽屉menu点击事件
106     @SuppressWarnings("StatementWithEmptyBody")
107     @Override
108     public boolean onNavigationItemSelected(MenuItem item) {
109         int id = item.getItemId();
110 
111         if (id == R.id.nav_home) {
112 
113         } else if (id == R.id.nav_notifications) {
114 
115         } else if (id == R.id.nav_send) {
116 
117         } else if (id == R.id.nav_share) {
118 
119         }
120 
121         drawer.closeDrawer(GravityCompat.START);
122         return true;
123     }
124 
125 }

知识点:

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

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

 1 package com.example.lanxingren.imitating9gag.fragment;
 2 
 3 import android.os.Bundle;
 4 import android.support.v4.app.Fragment;
 5 import android.support.v7.widget.LinearLayoutManager;
 6 import android.support.v7.widget.RecyclerView;
 7 import android.view.LayoutInflater;
 8 import android.view.View;
 9 import android.view.ViewGroup;
10 
11 import com.example.lanxingren.imitating9gag.R;
12 import com.example.lanxingren.imitating9gag.adapter.NewsAdapter;
13 import com.example.lanxingren.imitating9gag.bean.NewsBean;
14 
15 import java.util.ArrayList;
16 import java.util.List;
17 
18 /**
19  */
20 public class HomeFragment extends Fragment {
21 
22     @Override
23     public void onCreate(Bundle savedInstanceState) {
24         super.onCreate(savedInstanceState);
25     }
26 
27     @Override
28     public View onCreateView(LayoutInflater inflater, ViewGroup container,
29                              Bundle savedInstanceState) {
30         return inflater.inflate(R.layout.fragment_home, container, false);
31     }
32 
33     @Override
34     public void onStart() {
35         super.onStart();
36         List<NewsBean> newsBeans = new ArrayList<NewsBean>();
37 
38         for (int i = 0; i < 30; i++) {
39             newsBeans.add(new NewsBean("这是第 " + Integer.toString(i+1) + " 条有趣的段子!",
40                     "http://ws4.sinaimg.cn/mw600/6c560b83ly1fruncq3z03j20ks0rs41b.jpg", 0));
41         }
42 
43         LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
44 
45         RecyclerView recyclerView = getView().findViewById(R.id.recyclerView);
46         recyclerView.setAdapter(new NewsAdapter(newsBeans));
47         recyclerView.setLayoutManager(linearLayoutManager);
48     }
49 }

 

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

 1 package com.example.lanxingren.imitating9gag.adapter;
 2 
 3 import android.content.Context;
 4 import android.support.annotation.NonNull;
 5 import android.support.v7.widget.CardView;
 6 import android.support.v7.widget.RecyclerView;
 7 import android.view.LayoutInflater;
 8 import android.view.View;
 9 import android.view.ViewGroup;
10 import android.widget.ImageView;
11 import android.widget.TextView;
12 
13 import com.example.lanxingren.imitating9gag.R;
14 import com.example.lanxingren.imitating9gag.bean.NewsBean;
15 import com.squareup.picasso.Picasso;
16 
17 import java.util.List;
18 
19 public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.NewsHolder> {
20 
21     private List<NewsBean> myNewsList;
22     private Context myContext;
23 
24     static class NewsHolder extends RecyclerView.ViewHolder {
25         CardView cardView;
26         TextView textView;
27         ImageView imageView;
28 
29         private NewsHolder (View view) {
30             super(view);
31             cardView = (CardView) view;
32             textView = view.findViewById(R.id.item_text);
33             imageView = view.findViewById(R.id.item_image);
34         }
35     }
36 
37     public NewsAdapter (List<NewsBean> newsList) {
38         this.myNewsList = newsList;
39     }
40 
41     @Override
42     public int getItemCount() {
43         int count = 0;
44         if (myNewsList != null) {
45             count = myNewsList.size();
46         }
47         return count;
48     }
49 
50     @NonNull
51     @Override
52     public NewsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
53         if (myContext == null) {
54             myContext = parent.getContext();
55         }
56         View view = LayoutInflater.from(myContext).inflate(R.layout.item_news, parent, false);
57         return new NewsHolder(view);
58     }
59 
60     @Override
61     public void onBindViewHolder(@NonNull NewsHolder holder, int position) {
62         NewsBean newsBean = myNewsList.get(position);
63         holder.textView.setText(newsBean.getTitle());
64 
65         int screenWidth = myContext.getResources()
66                 .getDisplayMetrics()
67                 .widthPixels;
68         Picasso.with(myContext)
69                 .load(newsBean.getPicUrl())
70                 .resize(screenWidth, 0)
71                 .into(holder.imageView);
72     }
73 }

 

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

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

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

  1 <?xml version="1.0" encoding="utf-8"?>
  2 <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
  3     android:layout_width="match_parent"
  4     android:layout_height="wrap_content"
  5     xmlns:app="http://schemas.android.com/apk/res-auto"
  6     android:layout_marginVertical="10dp"
  7     app:cardCornerRadius="0dp"
  8     android:elevation="0dp">
  9     <LinearLayout
 10         android:layout_width="match_parent"
 11         android:layout_height="wrap_content"
 12         android:orientation="vertical"
 13         >
 14         <RelativeLayout
 15             android:layout_width="match_parent"
 16             android:layout_height="40dp"
 17             android:gravity="center">
 18             <TextView
 19                 android:id="@+id/item_text"
 20                 android:textStyle="bold"
 21                 android:textColor="@color/colorPrimary"
 22                 android:layout_width="wrap_content"
 23                 android:layout_height="wrap_content"
 24                 android:layout_alignParentLeft="true"
 25                 android:layout_marginLeft="14dp"/>
 26             <ImageView
 27                 android:layout_width="wrap_content"
 28                 android:layout_height="wrap_content"
 29                 android:layout_alignParentRight="true"
 30                 android:src="@drawable/ic_expand_more_gray_24dp"
 31                 android:layout_marginRight="14dp"/>
 32         </RelativeLayout>
 33         <ImageView
 34             android:id="@+id/item_image"
 35             android:layout_width="match_parent"
 36             android:layout_height="wrap_content"
 37             android:scaleType="fitCenter"/>
 38         <LinearLayout
 39             android:layout_width="match_parent"
 40             android:layout_height="40dp"
 41             android:orientation="horizontal"
 42             android:gravity="center">
 43             <LinearLayout
 44                 android:layout_width="0dp"
 45                 android:layout_weight="1"
 46                 android:layout_height="wrap_content"
 47                 android:orientation="horizontal">
 48                 <ImageView
 49                     android:layout_width="0dp"
 50                     android:layout_weight="1"
 51                     android:layout_height="wrap_content"
 52                     android:src="@drawable/ic_thumb_up_gray_24dp"
 53                     android:scaleType="fitEnd"/>
 54                 <TextView
 55                     android:layout_width="0dp"
 56                     android:layout_weight="1"
 57                     android:layout_height="wrap_content"
 58                     android:gravity="center"
 59                     android:text="5k"/>
 60                 <ImageView
 61                     android:layout_width="0dp"
 62                     android:layout_weight="1"
 63                     android:layout_height="wrap_content"
 64                     android:src="@drawable/ic_thumb_down_gray_24dp"
 65                     android:scaleType="fitStart"/>
 66             </LinearLayout>
 67 
 68             <View
 69                 android:layout_width="1dp"
 70                 android:layout_height="20dp"
 71                 android:background="?android:listDivider"/>
 72 
 73             <LinearLayout
 74                 android:layout_width="0dp"
 75                 android:layout_weight="1"
 76                 android:layout_height="wrap_content"
 77                 android:gravity="center">
 78                 <ImageView
 79                     android:layout_width="0dp"
 80                     android:layout_weight="1"
 81                     android:layout_height="wrap_content"
 82                     android:src="@drawable/ic_comment_gray_24dp"
 83                     android:scaleType="fitEnd"
 84                     android:paddingRight="5dp"/>
 85                 <TextView
 86                     android:layout_width="0dp"
 87                     android:layout_weight="1"
 88                     android:layout_height="wrap_content"
 89                     android:gravity="left"
 90                     android:text="46"
 91                     android:paddingLeft="5dp"/>
 92             </LinearLayout>
 93 
 94             <View
 95                 android:layout_width="1dp"
 96                 android:layout_height="20dp"
 97                 android:background="?android:listDivider"/>
 98 
 99             <LinearLayout
100                 android:layout_width="0dp"
101                 android:layout_weight="1"
102                 android:layout_height="wrap_content"
103                 android:gravity="center">
104                 <ImageView
105                     android:layout_width="0dp"
106                     android:layout_weight="1"
107                     android:layout_height="wrap_content"
108                     android:src="@drawable/ic_share_gray_24dp"
109                     android:scaleType="fitEnd"
110                     android:paddingRight="5dp"/>
111                 <TextView
112                     android:layout_width="0dp"
113                     android:layout_weight="1"
114                     android:layout_height="wrap_content"
115                     android:gravity="left"
116                     android:text="分享"
117                     android:paddingLeft="5dp"/>
118             </LinearLayout>
119         </LinearLayout>
120     </LinearLayout>
121 
122 </android.support.v7.widget.CardView>

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

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

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

 

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