快速開發之加載動畫

現在的App或多或少都會用到加載動畫,那麼如何將多樣性的加載動畫集成到我們的頁面框架中呢?

一些實現方式:

方式一:將加載動畫封裝成自定義View,在佈局中進行添加。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_project_select_designer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    <LoadingProgress
        android:id="@+id/loading_progress"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

這樣的實現方式,需要在每一個使用加載動畫的佈局文件中添加LoadingProgress,並且在界面調用時控制顯示邏輯。如果大面積的使用到重複的代碼,我們可以對代碼進行封裝,例如封裝成一個RecyclerWithLoadAnimView,在該類中進行對LoadingProgress的顯示控制。

但是問題來了,如果有特定需求需要繼承RecyclerView實現特定功能呢?

所以我們還是不這樣寫了,爲了解耦合,我們還是講LoadingProgress放在別的地方去寫。

方式二:在BaseActivity中添加LoadingProgress

那麼問題來了,我們怎麼在不通過佈局文件,並且不影響子類的情況下,添加LoadingProgress呢?
其實很簡單,DecorView大家都知道吧,這是我們在onCreate() 的setContentView()方法返回的佈局的一個父容器。我們可以直接將LoadingProgress添加到佈局的一個父容器中就可以了,然後在BaseActivity中對LoadingProgress進行邏輯控制。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(...);
    }

現在我們找一下這個DecorView。大家都知道,我們視圖都會放在Window裏,而Window類卻是一個抽象類,所以我們要找到它的子類,PhoneWindow類,就是這個類,我們的setContentView方法最終會調用PhoneWindow類中的

@Override
public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        ...
    }

多餘的代碼我們不用看,我們只需要看installDecor()方法,這就是創建DecorView的方,我們看一下:

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        ...
        }
}

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
}

private final class DecorView extends FrameLayout{
    ...
}

這裏我們看到generateDecor()方法,這只是創建了一個DecorView,不過我們可以知道DecorView是FrameLayout的子類。
但是我們沒有找到對我們有利的東西,不過大家不要氣餒,相信有些人已經看到另一個對象mContentParent,顧名思義,它應該是內容的父類,我們繼續往下看 mContentParent = generateLayout(mDecor); 這個MContentParent是通過mDecor創建的。接下來,我們看一下generateLayout()方法:

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

protected ViewGroup generateLayout(DecorView decor) {
    ...
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ...
    return contentParent;
}

通過對源碼的查看,我們知道了mContentParent是DecorView 的一個子View,而contentParent是通過findViewById()這個方法得到的,而這個Id就是 R.id.content ,所以我們找到了頁面的另一父容器mContentParent,並獲取到了它在佈局中的Id。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

這樣我們在BaseActivity中就能得到mContentParent對象了。而且它也是一個幀佈局,講到這裏應該比較清楚了,我們可以直接將LoadingProgress添加到mContentParent中,並在BaseActivity中進行邏輯控制。

public abstract class BaseActivity extends AppCompatActivity {

    private LoadingProgress mProgress;
    private List<View> views = new ArrayList<>();
    private boolean isLoading = false;
    private ViewGroup mContentView;

    @Override
    public void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        initView();
        initData();
        initListener();
    }

    @Override
    public void setContentView(int layoutResID) {
        super.setContentView(layoutResID);
        //在setContentView() 中所設置佈局的父容器的ID 是 android.R.id.content
        mContentView = (ViewGroup) findViewById(android.R.id.content);
        setupLoadView();
    }

    /**
     * 初始化加載動畫視圖
     * 找到佈局中的所有一級子view
     */
    private void setupLoadView() {
        if (mProgress != null)
            return;
        mProgress = new LoadingProgress(this);
        mProgress.setBackgroundResource(R.color.all_bg);
        View contentView = mContentView.getChildAt(0);
        if (contentView instanceof ViewGroup) {
            ViewGroup contentGroup = (ViewGroup) contentView;
            for (int i = 1; i < contentGroup.getChildCount(); i++) {
                views.add(contentGroup.getChildAt(i));
            }
        }
        int marginTop = getTitleHeight();
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        layoutParams.setMargins(0, marginTop, 0, 0);
        mContentView.addView(mProgress, layoutParams);
    }

    /**
     * 開啓動畫
     */
    protected void openLoadAnim() {
        if (isLoading || mProgress == null)
            return;
        mProgress.show();
        isLoading = true;
    }

    /**
     * 關閉動畫
     */
    protected void closeLoadAnim() {
        if (!isLoading || mProgress == null)
            return;
        mProgress.dismiss();
        isLoading = false;
    }

    /**
     * 初始化view
     * 設置view 部分屬性(顯示隱藏等)
     */
    protected abstract void initView();

    /**
     * 加載數據
     * 在這裏調用 openLoadAnim() 方法
     */
    protected abstract void initData();

    /**
     * 初始化監聽
     */
    protected abstract void initListener();

    /**
     * 返回title高度,防止加載動畫格擋標題
     */
    protected abstract int getTitleHeight();

    /**
     * 父容器獲取焦點,禁止子控件自動獲取焦點
     * 佈局中有EditText時,禁止彈出軟鍵盤
     */
    protected void containerFocus() {
        mContentView.getChildAt(0).setFocusable(true);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        closeLoadAnim();
        mProgress = null;
        views.clear();
        views = null;
        mContentView = null;
    }
}

大家可能看到我沒有在onCreate中調用initView(),initData(),initListener()方法,而是在onPostCreate方法中調用。原因就留給大家去探索了。

項目地址

https://github.com/Huang102/LoadAnimTemplate

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章