在配置文件build.gradle(project:xxx)中将代码修改为

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
buildscript { 
repositories {
//添加以下4行代码
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
maven { url 'https://maven.aliyun.com/repository/public' }
mavenCentral()
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.0
}

allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
maven { url 'https://maven.aliyun.com/repository/public' }
mavenCentral()
mavenLocal()
jcenter()
maven {
url "https://maven.google.com"
}
maven { url "https://jitpack.io" }
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

AppbarLayout继承自LinearLayout,它就是一个垂直方向的LinearLayout,在LinearLayout的基础上添加了一些材料设计的概念和特性,即滑动手势。它可以让你定制在某个可滑动的View(如:ScrollView ,ListView ,RecyclerView 等)滑动手势发生改变时,内部的子View 该做什么动作。子View应该提供滑动时他们期望的响应的动作Behavior,通过setScrollFlags(int),或者xml 中使用属性:

1
app:layout_scrollFlags

注意:AppbarLayout 严重依赖于CoordinatorLayout,必须用于CoordinatorLayout 的直接子View,如果你将AppbarLayout 放在其他的ViewGroup 里面,那么它的这些功能是无效的。

#####AppbarLayout 子View 的几种动作
上面说了 AppbarLayout 可以定制当某个可滑动的View滑动手势改变时内部子View的动作,通过app:layout_scrollFlags来指定,那么现在我们就看一下layout_scrollFlags有哪几种动作。layout_scrollFlags有5种动作,分别是

1
scroll,enterAlways,enterAlwaysCollapsed,exitUntilCollapsed,snap

我们来分别看一下这五种动作的含义。
#####1. scroll :
子View 添加layout_scrollFlags属性 的值scroll 时,这个View将会随着可滚动View(如:ScrollView,以下都会用ScrollView 来代替可滚动的View )一起滚动,就好像子View 是属于ScrollView的一部分一样。
(子view指的不一定是Toolbar)

1
2
3
4
5
6
7
8
<android.support.v7.widget.Toolbar       
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="AppbarLayout"
app:titleTextColor="@color/white"
app:layout_scrollFlags="scroll"
>
</android.support.v7.widget.Toolbar>

#####2. enterAlways
子View 添加layout_scrollFlags属性 的值有enterAlways 时, 当ScrollView 向下滑动时,子View 将直接向下滑动,而不管ScrollView 是否在滑动。注意:要与scroll 搭配使用,否者是不能滑动的。
代码如下:

1
2
3
4
5
6
7
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="AppbarLayout"
app:titleTextColor="@color/white"
app:layout_scrollFlags="scroll|enterAlways"
/>

#####3. enterAlwaysCollapsed
enterAlwaysCollapsed 是对enterAlways 的补充,当ScrollView 向下滑动的时候,滑动View(也就是设置了enterAlwaysCollapsed 的View)下滑至折叠的高度,当ScrollView 到达滑动范围的结束值的时候,滑动View剩下的部分开始滑动。这个折叠的高度是通过View的minimum height (最小高度)指定的。
补充说明:要配合scroll|enterAlways 一起使用

1
2
3
4
5
6
7
8
9
10
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="200dp"
android:minHeight="?attr/actionBarSize"
app:title="AppbarLayout"
android:gravity="bottom"
android:layout_marginBottom="25dp"
app:titleTextColor="@color/white"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
/>

#####4. exitUntilCollapsed
当ScrollView 滑出屏幕时(也就时向上滑动时),滑动View先响应滑动事件,滑动至折叠高度,也就是通过minimum height 设置的最小高度后,就固定不动了,再把滑动事件交给 scrollview 继续滑动。

1
2
3
4
5
6
7
8
9
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="200dp"
android:minHeight="?attr/actionBarSize"
app:title="AppbarLayout"
android:gravity="bottom"
app:titleTextColor="@color/white"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
/>

#####5. snap
意思是:在滚动结束后,如果view只是部分可见,它将滑动到最近的边界。比如,如果view的底部只有25%可见,它将滚动离开屏幕,而如果底部有75%可见,它将滚动到完全显示。

解释:可能这段话有点难懂,解释一下,就是说,比如在屏幕的顶部有个View ,高度200dp,我向上滑动40%后停止,也就 40% 滑出了屏幕,剩下的60%留在屏幕,那么这个属性就会自动将屏幕外的40% 滑回屏幕,结果的整个View都留在屏幕上,相反,如果我向上将60%的部分滑出屏幕,然后停止滑动,那么这个属性会将剩下的40% 也自动滑出屏幕,结果是整个View都在屏幕之外。这就是上面所说的滑动到最近的边界。

1
2
3
4
5
6
7
8
9
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="200dp"
android:minHeight="?attr/actionBarSize"
app:title="AppbarLayout"
android:gravity="bottom"
app:titleTextColor="@color/white"
app:layout_scrollFlags="scroll|snap"
/>

####AppbarLayout 的几个重要方法
介绍一下AppbarLayout几个常用且重要的方法

  • addOnOffsetChangedListener 当AppbarLayout 的偏移发生改变的时候回调,也就是子View滑动。
  • getTotalScrollRange 返回AppbarLayout 所有子View的滑动范围
  • removeOnOffsetChangedListener移除监听器
  • setExpanded (boolean expanded, boolean animate)**设置AppbarLayout 是展开状态还是折叠状态,animate 参数控制切换到新的状态时是否需要动画
  • setExpanded (boolean expanded)** 设置AppbarLayout 是展开状态还是折叠状态,默认有动画

####完整代码:

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
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

<!--app:layout_scrollFlags
1、scroll: 所有想滚动出屏幕的view都需要设置这个flag, 没有设置这个flag的view将被固定在屏幕顶部。
例如,TabLayout 没有设置这个值,将会停留在屏幕顶部。
2、enterAlways: 设置这个flag时,向下的滚动都会导致该view变为可见,启用快速“返回模式”。
3、enterAlwaysCollapsed: 当你的视图已经设置minHeight属性又使用此标志时,你的视图只能已最小高度进入,
只有当滚动视图到达顶部时才扩大到完整高度。
4、exitUntilCollapsed: 滚动退出屏幕,最后折叠在顶端。-->
<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/Theme.AppCompat.Light" />

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

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

转自:https://www.jianshu.com/p/ac56f11e7ce1

(这不算是Materual Design中的新控件,只是第一次用到特此记录一下)
安卓提供了一个计时器组件:Chronometer,该组件extends TextView,因此都会显示一段文本,但是它显示的时间是从某个起始时间开始过去了多少时间,它只提供了android:format一个属性用于指定计时器的计数格式。

####Chronometer的用法很简单,它支持如下用法:

  • getBase():返回时间。
  • setBase(long base):设置计时器的起始时间。
  • start():开始计时。
  • stop():停止计时。
  • setFormat(String format):设置显示时间的格式。
  • setOnChronometerTickListener(Chronometer.OnChronometerTickListener listener):为计时器绑定监听事件。

####示例:
一个一分钟的计时器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">

<Chronometer
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/chronometer"
android:textColor="#ff0303"
android:textSize="12pt"
android:layout_gravity="center_horizontal"
android:focusable="true" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动"
android:id="@+id/button"
android:layout_gravity="center_horizontal" />

</LinearLayout>
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
public class MainActivity extends AppCompatActivity {

Chronometer ch ;
Button start ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
// 获取计时器组件
ch=(Chronometer)findViewById(R.id.chronometer);
// 获取开始组件
start = (Button)findViewById(R.id.button);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//设置开始计时时间
ch.setBase(SystemClock.elapsedRealtime()-timeDifference*1000);//timeDifference真正想要显示的时间 单位秒
//启动计时器
ch.start();
}
});
//为计时器绑定监听事件
ch.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener()
{
@Override
public void onChronometerTick(Chronometer ch)
{
// 如果从开始计时到现在超过了60s
if (SystemClock.elapsedRealtime() - ch.getBase() > 60 * 1000 {
ch.stop();
start.setEnabled(true);
}
}
});
}
}

