Compose简介

Jetpack Compose 是一种全新的 Android UI 东西包,它答应开发者运用 Kotlin 语言编写声明式用户界面。

Jetpack Compose 的设计思想是运用函数式编程和响应式编程的形式来构建用户界面。开发者能够将 UI 组件界说为不可变的函数,这些组件能够接受参数、回来值,并且能够通过组合、嵌套等方式来创建杂乱的 UI 层次结构。而且,在 Jetpack Compose 中,能够运用状况、事情等响应式数据流来处理 UI 更新和交互。 Jetpack Compose 供给了丰厚的 UI 组件库,并且支撑自界说组件的开发和集成。它还供给了许多快捷的东西和功用,例如动画、主题、布局体系等,能够协助开发者快速构建高质量的用户体验。

总之,Jetpack Compose 是 Android 开发领域中一项重要的技术创新,它能够协助开发者愈加轻松、高效地构建和设计用户界面,并且供给了愈加现代化和强壮的东西和功用。

优势

  1. 更少的代码量。只需求运用kotlin而不用再分开编写kotlin与XML,跟踪变得跟简单。
  2. 更直观的代码。声明式UI,只需求构筑界面。
  3. 加速开发进程。传统View与Compose能够互调用,开发进程能够实时预览布局

下风

  1. Compose与原生View体系混合开发时,包体积增大
  2. 功用与传统XML布局相比并没有优势,通过多次迭代,目前与XML功用相等

开发实践

Talk is cheap, show me the code

登录页面

一个包含了头部图片、Logo、用户名暗码输入框和一个登录按钮的登录页。完成效果如下:

Jetpack Compose初体验

具体完成(解析在注释中):

// Composable注解函数标明此办法回来值为可组合视图
// 刷新次数和机遇不定,不要在其中处理事务(杂乱逻辑悉数放入ViewModel)  
@Composable  
fun LoginPage(loginViewModel: LoginViewModel, navigationActions: NavigationActions) {  
    // 在生命周期内采集StateFlow最新数据,以State形式出现  
    val uiState by loginViewModel.uiState.collectAsStateWithLifecycle()  
    // 获取Context办法  
    val context = LocalContext.current  
    // Box 可堆叠布局  
    Box {  
        // 图片  
        Image(  
            // 调整样式(width、height、background、padding、border、clickable等)  
            modifier = Modifier  
                // 扩展函数办法,转换成dp单位  
                .height(height = 265.dp)  
                .fillMaxWidth(),  
            // 图片缩放类型  
            contentScale = ContentScale.FillHeight,  
            // 图片内容  
            painter = painterResource(id = R.drawable.icon_heading_common),  
            // 图片描绘,xml中也有此属性  
            contentDescription = stringResource(  
                id = R.string.app_name  
            )  
        )  
        // 纵向布局  
        Column {  
            // 间隔,充任margin  
            Spacer(modifier = Modifier.height(250.dp))  
            Column(  
                Modifier  
                    // 圆角  
                    .clip(RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp))  
                    .background(Color.White)  
                    .fillMaxWidth(),  
                horizontalAlignment = Alignment.CenterHorizontally  
            ) {  
                // 间距  
                Spacer(modifier = Modifier.height(48.dp))  
                Image(  
                    painter = painterResource(id = R.drawable.icon_login_header_pos),  
                    contentDescription = stringResource(  
                        id = R.string.app_name  
                    )  
                )  
                Spacer(modifier = Modifier.height(48.dp))  
				// 笼统出来的输入框办法
                InputText(  
                    hint = "用户名",  
                    needEncrypt = false,  
                    value = uiState.userName,  
                    onValueChanged = {  
                        loginViewModel.onUserNameChanged(it)  
                        loginViewModel.judgeCanLogin()  
                    })  
                Spacer(modifier = Modifier.height(16.dp))  
                InputText(  
                    hint = "暗码",  
                    needEncrypt = true,  
                    value = uiState.userPassword,  
                    onValueChanged = {  
                        loginViewModel.onUserPasswordChanged(it)  
                        loginViewModel.judgeCanLogin()  
                    })  
                Spacer(modifier = Modifier.height(32.dp))  
                // 制作登录按钮  
                Button(  
                    shape = RoundedCornerShape(4.dp),  
                    modifier = Modifier  
                        .height(54.dp)  
                        .fillMaxWidth()  
                        .padding(horizontal = 20.dp)  
                        .background(Color(247, 248, 249)),  
                    colors = ButtonDefaults.buttonColors(  
                        backgroundColor = Color(0xff287dfa),  
                        contentColor = Color(0xffffffff),  
                        // 不可点击时的样式  
                        disabledBackgroundColor = Color(0xffbfbfbf),  
                        disabledContentColor = Color(0xffffffff),  
                    ),  
                    // 能否点击  
                    enabled = uiState.isLoginEnable,  
                    onClick = {  
                        // 履行ViewModel中的登录逻辑  
                        loginViewModel.login(loginSuccess = {  
                            // 登录成功回调,此为跳转到首页  
                            navigationActions.navigateToHome()  
                        }, loginFailed = {})  
                    }) {  
                    Text(  
                        "登录",  
                        color = Color(0xffffffff),  
                        fontSize = 16.sp  
                    )  
                }  
            }        }  
        // Loading显隐,只需求直接依据UiState中的布尔值,判断是否履行Loading的Composable办法  
        if (uiState.showLoading) {  
            Row {  
                Loading()  
            }  
        }  
    }  
}
/**  
 * 自界说的EditText  
 */
