转载请注明出处: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中加入
首先在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文件,加入如下代码:
这个布局很简单我就不多做解释了,就是三个按钮 一个是开启新的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之下,所以通过了以下代码解决:
@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没有测试,没有夜间模式效果
如果大家还有什么疑问,请在下面留言。