主頁 > 移動端開發 > 初探 Compose 版本的玩安卓

初探 Compose 版本的玩安卓

2021-03-07 15:06:04 移動端開發

前言

風起云涌

最近移動端新聞很多啊,都有點看不過來了,Google 官方先是發布了 beta 版本的 Compose,緊接著又發布了 Flutter 2.0 版本,這是要搞事情啊!!!

Google 官方發布了 beta 版本的 Compose 之后舉辦了一個小比賽,分為四場,每周一場,現在為止已經過去了一場,沒有趕上第一場的不要擔心,還有剩下三場,

國內的安卓大佬們都開始摩拳擦掌,扔物線大佬先發了一篇公眾號,里面介紹了這個比賽的規則,又表明他已經參賽了,并靜等禮物的到來;扔物線發表公眾號的第二天,郭霖大佬也宣布參加了這個比賽,,,應該還有好多大佬參加了,但是我沒有進行關注,

參加比賽

這么多大佬參加, 還是 Google 官方舉辦的比賽,所以我也湊了湊熱鬧,雖然寫的也不好看,但也順手提交上去了,

如果好奇我寫的是什么樣子?那下面就給大家看看吧,看之前先給大家看看要求吧:

要求

是不是很簡單,只需要兩個頁面,然后,,,,,

小狗應用截圖
嗯,,,,差不多就是這樣我就直接提交了,不過得不得獎無所謂,開心就好,,,

ComposeFlutter

在寫完這個小 Demo 之后,有一個感覺,ComposeFlutter 他們兩個,,,,不能說有點相似,只能說完全一樣!

Compose 完全打破了我之前對安卓的看法,,和 Flutter 簡直是一個模子里刻出來的,連命名都有些相似,雖然 Google 官方說不會放棄 Java,但是看看協程和 Compose,真的想說一句:我信你個鬼,你個糟老頭子壞的很!

雖然二者很像,但是發力的方向是完全不同的:

Flutter 大家都知道,是個 跨平臺 的 UI 框架,注意,只是 UI 框架!各種復雜實作全都是安卓和蘋果原生寫的,可以一套代碼多處使用,特別是現在 Flutter 2.0 的發布,更是不得了,支持安卓、蘋果、windowsmac、網頁,不得不說實在是太強了,

Compose 為什么會出現?又或者說它有什么用呢?

Compose 為什么會出現

這一小節的內容很明確,寫 Compose 為什么會出現,這個問題其實寫安卓的都比較清楚,有的就算不清楚也可以猜出二三,

以前咱們撰寫安卓程式的時候頁面都寫在 res -> layout 檔案夾下,以 xml 的形式展現,這樣的好處顯而易見,將邏輯代碼和頁面徹底分開,確實分開了,但是使用的時候又需要去 findViewByIdxml 布局加載的時候又需要耗費大量時間,加載完成之后還需要通過反射來獲取 View,又是一大耗時操作,,

記得之前有個大神寫過一篇文章,里面通過直接 new 布局和 xml 方式寫布局進行比較,速度甚至相差幾十倍,然后各種各樣的優化就出來了,好像有個團隊甚至自己撰寫了一整套布局,完全沒有使用 xml,也是神人了!

寫到這里應該都知道 Compose 為什么會出現了吧!

Compose 有啥?

Compose 還有一個重要的特點——宣告式,

宣告式?這是什么東西?怎么說呢,就是不以命令方式改變前端視圖的情況下呈現應用界面,從而使撰寫和維護應用界面變得更加容易,

不解釋還好,一解釋更加懵逼,,,簡單來說就是通過對資料的改變而改變布局,不用以前 findViewById 那樣遍歷樹,減少出錯的可能性,而且軟體維護復雜性會隨著需要更新的視圖數量而增長,不行不行,我自己都有點懵了,給大家舉個例子吧!

其實咱們所熟知的 MVVM 其實就是資料驅動布局改變的,為什么這樣說呢?你想一下,你的 ViewModel 中是不是通常會定義一個 LiveData,然后在你的 Activity 或者 Fragment 中進行 observe,然后將你的 UI 操作放到這里,當資料改變的時候相應地去修改你的 UI,這樣說的話是不是好理解一些呢?

正文

準備撰寫玩安卓

這個小標題命名地不太優雅,,,湊合看吧!

這是我寫的最長的一篇前言,而且好像還沒解釋得太明白,大家將就下吧,

其實這是我個人的一個小習慣,學習什么新東西的時候就會寫個 Demo,之前我寫過一個 MVVM 版的玩安卓,而且還為這個專案寫過一個系列的文章,感興趣的可以去我的文章串列看看,

