转载请标明出处:
http://blog.csdn.net/tyzlmjj/article/details/49255019
本文出自:【M家杰的博客】
概述
做安卓开发有一段时间了,夜间模式的切换在各种APP中见多了,大部分都是点一下切换然后马上改变配色,很是生硬,从来都没去注意过!然后有一天我点了下知乎日报的夜间模式切换,直接被亮瞎狗眼!什么?这还能渐变过去?不行了,得去研究研究……然后就有了这篇博客,主要讲解下我自己如何实现这个切换时渐变的效果。
先来看看最后实现的效果
这是5.0及以上版本的效果,主要是状态栏也实现了渐变,5.0以下状态栏是不修改的
开发程序代码不是重点,关键是知道可以做到什么,如何做到。
SO,现在来讲解下这个效果的大体实现思路。
首先,改变主题比较简单的方法是直接用setTheme()。其它的方法呢我不知道!请恕在下才疏学浅
用setTheme()之前需要先写好样式文件xml,attr、style等,这些后面再讲。
然后写完样式文件,你在Activity中兴匆匆的调用setTheme()也是没用的同学!(如果在setContentView()方法之前调用是可以改变主题的,因为布局还没生成)
setTheme()不起作用的原因是Activity需要重新生成才能自动刷新UI,这个完全重新生成的过程用户体验肯定坑爹。
网上比较常见的处理方法是自己遍历一下目前界面上需要改变颜色的控件,然后手动设置一遍颜色。感觉这样很LOW
我最后想到的让seTheme()生效的方法是:
在Activity中动态加载1个fragment,需要的布局都放在fragment中,然后切换模式的时候销毁现有的fragment ,在动态加载一个新的fragment,用Activity中自己存储的数据去恢复Fragment状态(恢复状态按布局复杂度难易不同),并且在恢复的过程中,用一个ImageView覆盖在上面等恢复完了再渐变透明度(达到渐变效果)。
理一下思路,总共需要的步骤:
1.写好样式和布局文件。
2.切换时调用setTheme()
3.截取当前屏幕的图片放到ImageView中并覆盖在fragment上面
4.销毁fragment,扭一个fragment
5.恢复fragment的状态
6.ImageView透明度渐变达到视觉上的切换效果
7.收工回家
样式文件的写法比较简单,下面给出一个例子:
在attrs.xml(文件名无所谓)中定义一个属性
"textColorFirst" format="color"/>
在Styles.xml中定义白天和晚上的样式
<style name="DayTheme" parent="AppTheme">
<item name="textColorFirst">@color/textColorFirst_Day
style>
<style name="NightTheme" parent="AppTheme">
<item name="textColorFirst">@color/textColorFirst_Night
style>
最后在layout文件中调用这个属性,在切换主题的时候这个属性就会被改变了
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/textColorFirst"
/>
设置主题很直接setTheme(R.style.NightTheme);
具体怎么去判断当前主题然后切换,方法太多了这里就不讲了。Demo也有给出简单方法。
这里说截屏其实不准确,但也想不到什么好的词。
主要是获取当前APP的静态图像。显示的控件都在fragment中,那么可以获取fragment的父布局的DrawingCache.实现如下:
//mViewGroup就是Demo中fragment的父布局
//设置false清除缓存
mViewGroup.setDrawingCacheEnabled(false);
//设置true之后才可以获取Bitmap
mViewGroup.setDrawingCacheEnabled(true);
mImageView.setImageBitmap(mViewGroup.getDrawingCache());
mImageView.setAlpha(1f);
//显示imageView,在布局中默认设置不可见
mImageView.setVisibility(View.VISIBLE);
首先需要获取状态
在Demo中只有一个RecyclerView当前的偏移位置。这个位置的定位,如何实现,可以看我之前的博客[Android RecyclerView定位]
然后重新生成fragment
private void addFragment(int position,int scroll) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
//如果Fragment存在就移除
if (mMainFragment != null){
fragmentTransaction.remove(mMainFragment);
}
mMainFragment = new MainFragment();
//恢复Demo中Fragment状态所需要的数据
Bundle bundle = new Bundle();
bundle.putInt("position",position);
bundle.putInt("scroll",scroll);
mMainFragment.setArguments(bundle);
//添加新的fragment
fragmentTransaction.add(R.id.layout_fragment, mMainFragment);
//提交事务
fragmentTransaction.commit();
}
最后的一步,也就是给ImageView做一个透明度动画,实现视觉上切换的效果。至于这个动画何时开始运行,那就需要做一个Fragment状态恢复完的回调了,这个回调放在哪里就跟布局恢复的不同有所不同,Demo中是在RecyclerView恢复位置的时候做的回调。
贴一下动画代码
private void startAnimation(final View view) {
ValueAnimator animator = ValueAnimator.ofFloat(1f).setDuration(ANIMTION_TIME);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float n = (float) animation.getAnimatedValue();
view.setAlpha(1f - n);
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mImageView.setVisibility(View.INVISIBLE);
}
});
animator.start();
}
总体来说夜间模式切换渐变的效果实现并不是很难,难在如何让样式生效,我是采用了重新生成Fragment的方法,网上也有遍历控件的方法等等。SO,让样式生效的方法是很多的,具体怎么做就要看APP的具体需求了。以上只是提供一个我想到思路。
需要注意的地方:
1.切换模式的地方最好放在栈底Activity中,如果还有回退的Activity就会更麻烦。
2.设置主题之后的打开别的Activity时可以将主题判断放在Activity的setContentView()
方法之前。
3.本地存储主题设置可以放在Activity的onPause()或者onDestroy()中去存储。
[CSDN]
[GitHub]