####效果图:
image

####FloatingActionButton简称FAB。
一. 对于App或某个页面中是否要使用FloatingActionButton必要性:
FAB代表一个App或一个页面中最主要的操作,如果一个App的每个页面都有FAB,则通常表示该App最主要的功能是通过该FAB操作的。
为了突出FAB的重要性,一个页面最好只有一个FAB。
二. FloatingActionButton大小
通常有两种尺寸
1. 56 * 56dp :默认的大小,最常用的尺寸。
2. 40 * 40 dp :Mini版。
当然也可以改它的大小。
FAB中间的图标,google推荐的大小是:24 * 24dp
三. 哪些操作推荐使用FloatingActionButton
如: 添加,收藏,编辑,分享,导航等,如下图:
image.png
而如:删除,警告,错误,剪切等操作,则不推荐使用FloatingActionButton。
四. FAB的使用
Android 5.0 中引入Material Design,FAB为Android Design Support Library支持包中的中Material Design控件,要使用FAB,先要引入Design Support Library包,如在build.gradle中加入:
  compile 'com.android.support:design:26.0.0'
FAB的继承关系:
image.png
FAB常用属性

  1. app:elevation=”8dp”:阴影的高度,elevation是Android 5.0中引入的新属性,设置该属性使控件有一个阴影,感觉该
    控件像是“浮”起来一样,这样达到3D效果。对应的方法:setCompatElevation(float)
  2. app:fabSize=”normal”:FAB的大小,为normal时,大小为:56 * 56dp ,为mini时,大小为: 40 * 40 dp。
  3. app:backgroundTint=”#31bfcf”:FAB的背景颜色。
  4. app:rippleColor=”#e7d16b”:点击FAB时,形成的波纹颜色。
  5. app:borderWidth=”0dp”:边框宽度,通常设置为0 ,用于解决Android 5.X设备上阴影无法正常显示的问题
  6. app:pressedTranslationZ=”16dp”:点击按钮时,按钮边缘阴影的宽度,通常设置比elevation的数值大!
    注意:引用上面属性的时候需要在根节点添加
    xmlns:app="http://schemas.android.com/apk/res-auto"
    示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <android.support.design.widget.FloatingActionButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scaleType="fitXY"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:layout_margin="16dp"
    android:src="@mipmap/fload_add"
    app:backgroundTint="@color/deepskyblue"
    app:elevation="5dp"
    app:pressedTranslationZ="12dp"
    app:fabSize="normal"
    app:borderWidth="0dp"
    app:rippleColor="#cccccc" />
    另外我们希望点击按钮一个颜色,正常状态一个颜色 以提高用户体验,那么就是drawable文件夹中创建文件 floatbutton.xml
    1
    2
    3
    4
    5
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/colorNormal"></item>
    <item android:state_pressed="true" android:drawable="@color/colorPressed"></item>
    </selector>
    然后我们设置FloatActionButton的backgroud为这个drawable文件即可,同时app:backgroundTint=””属性也可以不再设置
 

