1. 程式人生 > >Android 夜間模式切換,顏色漸變效果實現

Android 夜間模式切換,顏色漸變效果實現

概述
做安卓開發有一段時間了,夜間模式的切換在各種APP中見多了,大部分都是點一下切換然後馬上改變配色,很是生硬,從來都沒去注意過!然後有一天我點了下知乎日報的夜間模式切換,直接被亮瞎狗眼!什麼?這還能漸變過去?不行了,得去研究研究……然後就有了這篇部落格,主要講解下我自己如何實現這個切換時漸變的效果。

Demo

先來看看最後實現的效果
這是5.0及以上版本的效果,主要是狀態列也實現了漸變,5.0以下狀態列是不修改的
5.0及以上的效果

如何實現

開發程式程式碼不是重點,關鍵是知道可以做到什麼,如何做到。
SO,現在來講解下這個效果的大體實現思路。
首先,改變主題比較簡單的方法是直接用setTheme()。其它的方法呢我不知道!請恕在下才疏學淺


用setTheme()之前需要先寫好樣式檔案xml,attr、style等,這些後面再講。
然後寫完樣式檔案,你在Activity中興匆匆的呼叫setTheme()也是沒用的同學!(如果在setContentView()方法之前呼叫是可以改變主題的,因為佈局還沒生成)
setTheme()不起作用的原因是Activity需要重新生成才能自動重新整理UI,這個完全重新生成的過程使用者體驗肯定坑爹。
網上比較常見的處理方法是自己遍歷一下目前介面上需要改變顏色的控制元件,然後手動設定一遍顏色。感覺這樣很LOW

我最後想到的讓seTheme()生效的方法是:
在Activity中動態載入1個fragment,需要的佈局都放在fragment中,然後切換模式的時候銷燬現有的fragment ,在動態載入一個新的fragment,用Activity中自己儲存的資料去恢復Fragment狀態(恢復狀態按佈局複雜度難易不同),並且在恢復的過程中,用一個ImageView覆蓋在上面等恢復完了再漸變透明度(達到漸變效果)。

理一下思路,總共需要的步驟:
1.寫好樣式和佈局檔案。
2.切換時呼叫setTheme()
3.擷取當前螢幕的圖片放到ImageView中並覆蓋在fragment上面
4.銷燬fragment,扭一個fragment
5.恢復fragment的狀態
6.ImageView透明度漸變達到視覺上的切換效果
7.收工回家

樣式檔案

樣式檔案的寫法比較簡單,下面給出一個例子:

在attrs.xml(檔名無所謂)中定義一個屬性

<attr name="textColorFirst" format="color"/>

在Styles.xml中定義白天和晚上的樣式

<!--日間主題-->
<style name="DayTheme" parent="AppTheme">
    <item name="textColorFirst">@color/textColorFirst_Day</item>
</style>
<!--夜間主題-->
<style name="NightTheme" parent="AppTheme">
    <item name="textColorFirst">@color/textColorFirst_Night</item>
</style>

最後在layout檔案中呼叫這個屬性,在切換主題的時候這個屬性就會被改變了

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:textColor="?attr/textColorFirst"
    />

設定主題

設定主題很直接setTheme(R.style.NightTheme);
具體怎麼去判斷當前主題然後切換,方法太多了這裡就不講了。Demo也有給出簡單方法。

截圖

這裡說截圖其實不準確,但也想不到什麼好的詞。
主要是獲取當前APP的靜態影象。顯示的控制元件都在fragment中,那麼可以獲取fragment的父佈局的DrawingCache.實現如下:

 //mViewGroup就是Demo中fragment的父佈局
 //設定false清除快取
 mViewGroup.setDrawingCacheEnabled(false);
 //設定true之後才可以獲取Bitmap
 mViewGroup.setDrawingCacheEnabled(true);
 mImageView.setImageBitmap(mViewGroup.getDrawingCache());
 mImageView.setAlpha(1f);
 //顯示imageView,在佈局中預設設定不可見
 mImageView.setVisibility(View.VISIBLE);

獲取Fragment狀態,並重新生成

  • 首先需要獲取狀態
    在Demo中只有一個RecyclerView當前的偏移位置。這個位置的定位,如何實現,可以看我之前的部落格[Android RecyclerView定位]

  • 然後重新生成fragment

 private void addFragment(int position,int scroll) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        //如果Fragment存在就移除
        if (mMainFragment != null){
            fragmentTransaction.remove(mMainFragment);
        }
        mMainFragment =  new MainFragment();

        //恢復Demo中Fragment狀態所需要的資料
        Bundle bundle = new Bundle();
        bundle.putInt("position",position);
        bundle.putInt("scroll",scroll);
        mMainFragment.setArguments(bundle);
        //新增新的fragment
        fragmentTransaction.add(R.id.layout_fragment, mMainFragment);
        //提交事務
        fragmentTransaction.commit();
}

漸變效果

最後的一步,也就是給ImageView做一個透明度動畫,實現視覺上切換的效果。至於這個動畫何時開始執行,那就需要做一個Fragment狀態恢復完的回調了,這個回撥放在哪裡就跟佈局恢復的不同有所不同,Demo中是在RecyclerView恢復位置的時候做的回撥。
貼一下動畫程式碼

private void startAnimation(final View view) {
       ValueAnimator animator = ValueAnimator.ofFloat(1f).setDuration(ANIMTION_TIME);
       animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
           @Override
           public void onAnimationUpdate(ValueAnimator animation) {
               float n = (float) animation.getAnimatedValue();
               view.setAlpha(1f - n);
           }
       });
       animator.addListener(new AnimatorListenerAdapter() {
           @Override
           public void onAnimationEnd(Animator animation) {
               super.onAnimationEnd(animation);
               mImageView.setVisibility(View.INVISIBLE);
           }
       });
       animator.start();
}

總結

總體來說夜間模式切換漸變的效果實現並不是很難,難在如何讓樣式生效,我是採用了重新生成Fragment的方法,網上也有遍歷控制元件的方法等等。SO,讓樣式生效的方法是很多的,具體怎麼做就要看APP的具體需求了。以上只是提供一個我想到思路。
需要注意的地方:
1.切換模式的地方最好放在棧底Activity中,如果還有回退的Activity就會更麻煩。
2.設定主題之後的開啟別的Activity時可以將主題判斷放在Activity的setContentView()方法之前。
3.本地儲存主題設定可以放在Activity的onPause()或者onDestroy()中去儲存。

Demo下載