這次寫完官方比賽的小 Demo 之后覺得 Compose 挺好玩,并且好多大佬都說 Compose 是未來的趨勢,于是就想著把那個 MVVM 版的玩安卓改用 Compose 實作一下試試,

好,說干就干!

先來看看成品吧:

首頁專案
我的公眾號
文章詳情登錄

看著是不是也還可以?那就開始著手撰寫吧!

準備作業

在撰寫之前還是放一下 Github 地址吧:

Github 地址:github.com/zhujiang521…

由于之前已經撰寫過 MVVM 版本的玩安卓了,所以說很多東西咱們就可以直接進行使用了,比如說一下圖片資源,又比如說資料、網路請求等等都是現成了,咱們要做的只是將以前的 xml 布局改成 Compose 即可,

聽著是不是很簡單?但是寫的時候有點懵,這還是我之前寫過 Flutter 的情況下,如果大家沒有寫過 Flutter 或者 SwiftUI 的話看起來可能會更懵,因為里面好多東西都顛覆了我對安卓的看法,,,

為了區分和之前 MVVM 版本的區別,我把這次的 Compose 的版本分支改為了 main 分支,大家下載代碼的時候切換下分支就可以了,或者直接下載 main 分支的代碼也可以,

引入依賴

一般要使用一個新東西的第一步,來吧!

// `Compose`
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.activity:activity-compose:1.3.0-alpha03"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha02"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"

androidTestImplementation "androidx.compose.ui:ui-test:$compose_version"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"

// navigation
implementation "androidx.navigation:navigation-compose:1.0.0-alpha08"

what?不是一個 Compose 庫嗎?干嘛引入這么多?我之前也是這么想的,但是在用的時候一個個又加進去的,,,如果不知道每一個包是什么意思的話可以去官方檔案中查看下,不過光看依賴名稱基本就知道是什么意思了,,,

如果你也想像我一樣在以前的專案中使用 Compose,那么下面的這一步千萬別忘了,我就是忘了添加下面這一步找了整整一天的錯,,,

android {
		…………
    buildFeatures {
        `Compose` true
        viewBinding true
    }

    `Compose`Options {
        kotlinCompilerExtensionVersion compose_version
        kotlinCompilerVersion kotlin_version
    }
}

就是上面的,一定別忘了進行配置,不然找錯誤能找死,,,給我提示的是 Kotlin 內部 JVM 錯誤,搞得我我都準備給 KotlinBug 了,,,

開始撰寫

我的天,寫了兩千字,終于開始真正的正文了,,,我太難了,

大家可能看到上面的依賴中有 navigation,看名字就知道是專門為 Compose 寫的,這也是 Compose 跳轉的重要工具,也許有更好的,只是我沒有發現吧,,,

上面也不止一次提到 Compose 顛覆了我之前對安卓的看法,之前的我認為安卓就是一堆 Activity 加上 Fragment ,但是寫了 Compose 之后我發現并不是這樣的,好多官方的 Demo 只有一個 Activity,,

看得我有點懵,但是后來想了想就明白了,還是類似 Flutter ,在 Flutter 中不也是一個 Activity 嘛,每一個頁面也都是一個 Widget!跳轉也不是之前的 Intent ,而是路由,,現在的 Compose 也是一樣,只不過 Widget 改為了 Composable,路由改為了 navigation

真的要開始寫了

好了好了,真的要開始寫了,,,

今天的標題既然是 初探,那么今天就寫一下首頁框架吧,就是一個底部導航欄加上四個頁面,實作點擊進行切換,

新建 Activity

先來新建一個 Activity 吧,咱們這個專案之后就用這一個 Activity

先來看一下 AndroidManifest 吧:

<activity
    android:name=".`Compose`.NewMainActivity"
    android:theme="@style/AppTheme.NoActionBars">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

這沒什么說的,就是把之前的首頁改為了新的首頁,其它的 Activity 都已經用不到了,

class NewMainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PlayTheme {
                Home()
            }
        }
    }

}

這個 Activity 很簡單,大家發現了沒有,咱們熟悉的 setContentView 方法沒有呼叫,取而代之的是 setContent 方法,不過不重要,這是 Compose 的固定寫法而已,當然也可以將 Compose 寫到 xml 中然后通過 findViewById 來找到再進行操作,還可以直接 new 出一個 Compose 來進行操作,這里咱們就選擇 setContent 這種方法吧,

還是來看下 setContent 這個方法吧:

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
) {
    val existingComposeView = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? ComposeView

    if (existingComposeView != null) with(existingComposeView) {
        setParentCompositionContext(parent)
        setContent(content)
    } else ComposeView(this).apply {
        // Set content and parent **before** setContentView
        // to have ComposeView create the composition on attach
        setParentCompositionContext(parent)
        setContent(content)
        setContentView(this, DefaultActivityContentLayoutParams)
    }
}