效果图:
image.png

Snackbar是Android支持库中用于显示简单消息并且提供和用户的一个简单操作的一种弹出式提醒。当使用Snackbar时,提示会出现在消息最底部,通常含有一段信息和一个可点击的按钮。下图是Gmail中删除一封邮件时弹出的Snackbar:
image
在上图中,最下方的黑色区域,包含左边文字和右边”撤销”字样的就是Snackbar。Snackbar在显示一段时间后就会自动消失。同样作为消息提示,Snackbar相比于Toast而言,增加了一个用户操作,并且在同时弹出多个消息时,Snackbar会停止前一个,直接显示后一个,也就是说同一时刻只会有一个Snackbar在显示;而Toast则不然,如果不做特殊处理,那么同时可以有多个Toast出现;Snackbar相比于Dialog,操作更少,因为只有一个用户操作的接口,而Dialog最多可以设置三个,另外Snackbar的出现并不影响用户的继续操作,而Dialog则必须需要用户做出响应,所以相比Dialog,Snackbar更轻量。
经过上面的比较,可以看出Snackbar可以用于显示用户信息并且该信息不需要用户立即做出反馈的时候。

#####一、如何使用Snackbar?
Snackbar没有公有的构造方法,但是提供了静态方法make方法:

1
2
static Snackbar make(View view, CharSequence text, int duration)
static Snackbar make(View view, int resId, int duration)

