Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 換膚框架

換膚框架

編輯:關於Android編程

序言

現在說是換膚框架還有點誇大其詞,因為目前只實現了顏色的替換,目前網上已有的換膚框架我都研究過,主要感覺給每個View設置樣式,還要保存每個需要換膚的View,實在是太繁瑣,而且目前我的項目中不需要皮膚功能,開發這個框架也僅僅是為了實現夜間模式,而又不用過多的改造原有的代碼,比如給每個顏色替換成引用等等。從目前實現的效果來看,基本能達到簡單方便的目的,而且也能實現WebView的換膚,且不會重啟Activity,還有過渡動畫,我相信這個框架已經能滿足大多數的項目了。

效果

效果

功能

1.支持通過配置文件設置全局樣式

2.配置文件實現繼承功能,編寫更靈活

3.不需要重啟Activity,並帶有過渡動畫。

4.對原有項目的改造可以說很小,基本只需要改造XML文件。

5.可在多種樣式中切換

6.實現了對WebView的支持。

使用

1.配置樣式文件

目前的Library中已經集成了幾種樣式,一種是白天的,一種是網易新聞的夜間模式,一種是百度貼吧的夜間模式,如果你覺得不滿意,可以在你自己項目的raw目錄項新建自己的,注意:配置文件的格式是JSON格式。

1.Library中自帶的style文件

 

技術分享

 

2.style文件的定義,具體的請大家看源文件吧。

 

技術分享

2.在Application中初始化

    //將需要使用的Style文件名傳入即可,注意不需要後綴
  StyleHelper.init(this,"wangyi","baidu", "day");

3.在BaseActivity中初始化

其實此處的注冊就是將acitivty添加到StyleHelper的acitiviy棧中,這樣方便遍歷所有的view,為了防止可能發生的內容洩漏,在activity棧中我使用的是弱引用。

/**
 * Created by zhuguohui on 2016/7/18.
 */
public abstract class BaseActivity extends AppCompatActivity {
    @Override
    public final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        childOnCreate(savedInstanceState);
        //注意,只有到所有view都創建完成以後再初始化
        StyleHelper.initActivity(this);

    }


    public abstract   void childOnCreate(Bundle savedInstanceState);

    public void changeMode(){
        StyleHelper.changeStyle(0, 1);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        //銷毀Activity
        StyleHelper.destroyActivity();
    }
}

4.樣式切換

在需要切換的地方,調用StyleHelper.changeStyle傳入需要切換的StyleId就行了,這個id是在配置文件中注冊的,注意id必需唯一。

   findViewById(R.id.btn_baidu).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StyleHelper.changeStyle(0,1);
            }
        });
        findViewById(R.id.btn_wangyi).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StyleHelper.changeStyle(0,2);
            }
        });

5.個性化定制

通過以上的配置已經能使用了,但是字體顏色背景色都會統一,對於需要不同字體顏色的需要,還需要我們自定義type。

1.自定義type實現不同的字體顏色

在我的Demo中每個Item中有兩個TextView,一個用來顯示標題,一個用來顯示內容。而且兩個字體顏色並不同。

 

技術分享

 

我的實現方式是,將內容的字體顏色定義在type_base中,然後再定義一個type_title繼承自type_base重寫字體顏色,如下:

 

技術分享

 

最後再XML中設置type

 

技術分享

2.給子View設置type

對於一些系統的View我們不能直接給其內部的view設置type,我們可以通過這種方式設置,關於怎麼實現的我們後面再講。

技術分享

3.不需要換膚

有一些控件我們不需要換膚,直接設置為type_no就行了,這個type已經定義在StyleHelper中了。

技術分享

4.WebView換膚

WebView換膚我主要通過JavaScript實現的,所以需要在每個頁面加載完成以後調用 StyleHelper.setupWebView();

      webview.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                StyleHelper.setupWebView(view);
            }
        });

但是這裡面還有很多細節需要注意,為了實現在夜間模式進入webview的時候,webview的背景不是白色,此處需要將webview的背景設置為透明,且需要在外側包裹一層layout,通過對layout背景的變色實現webview背景的變色,布局文件可以參考這樣。



    

在Android4.4以上還必須關閉硬件加速才能實現webview背景透明