明白了吧?這是 ComponentActivity 的一個擴展方法,里面其實還會執行 setContentView 方法的,并沒有什么神器的魔法,,

創建 Compose

雖然這里選擇了 setContent 這種方法,但是還是說下別的情況下怎么使用吧:

可以將 ComposeView 放在 XML 布局中,就像放置其他任何 View 一樣:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/hello_world"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello Android!" />

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

上面的布局很簡單,大家注意看,咱們把 Compose 直接寫在了 xml 中,這樣也是可以進行使用的,怎么使用呢?

class ExampleFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        return inflater.inflate(
            R.layout.fragment_example, container, false
        ).apply {
            findViewById<ComposeView>(R.id.compose_view).setContent {
                // In Compose world
                MaterialTheme {
                    Text("Hello Compose!")
                }
            }
        }
    }
}

是不是很簡單,當然也可以直接 new 出一個 Compose 來進行操作:

class ExampleFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            setContent {
                MaterialTheme {
                    // In Compose world
                    Text("Hello Compose!")
                }
            }
        }
    }
}

好的,這樣其實已經滿足大部分的需求了,不過還有一種情況:如果同一布局中存在多個 ComposeView 元素,每個元素必須具有唯一的 ID 才能使 savedInstanceState 發揮作用:

class ExampleFragment : Fragment() {

  override fun onCreateView(...): View = LinearLayout(...).apply {
      addView(ComposeView(...).apply {
        id = R.id.compose_view_x
        ...
      })
      addView(TextView(...))
      addView(ComposeView(...).apply {
        id = R.id.compose_view_y
        ...
      })
    }
  }
}

上面的代碼也不難,里面需要注意一點,ComposeView ID 需要在 res/values/ids.xml 檔案中進行定義:

<resources>
    <item name="compose_view_x" type="id" />
    <item name="compose_view_y" type="id" />
</resources>

PlayTheme

上面本來想直接寫主題來著,但是想了想還是說清楚一點吧,要不使用不同場景的就不知道該如何使用了,咱們來接著看剛才定義的 Activity

setContent 中包裹了一層 PlayTheme,顧名思義,這是一個自定義的主題,這塊也是顛覆我的一個地方,在我之前對安卓的認知中,主題一般存放在 values 中的 styles 檔案中,現在 Compose 中已經不再使用 xml 中的主題了,取而代之的是 Compose 自己的一套主題系統,在這里我也吃過一次虧:死活修改不了顏色,,,

看來 Composexml 是深惡痛絕啊,一個 xml 都不想使用,包括 color,,,,來看下 PlayTheme 的定義吧:

@Composable
fun PlayTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        PlayThemeDark
    } else {
        PlayThemeLight
    }
    MaterialTheme(
        colors = colors,
        typography = typography,
        content = content
    )
}

private val PlayThemeLight = lightColors(
    primary = blue,
    onPrimary = Color.White,
    primaryVariant = blue,
    secondary = blue
)

private val PlayThemeDark = darkColors(
    primary = blueDark,
    onPrimary = Color.White,
    secondary = blueDark,
    surface = blueDark
)

定義很簡單,但是根據上面描述的內容發現了一些什么沒有,連主題都是 Composable ,真的像 Flutter 中的 Widget ,再來看下里面的顏色值:

val blue = Color(0xFF2772F3)
val blueDark = Color(0xFF0B182E)

val Purple300 = Color(0xFFCD52FC)
val Purple700 = Color(0xFF8100EF)

畫頁面

準備作業做得差不多了,來開始畫頁面吧!

先想想咱們最終需要做的樣子,忘記的可以滑到上面再看看,

其實很簡單,今天咱們只是初探嘛!只需要畫出下面的底部導航欄和上面幾個空頁面就行了!

說干就干!先來創建一個新的 Composable

@Composable
fun Home() {
  
}

很簡單,一個方法加上 @Composable 的注解就是一個新的 Composable 了,咱們需要在這里畫咱們的首頁了,

底部導航欄

查了一下官方檔案, Compose 中和 Flutter 一樣有現成底部導航欄,完全夠咱們使用了:

