主頁 > 移動端開發 > 《第一行代碼:Android篇》學習筆記(四)

《第一行代碼:Android篇》學習筆記(四)

2022-05-11 08:39:11 移動端開發

本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了,
每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以后閱讀和查閱,最后,感激感激郭霖先生提供這么好的書籍,

第4章 手機平板要兼顧——探究碎片

平板電腦和手機最大的區別就在于螢屏的大小,一般手機螢屏的大小會在3英寸到6英寸之間,而一般平板電腦螢屏的大小會在7英寸到10英寸之間,

螢屏大小差距過大有可能會讓同樣的界面在視覺效果上有較大的差異,比如一些界面在手機上看起來非常美觀,但在平板電腦上看起來就可能會有控制元件被過分拉長、元素之間空隙過大等情況,作為一名專業的Android開發人員,能夠同時兼顧手機和平板的開發是我們必須做到的事情,Android自3.0版本開始引入了碎片的概念,它可以讓界面在平板上更好地展示,

4.1 碎片是什么

碎片(Fragment)是一種可以嵌入在活動當中的UI片段,它能讓程式更加合理和充分地利用大螢屏的空間,因而在平板上應用得非常廣泛,

它和活動實在是太像了,同樣都能包含布局,同樣都有自己的生命周期,你甚至可以將碎片理解成一個迷你型的活動,雖然這個迷你型的活動有可能和普通的活動是一樣大的,

那么究竟要如何使用碎片才能充分地利用平板螢屏的空間呢?想象我們正在開發一個新聞應用,其中一個界面使用RecyclerView展示了一組新聞的標題,當點擊了其中一個標題時,就打開另一個界面顯示新聞的詳細內容,如果是在手機中設計,我們可以將新聞標題串列放在一個活動中,將新聞的詳細內容放在另一個活動中,如圖所示:

image

可是如果在平板上也這么設計,那么新聞標題串列將會被拉長至填充滿整個平板的螢屏,而新聞的標題一般都不會太長,這樣將會導致界面上有大量的空白區域,如圖:

image

因此,更好的設計方案是將新聞標題串列界面和新聞詳細內容界面分別放在兩個碎片中,然后在同一個活動里引入這兩個碎片,這樣就可以將螢屏空間充分地利用起來了,如圖:

image

4.2 碎片的使用方式

碎片通常都是在平板開發中使用的,新建一個FragmentTest專案,創建一個平板模擬器,開始我們的碎片探索之旅吧,效果如圖:

image

4.2.1 碎片的簡單用法

最簡單的碎片示例來練練手,在一個活動當中添加兩個碎片,并讓這兩個碎片平分活動空間,新建一個左側碎片布局left_fragment.xml,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_
    android:layout_height="match_parent">
    <Button
        android:layout_
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:layout_gravity="center_horizontal"
        android:text="Button"/>
</LinearLayout>

只放置了一個按鈕,并讓它水平居中顯示,然后新建右側碎片布局right_fragment.xml,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#00ff00"
    android:layout_
    android:layout_height="match_parent">
    <TextView
        android:layout_
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="This is right fragment"/>
</LinearLayout>

將這個布局的背景色設定成了綠色,并放置了一個TextView用于顯示一段文本,

接著新建一個LeftFragment類,并讓它繼承自Fragment,

(書中所述,:“注意,這里可能會有兩個不同包下的Fragment供你選擇,一個是系統內置的android.app.Fragment,一個是support-v4庫中的android.support.v4.app.Fragment,這里我強烈建議你使用support-v4庫中的Fragment,因為它可以讓碎片在所有Android系統版本中保持功能一致性,

比如說在Fragment中嵌套使用Fragment,這個功能是在Android 4.2系統中才開始支持的,如果你使用的是系統內置的Fragment,那么很遺憾,4.2系統之前的設備運行你的程式就會崩潰,而使用support-v4庫中的Fragment就不會出現這個問題,只要你保證使用的是最新的support-v4庫就可以了,另外,我們并不需要在build.gradle檔案中添加support-v4庫的依賴,因為build.gradle檔案中已經添加了appcompat-v7庫的依賴,而這個庫會將support-v4庫也一起引入進來,”)

實際上,android.support.v4.app.Fragment 沒有;android.app.Fragment 過時;用的是import androidx.fragment.app.Fragment;

