Material Design
是由Google推出的全新的设计语言,谷歌表示,这种设计语言旨在为手机、平板电脑、台式机和“其他平台”提供更一致、更广泛的“外观和感觉”。Material Design的核心思想是把物理世界中的体验带入屏幕之中再配合虚拟世界的灵活特性,达到最贴近真实的体验。作为Android开发人员,我们也是有必要了解它的一些基础控件的使用方法。好了那么下面我们就开始学习吧。
ToolBar
我们都知道ActionBar,生成一个默认的Android项目后顶部的标题栏就是ActionBar,因为ActionBar只能位于活动的顶部并且不能实现Material Design的效果,Google已经不建议我们使用ActionBar,那么我们如何去除它呢?更改项目主体不就行了。
更改app:theme如下:
好了ActionBar去掉了,但是我还想要标题栏啊,别着急,我们不是还有Toolbar嘛。
首先在布局文件里添加toolbar并指定背景色。
MainActivity代码如下:
public class MainActivity extends AppCompatActivity {
Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar=findViewById(R.id.tl);
setSupportActionBar(toolbar);
}
}
代码很简单,就是将ActionBar替换成了ToolBar,运行效果如下:
好像不太对劲吧?这标题栏上的文字怎么成黑的了?我明明记得以前都是白色的啊,其实这与app主题的设置有关系,andorid默认的主题是背景色是浅的,陪衬颜色是深的,这也就是文字是深色的缘故,我们只要给toolbar指定一个深色主题就好了
但这样好会有问题,那就是Toolbar中的菜单按钮中的菜单栏文字也会变为黑色,这又与我们平常见到的不一致了,所以还需要给弹出的菜单栏单独设置主题样式。
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
上面的话可能有些不好理解,所以自己敲一遍是很重要的,这会让你加深理解。
好了现在toolbar已经可以和ActionBar一样显示了,现在在给toolbar加上一些菜单项
首先在res下新建menu文件夹并新建menu_toolbar
现在解释一下showAsAction属性,always表示会一直出现在标题栏上,ifRoom表示如果标题栏放得下则显示在标题栏上否则就显示在菜单中,never表示显示在菜单中。
调用onCreateOptionsMenu(Menu menu)方法加载菜单文件。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_toolbar,menu);
return true;
}
效果如下:
感觉这和ActionBar没什么区别啊,是的,如果单单只是用ToolBar的话确实没有什么明显的效果,但ToolBar与其他控件组合便会有一些“神奇”的效果。ToolBar先说到这里。
NavigationView
侧滑菜单在应用中很常见,一种简单的方法就是包裹上一层DrawerLayout,自己再去写侧滑栏的布局,Material Design提供了NavigationView来实现侧滑栏的效果。现在就来使用一下吧。
首先导入Design Support库
implementation 'com.android.support:design:26.1.0'
在MainActivity的布局文件中包裹上DrawerLayout并添加NavigationView控件。
其中headerLayout为侧滑栏顶部布局,这里我就放了一张图片,menu为菜单项和上面toolbar设置菜单文件一样,这里不再赘述。注意不要忘记添加layout_gravity,这里的作用是说明侧滑栏从哪一侧拉出,默认为left,也就是左侧拉出,这里使用的是start,表示跟随系统语言的行文方式(中文,英文在左边,阿拉伯文在右边...)。效果如下:
额图片颜色去哪了?其实这里是有一个统一的默认颜色,如果你不喜欢换点就是了。现在我们就换一下。
public class MainActivity extends AppCompatActivity {
Toolbar toolbar;
NavigationView nav;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar=findViewById(R.id.tl);
setSupportActionBar(toolbar);
nav=findViewById(R.id.nav);
//这里传入null即可恢复菜单栏里icon的颜色
nav.setItemIconTintList(null);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_toolbar,menu);
return true;
}
}
至于NavigationView的其他功能相信聪明的你一定比我这个小白要厉害的多,自学肯定不在话下。
现在还有一个小问题,用户怎么知道从左边拉出菜单还是从右边拉出菜单,可能用户连有侧滑栏存在这个事实都不一定知道,那怎么办呢?Material Design建议实在标题栏最左侧加入一个导航按钮,用户可以点击该按钮拉出侧滑栏。
public class MainActivity extends AppCompatActivity {
Toolbar toolbar;
NavigationView nav;
DrawerLayout drawerLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar=findViewById(R.id.tl);
setSupportActionBar(toolbar);
nav=findViewById(R.id.nav);
drawerLayout=findViewById(R.id.dl);
//这里传入null即可恢复菜单栏里icon的颜色
nav.setItemIconTintList(null);
//注意这里的ActionBar其实就是我们设置的ToolBar
ActionBar supportActionBar = getSupportActionBar();
//显示HomeAsUp按钮
supportActionBar.setDisplayHomeAsUpEnabled(true);
//设置小图标
supportActionBar.setHomeAsUpIndicator(R.mipmap.menu);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_toolbar,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
//跟随系统语言行文方向拉出侧滑栏
drawerLayout.openDrawer(Gravity.START);
}
return true;
}
}
这里要说明的是HomeAsUp按钮原本是一个返回上一个页面功能按钮,通过修改icon图标和onOptionsItemSelected()对它的功能进行修改实现拉出侧滑栏的效果。
好了现在侧滑栏也有了,现在我想实现像新闻app那样点击选项卡切换页面的效果怎么办?TabLayout就可以实现这样的效果。
TabLayout
Tablayout继承自HorizontalScrollView,用作页面切换指示器,因使用简便功能强大而广泛使用在App中。现在我们就学习下如何使用吧。
首先我们想象页面切换的步骤,当我们点击一个选项卡就切换到对应的页面去,页面的切换我们可以使用ViewPager来实现,只要将TabLayout与View建立起联系就可以了。
在布局文件里添加TabLayout
设置选项卡名称
public void initTab(){
//选项卡名称
String[] tabs=new String[]{"英超","中超","挪超","西甲","德甲","法甲","意甲","中甲","荷甲","英冠"};
for (int i=0;i
显示效果如下:
可以看出TabLayout与ToolBar重叠在了一起,原因很简单因为他们都是FrameLayout的子控件,哪有人可能会说了我用LinearLayout不就行了,当然是可以的,但我们想要的是"心连着心永远在一起",这时候就需要AppBarLayout的帮助了。
AppBarLayout
AppBarLayout 继承自LineaLayout,所以在设计布局时可以参考LinearLayout的布局方式进行布局,AppBarLayout是在LinearLayou上加了一些材料设计的概念,它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作。说白了就是例如当RecyclerView或者被NestedScrollView包裹的控件滑动滑动时被AppBarLayout包裹的子View能够跟着一起滑动,而这些都是系统已经实现好的,只要在布局文件中声明就好。当然这一切还需要一个控件的配合,它就是CoordinatorLayout,这个我们会在后面进行说明。
那我们现在在布局文件中添加AppBarLayout作为ToolBar和TabLayout的父控件。
现在我们看来看一下运行后的效果:
其实到现在为止我们还是利用的LinearLayout的特性让TabLayout位于ToolBar的下边。
ViewPager的创建和数据初始化如下:
public void initViewPager(){
List fragments=new ArrayList<>();
for (int i=0;i
MyViewPagerAdapter
package com.zxl.domain.materialdemo5;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import java.util.List;
/**
* Created by jackal on 2019/2/15.
*/
public class MyViewPagerAdapter extends FragmentPagerAdapter {
private List list;
private String[] titles;
public MyViewPagerAdapter(FragmentManager fm, List list,String[] titles) {
super(fm);
this.list=list;
this.titles=titles;
}
@Override
public Fragment getItem(int position) {
return list.get(position);
}
@Override
public int getCount() {
return list.size();
}
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
}
FruitFragment
package com.zxl.domain.materialdemo5;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Created by jackal on 2019/2/15.
*/
public class FruitFragment extends Fragment {
RecyclerView rc;
private List fruits;
private static final int[] imageIds=new int[]{R.mipmap.apple,R.mipmap.banana,R.mipmap.cherry,R.mipmap.lemon,R.mipmap.watermelon};
private static final String[] fruitNames=new String[]{"苹果","香蕉","樱桃","柠檬","西瓜"};
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_fruit, container, false);
rc=view.findViewById(R.id.rc);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//初始化RecyclerView数据
initData();
//初始化RecyclerView
initView();
}
public void initData(){
fruits=new ArrayList<>();
for (int i=0;i<30;i++){
//随机生成30个水果
Random random=new Random();
int index = random.nextInt(imageIds.length);
Fruit fruit=new Fruit(fruitNames[index],imageIds[index]);
fruits.add(fruit);
}
}
public void initView(){
rc.setLayoutManager(new GridLayoutManager(getContext(),2));
rc.setAdapter(new MyRecyclerViewAdapter(getContext(),fruits));
}
}
MyRecyclerViewAdapter
package com.zxl.domain.materialdemo5;
import android.content.Context;
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 java.util.List;
/**
* Created by jackal on 2019/2/15.
*/
public class MyRecyclerViewAdapter extends RecyclerView.Adapter{
private Context context;
private List list;
public MyRecyclerViewAdapter(Context context,List list){
this.context=context;
this.list=list;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_fruit, parent, false);
MyViewHolder holder=new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.iv.setImageResource(list.get(position).getImageId());
holder.tv.setText(list.get(position).getName());
}
@Override
public int getItemCount() {
return list.size();
}
class MyViewHolder extends RecyclerView.ViewHolder{
ImageView iv;
TextView tv;
public MyViewHolder(View itemView) {
super(itemView);
iv=itemView.findViewById(R.id.iv);
tv=itemView.findViewById(R.id.tv);
}
}
}
Fruit
package com.zxl.domain.materialdemo5;
/**
* Created by jackal on 2019/2/15.
*/
public class Fruit {
private String name;
private int imageId;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
public Fruit(String name,int imageId){
this.name=name;
this.imageId=imageId;
}
}
item_fruit.xml
实现效果如下所示:
这是怎么回事?怎么RecyclerView的上部显示不全?我明明是让RecyclerView(其实应该是ViewPager里的Fragment的布局里只有一个RecyclerView,而它占据了整个屏幕)占用整个屏幕的。原因是这样的AppBarLayout将ToolBar和TabLayout整合在一起作为了标题栏,那么标题栏默认实在顶部的,所以会导致RecyclerView部分被遮挡。那怎么解决呢?让RecyclerView避开标题栏?其实Material Design已经为我们解决了这个问题,就是使用CoordinatorLayout,通过CoordinatorLayout告诉ViewPager,大兄弟顶上有一个AppBarLayout,你往下移动一下被,这个过程是通过app:layout_behavior="@string/appbar_scrolling_view_behavior"来实现。这也就引出了CoordinatorLayout,现在我们来介绍一下CoordinatorLayout。
CoordinatorLayout
CoordinatorLayout是Android Design Support Library中比较难的控件。顾名思义,它是用来组织其子
View 之间协作的一个父 View。CoordinatorLayout 默认情况下可被理解为一个FrameLayout,它的布局方式默认是一层一层叠上去的。
当我们在ViewPager里添加入 app:layout_behavior="@string/appbar_scrolling_view_behavior"时,ViewPager会随着AppBarLayout的状态变化而进行移动。效果如下:
一切都完好如初。RecyclerView也可以正常显示了。那么如果想在向上滑动时隐藏ToolBar,则可以在ToolBar上加上app:layout_scrollFlags="scroll",这样在向上滑动时ToolBar会随着滑动而隐藏。向下滑到底端时ToolBar会重新出现。
其实layout_scrollFlags作用在ToolBar上作用就是可滑动的控件滑动时ToolBar也随着做出相应的改变。比如说设置为scroll时,ToolBar会随着向上滑动也一起向上滑动直至隐藏起来,enteralways会让ToolBar随着向下滑动而一块向下滑动直至显示。snap则会根据现在滑动的距离自动判断是隐藏还是显示。如果现在我想要的是标题栏在没有滑动时是一个样子,随着滑动折叠成另外一个样子该怎么做呢?其实再给ToolBar加上一层CollapsingToolbarLayout就好了。
CollapsingToolbarLayout
CollapsingToolbarLayout生来就是为Toolbar服务的,通过它ToolBar可以获得更多的变换,效果也会更加丰富。需要注意的是CollapsingToolbarLayout是一个FragmeLayout。
首先添加CollapsingToolbarLayout作为ToolBar的父控件和一个ImageView
这里AppBarLayout是"爷爷",CollapsingToolbarLayout是"爸爸",ToolBar是"儿子",儿子要听"爸爸"的,所以以前加在"儿子"身上的主题,flag现在统统交给"爸爸"。
效果如下:
原谅我选的图片是黑色背景导致menu都看不见了....大家可以看到图片填充了整个标题栏,这个可以理解应为我们就是让图片填充整个标题栏,但是Title文字怎么到了左下方了?其实这是CollapsingToolbarLayout默认让Toolbar显示在左下角,如果你不喜欢可以通过 app:expandedTitleGravity来更改,比如说改到右上方
标题跑到了右上方,还有一个app:contentScrim="xxx"指的是用于指定CollapsingToolbarLayout趋于折叠状态或者处于折叠状态时标题栏的背景色。这个就不在演示了。可是到这里还是没有实现标题栏的折叠,不过要做到这点也很简单,加上app:layout_collapseMode="",如果为pin则表示在折叠时不隐藏,parallax表示在折叠时进行隐藏。那么现在我想在滑动时折叠图片,而ToolBar不做改变就可以将ImageView的app:layout_collapseMode设置为parallax,ToolBar的设置成pin,将CollapsingToolbarLayout的layout_scrollFlags设置成exitUntilCollapsed,表示当在完成折叠之后CollapsingToolbarLayout会留在界面上。
缺图片
完整的布局文件如下:
好了绝大部分的知识已经说完了,现在还有几个小的知识点需要再说明一下。
FloatingActionButton
没什么好说的,他就是一个圆形的Button,还带了一点阴影显得比较有立体感,使用方法和普通Button差不多,不过在CollapsingToolbarLayout下可以通过 app:layout_anchor="@id/vp"和app:layout_anchorGravity="bottom|right"来制定它的锚点View和在该View的位置。
效果如下:
SnackBar
大家都知道吐司只能告诉用户一些信息,而无法做到用户与app的交互而SnackBar做到了这一点,通过setAction可以为用户提供一个按钮来进行交互。
public void initSnackBar(){
Snackbar.make(fab,"你好",Snackbar.LENGTH_LONG).setAction("answer", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"吃了吗",Toast.LENGTH_SHORT).show();
}
}).show();
}
效果如下:
CardView
CardView适用于实现卡片式布局效果的重要控件,由appcompat-v7库提供,实际上CardView也是一个FrameLayout,只是额外提供了圆角和阴影效果,看上去有立体的感觉。一般CardView都用在ListView或RecyclerView的item布局中。
引入CardView库
implementation 'com.android.support:cardview-v7:26.1.0'
其中elevation表示阴影效果
cardCornerRadius表示圆角弧度
效果如下:
总结
今天主要是学习了Material Design Support 库中的常用控件,总体来说不难,希望我的这篇文章能够对你有所帮助。
PS:感觉今天一个白天什么也没做,全在写文章了,这也是我的第三篇文章,也是我写过的最长的一篇,感觉大篇幅的废话连篇只是想让大家能看的明白,也是对自己知识的一次总结。本来想拆开来写但是作为一个Demo感觉还是写在一起才好。