其中view参数是用于查找合适父布局的一个起点,下面分析源码的时候会解释到。如果父布局是一个CoordinatorLayout,那么Snackbar还会有别的一些特性:可以滑动消除;并且如果有FloatingActionButton时,会将FloatingActionButton上移,而不会挡住Snackbar的显示。
父布局不是CoordinatorLayout
在创建了一个Snackbar对象后,可以调用一些set**方法进行设置,其中setAction()方法用于设置右侧的文字显示以及点击事件,setCallback()方法用于设置一个状态回调,在Snackbar显示和消失的时候会触发方法。下面是一段创建Snackbar的代码:

1
2
3
4
5
6
7
8
9
Snackbar.make(view, "已删除一个会话", Snackbar.LENGTH_SHORT)
.setAction("撤销", new View.OnClickListener() {
@Override
public void onClick(View v) {

Toast.makeText(Main2Activity.this, "撤销了删除", Toast.LENGTH_SHORT).show();

}
}).show();

以上代码在一个按钮的点击事件中创建一个Snackbar并显示,内容模仿上面的Gmail例子,并且给“撤销”一个点击事件,只是简单的显示一个Toast。Activity的根布局是一个RelativeLayout,并且下部有一个FloatingActionButton,在Snackbar出现后,可以看到Snackbar遮挡了FlaotingActionButton的一部分
父布局是CoordinatorLayout
在父布局不是CoordinatorLayout的情况下,如果有FloaingActionButton,那么弹出的Snackbar会遮挡FloatingActionButton,为了解决这个问题,可以将父布局改成CoordinatorLayout,并且这会带来一个新特性,就是Snackbar可以通过右滑消失。代码一样,只是布局不同。直接看效果图:
image
可以看到当Snackbar出现时,FloatingActionButton会上移并且支持右滑消失。
Snackbar消失的几种方式
Snackbar显示只有一种方式,那就是调用show()方法,但是消失有几种方式:时间到了自动消失、点击了右侧按钮消失、新的Snackbar出现导致旧的Snackbar消失、滑动消失或者通过调用dismiss()消失。这些方式分别对应于Snackbar.Callback中的几个常量值。

  • DISMISS_EVENT_ACTION:点击了右侧按钮导致消失
  • DISMISS_EVENT_CONSECUTIVE:新的Snackbar出现导致旧的消失
  • DISMISS_EVENT_MANUAL:调用了dismiss方法导致消失
  • DISMISS_EVENT_SWIPE:滑动导致消失
  • DISMISS_EVENT_TIMEOUT:设置的显示时间到了导致消失
    Callback有两个方法:
    1
    2
    void    onDismissed(Snackbar snackbar, int event)
    void onShown(Snackbar snackbar)
    其中onShown在Snackbar可见时调用,onDismissed在Snackbar准备消失时调用。一般我们可以在onDismissed方法中正在处理我们所需要的操作,比如删除一封邮件,那么如果是点击了“撤销”按钮,那就应该不再删除邮件直接消失就可以了,但是对于其他的几种情况,就需要真正地删除邮件了(发送数据到后台等等…)。下面是模拟这样一段过程:
    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
    Snackbar.make(view, "已删除一个会话", Snackbar.LENGTH_SHORT).setAction("撤销", new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    }
    }).setCallback(new Snackbar.Callback() {
    @Override
    public void onDismissed(Snackbar snackbar, int event) {
    switch (event) {
    case Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE:
    case Snackbar.Callback.DISMISS_EVENT_MANUAL:
    case Snackbar.Callback.DISMISS_EVENT_SWIPE:
    case Snackbar.Callback.DISMISS_EVENT_TIMEOUT:
    //TODO 网络操作
    Toast.makeText(MainActivity.this, "删除成功", Toast.LENGTH_SHORT).show();
    break;
    case Snackbar.Callback.DISMISS_EVENT_ACTION:
    Toast.makeText(MainActivity.this, "撤销了删除操作", Toast.LENGTH_SHORT).show();
    break;
    }
    }
    @Override
    public void onShown(Snackbar snackbar) {
    super.onShown(snackbar);
    Log.i(TAG, "onShown");
    }
    }).show();

SwitchCompat是符合谷歌Material design的Selection control组件,与传统的Switch以及ToggleButton不同,v7包中的这个组件兼容了绝大多数低版本手机,令组件的兼容性得到了极大的提升。