現在撰寫一下LeftFragment中的代碼,如下所示:

package com.zhouzhou.fragmenttest;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class LeftFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.left_fragment,container,false);
        return view;
    }
}

這里僅僅是重寫了Fragment的onCreateView()方法,然后在這個方法中通過LayoutInflater的inflate()方法將剛才定義的left_fragment布局動態加載進來,整個方法簡單明了,接著我們用同樣的方法再新建一個RightFragment,代碼如下所示:

package com.zhouzhou.fragmenttest;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class RightFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.right_fragment,container,false);
        return view;
    }
}

接下來修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_
    android:layout_height="match_parent">
    <fragment
        android:id="@+id/left_fragment"
        android:name="com.zhouzhou.fragmenttest.LeftFragment"
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="1"/>
    <fragment
        android:id="@+id/right_fragment"
        android:name="com.zhouzhou.fragmenttest.RightFragment"
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="1"/>
</LinearLayout>

用了<fragment>標簽在布局中添加碎片,其中還需要通過android:name屬性來顯式指明要添加的碎片類名,注意一定要將類的包名也加上,這樣最簡單的碎片示例就已經寫好了,現在運行一下程式,效果如圖:

image

兩個碎片平分了整個活動的布局,不過這個例子實在是太簡單了,在真正的專案中很難有什么實際的作用,

4.2.2 動態添加碎片

在上一節當中,你已經學會了在布局檔案中添加碎片的方法,不過碎片真正的強大之處在于,它可以在程式運行時動態地添加到活動當中,根據具體情況來動態地添加碎片,你就可以將程式界面定制得更加多樣化,還是在上一節代碼的基礎上繼續完善,新建another_right_fragment.xml,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#ffff00"
    android:layout_
    android:layout_height="match_parent">
    <TextView
        android:layout_
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="Another right fragment"/>
</LinearLayout>

這個布局檔案的代碼和right_fragment.xml中的代碼基本相同,只是將背景色改成了黃色,并將顯示的文字改了改,然后新建AnotherRightFragment作為另一個右側碎片,代碼如下所示:

package com.zhouzhou.fragmenttest;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class AnotherRightFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.another_right_fragment,container,false);
        return view;
    }
}

代碼同樣非常簡單,在onCreateView()方法中加載了剛剛創建的another_right_fragment布局,這樣我們就準備好了另一個碎片,接下來看一下如何將它動態地添加到活動當中,修改activity_main.xml,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_
    android:layout_height="match_parent">
    <fragment
        android:id="@+id/left_fragment"
        android:name="com.zhouzhou.fragmenttest.LeftFragment"
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="1"/>
    <FrameLayout
        android:id="@+id/right_layout"
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="1"/>
</LinearLayout>

可以看到,現在將右側碎片替換成了一個FrameLayout中,在上一章中學過,這是Android中最簡單的一種布局,所有的控制元件默認都會擺放在布局的左上角,由于這里僅需要在布局里放入一個碎片,不需要任何定位,因此非常適合使用FrameLayout,

下面在代碼中向FrameLayout里添加內容,從而實作動態添加碎片的功能,修改MainActivity中的代碼,如下所示:

package com.zhouzhou.fragmenttest;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
        replaceFragment(new RightFragment());
    }
    @Override
    public void onClick(View view) {
        switch (view.getId()){
                case R.id.button:
                    replaceFragment(new AnotherRightFragment());
                break;
            default:
                break;
        }
    }
    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.right_layout,fragment);
        transaction.commit();
    }
}

首先給左側碎片中的按鈕注冊了一個點擊事件,然后呼叫replaceFragment()方法動態添加了RightFragment這個碎片,當點擊左側碎片中的按鈕時,又會呼叫replaceFragment()方法將右側碎片替換成AnotherRightFragment,

結合replaceFragment()方法中的代碼可以看出,動態添加碎片主要分為5步,

  1. 創建待添加的碎片實體,
  2. 獲取FragmentManager,在活動中可以直接通過呼叫getSupportFragmentManager()方法得到,
  3. 開啟一個事務,通過呼叫beginTransaction()方法開啟,
  4. 向容器內添加或替換碎片,一般使用replace()方法實作,需要傳入容器的id和待添加的碎片實體,
  5. 提交事務,呼叫commit()方法來完成,

