安卓实战开发之底部导航
提示:本篇文中对基本的操作不做细节讲解,后续会通过链接方式跳转到对应的知识点。
文章目录
前言
本片文章将使用Fragment来构建底部导航。底部导航栏我们使用矢量图标(png/jpg等也可以,其中优劣自行体会),这里我们通过Android Stdio 自带的矢量图标下载功能进行下载。
一、准备
1、矢量图标准备
- 通过New——>Vector Asset,点击Clip Art打开图标的搜索界面。注意搜索时是需要联网的,且通过英文搜索。比如像常用的首页图标,我们这里输入“home”。
- 选择完之后,点击OK,设置图片属性,Next选择资源文件位置(main),最终会保存到res资源目录的drawable目录下
- 下载后是一个xml文件,我们可以更改它的颜色和宽高。这里我们将同一个图标设置拷贝成两份颜色不同的图标,用作选中(蓝色)和未选中(灰色)使用
- 这里我们总共准备了五个选中图标、五个未选中图标
2、文件准备
- 有五个图标我们就需要五个Fragment文件>
- 最后再准备一个底部导航的布局文件,这里的底部导航是一个独立的布局,我们是通过include(包含)来引入这个底部导航的布局的。(布局自行构建这里不做讲解)
注意:这里需要设置默认图标,和默认文字颜色都设为未选中的颜色(第一个图标和文字控件设置为选中状态)
二、使用步骤
1.在fragment中动态加载布局
- 继承Fragment
代码如下(示例):
public class HomeFragment extends Fragment {
}
- 通过快捷键(CTRL + O)快速生成onCreateView和onViewCreate方法。
代码如下(示例):
//onCreateView的意思是:创建视图。
//我们在这个方法里来动态加载布局
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}
//onViewCreate的意思是:视图创建
//我们在这个方法里面操作视图控件、逻辑方法就像是Activity的onCreate方法。
//但是要注意我们操作的控件的时候使用的是:onCreateView返回view,通过传递实参到onViewCreated方法的形参View中,所以在初始化控件的时候是view.findViewById();
//最后要注意如果有些方法使用不了,大概是因为那些方法实在Actiivty中(未作具体了解),所以需要先调用getActivity()方法。
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
onCreateView方法分析:
一、首先我们可以看到这个方法的返回值类型为View(视图),所以我们需要创建View对象加载视图然后返回
1、动态加载视图的写法:首先需要实例化LayoutInflater对象
LayoutInflater layoutInflater = getLayoutInflater();
View view = layoutInflater.inflate(R.layout.fragment,container,false);
2、我们可以通过快捷键(CTRL + P)来查看inflate的参数列表(@LayoutRes int resource,ViewGroup root,boolean attachToRoot)
从字面意思来分析:
a.@LayoutRes int resource
第一个参数是一个@LayoutRes int类型的参数,通多CTRL + 左击查看@LayoutRes注解,我们可以通过注释知道这个整数参数返回的是一个布局资源引用(即R.layout.资源名)
b.ViewGroup root
第二个参数是一个ViewGroup(视图组),相当于一个容器,我们可以将若干个控件或布局放进去组成一个大的布局。root(根,底部的意思)看到这个单词我们通常会联想到权限,我们也可以理解为:我们引入的布局只在这个视图组内生效。
c.boolean attachToRoot
第三个参数是布尔类型,attachToRoot(附加到根),这里我们不附加到根(false),如果填的是附加(true)就会报错。原因和方案可以参考这个博主的文章:https://blog.csdn.net/yaolingrui/article/details/7339913
二、这里我们可以看到在onCreateView方法里面有LayoutInflater对象和ViewGroup对象,所以既然有意思和明显就是要你使用参数列表里面传过来的实参
三、最完整的写法是:View view = layoutInflater.inflate(R.layout.fragment,container,false); 这里我们就简写了
2.构建导航框架
- 在activity_main.xml中引入导航的布局
代码如下(示例):
<!--通过layout这个属性来添加写好的底部导航布局-->
<include
android:id="@+id/include"
layout="@layout/navigation"
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
- 添加FrameLayout布局管理器
代码如下(示例):
<FrameLayout
android:id="@+id/framelayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints" />
- 我们可以嵌套一个滑动组件ScrollView包裹FrameLayout,注意引入的导航布局include不要嵌套进去,不然底部导航也会被滑动(那就不能算是底部导航了,懂我意思吧)
代码如下(示例):
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent">
<FrameLayout
android:id="@+id/framelayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints" />
</ScrollView>
- 效果
3、初始化控件
- 在MainActivity.java中初始化控件
代码如下(示例):
//全局变量
//底部导航图片控件声明
private ImageView[] imageViews;
private int[] imageViewsId = {
R.id.imageView01,
R.id.imageView02,
R.id.imageView03,
R.id.imageView04,
R.id.imageView05,
};
//底部导航文字控件声明
private TextView[] textViews;
private int[] textViewsId = {
R.id.textView01,
R.id.textView02,
R.id.textView03,
R.id.textView04,
R.id.textView05
};
//五个Fragment布局声明
private Fragment[] fragments = {
new HomeFragment(),
new ServiceFragment(),
new PartyFragment(),
new NewsFragment(),
new PersionFragment()
};
- 我们使用数组的方式声明,所有可以通过数组遍历来赋值:
//声明数组
imageViews = new ImageView[imageViewsId.length];
textViews = new TextView[textViewsId.length];
//初始化
for (int i = 0; i < imageViewsId.length; i++) {
imageViews[i] = findViewById(imageViewsId[i]);
textViews[i] = findViewById(textViewsId[i]);
}
4、 给FrameLayout添加默认布局
//这里需要注意的是getSupportFragment()方法
//还有一个getFragment()方法,这两方法等效
//区别在一所属包不同。第一个是在androidx中,第二个是在android中。
//所以要注意这里的我们之前准备的Fragment是继承自哪个包的Fragment,根据那个包选择方法,否则会报错
getSupportFragmentManager().beginTransaction().replace(R.id.framelayout,new HomeFragment()).commit();
5、实例化一个View.OnClickListener对象来写一个点击事件
View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
for (int i = 0; i < 5; i++) {
if(view.getId() == imageViewsId[i]) {
//替换布局
getSupportFragmentManager().beginTransaction().replace(R.id.framelayout, fragments[i]).commitAllowingStateLoss();
//设置图标文字状态
imageViews[i].setImageDrawable(getDrawable(drawablesTrue[i]));
textViews[i].setTextColor(getResources().getColor(R.color.text_true));
}else{
imageViews[i].setImageDrawable(getDrawable(drawablesFalse[i]));
textViews[i].setTextColor(getResources().getColor(R.color.text_false));
}
}
}
};
注意:这里在调用图标资源的时候使用getDrawable()方法,但是在调用颜色资源的时候得先调用getResources()再调用getColor()方法,否则会报错。
分析:这个View.OnClickListener对象需要重写onClick方法,在onClick方法里会传入一个View对象的实参,这个对象实质上就是设置点击事件的控件。所以view.getId()方法实质上就是获取这个控件的id值,就是R.id.imageView01。
R.id.imageView01是一个@IdRes int类型的数据,本质上就是一串特定的数字。
6、给控件设置写好的点击事件
for (int i = 0,j = 0; i < imageViewsId.length; i++,j++) {
imageViews[i].setOnClickListener(onClickListener);
textView[i].setOnClickListener(onClickListener);
}
三、 总结
至此,底部导航完成。如有问题欢迎留言评论~