好,首先来看看组件的效果
Light theme
Dark theme
相当漂亮,然后我们看看谷歌的官方文档(请自带梯子)
https://developer.android.com/reference/android/widget/Switch.html
没法看也没关系,看其它大神的翻译

属性 bird
showText:true/false 决定是否显示开关按钮上的文字
splitTrack: true/false 开关的样式
switchMinWidth 开关的最小宽度
switchPadding 文字和开关的最小距离
switchTextAppearance 开关文字样式
thumbTextPadding 文字距两侧的距离
thumbTint 开关上按钮的颜色
thumbTintMode 按钮样式
track 轨道,类似音乐进度条可滑动
trackTint 轨道颜色
trackTintMode 轨道样式
textOff 设置按钮关闭状态显示的文字
textOn 设置按钮打开状态显示的文字
thumb 引用主题颜色
#####No,如果你按照上面的属性去修改此控件的各种颜色,你就浪费它了!
如果你的Activity继承自android.support.v7.app.AppCompatActivity
那就去res/values/styles.xml文件中去修改
1
<item name="colorAccent">#666666</item>

这个主题颜色吧~~
瞬间switchCompat按钮和后面的滑动条就获得了主题颜色,特效全开

记得AndroidManifest.xml里需要设定我们的Theme.AppCompat主题的子类

1
android:theme="@style/AppTheme"

####一、简述
TabLayout是Android Support Design库的新控件,可以用来实现开源框架ViewPageIndicator的效果(在MaterialDesign没出来之前基本都用这玩意儿吧~),TabLayout相比它使用上更加简单,且不一定要跟ViewPager一起使用,毕竟谷歌做出来的,稳定性更是不用说啦,此外,本文还会仔细列出本人对该控件的探索过程,从而实现一些控件本身没法实现的自定义效果,下面来看看它都有哪些操作吧。
####二、使用
1、创建Tab及Tab的点击事件
要使用TabLayout,一般会先在布局文件中放好,如:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

然后在Activity中找到它,对它进行设置,如果不跟ViewPager一起使用的话,可以对TabLayout手动添加多个tab,并设置其点击事件,如:

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
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
// 添加多个tab
for (int i = 0; i < title.length; i++) {
TabLayout.Tab tab = mTabLayout.newTab();
tab.setText(title[i]);
// tab.setIcon(R.mipmap.ic_launcher);//icon会显示在文字上面
mTabLayout.addTab(tab);
}
// 给tab设置点击事件
mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
Toast.makeText(getApplicationContext(), title[tab.getPosition()], Toast.LENGTH_SHORT).show();
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {

}

@Override
public void onTabReselected(TabLayout.Tab tab) {

}
});
}

这里比较有意思的是Tab的创建需要调用TabLayout对象的newTab()方法,而不是直接new出一个Tab。Tab除了可以设置文字外,还能设置Icon,甚至可以自定义View,分别调用的是setIcon()和setCustomView(),有兴趣的可以试试看,上面代码效果如下:
image
####2、自定义TabLayout样式
这个TabLayout还是挺好看的,但开发中难免会要定制TabLayout的样式,如设置默认或选中文字的颜色和大小等,还好,TabLayout尽可能多的提供了这些自定义属性,可以让开发者很方便的修改样式,下面来看看都有哪些控件属性可以设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--设置Tab指示器-->
app:tabIndicatorColor=""
app:tabIndicatorHeight=""

<!--设置Tab位置及显示模式-->
app:tabGravity=""
app:tabMode=""

<!--设置Tab文字样式-->
app:tabSelectedTextColor=""
app:tabTextAppearance=""
app:tabTextColor=""

<!--设置Tab的宽度、背景、内间距-->
app:tabMaxWidth=""
app:tabMinWidth=""
app:tabBackground=""
app:tabPadding=""
  1. 设置Tab指示器
    TabLayout的指示器默认颜色是color.xml中的colorAccent,通过TabLayout提供的自定义属性,可以设置指示器的高度和颜色,如果不想显示指示器(Indicator),可以将其高度设置为0dp或设置其颜色为透明,这里为演示,我就把显示指示器(Indicator)的高度提高,颜色改为刺眼的红眼,如下:
    1
    2
    3
    4
    5
    6
    <android.support.design.widget.TabLayout
    android:id="@+id/tabLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabIndicatorColor="@color/red"
    app:tabIndicatorHeight="8dp"/>
    image
    设置Tab指示器样式