技術分享

在Java代碼中調用,這個方法的調用我已經寫在框架中了。

    webView.setBackgroundColor(0);

另外還有一點需要注意,由於我們要在頁面加載完成以後才使用JavaScript動態改變背景色,所以網頁中的body不能設置背景設,如果原來body的背景色為白色,等我們設置成黑色的時候就會有閃爍的感覺,用戶體驗很不好。我的Demo中跳轉到網易新聞的手機版,就是很好的實現,大家可以看看他們的網頁源碼實現。

實現

1.遍歷View的實現

主要通過方法遞歸調用實現,主要的難點在於一些有緩存池功能的View不然AbsListView要通過反射修改緩存池中的View的樣式.
  private static void setColor(View view, boolean IsRecursion) {
        int newColor = 0;
        if (view == null) {
            return;
        }
        //獲取tag_style,此處的作用是將view與styleId進行綁定,防止重復設置。
        Object tag = view.getTag(R.id.tag_style);
        if (tag == null && sCurrentStyleId == 0) {
            view.setTag(R.id.tag_style, sCurrentStyleId);
            return;
        }
        if (tag != null) {
            int viewStyle = (int) tag;
            if (viewStyle == sCurrentStyleId) {
                return;
            }
        }
        view.setTag(R.id.tag_style, sCurrentStyleId);
        //獲取需要顯示的樣式
        Object viewTag = view.getTag();
        String typeName = "";
        if (viewTag != null && viewTag instanceof String) {
            typeName = (String) viewTag;
        } else {
            //判斷是否設置了默認的type,如果有則使用默認的
            if (sHaveSetDefaluType) {
                typeName = sDefalutTypeName;
            }
        }
        //為了實現給子view設置type,我們每一次調用就去掉一個:,然後再設置給其子view
        //比如:tag,在這裡會被變成tag,然後設置給子view,最後遞歸。當遞歸到子view的時候就不含有:
        //就會進入正常的設置過程了。
        boolean needInherit = typeName.startsWith(":");
        if (needInherit) {
            String inheritName = typeName.substring(1);
            if (view instanceof ViewGroup) {
                ViewGroup group = (ViewGroup) view;
                int count = group.getChildCount();
                for (int i = 0; i < count; i++) {
                    View childView = group.getChildAt(i);
                    childView.setTag(inheritName);
                    setColor(childView, IsRecursion);
                }
                return;
            }
        }
        //表明此view不需要換膚,直接return
        if (DISABLE_TYPE_NAME.equals(typeName)) {
            return;
        }
        //webview需要單獨處理
        if (view instanceof WebView) {
            setupWebView((WebView) view);
            return;
        }
        //只有在view沒有設置背景色或者只使用顏色而不是其他
        if (view.getBackground() == null || (view.getBackground() instanceof ColorDrawable)) {
            newColor = getFSColor(sCurrentStyleId, typeName, Attribute.TYPE_BACKGROUND_COLOR);
            if (newColor != 0) {
                view.setBackgroundColor(newColor);
            }
        }


        if (view instanceof ViewGroup) {

            //只有在listview設置了diver的情況下才換膚
            if (view instanceof ListView && ((ListView) view).getDivider() instanceof ColorDrawable) {
                ListView lv = (ListView) view;
                newColor = getFSColor(sCurrentStyleId, typeName, Attribute.TYPE_DIVER_COLOR);
                if (newColor != 0) {
                    int height = lv.getDividerHeight();
                    lv.setDivider(new ColorDrawable(newColor));
                    lv.setDividerHeight(height);
                }

            }

            if (IsRecursion) {
                ViewGroup group = (ViewGroup) view;
                int count = group.getChildCount();
                for (int i = 0; i < count; i++) {
                    setColor(group.getChildAt(i), true);
                }
                if (view instanceof AbsListView) {
                    //如果是ListView則要通過反射將緩存池中的view換膚
                    AbsListView absListView = (AbsListView) view;
                    try {
                        Field field = AbsListView.class.getDeclaredField("mRecycler");
                        field.setAccessible(true);
                        Object o = field.get(absListView);
                        Class cls = Class.forName("android.widget.AbsListView$RecycleBin");
                        Field scrapViews = cls.getDeclaredField("mScrapViews");
                        scrapViews.setAccessible(true);
                        ArrayList[] views = (ArrayList[]) scrapViews.get(o);
                        for (int i = 0; i < views.length; i++) {
                            ArrayList vs = views[i];
                            for (View v : vs) {
                                setColor(v, true);
                            }
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            }
        } else {
            if (view instanceof TextView) {
                TextView tv = (TextView) view;
                newColor = getFSColor(sCurrentStyleId, typeName, Attribute.TYPE_FONT_COLOR);
                if (newColor != 0) {
                    tv.setTextColor(newColor);
                }
            }

        }

    }

2.對新添加的View的處理

通過以上的代碼,我們已經能使已有的view改變樣式了,但是如果用戶new了一個View,添加進入其樣式還是原來的。所以我們需要對給ViewGroup添加監聽事件,主要的代碼如下:

 public static void initActivity(Activity activity) {
        mActivityStack.push(new WeakReference(activity));
        View view = activity.findViewById(android.R.id.content);
        setColor(view, true);
        //給ViewGrou設置監聽器,當有新的view添加時也會設置樣式。
        setOnHierarchyChangeListener(view);
    }


    private static void setOnHierarchyChangeListener(View view) {
        if (view == null) {
            return;
        }

        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;
            viewGroup.setOnHierarchyChangeListener(mOnHierarchyChangeListener);
            int childCount = viewGroup.getChildCount();
            for (int i = 0; i < childCount; i++) {
                //遞歸調用
                setOnHierarchyChangeListener(viewGroup.getChildAt(i));
            }
        }

    }

    private static ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener = new ViewGroup.OnHierarchyChangeListener() {
        @Override
        public void onChildViewAdded(View parent, View child) {
            //如果parent被標記為type_no,則child也會被標記
            if (DISABLE_TYPE_NAME.equals(checkTag(child))) {
                child.setTag(DISABLE_TYPE_NAME);
            }

            setColor(child, true);
            setOnHierarchyChangeListener(child);
        }

        @Override
        public void onChildViewRemoved(View parent, View child) {

        }
    };

    private static String checkTag(View child) {
        String tag = "";
        if (child == null) {
            return tag;
        }
        try {
            View parent = null;
            if (child.getParent() instanceof View) {
                parent = (View) child.getParent();
            }
            String parentTag = (String) parent.getTag();
            while (parent != null && !DISABLE_TYPE_NAME.equals(parentTag)) {
                child = parent;
                parent = null;
                if (child.getParent() instanceof View) {
                    parent = (View) child.getParent();
                    parentTag = (String) parent.getTag();
                }
            }
            return parentTag;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return tag;
    }

3.WebView實現夜間模式

核心還是JavaScript,其他的內容已經在使用的時候說了。

        document.body.style.backgroundColor="#FFFFFF";//改變背景色  
        //改變字體顏色
        document.body.style.color="#000000";
        //改變A標簽顏色
        var as = document.getElementsByTagName("a");
        for(var i=0;i

過渡動畫

大家看注釋吧,沒什麼難度

  //生成動畫,原理:獲取當前界面截圖,生成一個imageview,添加到decorview中,即覆蓋在原來
    //界面的上層,並在一定時間內改變imageview的alpha值,當alpha值為0的時候,將imageview從decorview中移除出去
    private static void createAnimator(Activity activity) {
        if (activity == null) {
            return;
        }
        //獲取decorview
        View decorView = activity.getWindow().getDecorView();
        final ImageView imageView = new ImageView(activity);
        int width = activity.getResources().getDisplayMetrics().widthPixels;
        int height = activity.getResources().getDisplayMetrics().heightPixels;
        Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
        //生成截圖
        decorView.draw(new Canvas(b));
        imageView.setImageBitmap(b);
        final ViewGroup group = (ViewGroup) decorView;
        imageView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        group.addView(imageView);
        ValueAnimator animator = ValueAnimator.ofFloat(1, 0);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float alph = (float) animation.getAnimatedValue();
                imageView.setAlpha(alph);
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                group.removeView(imageView);
            }
        });
        animator.setDuration(1000);
        animator.start();
    }

項目

大家主要看StyleLib吧。

StyleDemo

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved