仿9GAG制作过程(一)
有话要说:
准备开始学习Android应用程序的一个完整的设计过程。准备做一个仿9GAG的APP,前端界面设计+后台数据爬虫+后台接口设计,整个流程体验一遍。今天准备先把前端界面的框架给完成了。
成果图:
布局代码:
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生成的,生成步骤如下:
接下来说一下主页面的实现,是通过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为右对齐填充。
写的比较仓促,如有疑问或者错误的地方欢迎留言指正。