Compose 项目笔记概要

[toc]

网址导航

Compose与Kotlin的兼容对应关系

Kotlin 预发布版本兼容的 Compose Compiler 版本

快速入门

Navigation

图片加载

Gradle国内镜像

动画

手势

Blur模糊效果

Scrcpy 手机投屏


网络请求

老方法

interface ApiServices{
    @Headers(value = ["Content-type:application/json;charset=UTF-8"])
    @POST
    fun post(@Url url: String, @Body params: RequestBody): ApiCall

    @GET
    fun get(@Url url: String): ApiCall

}
object ApiNetwork{
    private val service = ServiceCreator.create(ApiServices::class.java)

    fun get(url: String){
        service.get(url).enqueue(object : ApiCallback {
            override fun success(response: Response?) {
                super.success(response)
                // String 结果进行处理
            }

            override fun error(response: Response<*>?, t: Throwable?) {
                super.error(response, t)
            }

            override fun onComplete() {

            }
        })
    }
}
object ServiceCreator {

    fun  create(service: Class): T = create().create(service)

    private fun create(): Retrofit {
        val okHttpClient = OkHttpClient().newBuilder()
        if (BuildConfig.NetTest) {
            okHttpClient.addInterceptor(HttpLoggingInterceptor().apply {
                this.level = HttpLoggingInterceptor.Level.BODY
            })
        }
        val build = okHttpClient
            .retryOnConnectionFailure(true)
            .writeTimeout(60, TimeUnit.SECONDS)
            .connectTimeout(60, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .proxy(Proxy.NO_PROXY) // 有效避免抓包,请求不走任何协议, 未测试,谨慎使用
            .build()
        return Retrofit.Builder()
            .client(build)
            .baseUrl("https://www.google.com/")
            .addCallAdapterFactory(ErrorHandlingCallAdapterFactory())
            .addConverterFactory(ScalarsConverterFactory.create())
//                .addConverterFactory(MoshiConverterFactory.create())
//                .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}
interface ApiCallback {
    /** Called for [200, 300) responses.  */
    fun success(response: Response?) {
    }

    /** Called for 401 responses.  */
    fun unauthenticated(response: Response<*>?) {
        error(response)
    }

    /** Called for [400, 500) responses, except 401.  */
    fun clientError(response: Response<*>?) {
        error(response)
    }

    /** Called for [500, 600) response.  */
    fun serverError(response: Response<*>?) {
        error(response)
    }

    /** Called for network errors while making the call.  */
    fun networkError(e: Exception?) {
        error(t = e)
    }

    /** Called for unexpected errors while making the call.  */
    fun unexpectedError(t: Throwable?) {
        error(t = t)
    }

    fun error(response: Response<*>? = null, t: Throwable? = null) {
    }

    fun onComplete()
}
interface ApiCall {
    fun cancel()
    fun enqueue(callback: ApiCallback?)
    fun clone(): ApiCall // Left as an exercise for the reader...
    fun isRunning(): Boolean
}
class ErrorHandlingCallAdapterFactory : CallAdapter.Factory() {
    @Nullable
    override fun get(
        returnType: Type,
        annotations: Array,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        if (getRawType(returnType) != ApiCall::class.java) {
            return null
        }
        check(returnType is ParameterizedType) { "ApiCall must have generic type (e.g., ApiCall)" }
        val responseType =
            getParameterUpperBound(
                0,
                returnType
            )
        val callbackExecutor = retrofit.callbackExecutor()
        return ErrorHandlingCallAdapter(
            responseType,
            callbackExecutor
        )
    }

    private class ErrorHandlingCallAdapter internal constructor(
        private val responseType: Type,
        private val callbackExecutor: Executor?
    ) :
        CallAdapter> {
        override fun responseType(): Type {
            return responseType
        }

        override fun adapt(call: Call): ApiCall {
            return ApiCallAdapter(
                call,
                callbackExecutor
            )
        }

    }
}

/** Adapts a [Call] to [ApiCall].  */
internal class ApiCallAdapter(
    private val call: Call,
    private val callbackExecutor: Executor?
) :
    ApiCall {
    override fun cancel() {
        call.cancel()
    }

    var isAsyncRunning = false
    override fun enqueue(callback: ApiCallback?) {
        isAsyncRunning = true
        call.enqueue(
            object : Callback {
                override fun onResponse(
                    call: Call,
                    response: Response
                ) {
                    try {
                        when (response.code()) {
                            in 200..299 -> {
                                callback?.success(response)
                            }
                            401 -> {
                                callback?.unauthenticated(response)
                            }
                            in 400..499 -> {
                                callback?.clientError(response)
                            }
                            in 500..599 -> {
                                callback?.serverError(response)
                            }
                            else -> {
                                callback?.unexpectedError(RuntimeException("Unexpected response $response"))
                            }
                        }
                    } catch (e: Exception) {

                    }
                    try {
                        callback?.onComplete()
                    } catch (e: Exception) {
                    }
                    isAsyncRunning = false
                }

                override fun onFailure(
                    call: Call,
                    t: Throwable
                ) {
                    try {
                        if (t is Exception) {
                            callback?.networkError(t)
                        } else {
                            callback?.unexpectedError(t)
                        }
                    } catch (e: Exception) {

                    }
                    try {
                        callback?.onComplete()
                    } catch (e: Exception) {
                    }
                    isAsyncRunning = false
                }
            })
    }

    override fun clone(): ApiCall {
        return ApiCallAdapter(
            call.clone(),
            callbackExecutor
        )
    }

    override fun isRunning(): Boolean {
        return isAsyncRunning
    }


}

新方法

val viewModel: HomePageViewModel = viewModel()
val bannerData by viewModel.bannerState.observeAsState(PlayLoading)

              if (bannerData !is PlaySuccess<*>) {
                            viewModel.getBanner()
              }
data class BaseModel(
    val `data`: T,
    val errorCode: Int,
    val errorMsg: String
)
sealed class PlayState {
    fun isLoading() = this is PlayLoading
    fun isSuccessful() = this is PlaySuccess

    override fun toString(): String {
        return when (this) {
            is PlaySuccess<*> -> "Success[data=$data]"
            is PlayError -> "Error[exception=${error}]"
            PlayLoading -> "Loading"
        }
    }
}

data class PlaySuccess(val data: T) : PlayState()
data class PlayError(val error: Throwable) : PlayState()
object PlayLoading : PlayState()

/**
 * [PlayState.data] if [Result] is of query [PlayState]
 */
fun  PlayState?.successOr(fallback: T): T {
    if (this == null) return fallback
    return (this as? PlaySuccess)?.data ?: fallback
}

val  PlayState.data: T?
    get() = (this as? PlaySuccess)?.data
abstract class BaseArticleViewModel(application: Application) : AndroidViewModel(application) {

    abstract val repositoryArticle: BaseArticlePagingRepository

}
class HomePageViewModel(application: Application) : BaseArticleViewModel(application) {

    override val repositoryArticle: BaseArticlePagingRepository
        get() = HomeArticlePagingRepository()

    private var bannerJob: Job? = null

    private val _bannerState = MutableLiveData>>()

    val bannerState: LiveData>>
        get() = _bannerState



    fun getBanner() {
        bannerJob?.cancel()
        bannerJob = viewModelScope.launch(Dispatchers.IO) {
            (repositoryArticle as HomeArticlePagingRepository).getBanner(_bannerState)
        }
    }

}
abstract class BaseArticlePagingRepository {}
class HomeArticlePagingRepository : BaseArticlePagingRepository() {

    suspend fun getBanner(state: MutableLiveData>>) {
        state.postValue(PlayLoading)
        try {
            val bannerResponse = PlayAndroidNetwork.getBanner()
            if (bannerResponse.errorCode == 0) {
                val bannerList = bannerResponse.data
                bannerList.forEach {
                    it.data = it.imagePath
                }
                state.postValue(PlaySuccess(bannerList))
            } else {
                state.postValue(PlayError(RuntimeException("response status is ${bannerResponse.errorCode}  msg is ${bannerResponse.errorMsg}")))
            }
        } catch (e: Exception) {
            if (e is HttpException) {
                state.postValue(PlayError(RuntimeException("response status is ${e.code()}  msg is ${e.message()}")))
            } else {
                state.postValue(PlayError(RuntimeException("response status is unKnow"))) 
            }
        }
    }

}
object PlayAndroidNetwork{
      private val homePageService = ServiceCreator.create(HomePageService::class.java)

    suspend fun getBanner() = homePageService.getBanner()
}
interface HomePageService {
    @GET
    suspend fun get(@Url url: String): String
    @GET("banner/json")
    suspend fun getBanner(): BaseModel>

}
object ServiceCreator {


    private fun create(): Retrofit {
        // okHttpClientBuilder
        val okHttpClientBuilder = OkHttpClient().newBuilder().apply {
            connectTimeout(30L, TimeUnit.SECONDS)
            readTimeout(10L, TimeUnit.SECONDS)
            addInterceptor(HttpLoggingInterceptor().apply {
                this.level = HttpLoggingInterceptor.Level.BODY
            })
           
        }

        return RetrofitBuild(
            url = "https://www.google.com/",
            client = okHttpClientBuilder.build(),
            gsonFactory = GsonConverterFactory.create()
        ).retrofit
    }

    /**
     * get ServiceApi
     */
    fun  create(service: Class): T = create().create(service)



}
class RetrofitBuild(
    url: String, client: OkHttpClient,
    gsonFactory: GsonConverterFactory
) {
    val retrofit: Retrofit = Retrofit.Builder().apply {
        baseUrl(url)
        client(client)
        addConverterFactory(ScalarsConverterFactory.create())
        addConverterFactory(gsonFactory)
  
    }.build()
}

使用Page+Flow请求分页数据

class HomePagingSource : PagingSource() {

    override suspend fun load(params: LoadParams): LoadResult {
        return try {
            val page = params.key ?: 1 // set page 1 as default
            val articleList = getPageData(page)
            val prevKey = if (page > 1) page - 1 else null
            val nextKey = if (articleList.isNotEmpty()) page + 1 else null
            LoadResult.Page(articleList, prevKey, nextKey)
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState): Int? =null

    suspend fun getPageData(page: Int): List {
        val apiResponse = PlayAndroidNetwork.getPageData(page)
        return apiResponse.data.datas
    }
}
class HomeArticlePagingRepository : BaseArticlePagingRepository() {
 @ExperimentalPagingApi
    override fun getPagingData(query: Query) = Pager(
        PagingConfig(
            pageSize = 15,
            enablePlaceholders = false
        )
    ) {
        HomePagingSource()
    }.flow

}
class HomePageViewModel(application: Application) : AndroidViewModel(application) {

    val repositoryArticle = HomeArticlePagingRepository()

    private val searchResults = MutableSharedFlow(replay = 1)

    @OptIn(ExperimentalCoroutinesApi::class)
    val dataResult: Flow> =   repositoryArticle.getPagingData(Query()).cachedIn(viewModelScope)


}
val viewModel: HomePageViewModel = viewModel()
val lazyPagingItems = viewModel.dataResult.collectAsLazyPagingItems()
 
 val listState = rememberLazyListState()
 val context = LocalContext.current
    LazyColumn(
        modifier = modifier,
        state = listState
    ) {

        items(lazyPagingItems) { data ->
            // do what you do
        }
        val loadStates = lazyPagingItems.loadState
        when {
            loadStates.refresh is LoadState.Loading -> {
               
            }
            loadStates.append is LoadState.Loading -> {
                
            }
            loadStates.refresh is LoadState.Error -> {
               
            }
            loadStates.append is LoadState.Error -> {
                 val e = lazyPagingItems.loadState.append as LoadState.Error
                showToast(context, e.error.localizedMessage ?: "")
                item {
                    Row(
                        modifier = Modifier.fillMaxWidth().padding(8.dp),
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.Center,
                    ) {
                        Button(
                            onClick = { lazyPagingItems.retry() }) {
                            Text("Retry")
                        }
                    }
                }
            }
        }
    }

沉浸式状态栏


/**
 * 设置透明状态栏
 */
fun Activity.transparentStatusBar() {
    transparentStatusBar(window)
}

private fun transparentStatusBar(window: Window) {
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
    val option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
    val vis = window.decorView.systemUiVisibility
    window.decorView.systemUiVisibility = option or vis
    window.statusBarColor = Color.TRANSPARENT
}


/**
 * 状态栏反色
 */
fun Activity.setAndroidNativeLightStatusBar() {
    val decor = window.decorView
    val isDark = resources.configuration.uiMode == 0x21
    if (!isDark) {
        decor.systemUiVisibility =
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    } else {
        decor.systemUiVisibility =
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    }
}
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        transparentStatusBar()
        setAndroidNativeLightStatusBar()
        setContent {
            PlayAndroidTheme {
                ProvideWindowInsets { // 沉浸式状态栏 
                     Column(modifier = Modifier.background(color = MaterialTheme.colors.primary)) {
        Spacer(Modifier.statusBarsHeight()). // 状态栏高度
                  }
                }
            }
        }
    }

ViewModel 三板斧

如果ViewMode 需要使用Context 则继承 AndroidViewModel,否则继承 ViewModel。

class BaseViewModel(application: Application) : AndroidViewModel(application) {


    private val _position = MutableLiveData(0)
    val position: LiveData = _position

    fun onPositionChanged(position: Int) {
        _position.value = position
    }

}
    val viewModel by viewModels< BaseViewModel >()
val treePosition by viewModel.position.observeAsState(0)

使用全局统一的 ViewModel, 把ViewModel写到 Application里:

    val mainViewModel by lazy {
        ViewModelProvider.AndroidViewModelFactory(this).create(MainViewModel::class.java)
    }


添加 Android View


@Composable
fun rememberWebViewWithLifecycle(): WebView {
    val context = LocalContext.current
    val webView = remember {
        WebView(context)
    }
    val lifecycleObserver = rememberWebViewLifecycleObserver(webView)
    val lifecycle = LocalLifecycleOwner.current.lifecycle
    DisposableEffect(lifecycle) {
        lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycle.removeObserver(lifecycleObserver)
        }
    }

    return webView
}

@Composable
private fun rememberWebViewLifecycleObserver(webView: WebView): LifecycleEventObserver =
    remember(webView) {
        LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_RESUME -> webView.onResume()
                Lifecycle.Event.ON_PAUSE -> webView.onPause()
                Lifecycle.Event.ON_DESTROY -> webView.destroy()
                else -> Log.e("WebView", event.name)
            }
        }
    }
    val webView = rememberWebViewWithLifecycle() 

 AndroidView(
                factory = { webView },
                modifier = Modifier
                    .fillMaxSize()
            ) { view ->
                view.webViewClient = object : WebViewClient() {
                    override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
                        return try {
                            if (url.startsWith("http:") || url.startsWith("https:")) {
                                view!!.loadUrl(url)
                            } else {
                                val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
                                webView.context.startActivity(intent)
                            }
                            true
                        } catch (e: Exception) {
                            false
                        }
                    }
                }
                val settings: WebSettings = view.settings
                settings.mixedContentMode =
                    WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
                settings.javaScriptEnabled = true //启用js
                settings.blockNetworkImage = false //解决图片不显示
                view.loadUrl("https://www.baidu.com")
            }

XML使用 ComposeView




    

    


composeView.setContent {
                Button(onClick = {
                    Toast.makeText(this@MainActivity,mainEditName.text.toString(),Toast.LENGTH_LONG).show()
                }){
                    Text("ComposeView")
                }
            }

判断横竖屏

// 当前是否为横屏
val isLand = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE

设置点击效果颜色

 .clickable(interactionSource = remember { MutableInteractionSource() },
                  indication = rememberRipple(color = AppColor))

PX DP SP 互转

@Composable
fun dpToPx(dp: Dp) = with(LocalDensity.current) { dp.toPx() }

@Composable
fun pxToDp(px: Int) = with(LocalDensity.current) { px.toDp() }

@Composable
fun spToDp(sp: TextUnit) = with(LocalDensity.current) { sp.toDp() }

@Composable
fun spToPx(sp: TextUnit) = with(LocalDensity.current) { sp.toPx() }

END

你可能感兴趣的:(Compose 项目笔记概要)