####2. 设置Tab位置及显示模式
TabLayout的显示模式(tabMode)默认是固定不可滚动(fixed),位置(tabGravity)默认填满(fill)整个TabLayout,我们先保持app:tabMode=”fixed”,把tabGravity的值换成fill和center对比下,为了方便对比,我把背景也设置了。

1
2
3
4
5
6
7
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/colorPrimaryDark"
app:tabGravity="fill" // 再换成center
app:tabMode="fixed"/>

image
image
当tab比较多的时候,一个屏幕宽度容纳不下,这时候就需要让TabLayout可以横向滚动了,只需要修改app:tabMode=”scrollable”即可。注意,当app:tabMode=”scrollable”时,app:tabGravity=””不管取什么值都不会生效。

1
2
3
4
5
6
7
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/colorPrimaryDark"
app:tabGravity="center"
app:tabMode="scrollable"/>

image
####3. 设置Tab文字样式
上面的效果不好看,我想让它默认文字颜色为灰色,选中时文字为白色,文字大小为16sp,但TabLayout没有提供直接设置文字大小的属性,这时候就需要用到app:tabTextAppearance=””了。操作如下:
######在Style.xml中声明文字样式

1
2
3
4
<style name="TabLayout.TabText" parent="TextAppearance.Design.Tab">
<item name="android:textSize">16sp</item>
<item name="textAllCaps">false</item>
</style>

其中除了可以设置字体大小外,还可以设置英文是否都全部大写显示。textAllCaps的默认值为true,即英文全部大写。
#####在布局文件中设置TabLayout的文字相关属性

1
2
3
4
5
6
7
8
9
10
11
12
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/colorPrimaryDark"
app:tabGravity="center"
app:tabMode="scrollable"
...
app:tabSelectedTextColor="@android:color/white"
app:tabTextAppearance="@style/TabLayout.TabText"
app:tabTextColor="@android:color/darker_gray"/>

好了,看看效果如何:
image
好了,关于Tab的宽度、内间距等设置比较简单,自己需要的时候试试吧,这里就不演示了。
####3、与ViewPager结合
上面通过对TabLayout的单独使用学习了TabLayout的样式自定义、创建Tab及设置Tab的点击事件等,可以说常用的也就那些了,下面来看看TabLayout如何与ViewPager的结合使用。这种需求也是很常见的,界面顶部有一个标签栏,中下部是与标签对应的内容,可以左右滑动,同时标签也跟随其切换,相反的,在切换标签时,内容部分也会跟着变化,不太明白的可以参考下“今日头条”APP的首页界面。这样的效果就可以用TabLayout+ViewPager+Fragment来实现。
#####1. 先在布局文件中放好TabLayout和ViewPager:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/colorPrimaryDark"
app:tabMode="scrollable"
app:tabSelectedTextColor="@android:color/white"
app:tabTextColor="@android:color/darker_gray"/>

<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

#####2. 在代码中设置TabLayout与ViewPager相互关联:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
mViewPager = (ViewPager) findViewById(R.id.viewPager);
MyViewPagerAdapter adapter = new MyViewPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(adapter);

// 适配器必须重写getPageTitle()方法
mTabLayout.setTabsFromPagerAdapter(adapter);
// 监听TabLayout的标签选择,当标签选中时ViewPager切换
mTabLayout.setOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager));
// 监听ViewPager的页面切换,当页面切换时TabLayout的标签跟着切换
mViewPager.setOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
}

这三句代码不难理解,就字面上的意思,但是这三句代码都已经过时,因为要关联TabLayout与ViewPager就得写三句代码似乎是麻烦了一点点(其实我觉得还好吧),所以TabLayout提供了可以通过一句代码搞定两者关联的方法:setupWithViewPager(),因此,上面的代码可以简化如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
mViewPager = (ViewPager) findViewById(R.id.viewPager);
MyViewPagerAdapter adapter = new MyViewPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(adapter);

