转载请注明出处:http://blog.csdn.net/lizhongstu/article/details/50779939
前言:大家好,由于公司项目需求,要加一个夜间模式的效果
夜间模式的实现方式有以下几种:
1.直接调整屏幕亮度
2.在Window上加一层半透明的View
3.换皮肤式解决方式(一)
每套皮肤使用自己的一套theme,使用attrs.xml+styles.xml+Activity.setTheme()来设置自己的主题以实现换皮肤,要求资源保存在本地。
4.换皮肤式解决方式(二)
图片等资源不在本地,可以由网上下载(可以作为.zip/.apk下载)后加载,但更换起来比较麻烦,需要大量代码配合,相对于.zip/.apk两种方式换肤我比较倾向于.zip方式,因为.apk方式我曾经弄过,很是复杂啊,必须保证.apk皮肤包中的皮肤图片跟主版本的皮肤图片和资源一 一对应起来,这是为了保证两个工程中的R文件中的id要一 一对应,如果皮肤包中的R文件中的id多一个或者少一个就会出现奔溃,反正这种方式没把我给折腾死。
我今天所讲的就是第二种方案实现夜间模式
第一种调整夜间模式的方式我也使用过,但是不是很好用
1. 如果用户把系统亮度调整到最低了,那你在夜间/白天模式几乎就没什么用了,因为亮度已经最低了。
用第二种方案实现夜间模式就能解决这个问题,亮度调整到最低了再window上加一层半透明的View,亮度就会变暗
网上也有讲解第二种实现夜间模式的方法,但是真正放到项目中去使用会出现很多问题,不知道你们遇到过没?网上都是用写几个简单的demo,demo毕竟不是一个真正上线的项目,真应用到项目中使用还是会有很多问题
在Window上加一层半透明的View
创建这种窗体需要向用户申请权限才可以的,因此首先在AndroidManifest.xml中加入<uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINDOW" />
首先在Eclipse中新建一个Android项目,项目名就叫做NightModeDemo
先创建一个BaseActivity的抽象类,所有的activity都继承这个抽象类,在里面加入如下代码:
public abstract class BaseActivity extends FragmentActivity { private WindowManager mWindowManager = null; private View mNightView = null; private WindowManager.LayoutParams mNightViewParam; private boolean mIsAddedView; /** * 夜间模式覆盖view 是否可用 * true:可用 false:不可用 */ private boolean nightModeEnable=true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); int readMode=SpUtil.getinstance(this).getReaderModeCode(); //是否是夜间模式 if( readMode==1 && nightModeEnable){ changeToNight(); } } @Override protected void onStart() { super.onStart(); } @Override public void setContentView(int layoutResID) { super.setContentView(layoutResID); initView(); initData(); setListener(); } @Override public void setContentView(View view) { super.setContentView(view); initView(); initData(); setListener(); } @Override public void setContentView(View view, LayoutParams params) { super.setContentView(view, params); initView(); initData(); setListener(); } @Override protected void onDestroy() { changeToDay(); super.onDestroy(); } @Override protected void onPause() { super.onPause(); } @Override protected void onResume() { super.onResume(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } @Override protected void onStop() { super.onStop(); } protected abstract void initView(); protected abstract void initData(); protected abstract void setListener(); /** * 设置夜间模式 */ private void changeToNight() { if (mIsAddedView == true) return; mNightViewParam = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); mWindowManager = getWindowManager(); mNightView = new View(this); mNightView.setBackgroundResource(R.color.night_float_color); mWindowManager.addView(mNightView, mNightViewParam); mIsAddedView = true; } /** * 设置白天模式 */ public void changeToDay(){ if (mIsAddedView && mNightView!=null) { mWindowManager.removeViewImmediate(mNightView); mWindowManager = null; mNightView = null; mIsAddedView=false; } } /** * 设置夜间模式 添加view是否可用 * 必须在super.onCreate(savedInstanceState);之前调用 */ public void setChangeModeUnEnable(){ nightModeEnable=false; } }
然后写一下布局文件,创建或打开layout目录下的activity_main.xml文件,加入如下代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/button_start_activity" android:layout_width="150dip" android:layout_height="70dip" android:text="@string/start_activity" /> <Button android:id="@+id/button_start_night_mode" android:layout_width="150dip" android:layout_height="70dip" android:layout_marginTop="20dip" android:layout_below="@id/button_start_activity" android:text="@string/start_night_mode" /> <Button android:id="@+id/button_dialog" android:layout_width="150dip" android:layout_height="70dip" android:layout_marginTop="20dip" android:layout_below="@id/button_start_night_mode" android:text="@string/start_dialog" /> </RelativeLayout>这个布局很简单我就不多做解释了,就是三个按钮 一个是开启新的activity,一个是设置夜间模式和日间模式,还有一个是弹出Dialog
创建或打开MainActivity,这个类仍然是程序的主Activity,继承BaseActivity,也是这次demo唯一的Activity,在里面加入如下代码:
public class MainActivity extends BaseActivity implements OnClickListener{ private Button button_start_activity,button_dialog,button_start_night_mode; private WindowManager mWindowManager = null; private View mNightView = null; private WindowManager.LayoutParams mNightViewParam; private boolean mIsAddedView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void initView() { button_start_activity=(Button)findViewById(R.id.button_start_activity); button_dialog=(Button)findViewById(R.id.button_dialog); button_start_night_mode=(Button)findViewById(R.id.button_start_night_mode); } @Override protected void initData() { // 主Activity中初始化数据 加载数据的操作都放在这里 } @Override protected void setListener() { // TODO Auto-generated method stub button_start_activity.setOnClickListener(this); button_dialog.setOnClickListener(this); button_start_night_mode.setOnClickListener(this); } @Override public void onClick(View v) { if(v!=null){ int id=v.getId(); if(id==R.id.button_dialog){ DialogBookShelfQuit dialogBookShelfQuit=new DialogBookShelfQuit(this); dialogBookShelfQuit.show(); }else if(id==R.id.button_start_activity){ Intent intent=new Intent(this,TestActivity.class); startActivity(intent); }else if(id==R.id.button_start_night_mode){ int readMode=SpUtil.getinstance(this).getReaderModeCode(); if(readMode==1 ){//夜间模式 button_start_night_mode.setText("开启夜间模式"); SpUtil.getinstance(this).setReaderModeCode(0); changeToDay(); }else if(readMode==0){ button_start_night_mode.setText("开启日间模式"); SpUtil.getinstance(this).setReaderModeCode(1); changeToNight(); } } } } @Override protected void onDestroy() { //恢复日间模式 changeToDay(); super.onDestroy(); } /** * 设置夜间模式 */ private void changeToNight() { if (mIsAddedView == true) return; mNightViewParam = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); mWindowManager = getWindowManager(); mNightView = new View(this); mNightView.setBackgroundResource(R.color.night_float_color); mWindowManager.addView(mNightView, mNightViewParam); mIsAddedView = true; } /** * 设置白天模式 */ public void changeToDay(){ if (mIsAddedView && mNightView!=null) { mWindowManager.removeViewImmediate(mNightView); mWindowManager = null; mNightView = null; mIsAddedView=false; } } }
主Activity的代码其实也很简单,就是三个 1.开启activity(这个activity代码就不贴了,主要作用是开启夜间模式后点击此按钮是否新的activity也在夜间模式) 2.夜间/日间模式切换 3.弹出dialog等三个按钮
MainActivity主要代码是这段
/** * 设置夜间模式 */ private void changeToNight() { if (mIsAddedView == true) return; mNightViewParam = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); mWindowManager = getWindowManager(); mNightView = new View(this); mNightView.setBackgroundResource(R.color.night_float_color); mWindowManager.addView(mNightView, mNightViewParam); mIsAddedView = true; } /** * 设置白天模式 */ public void changeToDay(){ if (mIsAddedView && mNightView!=null) { mWindowManager.removeViewImmediate(mNightView); mWindowManager = null; mNightView = null; mIsAddedView=false; } }看到了吧,设置夜间模式的方法就是changeToNight(),如果跳转日间模式则直接调用changeToDay方法即可, 但是一定要注意WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
这段代码一定要记得加上,这句代码的意思是为了不让view获取焦点,如果有人对WindowManager不熟悉的话,请先去查询下api。
网上有些做法坑点一:很多做法是不在 设置夜间/日间模式按钮 所属的MainActivity中设置 夜间/日间模式,因为BaseActivity中有了设置夜间/日间模式的代码,
MainActivity又是继承于BaseActivity的,所以网上好多的做法是以下的代码
else if(id==R.id.button_start_night_mode){ int readMode=SpUtil.getinstance(this).getReaderModeCode(); if(readMode==1 ){//夜间模式 button_start_night_mode.setText("开启夜间模式"); SpUtil.getinstance(this).setReaderModeCode(0); // changeToDay(); recreate();//网上做法的错误做法 }else if(readMode==0){ button_start_night_mode.setText("开启日间模式"); SpUtil.getinstance(this).setReaderModeCode(1); // changeToNight(); recreate();//网上做法的错误做法 } }
而recreate方法的执行步骤如下:
它会重新执行onCreate onStart方法,这样如果你的MainActivity中有很多数据库操作,并且Layout比较复杂或者你的MainActivity中嵌入了很多fragement,
会出现黑屏闪动的现象,体验非常之差,所以不建议用这种方法,还有好多人换肤也用这个方法重建activity之后自动换肤,我不知道他们的activity比较简单还是怎么了,
反正我自己尝试会出现闪屏
难道这样就完了,夜间/白天模式就打工告成了?
其实用这种方式会出现一个问题,就是弹出的dialog顶层还是没有半透明的view覆盖
新建一个抽象的AbsDialog,所有的dialog都继承AbsDialog,在AbsDialog中加入以下代码
public abstract class AbsDialog extends Dialog { public AbsDialog(Context context) { super(context); } public AbsDialog(Context context, boolean cancelable, OnCancelListener cancelListener) { super(context, cancelable, cancelListener); } public AbsDialog(Context context, int theme) { super(context, theme); } @Override public void setContentView(int layoutResID) { super.setContentView(layoutResID); initView(); initData(); setListener(); } @Override public void setContentView(View view) { super.setContentView(view); initView(); initData(); setListener(); } @Override public void setContentView(View view, LayoutParams params) { super.setContentView(view, params); initView(); initData(); setListener(); } @SuppressWarnings("deprecation") protected void setProperty(int width, int height) { Window window = getWindow(); WindowManager.LayoutParams p = window.getAttributes(); //夜间模式 int readMode=SpUtil.getinstance(getContext()).getReaderModeCode(); if( readMode==1){ p.type=WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; } Display d = getWindow().getWindowManager().getDefaultDisplay(); p.height = (int) (d.getHeight() * 1); p.width = (int) (d.getWidth() * 1); window.setAttributes(p); } protected abstract void initView(); protected abstract void initData(); protected abstract void setListener(); }
在新建一个DialogBookShelfQuit,在其中加入以下代码:
public class DialogBookShelfQuit extends AbsDialog implements android.view.View.OnClickListener { private Activity context; public DialogBookShelfQuit(Activity context) { super(context, R.style.dialog_normal); this.context = context; setContentView(R.layout.dialog_bookshelf_quit); setProperty(1, 1); } @Override protected void initView() { } @Override protected void initData() { this.setCancelable(true); this.setCanceledOnTouchOutside(false); } @Override protected void setListener() { } @Override public void onClick(View v) { } }
我研究了dialog的源码发现,其实一切 界面 全都是windowManager添加显示的,通过Dialog以下代码打出的WindwManager.LayouParams
Window window = getWindow(); WindowManager.LayoutParams p = window.getAttributes();的type是跟MainActivity夜间模式设置的type是一样的,都是 WindowManager.LayoutParams. TYPE_APPLICATION,这样就确定了MainActivity设置夜间模式后再开启的dialog肯定在MainActivity加一层半透明view之上 ,而我们需要dialog在半透明view之下,所以通过了以下代码解决:
<span style="white-space:pre"> </span>@SuppressWarnings("deprecation") protected void setProperty(int width, int height) { Window window = getWindow(); WindowManager.LayoutParams p = window.getAttributes(); //夜间模式 int readMode=SpUtil.getinstance(getContext()).getReaderModeCode(); if( readMode==1){ p.type=WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; } Display d = getWindow().getWindowManager().getDefaultDisplay(); p.height = (int) (d.getHeight() * 1); p.width = (int) (d.getWidth() * 1); window.setAttributes(p); }
网上有些做法坑点二:就是弹出的dialog没有测试,没有夜间模式效果
如果大家还有什么疑问,请在下面留言。