這樣就完成了在活動中動態添加碎片的功能,重新運行程式,可以看到和之前相同的界面,然后點擊一下按鈕,效果如圖:

image

4.2.3 在碎片中模擬回傳堆疊

成功實作了向活動中動態添加碎片的功能,不過嘗試一下就會發現,通過點擊按鈕添加了一個碎片之后,這時按下Back鍵程式就會直接退出,

如果這里想模仿類似于回傳堆疊的效果,按下Back鍵可以回到上一個碎片,該如何實作呢?

其實很簡單,FragmentTransaction中提供了一個addToBackStack()方法,可以用于將一個事務添加到回傳堆疊中,修改MainActivity中的代碼,如下所:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    ...
    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.right_layout,fragment);
        //在事務提交之前呼叫了FragmentTransaction的addToBackStack()方法,它可以接收一個名字用于描述回傳堆疊的狀態,一般傳入null即可,
        transaction.addToBackStack(null);
        transaction.commit();
    }
}

現在重新運行程式,并點擊按鈕將AnotherRightFragment添加到活動中,然后按下Back鍵,會發現程式并沒有退出,而是回到了RightFragment界面,繼續按下Back鍵,RightFragment界面也會消失,再次按下Back鍵,程式才會退出,

4.2.4 碎片和活動之間進行通信

雖然碎片都是嵌入在活動中顯示的,可是實際上它們的關系并沒有那么親密,

碎片和活動都是各自存在于一個獨立的類當中的,它們之間并沒有那么明顯的方式來直接進行通信,如果想要在活動中呼叫碎片里的方法,或者在碎片中呼叫活動里的方法,應該如何實作呢?

為了方便碎片和活動之間進行通信,FragmentManager提供了一個類似于findViewById()的方法,專門用于從布局檔案中獲取碎片的實體,代碼如下所示:

RightFragment rightFragment = (RightFragment) getSupportFragmentManager().findFragmentById(R.id.right_fragment);

呼叫FragmentManager的findFragmentById()方法,可以在活動中得到相應碎片的實體,然后就能輕松地呼叫碎片里的方法了,

掌握了如何在活動中呼叫碎片里的方法,那在碎片中又該怎樣呼叫活動里的方法呢?其實這就更簡單了,在每個碎片中都可以通過呼叫getActivity()方法來得到和當前碎片相關聯的活動實體,代碼如下所示:

MainActivity activity = (MainActivity) getActivity();

有了活動實體之后,在碎片中呼叫活動里的方法就變得輕而易舉了,另外當碎片中需要使用Context物件時,也可以使用getActivity()方法,因為獲取到的活動本身就是一個Context物件,

這時不知道你心中會不會產生一個疑問:既然碎片和活動之間的通信問題已經解決了,那么碎片和碎片之間可不可以進行通信呢?

這個問題并沒有看上去那么復雜,它的基本思路非常簡單,首先在一個碎片中可以得到與它相關聯的活動,然后再通過這個活動去獲取另外一個碎片的實體,這樣也就實作了不同碎片之間的通信功能,因此這里我們的答案是肯定的,

4.3 碎片的生命周期

和活動一樣,碎片也有自己的生命周期,并且它和活動的生命周期實在是太像了,

4.3.1 碎片的狀態和回呼

  1. 運行狀態

當一個碎片是可見的,并且它所關聯的活動正處于運行狀態時,該碎片也處于運行狀態,

  1. 暫停狀態

當一個活動進入暫停狀態時(由于另一個未占滿螢屏的活動被添加到了堆疊頂),與它相關聯的可見碎片就會進入到暫停狀態,

  1. 停止狀態

當一個活動進入停止狀態時,與它相關聯的碎片就會進入到停止狀態,或者通過呼叫FragmentTransaction的remove()、replace()方法將碎片從活動中移除,但如果在事務提交之前呼叫addToBackStack()方法,這時的碎片也會進入到停止狀態,總的來說,進入停止狀態的碎片對用戶來說是完全不可見的,有可能會被系統回收,

  1. 銷毀狀態

碎片總是依附于活動而存在的,因此當活動被銷毀時,與它相關聯的碎片就會進入到銷毀狀態,或者通過呼叫FragmentTransaction的remove()、replace()方法將碎片從活動中移除,但在事務提交之前并沒有呼叫addToBackStack()方法,這時的碎片也會進入到銷毀狀態,