@Composable  
fun InputText(  
    hint: String,  
    needEncrypt: Boolean,  
    value: String,  
    onValueChanged: (String) -> Unit  
) {  
	// 等价于EditText
    OutlinedTextField(  
        // 圆角  
        shape = RoundedCornerShape(4.dp),  
        modifier = Modifier  
            .height(54.dp)  
            .fillMaxWidth()  
            .padding(horizontal = 20.dp)  
            .background(Color(247, 248, 249)),  
        colors = TextFieldDefaults.outlinedTextFieldColors(  
            backgroundColor = Color(  
                0xfff7f8f9  
            ),  
            // 边框颜色  
            focusedBorderColor = Color(  
                0xfff7f8f9  
            ),  
            unfocusedBorderColor = Color(  
                0xfff7f8f9  
            ),  
            textColor = Color(0xff666666)  
        ), placeholder = {  
            // Hint现需求传入组件  
            Text(  
                hint,  
                color = Color(0xffbfbfbf),  
                fontSize = 16.sp  
            )  
        },  
        // 字体选项  
        textStyle = TextStyle(fontSize = 16.sp),  
        // 输入内容显隐  
        visualTransformation = if (needEncrypt) PasswordVisualTransformation() else VisualTransformation.None,  
        // 输入框内显现的值  
        value = value,  
        // 输入内容改变回调  
        onValueChange = onValueChanged  
    )  
}

首页

效果如下:

Jetpack Compose初体验


// Preview注解能够实时预览UI效果
@Preview  
@Composable  
fun Home(homeViewModel: HomeViewModel = viewModel(factory = HomeViewModel.provideFactory())) {  
    // 回来键阻拦(在后面会提到)  
    BackPressHandler(onBackPressed = {  
        "点击了回来键".toast()  
    })  
    val uiState by homeViewModel.uiState.collectAsStateWithLifecycle()  
    Column(  
        modifier = Modifier  
            .fillMaxHeight()  
            .fillMaxWidth(),  
    ) {  
        Column(  
            modifier = Modifier.padding(horizontal = 16.dp)  
        ) {  
            Spacer(modifier = Modifier.height(40.dp))  
            Column(  
                modifier = Modifier  
                    .height(125.dp)  
                    .fillMaxWidth()  
                    .clip(  
                        shape = RoundedCornerShape(10.dp),  
                    )  
                    .background(color = Color(0xff287dfa))  
            ) {}  
        }        Spacer(modifier = Modifier.height(20.dp))  
        MenuGrid(itemDatas = uiState.menuList)  
    }  
}  
@Composable  
fun MenuGrid(itemDatas: List<MenuBean>) {  
	// 懒加载列表,能够完成RecyclerView的效果。
	// 同样的运用还有LazyColum、LazyRow、LazyHorizontalGrid等
    LazyVerticalGrid(  
        modifier = Modifier.padding(horizontal = 8.dp),  
        // 分为3列
        columns = GridCells.Fixed(3),  
        content = {  
            this.items(itemDatas) {  
	            // 遍历,依据数据列表数量回来每个Item的布局
                MenuItem(itemData = it)  
            }  
        })  
}  
/**  
 * 列表子项  
 */  
