編輯:關於Android編程
現在說是換膚框架還有點誇大其詞,因為目前只實現了顏色的替換,目前網上已有的換膚框架我都研究過,主要感覺給每個View設置樣式,還要保存每個需要換膚的View,實在是太繁瑣,而且目前我的項目中不需要皮膚功能,開發這個框架也僅僅是為了實現夜間模式,而又不用過多的改造原有的代碼,比如給每個顏色替換成引用等等。從目前實現的效果來看,基本能達到簡單方便的目的,而且也能實現WebView的換膚,且不會重啟Activity,還有過渡動畫,我相信這個框架已經能滿足大多數的項目了。
1.支持通過配置文件設置全局樣式
2.配置文件實現繼承功能,編寫更靈活
3.不需要重啟Activity,並帶有過渡動畫。
4.對原有項目的改造可以說很小,基本只需要改造XML文件。
5.可在多種樣式中切換
6.實現了對WebView的支持。
目前的Library中已經集成了幾種樣式,一種是白天的,一種是網易新聞的夜間模式,一種是百度貼吧的夜間模式,如果你覺得不滿意,可以在你自己項目的raw目錄項新建自己的,注意:配置文件的格式是JSON格式。
1.Library中自帶的style文件
2.style文件的定義,具體的請大家看源文件吧。
//將需要使用的Style文件名傳入即可,注意不需要後綴 StyleHelper.init(this,"wangyi","baidu", "day");
其實此處的注冊就是將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(); } }
在需要切換的地方,調用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); } });
通過以上的配置已經能使用了,但是字體顏色背景色都會統一,對於需要不同字體顏色的需要,還需要我們自定義type。
在我的Demo中每個Item中有兩個TextView,一個用來顯示標題,一個用來顯示內容。而且兩個字體顏色並不同。
我的實現方式是,將內容的字體顏色定義在type_base中,然後再定義一個type_title繼承自type_base重寫字體顏色,如下:
最後再XML中設置type
對於一些系統的View我們不能直接給其內部的view設置type,我們可以通過這種方式設置,關於怎麼實現的我們後面再講。
有一些控件我們不需要換膚,直接設置為type_no就行了,這個type已經定義在StyleHelper中了。
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中跳轉到網易新聞的手機版,就是很好的實現,大家可以看看他們的網頁源碼實現。
主要通過方法遞歸調用實現,主要的難點在於一些有緩存池功能的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
什麼是反射?反射是一種能夠在程序運行時動態訪問、修改某個類中任意屬性(狀態)和方法(行為)的機制(包括private實例和方法),java反射機制提供了以下幾個功能:在運
MainActivity.java代碼:package siso.supervideoplayer;import android.content.pm.ActivityI
熟悉了基礎動畫的實現後,便可以試著去實現常見APP中出現過的那些精美的動畫。今天我主要給大家引入一個APP的ListView的動畫效果: 當展示ListView時,Lis
· ·點此進入上篇: Android 學習(三)上: UI 控件 · · · · ·AnalogClock和DigitalClock // 獲得當前的時間,獲得小時和分鐘