同樣地,Fragment類中也提供了一系列的回呼方法,以覆寫碎片生命周期的每個環節,其中,活動中有的回呼方法,碎片中幾乎都有,不過碎片還提供了一些附加的回呼方法,那我們就重點看一下這幾個回呼,

  • onAttach(),當碎片和活動建立關聯的時候呼叫,
  • onCreateView(),為碎片創建視圖(加載布局)時呼叫,
  • onActivityCreated(),確保與碎片相關聯的活動一定已經創建完畢的時候呼叫,
  • onDestroyView(),當與碎片關聯的視圖被移除的時候呼叫,
  • onDetach(),當碎片和活動解除關聯的時候呼叫,

碎片完整的生命周期示意圖可參考圖,圖片源自Android官網,

image

4.3.2 體驗碎片的生命周期

為了能夠更加直觀地體驗碎片的生命周期,還是通過一個例子來實踐一下,例子很簡單,仍然是在FragmentTest專案的基礎上改動的,修改RightFragment中的代碼,如下所示:

package com.zhouzhou.fragmenttest;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class RightFragment extends Fragment {
    public static final String TAG = "RightFragment";

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        Log.d(TAG,"onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"onCreate");
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.d(TAG,"onCreateView");
        View view = inflater.inflate(R.layout.right_fragment,container,false);
        return view;
    }
    @Override
    //被棄用
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d(TAG,"onActivityCreated");
    }
    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG,"onStart");
    }
    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }
    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG,"onPause");
    }
    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG,"onStop");
    }
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG,"onDestroyView");
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy");
    }
    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(TAG,"onDetach");
    }
}

在RightFragment中的每一個回呼方法里都加入了列印日志的代碼,然后重新運行程式,這時觀察logcat中的列印資訊,如圖:

image

可以看到,當RightFragment第一次被加載到螢屏上時,會依次執行onAttach()、onCreate()、onCreateView()、onActivityCreated()、onStart()和onResume()方法,然后點擊LeftFragment中的按鈕,此時列印資訊如圖:

image

由于AnotherRightFragment替換了RightFragment,此時的RightFragment進入了停止狀態,因此onPause()、onStop()和onDestroyView()方法會得到執行,

當然如果在替換的時候沒有呼叫addToBackStack()方法,此時的RightFragment就會進入銷毀狀態,onDestroy()和onDetach()方法就會得到執行,接著按下Back鍵,RightFragment會重新回到螢屏,列印資訊如圖:

image

由于RightFragment重新回到了運行狀態,因此onCreateView()、onActivityCreated()、onStart()和onResume()方法會得到執行,注意此時onCreate()方法并不會執行,因為我們借助了addToBackStack()方法使得RightFragment并沒有被銷毀,現在再次按下Back鍵,列印資訊如圖:

image

依次會執行onPause()、onStop()、onDestroyView()、onDestroy()和onDetach()方法,最終將碎片銷毀掉,這樣碎片完整的生命周期體驗了一遍,另外值得一提的是,在碎片中你也是可以通過onSaveInstanceState()方法來保存資料的,因為進入停止狀態的碎片有可能在系統記憶體不足的時候被回收,保存下來的資料在onCreate()、onCreateView()和onActivityCreated()這3個方法中你都可以重新得到,它們都含有一個Bundle型別的savedInstanceState引數,

4.4 動態加載布局的技巧

雖然動態添加碎片的功能很強大,可以解決很多實際開發中的問題,但是它畢竟只是在一個布局檔案中進行一些添加和替換操作,

如果程式能夠根據設備的解析度或螢屏大小在運行時來決定加載哪個布局,那我們可發揮的空間就更多了,因此本節我們就來探討一下Android中動態加載布局的技巧,

4.4.1 使用限定符

現在很多的平板應用都采用的是雙頁模式(程式會在左側的面板上顯示一個包含子項的串列,在右側的面板上顯示內容),因為平板電腦的螢屏足夠大,完全可以同時顯示下兩頁的內容,但手機的螢屏一次就只能顯示一頁的內容,因此兩個頁面需要分開顯示,