// 关联TabLayout与ViewPager,且适配器必须重写getPageTitle()方法
mTabLayout.setupWithViewPager(mViewPager);
}

来看下效果:
image
关联TabLayout与ViewPager相当简单,只要注意ViewPager适配器需重写getPageTitle()方法,这里顺便贴出Demo中适配器的代码:

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
class MyViewPagerAdapter extends FragmentPagerAdapter {

private final String[] title = new String[]{
"推荐", "热点", "视频", "深圳", "通信",
"互联网", "问答", "图片", "电影",
"网络安全", "软件"};

public MyViewPagerAdapter(FragmentManager fm) {
super(fm);
}

@Override
public Fragment getItem(int i) {
Fragment fragment = new TextFragment();
Bundle bundle = new Bundle();
bundle.putString("title", title[i]);
fragment.setArguments(bundle);
return fragment;
}

@Override
public int getCount() {
return title.length;
}

@Override
public CharSequence getPageTitle(int position) {
return title[position];
}
}

####拓展
上面部分是TabLayout的正规使用说明,而这部分是对TabLayout的进一步探索,同时将列出本人在这个过程中的探索思路,可以说是对TabLayout的进一步自定义吧。废话不多说,下面就直接开车了。
假如,你手中的APP设计稿中有如下的三个需求那该怎么办:

  • 为TabLayout添加分割线,且分割线距离上下存在间距。
  • 选中时tab字体变大,未选中时tab字体变小。
  • 指示器(Indicator)不要充满整个标签(Tab)

简单的说就是为TabLayout添加分割线、设置不同状态下的字体大小和指示器的“长度”,这些在TabLayout中并没有提供直接的修改方法,你可能会想,那我们对TabLayout进行源码分析,然后通过反射等手段拿到其中的控件来设置?不!有时候解决问题不要循规蹈矩,应该适当转变下思路,或许解决问题的方法并不需要去看源码那么困难(如果你是大神,就当我没说),下面看我操作:

从结构上我们可以知道TabLayout(就是HorizontalScrollView)并不是直接就包裹这些Tab的,而是包裹了一个LinearLayout,然后这些Tab放在这个LinearLayout中,此外,可以发现Tab里包含了一个TextView,到这里对前面2个需求是不是有点想法了呢?
######为TabLayout添加分割线
LinearLayout自带就有设置分割线的方法,我们可以通过它来添加分割线,也没什么好说的,直接上代码:

1
2
3
4
5
6
7
mLinearLayout = (LinearLayout) mTabLayout.getChildAt(0);
// 在所有子控件的中间显示分割线(还可能只显示顶部、尾部和不显示分割线)
mLinearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
// 设置分割线的距离本身(LinearLayout)的内间距
mLinearLayout.setDividerPadding(20);
// 设置分割线的样式
mLinearLayout.setDividerDrawable(ContextCompat.getDrawable(this, R.drawable.divider_vertical));

divider_vertical.xml:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ccc"/>
<size android:width="1dp" />
</shape>

这样,分割线就有了。
image
看起来有点怪是吧,这是因为我们前面设置的app:tabBackground=”@color/colorPrimaryDark”只是给Tab设置了背景色,而不是给Tab的父级控件LinearLayout设置,这个LinearLayout默认的背景色是白色,所以才会是这个样子,解决方法自然就是给LinearLayout设置跟Tab一样的背景色就好了。

1
2
3
mLinearLayout = (LinearLayout) mTabLayout.getChildAt(0);
...
mLinearLayout.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));

这样就完美的为TabLayout设置分割线了。
image
####为TabLayout设置不同状态下的字体大小(并不能成功)
用同样的方式拿到Tab中的文本控件,判断当前是否被选中,再对该文本控件进行字体大小设置就欧了。借助上面拿到的用来包裹Tab的LinearLayout(mLinearLayout),遍历LinearLayout中的子控件,拿到一个个的子view(即Tab),再从Tab中拿到文本控件设置文字大小。

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
// 默认让所有没有选中的Tab的文字设置为小字体
for (int i = 0; i < mTabLayout.getTabCount(); i++) {
((TextView) ((LinearLayout) mLinearLayout.getChildAt(i)).getChildAt(1)).setTextSize(10);
// 也可以这么写,一样的
// ((TextView) ((LinearLayout) ((LinearLayout) mTabLayout.getChildAt(0)).getChildAt(i)).getChildAt(0)).setTextSize(12);
}
// 再把当前被选中的Tab文字设置为大字体
((TextView) ((LinearLayout) mLinearLayout.getChildAt(mTabLayout.getSelectedTabPosition())).getChildAt(1)).setTextSize(30);

