您的位置:首页 > 移动开发 > Android开发

Android夜间模式实现,通过在window上加一层半透明的View

2016-03-02 15:41 549 查看
转载请注明出处: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;
}

}


这里可以看到,BaseActivity的代码非常简单,主要是判断如果是夜间模式的时候添加用windowManage添加view,当activity销毁的时候设置日间模式。

然后写一下布局文件,创建或打开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的WindwManager.LayouParams 参数中的type

网上有些做法坑点二:就是弹出的dialog没有测试,没有夜间模式效果

如果大家还有什么疑问,请在下面留言。

源码下载,请点击这里

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: