Kotlin语言环境下在Fragment里面操作控件的注意事项:
在onCreateView()方法和onStart()方法里面直接通过id操作控件的话,会要求必须加findViewById()获取控件id,否则就会报"must not be null"这个错误。
但是在onViewCreated()方法里面就不用这样。另外自定义的方法里面也是必须获取控件id。
可以这么理解:
在Fragment里面,除了在onViewCreated()方法里面直接操作控件或者调用操作控件的方法外,其他任何地方操作控件或者调用操作控件的方法都是需要通过findViewById()获取控件id,否者就会报上面的错误。
Navigation各字段含义:
<fragment
android:id="@+id/nav_fragment"
android:layout_width="match_parent"
android:layout_height=" match_parent "
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_host"
app:defaultNavHost="true"/>
android:id:在Activity里面用于通过supportFragmentManager获取NavHostFragment对象。
android:name:是NavHostFragment,它实现了NavHost,这是一个用于放置管理destination的空视图。指向NavHost的实现类NavHostFragment。
app:navGraph:指向Navigation graph配置文件,用于将NavHostFragment和nav_graph.xml关联起来。
app:defaultNavHost:默认值为false,当该值为false的时候,当前Activity中的Fragment使用了Navigation,且使用Navigation跳转到下一个Fragment,在下一个Fragment页面中点击了Back键会退出当前Activity。为true的时候是回到上一个Fragment中,如果上一个Fragment为null才会退出当前Activity。类似于我们处理WebView的back事件。
<navigation xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:id="@+id/nav_host"
app:startDestination="@id/homeFragment"
tools:ignore="UnusedNavigation">
<fragment
android:id="@+id/homeFragment"
android:name="com.androidjetpack.navigation.HomeFragment"
android:label="home_dest"
tools:layout="@layout/home_dest" >
<action
android:id="@+id/action_homeFragment_to_oneFragment"
app:destination="@id/oneFragment" />
</fragment>
<fragment
android:id="@+id/oneFragment"
android:name="com.androidjetpack.navigation.OneFragment"
android:label="OneFragment"
tools:layout="@layout/flow_step_one_dest">
<!-- action标签不是必须的,比如后面的fragment标签就没有action标签,尤其是最后一个fragment -->
<!-- action里面id的规则:action_本fragment的id_to_下一个fragment的id -->
<action
android:id="@+id/action_oneFragment_to_deepLinkFragment"
app:destination="@+id/deepLinkFragment"/>
</fragment>
<fragment
android:id="@+id/deepLinkFragment"
android:name="com.androidjetpack.navigation.DeepLinkFragment"
android:label="DeepLinkFragment"
tools:layout="@layout/deep_link_dest">
<!-- argument标签不是必须的 -->
<argument
android:name="flowStepNumber"
app:argType="integer"
android:defaultValue="1"/>
</fragment>
</navigation>
name:指定Fragment的路径。
tools:layout:指定布局文件,如果不配置该属性在Design面板会看不见预览。
action:字面理解就是动作,作用是fragment之间进行切换。
app:startDestination:指定这个Fragment是start-destination。
app:destination:目的地,指定要跳转到Fragment的id。
app:id:定义这个action的id,代码里执行跳转时要用到。
enterAnim、exitAnim、popEnterAnim、popExitAnim:是页面切换和弹窗动画
如果某个Fragment是最后一个Fragment,那么我们就可以直接省略他的action。
def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
并在这个build.gradle里面添加:
apply plugin: "androidx.navigation.safeargs"
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
ZeroFragment及对应的xml布局文件:
xml文件布局
<TextView xmlns:android=http://schemas.android.com/apk/res/android
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:text="@string/deep"
android:textSize="32sp"
tools:ignore="HardcodedText" />
kotlin代码实现:
class ZeroFragment :Fragment(){
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_zero, container, false)
}
}
OneFragment及对应的xml布局文件:
xml文件布局:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:ignore="HardcodedText">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
android:text="@string/one"
android:textSize="32sp" />
</LinearLayout>
kotlin代码实现:
class OneFragment :Fragment(){
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_one, container, false)
}
}
HomeFragment及对应的xml布局文件:
xml文件布局:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context="com.androidjetpack.navigation.HomeFragment"
tools:ignore="HardcodedText">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
android:text="@string/home"
android:textSize="32sp" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@color/black"
android:padding="12dp"/>
</LinearLayout>
kotlin代码实现:
class HomeFragment :Fragment(){
var mActivity:NavigationPrimaryActivity? = null
override fun onAttach(context: Context) {
super.onAttach(context)
mActivity = (context as NavigationPrimaryActivity)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
//在onCreateView()方法和onStart()方法里面直接通过id操作控件的话,会要求必须加findViewById()获取控件id,
//否则就会报"must not be null"这个错误。
//但是在onViewCreated()方法里面就不用这样。另外自定义的方法里面也是必须获取控件id。
//可以这么理解:在Fragment里面,除了在onViewCreated()方法里面直接操作控件或者调用操作控件的方法外,
//其他任何地方操作控件或者调用操作控件的方法都是需要通过findViewById()获取控件id,否者就会报上面的错误。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
operationView()
}
fun operationView() {
open1.setOnClickListener {
mActivity?.printLoginfo("不能滑动,没什么鸟用")
}
tvTitle.text = "皇者号天令"
}
}
1)、在工程的res文件夹下创建新的文件夹:navigation;
2)、单击navigation文件夹,右键菜单选择New->Navigation Resource File,在File name后面输入文件名:nav_host,选择Resource type为Navigation,点击ok即可创建Navigation graph资源文件:nav_host.xml。
3)、把新建的Fragment引入到该资源文件内:
按照上面截图的顺序,在上面第二步创建的nav_host.xml文件里面依次操作。在标记有3的搜索框处搜索要添加的Fragment,然后从Design模式切换到Split模式,在里面编辑功能:跳转的顺序、action行为活动细节等。最终代码如下:
<navigation xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:id="@+id/nav_host"
app:startDestination="@id/homeFragment"
tools:ignore="UnusedNavigation">
<fragment
android:id="@+id/homeFragment"
android:name="com.androidjetpack.navigation.HomeFragment"
android:label="home_dest"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_oneFragment"
app:destination="@id/oneFragment" />
</fragment>
<fragment
android:id="@+id/oneFragment"
android:name="com.androidjetpack.navigation.OneFragment"
android:label="OneFragment"
tools:layout="@layout/fragment_one">
<!--action标签不是必须的,比如后面的fragment标签就没有action标签,尤其是最后一个fragment-->
<!--action里面id的规则:action_本fragment的id_to_下一个fragment的id-->
<action
android:id="@+id/action_oneFragment_to_deepLinkFragment"
app:destination="@+id/deepLinkFragment"/>
</fragment>
<fragment
android:id="@+id/deepLinkFragment"
android:name="com.androidjetpack.navigation.ZeroFragment"
android:label="DeepLinkFragment"
tools:layout="@layout/fragment_zero">
<!-- 下面的argument标签不是必须的 -->
<!--argument:类似于Activity的跳转传参,只不过传参取参更加方便简单,
如果某个Fragment要接收从别的Fragment传过来的参数,那么可以通过argument标签处理,
如果我们在kotlin代码里面没有传参,那么我们获取到的就是通过argument标签设定的默认值,具体见下面。-->
<!--下面defaultValue的类型必须与字段argType必须保持一致,比如下面argType="integer",defaultValue="1"-->
<argument
android:name="flowStepNumber"
app:argType="integer"
android:defaultValue="1"/>
</fragment>
</navigation>
argument:
类似于Activity的跳转传参,只不过传参取参更加方便简单,如果某个Fragment要接收从别的Fragment传过来的参数,那么可以通过argument标签处理,如果我们在kotlin代码里面没有传参,那么我们获取到的就是通过argument标签设定的默认值,具体如下:
//传值且跳转
val flowStepNumberArg=1
val action = HomeFragmentDirections.nextAction(flowStepNumberArg)
findNavController().navigate(action)
//取值
val safeArgs: FlowStepFragmentArgs by navArgs()
val flowStepNumber = safeArgs.flowStepNumber
<menu xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app="http://schemas.android.com/apk/res-auto">
<!--这里的id对应着nav_host.xml里面的fragment标签的id-->
<!--这里的item的顺序对应着nav_host.xml里面的fragment的顺序-->
<!--showsAction属性解释:
1、alaways:这个值会使菜单项一直显示在ActionBar上。
2、ifRoom:如果有足够的空间,这个值会使菜单显示在ActionBar上。
3、never:这个值菜单永远不会出现在ActionBar是。
4、withText:这个值使菜单和它的图标,菜单文本一起显示。
5、collapseActionView 声明了这个操作视窗应该被折叠到一个按钮中,
当用户选择这个按钮时,这个操作视窗展开。否则,这个操作视窗在默认的情况下是可见的,
并且即便在用于不适用的时候,也要占据操作栏的有效空间。-->
<item
android:id="@+id/homeFragment"
android:icon="@mipmap/home"
android:title="home"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/oneFragment"
android:icon="@mipmap/focus"
android:title="one" />
<item
android:id="@+id/deepLinkFragment"
android:icon="@mipmap/fire"
android:title="deep" />
</menu>
注意:
这里的item.id和Navigation graph资源文件中的fragment.id必须保持一致,否则,恭喜入坑。
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
tools:context=".navigation.NavigationPrimaryActivity">
<!--android:name:是NavHostFragment,它实现了NavHost,这是一个用于放置管理destination的空视图。指向NavHost的实现类NavHostFragment-->
<!--app:navGraph:指向Navigation graph配置文件,用于将NavHostFragment和nav_graph.xml关联起来。-->
<!--app:defaultNavHost:默认值为false,当该值为false的时候,当前Activity中的Fragment使用了Navigation,
且使用Navigation跳转到下一个Fragment,在下一个Fragment页面中点击了Back键会退出当前Activity。
为true的时候是回到上一个Fragment中,如果上一个Fragment为null才会退出当前Activity。类似于我们处理WebView的back事件。-->
<fragment
android:id="@+id/nav_fragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_host"
app:defaultNavHost="true"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav_view"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/write"
app:menu="@menu/bottom_nav_menu"/>
</LinearLayout>
上面fragment标签的id在Activity里面将会用到,这一点要特别注意,其余注意事项以及说明,代码里面解释的很清楚了。
接下来就是Activity里面的逻辑处理,一共分四步:
class NavigationPrimaryActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_navigation_primary)
//第一步获取到NavHostFragment,这里的nav_fragment就是本Activity对应的xml里面Fragment标签的id。
val host: NavHostFragment = supportFragmentManager.findFragmentById(R.id.nav_fragment) as NavHostFragment? ?: return
//第二步获取到NavController,这一步的内容几乎都是死的。
val navController = host.navController
//第三步配置BottomNavigationView,这里的bottom_nav_view就是Activity对应的xml里面BottomNavigationView控件的id。
val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
//这里,将导航控件BottomNavigationView与navController关联起来。
bottomNav?.setupWithNavController(navController)
//第四步添加路由监听,这一步的内容几乎都是死的。
navController.addOnDestinationChangedListener {
_, destination, _ ->
val dest: String = try {
resources.getResourceName(destination.id)
} catch (e: Resources.NotFoundException) {
destination.id.toString()
}
toast("Navigated to $dest")
}
}
}
要注意一个细节:
第三部其实分两个细节,一是初始化BottomNavigationView的id,二是将导航控件BottomNavigationView与navController关联起来。运行效果如下:
虽然Fragment之间传递参数的几率比较低,但是多少还是会用到。
在HomeFragment中的onViewCreated(view: View, savedInstanceState: Bundle?)函数中添加以下代码:
//其中,id值action_homeFragment_to_oneFragment是我们在nav_host.xml资源内添加的action属性。
//这里是定义了一个无参跳转:homeFragment_to_oneFragment
open1.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_homeFragment_to_oneFragment))
上面定义了一个从homeFragment跳转到oneFragment的动作。
其中R.id. action_homeFragment_to_oneFragment是我们在nav_host资源内添加的action属性的id
R.id. action_homeFragment_to_oneFragment中:
action:意指活动;
homeFragment_to_oneFragment:从homeFragment到oneFragment
这个目前尚未研究成功,无法编译生成带参跳转的重载方法,待后面成功了在完成这部分内容。
最后来说一个关于带参跳转的问题的解决方案,问题如下:
Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option.
android {
......
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}