那么怎樣才能在運行時判斷程式應該是使用雙頁模式還是單頁模式呢?這就需要借助限定符(Qualifiers)來實作了,下面我們通過一個例子來學習一下它的用法,修改FragmentTest專案中的activity_main.xml檔案,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_
    android:layout_height="match_parent">
    <fragment
        android:id="@+id/left_fragment"
        android:name="com.zhouzhou.fragmenttest.LeftFragment"
        android:layout_
        android:layout_height="match_parent"/>
</LinearLayout>

將多余的代碼都刪掉,只留下一個左側碎片,并讓它充滿整個父布局,接著在res目錄下新建layout-large檔案夾,在這個檔案夾下新建一個布局,也叫作activity_main.xml,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_
    android:layout_height="match_parent">
    <fragment
        android:id="@+id/left_fragment"
        android:name="com.zhouzhou.fragmenttest.LeftFragment"
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="1"/>
    <fragment
        android:id="@+id/right_fragment"
        android:name="com.zhouzhou.fragmenttest.RightFragment"
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="3"/>
</LinearLayout>

可以看到,layout/activity_main布局只包含了一個碎片,即單頁模式,而layout-large/activity_main布局包含了兩個碎片,即雙頁模式,其中large就是一個限定符,那些螢屏被認為是large的設備就會自動加載layout-large檔案夾下的布局,而小螢屏的設備則還是會加載layout檔案夾下的布局,

然后將MainActivity中replaceFragment()方法里的代碼注釋掉,并在平板模擬器上重新運行程式,效果如圖:

image

再啟動一個手機模擬器,并在這個模擬器上重新運行程式,效果如圖:

image

這樣我們就實作了在程式運行時動態加載布局的功能,Android中一些常見的限定符可以參考下表,

image

4.4.2 使用最小寬度限定符

使用large限定符成功解決了單頁雙頁的判斷問題,不過很快又有一個新的問題出現了,large到底是指多大呢?有的時候我們希望可以更加靈活地為不同設備加載布局,不管它們是不是被系統認定為large,這時就可以使用最小寬度限定符(Smallest-width Qualifier)了,最小寬度限定符允許我們對螢屏的寬度指定一個最小值(以dp為單位),然后以這個最小值為臨界點,螢屏寬度大于這個值的設備就加載一個布局,螢屏寬度小于這個值的設備就加載另一個布局,在res目錄下新建layout-sw600dp檔案夾,然后在這個檔案夾下新建activity_main.xml布局,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_
    android:layout_height="match_parent">
    <fragment
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:id="@+id/left_fragment"
        android:name="com.zhouzhou.fragmenttest.LeftFragment"/>
    <fragment
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:id="@+id/right_fragment"
        android:name="com.zhouzhou.fragmenttest.RightFragment"/>
</LinearLayout>

這就意味著,當程式運行在螢屏寬度大于等于600dp的設備上時,會加載layout-sw600dp/activity_main布局,當程式運行在螢屏寬度小于600dp的設備上時,則仍然加載默認的layout/activity_main布局,

4.5 碎片的最佳實踐——一個簡易版的新聞應用

前面有提到過,碎片很多時候都是在平板開發當中使用的,主要是為了解決螢屏空間不能充分利用的問題,那是不是就表明,我們開發的程式都需要提供一個手機版和一個Pad版呢?

確實有不少公司都是這么做的,但是這樣會浪費很多的人力物力,因為維護兩個版本的代碼成本很高,每當增加什么新功能時,需要在兩份代碼里各寫一遍,每當發現一個bug時,需要在兩份代碼里各修改一次,因此今天我們最佳實踐的內容就是,教你如何撰寫同時兼容手機和平板的應用程式,

現在就將運用本章中所學的知識來撰寫一個簡易版的新聞應用,并且要求它是可以同時兼容手機和平板的,

新建好一個FragmentBestPractice專案,并且會使用到RecyclerView,要準備好一個新聞的物體類,新建類News,代碼如下所示:

package com.zhouzhou.fragmentbestpractice;

public class News {
    private String title;
    private String content;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

News類的代碼還是比較簡單的,title欄位表示新聞標題,content欄位表示新聞內容,接著新建布局檔案news_content_frag.xml,用于作為新聞內容的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_
        android:layout_height="match_parent"
        android:id="@+id/visibility_layout"
        android:orientation="vertical"
        android:visibility="invisible">
        <TextView
            android:layout_
            android:layout_height="wrap_content"
            android:id="@+id/news_title"
            android:gravity="center"
            android:padding="10dp"
            android:textSize="20sp"/>
        <View
            android:layout_
            android:layout_height="1dp"
            android:background="#000"/>
        <TextView
            android:layout_
            android:layout_height="0dp"
            android:id="@+id/news_content"
            android:layout_weight="1"
            android:padding="15dp"
            android:textSize="18sp"/>
    </LinearLayout>
    <View
        android:layout_
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:background="#000"/>
</RelativeLayout>

新聞內容的布局主要可以分為兩個部分,頭部部分顯示新聞標題,正文部分顯示新聞內容,中間使用一條細線分隔開,這里的細線是利用View來實作的,將View的寬或高設定為1dp,再通過background屬性給細線設定一下顏色就可以了,這里我們把細線設定成黑色,

然后再新建一個NewsContentFragment類,繼承自Fragment,代碼如下所示:

package com.zhouzhou.fragmentbestpractice;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class NewsContentFragment extends Fragment {
    private View view;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.news_content_frag,container,false);
        return view;
    }
    public void refresh(String newsTitle,String newsContent){
        View visibilityLayout = view.findViewById(R.id.visibility_layout);
        visibilityLayout.setVisibility(View.VISIBLE);
        TextView newsTitleText = (TextView) view.findViewById(R.id.news_title);
        TextView newsContentText = (TextView) view.findViewById(R.id.news_content); 
        newsTitleText.setText(newsTitle);//重繪新聞的標題
        newsContentText.setText(newsContent);//重繪新聞內容
    }
}
  • 首先在onCreateView()方法里加載了我們剛剛創建的news_content_frag布局,
  • 又提供了一個refresh()方法,這個方法就是用于將新聞的標題和內容顯示在界面上的,這里通過findViewById()方法分別獲取到新聞標題和內容的控制元件,然后將方法傳遞進來的引數設定進去,

這樣我們就把新聞內容的碎片和布局都創建好了,但是它們都是在雙頁模式中使用的,如果想在單頁模式中使用的話,還需要再創建一個活動,右擊com.zhouzhou.fragmentbestpractice包→New→Activity→EmptyActivity,新建一個NewsContentActivity,并將布局名指定成news_content,然后修改news_content.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_
    android:layout_height="match_parent">
    <fragment
        android:layout_
        android:layout_height="match_parent"
        android:id="@+id/news_content_fragment"
        android:name="com.zhouzhou.fragmentbestpractice.NewsContentFragment"/>
</LinearLayout>

這里我們充分發揮了代碼的復用性,直接在布局中引入了NewsContentFragment,這樣也就相當于把news_content_frag布局的內容自動加了進來,然后修改NewsContentActivity中的代碼,如下所示:

package com.zhouzhou.fragmentbestpractice;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

public class NewsContentActivity extends AppCompatActivity {
    public static void actionStart(Context context,String newsTitle,String newsContent){
        Intent intent = new Intent(context,NewsContentActivity.class);
        intent.putExtra("news_title",newsTitle);
        intent.putExtra("news_content",newsContent);
        context.startActivity(intent);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_content);
        String newsTitle = getIntent().getStringExtra("news_title");//獲取傳入的新聞標題
        String newsContent = getIntent().getStringExtra("news_content");//獲取傳入的新聞的內容
        NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
        newsContentFragment.refresh(newsTitle,newsContent);//重繪NewsContentFragment界面
    }
}

可以看到,在onCreate()方法中通過Intent獲取到了傳入的新聞標題和新聞內容,然后呼叫FragmentManager的findFragmentById()方法得到了NewsContentFragment的實體,接著呼叫它的refresh()方法,并將新聞的標題和內容傳入,就可以把這些資料顯示出來了,

注意這里我們還提供了一個actionStart()方法,如果忘記的話就再去閱讀一遍2.6.3小節吧,接下來還需要再創建一個用于顯示新聞串列的布局,新建news_title_frag.xml,代碼如下所示:

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

    <androidx.recyclerview.widget.RecyclerView
        android:layout_
        android:layout_height="match_parent"
        android:id="@+id/news_title_recycler_view"/>
</LinearLayout>