// 当选中的Tab切换时,再调整Tab的字体大小
mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
((TextView) ((LinearLayout) mLinearLayout.getChildAt(tab.getPosition())).getChildAt(1)).setTextSize(30);
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {
((TextView) ((LinearLayout) mLinearLayout.getChildAt(tab.getPosition())).getChildAt(1)).setTextSize(12);
}

@Override
public void onTabReselected(TabLayout.Tab tab) {

}
});

上面代码中把得到的Tab强转成LinearLayout,这是因为Tab实际上是TabView,而TabView继承自LinearLayout,所以可以这样转换,我们可以看下TabLayout的newTab()方法:
image
然而事实并不如意,完全没有效果,去看了下源码,也不是很确定,我的猜想是这样的,当我们对Tab中的文本控件设置字体大小后,TabView的onMeasuer()方法会被重新调用,而这个方法里就对文字大小重新进行赋值,导致文字大小没法按上面的方式进行修改。
image
所以,文字的大小只能通过Style的方法去修改,且只能统一设置选中和未选中的文字大小,故,这个需求没法完成。
####自定义指示器长度
其实这有点标题党的意思了,TabLayout的指示器长度没法指定,它原本多长就是多长,但可以通过设置Tab外间距的方式,让指示器看起来像是与Tab保持一定距离,这里我在网上找到了方法,方法如下:

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
// 设置TabLayout的“长度”
setIndicator(mTabLayout,10,10);

// 具体方法(通过反射的方式)
public void setIndicator(TabLayout tabs, int leftDip, int rightDip) {
Class<?> tabLayout = tabs.getClass();
Field tabStrip = null;
try {
tabStrip = tabLayout.getDeclaredField("mTabStrip");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

tabStrip.setAccessible(true);
LinearLayout llTab = null;
try {
llTab = (LinearLayout) tabStrip.get(tabs);
} catch (IllegalAccessException e) {
e.printStackTrace();
}

int left = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, leftDip, Resources.getSystem().getDisplayMetrics());
int right = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rightDip, Resources.getSystem().getDisplayMetrics());

for (int i = 0; i < llTab.getChildCount(); i++) {
View child = llTab.getChildAt(i);
child.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
params.leftMargin = left;
params.rightMargin = right;
child.setLayoutParams(params);
child.invalidate();
}
}

这个setIndicator()方法主要是通过反射的方式,先拿到mTabStrip(其实就是TabLayout直接包裹的LinearLayout),再遍历出mTabStrip中的子控件Tab,再设置Tab的外间距,为了证明就是设置了Tab的外间距,这里我分别对mTabStrip设置了背景色和不设置其背景色,来看看对比:
mTabStrip设置了背景色
mTabStrip不设置背景色
设置了背景色看起来效果还马马虎虎吧,但这样的方式没办法让指示器的长度比文字长度短(无奈~)。好了,不管这个了,既然我前面说了mTabStrip其实就是TabLayout直接包裹的LinearLayout,那通过这个LinearLayout来设置也是可以的,证明一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 得到TabLayout包裹的LinearLayout并设置背景色
mLinearLayout = (LinearLayout) mTabLayout.getChildAt(0);
mLinearLayout.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
...
// 设置LinearLayout中子View(Tab)的外间距
int left = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, Resources.getSystem().getDisplayMetrics());
int right = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, Resources.getSystem().getDisplayMetrics());
for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
View tabView = mLinearLayout.getChildAt(0);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
params.leftMargin = left;
params.rightMargin = right;
tabView.setLayoutParams(params);
}

通过查找控件方式,让指示器与Tab存在间距

最后附上Demo链接

https://github.com/GitLqr/MaterialDesignDemo

作者:CSDN_LQR
链接:https://www.jianshu.com/p/bbefb97cccdd