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

转载请注明出处: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等三个按钮

Android夜间模式实现,通过在window上加一层半透明的View_第1张图片

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方法的执行步骤如下:

Android夜间模式实现,通过在window上加一层半透明的View_第2张图片


它会重新执行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没有测试,没有夜间模式效果


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

源码下载,请点击这里






你可能感兴趣的:(android,夜间模式,WindowsManager)