這個布局里面只有一個用于顯示新聞串列的RecyclerView,既然要用到RecyclerView,那么就必定少不了子項的布局,新建news_item.xml作為RecyclerView子項的布局,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_
        android:layout_height="wrap_content"
        android:id="@+id/news_title"
        android:maxLines="1"
        android:ellipsize="end"
        android:textSize="18sp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"/>

子項的布局也非常簡單,只有一個TextView,

  • android:padding表示給控制元件的周圍加上補白,這樣不至于讓文本內容會緊靠在邊緣上,
  • android:maxLines設定為1表示讓這個TextView只能單行顯示,
  • android:ellipsize用于設定當文本內容超出控制元件寬度時,文本的縮略方式,end表示在尾部進行縮略,

既然新聞串列和子項的布局都已經創建好了,那么接下來我們就需要一個用于展示新聞串列的地方,這里新建NewsTitleFragment作為展示新聞串列的碎片,代碼如下所示:

package com.zhouzhou.fragmentbestpractice;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class NewsTitleFragment extends Fragment {
    private boolean isTwoPane;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.news_title_frag,container,false);
        return view;
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout)!= null){
            isTwoPane = true;//可以找到news_content_layout布局時,為雙頁模式
        }else {
            isTwoPane = false;//找不到news_content_layout布局時,為單頁模式
        }
    }
}

NewsTitleFragment中并沒有多少代碼,在onCreateView()方法中加載了news_title_frag布局,

注意看一下onActivityCreated()方法,這個方法通過在活動中能否找到一個id為news_content_layout的View來判斷當前是雙頁模式還是單頁模式,因此我們需要讓這個id為news_content_layout的View只在雙頁模式中才會出現,那么怎樣才能實作這個功能呢?其實并不復雜,只需要借助我們剛剛學過的限定符就可以了,

首先修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/news_title_layout"
    android:layout_
    android:layout_height="match_parent">
    <fragment
        android:layout_
        android:layout_height="match_parent"
        android:id="@+id/news_title_fragment"
        android:name="com.zhouzhou.fragmentbestpractice.NewsTitleFragment"/>
</FrameLayout>

上述代碼表示,在單頁模式下,只會加載一個新聞標題的碎片,然后新建layout-sw600dp檔案夾,在這個檔案夾下再新建一個activity_main.xml檔案,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_
    android:layout_height="match_parent">
    <fragment
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:id="@+id/news_title_fragment"
        android:name="com.zhouzhou.fragmentbestpractice.NewsTitleFragment"/>
    <FrameLayout
        android:layout_
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:id="@+id/news_content_layout"/>
    <fragment
        android:layout_
        android:layout_height="match_parent"
        android:id="@+id/news_content_fragment"
        android:name="com.zhouzhou.fragmentbestpractice.NewsContentFragment"/>
</LinearLayout>

可以看出,在雙頁模式下我們同時引入了兩個碎片,并將新聞內容的碎片放在了一個FrameLayout布局下,而這個布局的id正是news_content_layout,因此,能夠找到這個id的時候就是雙頁模式,否則就是單面模式,

現在還剩下至關重要的一點,就是在NewsTitleFragment中通過RecyclerView將新聞串列展示出來,在NewsTitleFragment中新建一個內部類NewsAdapter來作為RecyclerView的配接器,如下所示:

package com.zhouzhou.fragmentbestpractice;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class NewsTitleFragment extends Fragment {
    private boolean isTwoPane;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.news_title_frag,container,false);
        return view;
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout)!= null){
            isTwoPane = true;//可以找到news_content_layout布局時,為雙頁模式
        }else {
            isTwoPane = false;//找不到news_content_layout布局時,為單頁模式
        }
    }
    class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{
        private List<News> mNewsList;

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item,parent,false);
            final ViewHolder holder = new ViewHolder(view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    News news = mNewsList.get(holder.getAdapterPosition());
                    if(isTwoPane){
                        //如果時雙頁模式,重繪NewsContentFragment中的內容
                        NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
                        newsContentFragment.refresh(news.getTitle(),news.getContent());
                    }else{
                        //如果是單頁模式,則直接啟動NewsContentActivity
                        NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent());
                    }
                }
            });
            return holder;
        }
        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            News news = mNewsList.get(position);
            holder.newsTitleText.setText(news.getTitle());
        }

        @Override
        public int getItemCount() {
            return mNewsList.size();
        }
        class ViewHolder extends RecyclerView.ViewHolder{
            TextView newsTitleText;
            public ViewHolder(View view){
                super(view);
                newsTitleText = (TextView) view.findViewById(R.id.news_title);
            }
        }
        public NewsAdapter(List<News>newsList){
            mNewsList = newsList;
        }
    }
}

