零碎的安卓开发小技巧

注意
本文最后更新于 2022-04-19,文中内容可能已过时。

Android 开发一直处于「面向谷歌编程, 下一次又不知道该怎么写」的状态 (指不会 startActivity). 前些天重写了一个东西, 又用到了一些奇奇怪怪的技巧. 遂记录, 供日后直接 copy.

可以省去 findViewById 这种繁琐的操作. (虽然实际上还是调用的 findViewById)

需要先在 build.grade 里, android 条目下加一条:

1
2
3
4
5
6
7
android {
  ...
  viewBinding {
      enabled = true
  }
  ...
}

假如有一个 layout 名为 example_layout.xml, 编译之后, 它会生成一个驼峰重命名加上 Binding 的类, 如 ExampleLayoutBinding. 获得实例化的引用以后就可以直接用 id 名获得 view 的引用了.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class ExampleActivity : AppCompatActivity() {

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.i("Home Activity", "onCreate: ")
        binding = ActivityHomeBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }

    fun useBinding() {
        binding.text_view_example.setText("Hello View Binding!")
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ExampleFragment : Fragment() {

    private var _binding: FragmentExampleBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentExampleBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    fun useBinding() {
        binding.text_view_example.setText("Hello View Binding!")
    }
}

需要注意的是记得在 onDestroyView() 中清除引用.

没学过 kotlin 不会委托, 反正用 by 写 ViewModel 的实例化很方便, 就记着先.

1
2
3
4
5
6
7
8
9
class ExampleActivity : AppCompatActivity() {

    private val viewModel : ExampleViewModel by viewModels()

    fun useViewModle() {
        viewModle.fun()
    }

}
1
2
3
4
5
6
7
8
9
class ExampleFragment : Fragment() {

    private val viewModel : ExampleViewModel by activityViewModels()

    fun useViewModle() {
        viewModle.fun()
    }

}

ViewModel 中处理数据, Fragment 或者 Activity 中观察数据的变化, 然后重绘 UI. 实现数据处理和 UI 绘制分离的 MVC 模式.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class ExampleViewModel : ViewModel() {

    private val userName: MutableLiveData<String> by lazy { MutableLiveData() }

    fun setName(name : String) {
        userName.postValue(name)
    }

    fun getUserName() : LiveData<String> {
        return userName
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class ExampleActivity : AppCompatActivity() {

    private val viewModel : ExampleViewModel by viewModels()

    fun observeUserName() {
        viewModel.getUserName.observe(this) {
            Toast.makeText(requireContext(), "$it", Toast.LENGTH_LONG).show()
        }
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class ExampleFragment : Fragment() {

    private val viewModel : ExampleViewModel by activityViewModels()

    fun observeUserName() {
        viewModel.getUserName.observe(viewLifecycleOwner) {
            Toast.makeText(requireContext(), "$it", Toast.LENGTH_LONG).show()
        }
    }

}

这玩意是之前遇到了一个问题: Fragment 恢复 (按下返回的那种) 时无论 MutableLiveData 中的变量是否改变 observer 都会获得通知.

然后找到了个大佬重写了个只会通知一次的 SingleLiveEvent.kt.

用法和 MutableLiveData 一样.

Fragment 的导航, 快速实现 Fragment 的跳转, 非常非常非常方便, 还有可视化.

先在 build.grade 的 dependencies 中加:

1
2
3
4
dependencies {
    implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
    implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
}

在 res 文件夹下新建一个 Android Resourse Directory, 并选择 type 为 navigation

/android-dev-tips/img/navigation-new-directory.png
新建 navigation

然后在 navigation 下新建 Navigation Resourse File, 同时在 layout 中的 activity.xml 中添加 NavHostFragment 视图. 并选择 navGraph 为刚刚新建的 navigation.xml. 这个 NavHostFragment 是 “用以导航的 Fragment”.

之后就可以去 navigation.xml 中可视化编辑了. 点击绿色加号的东西, 添加一个 destination, 一般为 Fragment. 然后可以非常直觉地添加跳转 action.

/android-dev-tips/img/navigation-visual.png
可视化编辑 navigation

在 Fragment 中执行跳转 action 也非常方便:

1
    Navigation.findNavController(binding.fragmentOne).navigate(R.id.action_fragmentOne_to_fragmentTwo)

其中 findNavController 的参数是当前 Fragment View, navigate 的参数是 action 的 id.

导航使用的是压栈式跳转. 可以设置 popUpTo 指定弹栈到某个 Fragment, 而不是只弹一个. 这样可以实现 “过渡页面” 的操作 — — 只需要把 A -> B -> C 中 B -> C 的 action 设置一下 popUpTo A. 这样 C 返回的时候就直接返回到 A 了.

1
2
    requireActivity().onBackPressedDispatcher.addCallback(this) {
    }

什么都不写就是禁用了. 暂时没搞懂监听事件的过程, 是不是像其他监听事件那样返回 true 或者 false 实现拦截或继续上一级监听. 正好当时做的需要禁用, 先不管了