@Composable  
fun MenuItem(itemData: MenuBean) {  
    val context = LocalContext.current  
    Column(modifier = Modifier  
        .padding(6.dp)  
        .clickable {  
            // 点击事情  
            MenuBean  
                .getMenuName(ActivityUtils.getTopActivity(), itemData.key)  
                .toast()  
        }) {  
        Column(  
            modifier = Modifier  
                .border(  
                    width = 1.dp,  
                    color = Color(0xfff5f5f5),  
                    shape = RoundedCornerShape(10.dp),  
                )  
                .height(90.dp)  
                .fillMaxWidth()  
                .padding(8.dp)  
                .background(color = Color(0xffffffff))  
        ) {  
            Spacer(modifier = Modifier.height(2.dp))  
            Image(  
                painter = painterResource(id = MenuBean.getMenuIcon(itemData.key)),  
                contentDescription = MenuBean.getActionName(itemData.key)  
            )  
            Spacer(modifier = Modifier.height(6.dp))  
            Text(  
                MenuBean.getMenuName(context, itemData.key),  
                color = Color(0xff333333),  
                fontSize = 13.sp  
            )  
        }  
    }  
}

有关Effect


/**  
 * 回来键监听回调  
 */  
@Composable  
fun BackPressHandler(onBackPressed: () -> Unit, enabled: Boolean = true) {  
    // 体系回来键阻拦器  
    val dispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher  
    // remember函数能够理解为:写在函数内部的大局变量。不会跟从Composable办法反复履行。  
    val backCallback = remember {  
        object : OnBackPressedCallback(enabled) {  
            override fun handleOnBackPressed() {  
                onBackPressed()  
            }  
        }  
    }  
    // 在Effect中对外部回调进行注册,在dispatcher改变时履行  
    DisposableEffect(dispatcher) {  
        // DisposableEffect默认在每次onCommit时都会履行,  
        // 每次从头注册  
        dispatcher?.addCallback(backCallback)  
        // DisposableEffect中有必要完成onDispose,如不需求,可运用简化API-SideEffect  
        onDispose {  
            backCallback.remove() // 避免泄露  
        }  
    }    // 能够在Effect中处理订阅逻辑  
}

ViewModel

class LoginViewModel : ViewModel() {
    // viewModel中处理逻辑  
    // 私有可变UiState  
    private val _uiState = MutableStateFlow(  
        LoginUiState(  
            isLoginEnable = false,  
            showLoading = false,  
            userName = "",  
            userPassword = ""  
        )  
    )  
    // 公开的不可变UiState  
    val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()  
    /**  
     * 更新用户名  
     */  
    fun onUserNameChanged(userName: String) {  
        _uiState.update {  
            // MVI架构,仅更新entity中的指定字段,前台Compose代码中检测到字段变动会直接从头履行  
            it.copy(userName = userName)  
        }  
    }  
    /**  
     * 更新用户暗码  
     */  
    fun onUserPasswordChanged(userPassword: String) {  
        _uiState.update {  
            it.copy(userPassword = userPassword)  
        }  
    }  
    /**  
     * 判断能否登录  
     */  
    fun judgeCanLogin() {  
        _uiState.update {  
            it.copy(isLoginEnable = it.userName.isNotEmpty() && it.userPassword.isNotEmpty())  
        }  
    }  
	/**
	 * 履行登录
	 */
    fun login(loginSuccess: () -> Unit, loginFailed: () -> Unit) {  
        viewModelScope.launch {  
            showLoading()  
            // 推迟1秒模拟请求场景  
            delay(1000)  
            hideLoading()  
            loginSuccess()  
        }  
    }  
    private fun showLoading() {  
        _uiState.update {  
            it.copy(showLoading = true)  
        }  
    }  
    private fun hideLoading() {  
        _uiState.update {  
            it.copy(showLoading = false)  
        }  
    }  
    companion object {  
        fun provideFactory(): ViewModelProvider.Factory = object : ViewModelProvider.Factory {  
            @Suppress("UNCHECKED_CAST")  
            override fun <T : ViewModel> create(modelClass: Class<T>): T {  
                return LoginViewModel() as T  
            }  
        }  
    }  
}