需要注意的是,之前我們都是將配接器寫成一個獨立的類,其實也是可以寫成內部類的,這里寫成內部類的好處就是可以直接訪問NewsTitleFragment的變數,比如isTwoPane,觀察一下onCreateViewHolder()方法中注冊的點擊事件,首先獲取到了點擊項的News實體,然后通過isTwoPane變數來判斷當前是單頁還是雙頁模式,如果是單頁模式,就啟動一個新的活動去顯示新聞內容,如果是雙頁模式,就更新新聞內容碎片里的資料,

最后就是向RecyclerView中填充資料了,修改NewsTitleFragment中的代碼,如下所示:

package com.zhouzhou.fragmentbestpractice;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class NewsTitleFragment extends Fragment {
    private boolean isTwoPane;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.news_title_frag,container,false);
        RecyclerView newsTitleRecyclerView = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        newsTitleRecyclerView.setLayoutManager(layoutManager);
        NewsAdapter adapter = new NewsAdapter(getNews());
        newsTitleRecyclerView.setAdapter(adapter);
        return view;
    }
    private List<News> getNews(){
        List<News> newsList = new ArrayList<>();
        for (int i = 0; i <=50 ; i++) {
            News news = new News();
            news.setTitle("This is news title " + i);
            news.setContent(getRandomLengthContent("News Content" + i + "."));
            newsList.add(news);
        }
        return newsList;
    }
    private String getRandomLengthContent(String content){
        Random random = new Random();
        int length = random.nextInt(20) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(content);
        }
        return builder.toString();
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout)!= null){
            isTwoPane = true;//可以找到news_content_layout布局時,為雙頁模式
        }else {
            isTwoPane = false;//找不到news_content_layout布局時,為單頁模式
        }
    }
    class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{
        private List<News> mNewsList;

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item,parent,false);
            final ViewHolder holder = new ViewHolder(view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    News news = mNewsList.get(holder.getAdapterPosition());
                    if(isTwoPane){
                        //如果時雙頁模式,重繪NewsContentFragment中的內容
                        NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
                        newsContentFragment.refresh(news.getTitle(),news.getContent());
                    }else{
                        //如果是單頁模式,則直接啟動NewsContentActivity
                        NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent());
                    }
                }
            });
            return holder;
        }
        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            News news = mNewsList.get(position);
            holder.newsTitleText.setText(news.getTitle());
        }

        @Override
        public int getItemCount() {
            return mNewsList.size();
        }
        class ViewHolder extends RecyclerView.ViewHolder{
            TextView newsTitleText;
            public ViewHolder(View view){
                super(view);
                newsTitleText = (TextView) view.findViewById(R.id.news_title);
            }
        }
        public NewsAdapter(List<News>newsList){
            mNewsList = newsList;
        }
    }
}

可以看到,onCreateView()方法中添加了RecyclerView標準的使用方法,在碎片中使用RecyclerView和在活動中使用幾乎是一模一樣的,另外,這里呼叫了getNews()方法來初始化50條模擬新聞資料,同樣使用了一個getRandomLengthContent()方法來隨機生成新聞內容的長度,以保證每條新聞的內容差距比較大,

這樣所有的撰寫作業就已經完成了,運行一下吧!首先在手機模擬器上運行,效果如圖:

image

可以看到許多條新聞的標題,然后點擊第一條新聞,會啟動一個新的活動來顯示新聞的內容,效果如圖:

image

接下來將程式在平板模擬器上運行,同樣點擊第一條新聞,效果如圖:

image

到這里為止,已經基本將Android UI相關的重要知識點都學完了,后面在很長一段時間內都不會再系統性地介紹UI方面的知識,而是將結合前面所學的UI知識來更好地講解相應章節的內容,

個人學習筆記,針對本人在自學中遇到的問題,

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

標籤:Android

上一篇:《第一行代碼:Android篇》學習筆記(三)

下一篇:《第一行代碼:Android篇》學習筆記(五)

標籤雲
其他(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