@Composable
fun Home() {
    ComposeDemoTheme {
        val (selectedTab, setSelectedTab) = remember { mutableStateOf(CourseTabs.HOME_PAGE) }
        val tabs = CourseTabs.values()
        Scaffold(
            backgroundColor = MaterialTheme.colors.primarySurface,
            bottomBar = {
                BottomNavigation(
                    Modifier.navigationBarsHeight(additional = 56.dp)
                ) {
                    tabs.forEach { tab ->
                        BottomNavigationItem(
                            icon = { Icon(painterResource(tab.icon), contentDescription = null) },
                            label = { Text(stringResource(tab.title).toUpperCase()) },
                            selected = tab == selectedTab,
                            onClick = { setSelectedTab(tab) },
                            alwaysShowLabel = false,
                            selectedContentColor = MaterialTheme.colors.secondary,
                            unselectedContentColor = LocalContentColor.current,
                            modifier = Modifier.navigationBarsPadding()
                        )
                    }
                }
            }
        ) { innerPadding ->
            val modifier = Modifier.padding(innerPadding)
            when (selectedTab) {
                CourseTabs.HOME_PAGE -> One(modifier)
                CourseTabs.PROJECT -> Two(modifier)
                CourseTabs.OFFICIAL_ACCOUNT -> Three(modifier)
                CourseTabs.MINE -> Four(modifier)
            }
        }
    }
}

上面代碼有點長,但是意思很簡單,稍微給大家說一下吧,ScaffoldFlutter 中也有,意思也是差不多的,來看一下 Scaffold 的原始碼吧:

@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
)

它就是一個腳手架,官方是這樣進行描述的:

Material 支持的最高級別的可組合項是 ScaffoldScaffold 可讓您實作具有基本 Material Design 布局結構的界面,Scaffold 可以為最常見的頂級 Material 組件(如 TopAppBarBottomAppBarFloatingActionButtonDrawer)提供插槽,通過使用 Scaffold,很容易確保這些組件得到適當放置且正確地協同作業,

意思很明確,如果不是要求自定義度特別高的頁面,使用 Scaffold 就完全能滿足需求了,這里咱們使用的就是,

上面代碼中的 CourseTabs 還沒有寫,它是一個列舉類,用來表示首頁的幾個頁面的:

enum class CourseTabs(
    @StringRes val title: Int,
    @DrawableRes val icon: Int
) {
    HOME_PAGE(R.string.home_page, R.drawable.ic_nav_news_normal),
    PROJECT(R.string.project, R.drawable.ic_nav_tweet_normal),
    OFFICIAL_ACCOUNT(R.string.official_account, R.drawable.ic_nav_discover_normal),
    MINE(R.string.mine, R.drawable.ic_nav_my_normal)
}

這里其實有的地方大家還是看不太懂的,比如上面的 remember 是個什么東西?

管理狀態

上面所說的的 remember 其實是用來管理 Compose 的狀態的,大家就先記著 remember 可以記錄咱們點擊的按鈕資料,從而驅使頁面發生改變吧,

mutableStateOf(CourseTabs.HOME_PAGE) 其實是 MutableState<CourseTabs> ,[mutableStateOf](https://developer.android.google.cn/reference/kotlin/androidx/compose/runtime/package-summary#mutableStateOf(androidx.compose.runtime.mutableStateOf.T, androidx.compose.runtime.SnapshotMutationPolicy)) 會創建可觀察的 MutableStateMutableState是與 Compose 運行時集成的可觀察型別,

這塊一時半會說不清,需要后面的文章慢慢來和大家講,咱們來日方長!

添加頁面

上面一共創建了四個字頁面:one、two、three、four,四個頁面非常簡單:

@Composable
fun One(modifier: Modifier) {
    Text(modifier = modifier
        .fillMaxSize()
        .padding(top = 100.dp), text = "One", color = Teal200)
}

@Composable
fun Two(modifier: Modifier) {
    Text(modifier = modifier
        .fillMaxSize()
        .padding(top = 100.dp), text = "Two", color = Teal200)
}

@Composable
fun Three(modifier: Modifier) {
    Text(modifier = modifier
        .fillMaxSize()
        .padding(top = 100.dp), text = "Three", color = Teal200)
}

@Composable
fun Four(modifier: Modifier) {
    Text(modifier = modifier
        .fillMaxSize()
        .padding(top = 100.dp), text = "Four", color = Teal200)
}

ok,這就差不多了,

首次運行

代碼寫的差不多了,咱們該運行下看下效果了:

底部導航欄

效果是不是還不錯,哈哈哈,代碼沒有幾行,但是影片還是很漂亮的,這都歸功于 Compose 為咱們封裝好的控制元件,

精致的結尾

本篇文章就先寫到這里吧,本篇簡單介紹了下 Compose,并撰寫了一個簡單的頁面,之后會將整個應用全部使用 Compose 撰寫完成的,大家別忘了點贊和關注啊,感激不盡!!!

最后再放一下 Github 地址吧,別忘了切換 main 分支:

Github 地址:github.com/zhujiang521…

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/267155.html

標籤:其他

上一篇:Android studio模擬器啟動失敗 The emulator process for AVD Pixel_2_API_29 was killed 之 配置環境變數

下一篇:三·八節,菊廠程式員用“運動軌跡繪制”向女神表白

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more