路由逻辑


object DemoDestinations {  
    const val SPLASH_ROUTE = "splash"  
    const val HOME_ROUTE = "home"  
    const val LOGIN_ROUTE = "login"  
}  
class NavigationActions(navController: NavHostController) {  
    val navigateToHome: () -> Unit = {  
	    // 在具体页面中履行 navigationActions.navigateToHome() 即可跳转
        navController.navigate(DemoDestinations.HOME_ROUTE) {  
            popUpTo(navController.graph.findStartDestination().id) {  
                saveState = true  
            }  
            launchSingleTop = true  
            restoreState = true  
        }  
    }    val navigateToLogin: () -> Unit = {  
        navController.navigate(DemoDestinations.LOGIN_ROUTE)  
    }  
    val navigatePopBack: () -> Unit = {  
        // 回来,相当于finish()  
        navController.navigateUp()  
    }  
}  
@Composable  
fun NaviGraph(  
    navController: NavHostController = rememberNavController(),  
    startDestination: String = DemoDestinations.SPLASH_ROUTE,  
    navigationActions: NavigationActions  
) {  
    NavHost(  
        navController = navController,  
        startDestination = startDestination  
    ) {  
        // 界说Splash页  
        composable(DemoDestinations.SPLASH_ROUTE) {  
            SplashPage(navigationActions)  
        }  
        // 定位登录页  
        composable(DemoDestinations.LOGIN_ROUTE) {  
            // ViewModel-Compose库中,专为Compose设计的大局ViewModel初始化办法  
            val loginViewModel: LoginViewModel =  
                viewModel(factory = LoginViewModel.provideFactory())  
            LoginPage(  
                loginViewModel, navigationActions  
            )  
        }  
        // 界说首页  
        composable(DemoDestinations.HOME_ROUTE) {  
            val homeViewModel: HomeViewModel = viewModel(factory = HomeViewModel.provideFactory())  
            Home(  
                homeViewModel  
            )  
        }  
    }}

将写好的页面放入Activity中

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        WindowCompat.setDecorFitsSystemWindows(window, false)  
        setContent {  
            // 和Flutter相同,Activity成为了Compose的展示容器  
            ComposeDemoApp()  
        }  
    }  
}
@Composable  
fun ComposeDemoApp() {  
    ComposeDemoTheme {  
        // remember函数是Compose中的重要API,用于包装一些计算成本较高的逻辑或一些state数据,避免函数重复履行  
        // 能够理解为:写在函数内部的大局变量。不会跟从Composable办法的反复履行而重复初始化。  
        // 有很多remember最初的API,都是不同功用的remember的具体完成  
        val systemUiController = rememberSystemUiController()  
        val useDarkIcons = MaterialTheme.colors.isLight  
        // SideEffect相当于DisposableEffect的简化版,能够用来更新外部状况。  
        SideEffect {  
            // 通明状况栏  
            systemUiController.setStatusBarColor(  
                color = Color.Transparent,  
                darkIcons = useDarkIcons  
            )  
        }  
        Surface(  
            modifier = Modifier  
                .fillMaxSize()  
                .fillMaxHeight(),  
            color = MaterialTheme.colors.background,  
        ) {  
            val navController = rememberNavController()  
            val navigationActions = remember(navController) {  
                NavigationActions(navController)  
            }  
            NaviGraph(navController = navController, navigationActions = navigationActions)  
        }  
    }}

参阅链接

Jetpack Compose 运用入门 | Android Developers