diff --git a/README.md b/README.md index 03deee0..32384e7 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,15 @@ - 本库libs下有个**AndroidUtils.aar**的依赖包,是由[AndroidUtils](https://github.com/D-clock/AndroidUtils)代码编译生成; - 想要查看**AndroidUtils.aar**中的源代码,可以参考[这里](https://github.com/D-clock/Doc/blob/master/Android/%E4%B8%AA%E4%BA%BA%E6%94%B6%E8%97%8F/%E5%A6%82%E4%BD%95%E6%9F%A5%E7%9C%8Baar%E7%9A%84%E6%BA%90%E4%BB%A3%E7%A0%81.md) -## 最新更新(编辑于2016-05-27) +## 最新更新(编辑于2016-08-29) -- 优化拍照实现; -- 优化项目导入体验; +- 兼添加夜间模式的实现分析,详见文章 [知乎和简书的夜间模式实现套路](notes/知乎和简书的夜间模式实现套路.md) ## 归档文章 - [你需要知道的Android拍照适配问题](notes/你需要知道的Android拍照适配问题.md) +- [你需要知道的Android拍照适配问题](notes/你需要知道的Android拍照适配问题.md) +- [深入理解Android中的Matrix](notes/深入理解Android中的Matrix.md) +- [知乎和简书的夜间模式实现套路](notes/知乎和简书的夜间模式实现套路.md) ## 找我 diff --git a/build.gradle b/build.gradle index 29a1179..a330344 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,14 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.3' + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + apply plugin: 'com.android.application' android { @@ -6,7 +17,7 @@ android { defaultConfig { applicationId "com.clock.study" - minSdkVersion 9 + minSdkVersion 11 targetSdkVersion 23 versionCode 1 versionName "1.0" @@ -31,4 +42,6 @@ dependencies { compile(name: 'AndroidUtils', ext: 'aar') //compile project(":AndroidUtils") compile 'com.android.support:appcompat-v7:23.2.1' -} + compile 'com.android.support:recyclerview-v7:23.2.1' + compile 'com.android.support:cardview-v7:23.1.1' +} \ No newline at end of file diff --git "a/notes/\346\267\261\345\205\245\347\220\206\350\247\243Android\344\270\255\347\232\204Matrix.md" "b/notes/\346\267\261\345\205\245\347\220\206\350\247\243Android\344\270\255\347\232\204Matrix.md" new file mode 100644 index 0000000..de10ad7 --- /dev/null +++ "b/notes/\346\267\261\345\205\245\347\220\206\350\247\243Android\344\270\255\347\232\204Matrix.md" @@ -0,0 +1,132 @@ +# 深入理解 Android 中的 Matrix + +在 Android 开发中,矩阵是一个功能强大并且应用广泛的神器,例如:用它来制作动画效果、改变图片大小、给图片加各类滤镜等。对于矩阵,Android 官方 SDK 为我们提供了一个强大的类 Matrix (还有 ColorMatrix )是一直困扰着我的问题,虽然大致能够调用相应的 API ,但却一直 get 不到其内在的梗。但是出来混总是别想着蒙混过关的,所以最近重新操起一年毕业的线性代数,再本着小事问老婆,大事问Google的心态,终于把多年不解的问题给破了。出于好记性不如烂笔头的原因,便有了本文。在此先感谢下面两篇令我茅舍顿开的文章: + +- [齐次坐标系入门级思考](https://oncemore2020.github.io/blog/homogeneous/) +- [仿射变换与齐次坐标](https://guangchun.wordpress.com/2011/10/12/affineandhomogeneous/) + +读完本文,相信你能够搞明白以下三个问题: + +- 为什么 Matrix 是个 3 X 3 的矩阵 +- Matrix 这个 3 X 3 的矩阵每个元素的作用 +- Matrix 的 setXXX、preXXX、postXXX API 方法的工作原理 + +## Matrix 的结构 + +Matrix 是 Android SDK 提供的一个矩阵类,它代表一个 3 X 3 的矩阵(不懂矩阵为何物的童鞋就要自行 Google 了)。 Matrix 提供了让我们获得 Matrix 值的 API —— **getValues** + +![](http://h.hiphotos.baidu.com/image/pic/item/43a7d933c895d1438a9f6d507bf082025baf07d3.jpg) + +利用此 API 传入一个长度为 9 的 float 数组,即可获得矩阵中每个元素的值。那么这 9 个浮点数的作用和意义是什么呢,从 Android 官方文档上看,它为这个数组中的每一个元素都定义了一个下标常量 + +![](http://c.hiphotos.baidu.com/image/pic/item/f3d3572c11dfa9ece819dc646ad0f703918fc18a.jpg) + +这个 9 个常量取值分别是 0 - 8 + +![](http://c.hiphotos.baidu.com/image/pic/item/908fa0ec08fa513d4cd5e6ad356d55fbb2fbd942.jpg) + +如果我们将这个 float 排成直观的矩阵格式,那它将是下面这样子的 + +![](http://d.hiphotos.baidu.com/image/pic/item/2934349b033b5bb5425dd68d3ed3d539b600bc3f.jpg) + +实际上我们平常利用 Matrix 来进行 Translate(平移)、Scale(缩放)、Rotate(旋转)的操作,就是在操作着这个矩阵中元素的数值来达到我们想要的效果。但是现在问题来了,上面提到的平移、缩放、旋转操作中,旋转和缩放可以用乘法表示,而平移就只能用加法表示,而且 Matrix 是一个 3 X 3 的矩阵,实际上表示这些操作 2 X 2 的矩阵足矣! + +![](http://e.hiphotos.baidu.com/image/pic/item/7acb0a46f21fbe096feccf6063600c338744ad17.jpg) + +![](http://h.hiphotos.baidu.com/image/pic/item/bf096b63f6246b600bbcbe00e3f81a4c510fa217.jpg) + +![](http://c.hiphotos.baidu.com/image/pic/item/d058ccbf6c81800a737a857ab93533fa828b4722.jpg) + +如上,可以依次看到平移、缩放、旋转的矩阵,其中 + +- (x',y')表示执行操作后的点的坐标,(x,y)表示执行操作前的点的坐标 +- tx、ty 分别表示x轴、y轴上平移的距离,Sx、Sy 分别表示x轴、y轴上的缩放比例 +- θ 则表示旋转角度 + +至于上面矩阵的推导过程,网络上很多,这里就不去赘述了。以前到了这里,我就会很纳闷,为什么 2 X 2 矩阵能干的事情,偏偏要用 3 X 3 矩阵去做,直到遇到前面提到的两篇文章才有所领悟。 + +其实在计算机图形应用涉及到几何变换,主要包括平移、旋转、缩放。以矩阵表达式来计算这些变换时,平移是矩阵相加,旋转和缩放则是矩阵相乘。那些数学大神们为了方便计算,所以引入了一样神器叫做**齐次坐标**(不懂的童鞋,老规矩自行搜索),将平移的加法合并用乘法表示。所以,2 X 2 的矩阵经过一番变换后,成了下面这样的。 + +![](http://h.hiphotos.baidu.com/image/pic/item/5d6034a85edf8db184610e100123dd54564e7472.jpg) + +![](http://b.hiphotos.baidu.com/image/pic/item/c83d70cf3bc79f3d89569944b2a1cd11728b290a.jpg) + +![](http://b.hiphotos.baidu.com/image/pic/item/5fdf8db1cb13495458519a105e4e9258d1094a72.jpg) + +至此,我们可以得知**为什么 Matrix 是一个 3 X 3 的矩阵**,其实 2 X 2 的矩阵是足以表示的,不过是为了方便计算而合并写成了 3 X 3 的格式。 + +## Matrix 元素的作用 + +一个 Matrix 共有 9 个元素,那么它每个元素的值发生改变会起到什么作用呢?按照前面所示的齐次坐标转换得到 3 X 3 的矩阵和 Android 文档提供的官方结构相对应,我们不难看出下面的对应关系(其实从 Matrix 中每个位置的常量命名也可以看出来): + +![](http://f.hiphotos.baidu.com/image/pic/item/aec379310a55b3193603a1034ba98226cefc17c9.jpg) + +![](http://e.hiphotos.baidu.com/image/pic/item/562c11dfa9ec8a136f837496ff03918fa0ecc02e.jpg) + +![](http://b.hiphotos.baidu.com/image/pic/item/aa64034f78f0f73676046f770255b319eac413c9.jpg) + +从这我们可以看出这个 Matrix 结构中的每个参数发挥着如下作用: + +- MTRANS_X、MTRANS_Y 同时控制着 Translate +- MSCALE_X、MSCALE_Y 同时控制着 Scale +- MSCALE_X、MSKEW_X、MSCALE_Y、MSKEW_Y 同时控制着 Rotate +- 从名称上看,我们可以顺带看出 MSKEW_X、MSKEW_Y 同时控制着 Skew + +如果要进行代码验证的话,也非常简单,例如直接只对 Matrix 做 Translate 的 API 调用操作,再将 Matrix 的信息打印到控制台,你会发现整个 Matrix 确实只有 MTRANS_X、MTRANS_Y 两个位置的数字在发生变化。其他 Scale、Rotate、Skew 操作也是一样,感兴趣的童鞋可以自行代码验证一番。 + +至此,我们可以大致弄清矩阵每个元素的作用。至于 MPERSP_0、MPERSP_1、MPERSP_2 这三个参数,目前暂时不得而知,网上有文章说这三个参数控制着透视变换,但是文档和 API 上都没怎么提及,所以还是有待验证研究的,有明白的童鞋不妨留言赐教一下,不胜感激。 + +## 理解 Matrix API 调用 + +按照第一小节里面通过齐次坐标转换而来的矩阵方程可以知道,假设一根线执行了平移操作,相当于线上每个点的坐标被下方的矩阵左乘。(缩放和旋转操作也是同理) + +![](http://f.hiphotos.baidu.com/image/pic/item/5882b2b7d0a20cf49c1f77a87e094b36acaf993e.jpg) + +如果要进行同时缩放、平移之类的符合变化操作,也无非就是选取相应的矩阵做左乘操作。为了加深矩阵变换对应 Matrix API 调用的理解,直接通过下面的一个自定义的动画效果和代码来讲解好了。 + +```java + +public class SimpleCustomAnimation extends Animation { + + private int mWidth, mHeight; + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + this.mWidth = width; + this.mHeight = height; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + Matrix matrix = t.getMatrix(); + matrix.preScale(interpolatedTime, interpolatedTime);//缩放 + matrix.preRotate(interpolatedTime * 360);//旋转 + //下面的Translate组合是为了将缩放和旋转的基点移动到整个View的中心,不然系统默认是以View的左上角作为基点 + matrix.preTranslate(-mWidth / 2, -mHeight / 2); + matrix.postTranslate(mWidth / 2, mHeight / 2); + } +} + +``` + +熟悉动画这块的童鞋肯定知道,Animation 就是通过不断改变 applyTransformation 函数传入的 Matrix 来实现各种各样的动画效果的,通过上面 applyTransformation 寥寥的几行 Matrix 的复合变换操作可以得到如下效果 + +![](http://f.hiphotos.baidu.com/image/pic/item/9358d109b3de9c82048c06f86481800a18d843d8.jpg) + +实际上这几行代码用矩阵来表示就相当于如下所示: + +![](http://a.hiphotos.baidu.com/image/pic/item/a686c9177f3e67097989758633c79f3df8dc5539.jpg) + +关于代码的作用上边已经给出了注释,这里就不多写了。主要还是要弄明白 Matrix 复合变换中 pre 、 post 等操作与其对应的矩阵发生的左乘、右乘变化。 + +## 总结 + +到此,整篇文章已经完结,相信已经能够让你明白开头提到的三个问题。其实我们也可以发现,Google 封装了 Matrix 已经是很完美了,几乎屏蔽了所有的数学细节,使得我这种数学水平一般的开发者也能够去调用相应的 API 实现一些简单的效果。虽然被封装得很完美,但掌握相应的一些原理,依旧可以帮你更好的理解一些技术实现,此次加深了对 Matrix 一些操作的理解,帮我自己解决了以前不少的困惑,不知道有没有帮你 get 到一些什么呢? + +上面给的示例代码很简单,复制黏贴即可运行玩耍,实在需要直接运行源码的童鞋就到 https://github.com/D-clock/AndroidStudyCode 找吧! + + + + + \ No newline at end of file diff --git "a/notes/\347\237\245\344\271\216\345\222\214\347\256\200\344\271\246\347\232\204\345\244\234\351\227\264\346\250\241\345\274\217\345\256\236\347\216\260\345\245\227\350\267\257.md" "b/notes/\347\237\245\344\271\216\345\222\214\347\256\200\344\271\246\347\232\204\345\244\234\351\227\264\346\250\241\345\274\217\345\256\236\347\216\260\345\245\227\350\267\257.md" new file mode 100644 index 0000000..6c5ca48 --- /dev/null +++ "b/notes/\347\237\245\344\271\216\345\222\214\347\256\200\344\271\246\347\232\204\345\244\234\351\227\264\346\250\241\345\274\217\345\256\236\347\216\260\345\245\227\350\267\257.md" @@ -0,0 +1,353 @@ +# 知乎和简书的夜间模式实现套路 + +Hello,大家好,我是Clock。今天要写的这篇文章主题是关于夜间模式的实现套路。本来这篇文章是上周要写的,结果因为上周末有其他事情,所以拖到这个周末才完成。曾经和薇薇(钛媒体漂亮的程序媛)聊过夜间模式实现的问题,当时薇薇酱负责钛媒体客户端的重构工作,有个夜间模式功能在考虑要不要用 Android M 新加的夜间模式特性。凭借稍微有点点老司机的经验,我直接说了 NO。按照以往的套路,通常新出的功能都会有坑,或者向下兼容性的问题。自己弄弄 Demo 玩玩是可以的,但是引入企业开发还是谨慎点,说白了就是先等等,让大家把坑填完了再用。果然,Android M 发正式版的时候,预览版里面的夜间模式功能被暂时移除了(哈哈哈哈,机智如我,最新发布的 Android N 正式版已经有夜间模式了,大家可以去玩玩)。 + +## 前言 + +好了,回归正题,说回夜间模式。在网上看到很多童鞋都说用什么什么框架来实现这个功能,然后仔细去看一下各个推荐的框架,发现其实都是动态换肤的,动态换肤可比夜间模式要复杂多了,未免大材小用了。说实话,我一直没用什么好思路,虽然网上有童鞋提供了一种思路是通过 setTheme 然后再 recreate Activity 的方式,但是这样带来的问题是非常多的,看起来就相当不科学(为什么不科学,后文会说)。**于是,直接想到了去逆向分析那些夜间模式做得好的应用的源代码,学习他们的实现套路。所以,本文的实现思路来自于编写这些应用的夜间模式功能的童鞋,先在这里向他们表示感谢。**我的手机里面使用高频的应用不少,其中简书和知乎是属于夜间模式做得相当 nice 的。先给两个效果图大家对比感受下 + + +| 简书 | 知乎 | +| ----------------- | ------------------ | +| ![](http://diycode.b0.upaiyun.com/photo/2016/eeb88e2a8812262c25e220479f05a3a3.gif)| ![](http://diycode.b0.upaiyun.com/photo/2016/59c44219315c5ea097afdc75e5563b87.gif)| + +如果大家仔细观察,肯定会发现,知乎的切换效果更漂亮些,因为它有一个渐变的效果。那么它们的夜间模式到底是如何实现的呢?别急接着往下看,你也可以。 + +## 实现套路 + +这里先展示一下我的实现效果吧 + +| 简书实现效果 | 知乎实现效果 | +| ----------------- | ------------------ | +| ![](http://diycode.b0.upaiyun.com/photo/2016/9886ffe716e2191fda98658a0446a4ca.gif)| ![](http://diycode.b0.upaiyun.com/photo/2016/a7a5cdfd844c0982410ef10d156dfff4.gif)| + +此处分为两个部分,一部分是 xml 文件中要干的活,一部分是 Java 代码要实现的活,先说 xml 吧。 + +### XML 配置 + +首先,先写一套UI界面出来,上方左边是两个 TextView,右边是两个 CheckBox,下方是一个 RecyclerView ,实现很简单,这里我不贴代码了。 + +![](http://diycode.b0.upaiyun.com/photo/2016/5d65ddae827097a0fe7097ac51f512d1.png) + +接着,在 styles 文件中添加两个 Theme,一个是日间主题,一个是夜间主题。它们的属性都是一样的,唯一区别在于颜色效果不同。 + +```xml + + + + + + + +``` + +需要注意的是,上面的 clockTextColor 和 clockBackground 是我自定义的 color 类型属性 + +```xml + + + + + +``` + +然后再到所有需要实现夜间模式功能的 xml 布局文件中,加入类似下面设置,比如我在 RecyclerView 的 Item 布局文件中做了如下设置 + +![](http://diycode.b0.upaiyun.com/photo/2016/0733c4fc7fcffc15ccf1d6bdb2ae366a.png) + +稍稍解释下其作用,如 TextView 里的 android:textColor="?attr/clockTextColor" 是让其字体颜色跟随所设置的 Theme。到这里,xml 需要做的配置全部完成,接下来是 Java 代码实现了。 + +### Java 代码实现 + +大家可以先看下面的实现代码,看不懂的童鞋可以边结合我代码下方实现思路解说。 + +```java + +package com.clock.study.activity; + +import ... + +/** + * 夜间模式实现方案 + * + * @author Clock + * @since 2016-08-11 + */ +public class DayNightActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener { + + private final static String TAG = DayNightActivity.class.getSimpleName(); + /**用于将主题设置保存到SharePreferences的工具类**/ + private DayNightHelper mDayNightHelper; + + private RecyclerView mRecyclerView; + + private LinearLayout mHeaderLayout; + private List mLayoutList; + private List mTextViewList; + private List mCheckBoxList; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); + initData(); + initTheme(); + setContentView(R.layout.activity_day_night); + initView(); + } + + private void initView() { + mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); + RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); + mRecyclerView.setLayoutManager(layoutManager); + mRecyclerView.setAdapter(new SimpleAuthorAdapter()); + + mHeaderLayout = (LinearLayout) findViewById(R.id.header_layout); + + mLayoutList = new ArrayList<>(); + mLayoutList.add((RelativeLayout) findViewById(R.id.jianshu_layout)); + mLayoutList.add((RelativeLayout) findViewById(R.id.zhihu_layout)); + + mTextViewList = new ArrayList<>(); + mTextViewList.add((TextView) findViewById(R.id.tv_jianshu)); + mTextViewList.add((TextView) findViewById(R.id.tv_zhihu)); + + mCheckBoxList = new ArrayList<>(); + CheckBox ckbJianshu = (CheckBox) findViewById(R.id.ckb_jianshu); + ckbJianshu.setOnCheckedChangeListener(this); + mCheckBoxList.add(ckbJianshu); + CheckBox ckbZhihu = (CheckBox) findViewById(R.id.ckb_zhihu); + ckbZhihu.setOnCheckedChangeListener(this); + mCheckBoxList.add(ckbZhihu); + + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + int viewId = buttonView.getId(); + if (viewId == R.id.ckb_jianshu) { + changeThemeByJianShu(); + + } else if (viewId == R.id.ckb_zhihu) { + changeThemeByZhiHu(); + + } + } + + private void initData() { + mDayNightHelper = new DayNightHelper(this); + } + + private void initTheme() { + if (mDayNightHelper.isDay()) { + setTheme(R.style.DayTheme); + } else { + setTheme(R.style.NightTheme); + } + } + + /** + * 切换主题设置 + */ + private void toggleThemeSetting() { + if (mDayNightHelper.isDay()) { + mDayNightHelper.setMode(DayNight.NIGHT); + setTheme(R.style.NightTheme); + } else { + mDayNightHelper.setMode(DayNight.DAY); + setTheme(R.style.DayTheme); + } + } + + /** + * 使用简书的实现套路来切换夜间主题 + */ + private void changeThemeByJianShu() { + toggleThemeSetting(); + refreshUI(); + } + + /** + * 使用知乎的实现套路来切换夜间主题 + */ + private void changeThemeByZhiHu() { + showAnimation(); + toggleThemeSetting(); + refreshUI(); + } + + /** + * 刷新UI界面 + */ + private void refreshUI() { + TypedValue background = new TypedValue();//背景色 + TypedValue textColor = new TypedValue();//字体颜色 + Resources.Theme theme = getTheme(); + theme.resolveAttribute(R.attr.clockBackground, background, true); + theme.resolveAttribute(R.attr.clockTextColor, textColor, true); + + mHeaderLayout.setBackgroundResource(background.resourceId); + for (RelativeLayout layout : mLayoutList) { + layout.setBackgroundResource(background.resourceId); + } + for (CheckBox checkBox : mCheckBoxList) { + checkBox.setBackgroundResource(background.resourceId); + } + for (TextView textView : mTextViewList) { + textView.setBackgroundResource(background.resourceId); + } + + Resources resources = getResources(); + for (TextView textView : mTextViewList) { + textView.setTextColor(resources.getColor(textColor.resourceId)); + } + + int childCount = mRecyclerView.getChildCount(); + for (int childIndex = 0; childIndex < childCount; childIndex++) { + ViewGroup childView = (ViewGroup) mRecyclerView.getChildAt(childIndex); + childView.setBackgroundResource(background.resourceId); + View infoLayout = childView.findViewById(R.id.info_layout); + infoLayout.setBackgroundResource(background.resourceId); + TextView nickName = (TextView) childView.findViewById(R.id.tv_nickname); + nickName.setBackgroundResource(background.resourceId); + nickName.setTextColor(resources.getColor(textColor.resourceId)); + TextView motto = (TextView) childView.findViewById(R.id.tv_motto); + motto.setBackgroundResource(background.resourceId); + motto.setTextColor(resources.getColor(textColor.resourceId)); + } + + //让 RecyclerView 缓存在 Pool 中的 Item 失效 + //那么,如果是ListView,要怎么做呢?这里的思路是通过反射拿到 AbsListView 类中的 RecycleBin 对象,然后同样再用反射去调用 clear 方法 + Class recyclerViewClass = RecyclerView.class; + try { + Field declaredField = recyclerViewClass.getDeclaredField("mRecycler"); + declaredField.setAccessible(true); + Method declaredMethod = Class.forName(RecyclerView.Recycler.class.getName()).getDeclaredMethod("clear", (Class[]) new Class[0]); + declaredMethod.setAccessible(true); + declaredMethod.invoke(declaredField.get(mRecyclerView), new Object[0]); + RecyclerView.RecycledViewPool recycledViewPool = mRecyclerView.getRecycledViewPool(); + recycledViewPool.clear(); + + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + refreshStatusBar(); + } + + /** + * 刷新 StatusBar + */ + private void refreshStatusBar() { + if (Build.VERSION.SDK_INT >= 21) { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = getTheme(); + theme.resolveAttribute(R.attr.colorPrimary, typedValue, true); + getWindow().setStatusBarColor(getResources().getColor(typedValue.resourceId)); + } + } + + /** + * 展示一个切换动画 + */ + private void showAnimation() { + final View decorView = getWindow().getDecorView(); + Bitmap cacheBitmap = getCacheBitmapFromView(decorView); + if (decorView instanceof ViewGroup && cacheBitmap != null) { + final View view = new View(this); + view.setBackgroundDrawable(new BitmapDrawable(getResources(), cacheBitmap)); + ViewGroup.LayoutParams layoutParam = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + ((ViewGroup) decorView).addView(view, layoutParam); + ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f); + objectAnimator.setDuration(300); + objectAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + ((ViewGroup) decorView).removeView(view); + } + }); + objectAnimator.start(); + } + } + + /** + * 获取一个 View 的缓存视图 + * + * @param view + * @return + */ + private Bitmap getCacheBitmapFromView(View view) { + final boolean drawingCacheEnabled = true; + view.setDrawingCacheEnabled(drawingCacheEnabled); + view.buildDrawingCache(drawingCacheEnabled); + final Bitmap drawingCache = view.getDrawingCache(); + Bitmap bitmap; + if (drawingCache != null) { + bitmap = Bitmap.createBitmap(drawingCache); + view.setDrawingCacheEnabled(false); + } else { + bitmap = null; + } + return bitmap; + } +} + + +``` + +实现思路和代码解说: + +1. DayNightHelper 类是用于保存夜间模式设置到 SharePreferences 的工具类,在 initData 函数中被初始化,其他的 View 和 Layout 都是界面布局,在 initView 函数中被初始化; +2. 在 Activity 的 onCreate 函数调用 setContentView 之前,需要先去 setTheme,因为当 View 创建成功后 ,再去 setTheme 是无法对 View 的 UI 效果产生影响的; +3. onCheckedChanged 用于监听日间模式和夜间模式的切换操作; +4. **refreshUI 是本实现的关键函数**,起着切换效果的作用,通过 TypedValue 和 Theme.resolveAttribute 在代码中获取 Theme 中设置的颜色,来重新设置控件的背景色或者字体颜色等等。**需要特别注意的是 RecyclerView 和 ListView 这种比较特殊的控件处理方式,代码注释中已经说明,大家可以看代码中注释**; +5. refreshStatusBar 用于刷新顶部通知栏位置的颜色; +6. **showAnimation 和 getCacheBitmapFromView 同样是本实现的关键函数**,getCacheBitmapFromView 用于将 View 中的内容转换成 Bitmap(类似于截屏操作那样),showAnimation 是用于展示一个渐隐效果的属性动画,这个属性作用在哪个对象上呢?是一个 View ,一个在代码中动态填充到 DecorView 中的 View(不知道 DecorView 的童鞋得回去看看 Android Window 相关的知识)。**知乎之所以在夜间模式切换过程中会有渐隐效果,是因为在切换前进行了截屏,同时将截屏拿到的 Bitmap 设置到动态填充到 DecorView 中的 View 上,并对这个 View 执行一个渐隐的属性动画,所以使得我们能够看到一个漂亮的渐隐过渡的动画效果。而且在动画结束的时候再把这个动态添加的 View 给 remove 了,避免了 Bitmap 造成内存飙升问题。**对待知乎客户端开发者这种处理方式,我必须双手点赞外加一个大写的服。 + +到这里,实现套路基本说完了,简书和知乎的实现套路如上所述,区别就是知乎多了个截屏和渐隐过渡动画效果而已。 + +## 一些思考 + +整理逆向分析的过程,也对夜间模式的实现有了不少思考,希望与各位童鞋们探讨分享。 + +> 最初步的逆向分析过程就发现了,知乎和简书并没有引入任何第三方框架来实现夜间模式,为什么呢? + +因为我看到的大部分都实现夜间模式的思路都是用开源的换肤框架,或多或少存在着些 BUG。简书和知乎不用可能是出于框架不稳定性,以及我前面提到的用换肤框架来实现夜间模式大材小用吧。(我也只是瞎猜,哈哈哈) + +> 前面我提到,通过 setTheme 然后再去 Activity recreate 的方案不可行,为什么呢? + +我认为不可行的原因有两点,一个是 Activity recreate 会有闪烁效果体验不加,二是 Activity recreate 涉及到状态状态保存问题,如自身的状态保存,如果 Activity 中包含着多个 Fragment ,那就更加头疼了。 + +> 知乎和简书设置夜间模式的位置,有点巧妙,巧妙在哪? + +知乎和简书出发夜间模式切换的地方,都是在 MainActivity 的一个 Fragment 中。也就是说,如果你要切换模式时,必须回到主界面,此时只存在主界面一个 Activity,只需要遍历主界面更新控件色调即可。而对于其他设置夜间模式后新建的 Activity ,只需要在 setContentView 之前做一下判断并 setTheme 即可。 + +## 总结 + +关于简书和知乎夜间模式功能实现的套路就讲解到这里,**整个实现套路都是我通过逆向分析简书和知乎的代码取得,这里再一次向实现这些代码的童鞋以示感谢。**当然,上面的代码我是经过精简提炼过的,在原先简书和知乎客户端中的实现代码还做了相应的抽象设计和递归遍历等等,这里是为了方便讲解而做了精简。如果有童鞋喜欢这种实现套路,也可以自己加以抽象封装。这里也推荐各位童鞋一个我常用的思路,就是当你对一个功能没有思路时,大可找一些实现了这类功能的优秀应用进行逆向代码分析。需要实现代码的童鞋,可以访问: + +> https://github.com/D-clock/AndroidStudyCode + +**也欢迎大家关注我的[简书](http://www.jianshu.com/users/ec95b5891948/latest_articles)和[Github](https://github.com/D-clock)。** \ No newline at end of file diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 794c2b8..5417e19 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -29,6 +29,21 @@ android:name=".activity.PhotoPreviewActivity" android:label="@string/photo_preview" android:screenOrientation="portrait" /> + + + + + \ No newline at end of file diff --git a/src/main/java/com/clock/study/activity/AnimationActivity.java b/src/main/java/com/clock/study/activity/AnimationActivity.java new file mode 100644 index 0000000..523f1a9 --- /dev/null +++ b/src/main/java/com/clock/study/activity/AnimationActivity.java @@ -0,0 +1,110 @@ +package com.clock.study.activity; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.RotateAnimation; +import android.view.animation.ScaleAnimation; +import android.view.animation.TranslateAnimation; +import android.widget.Button; + +import com.clock.study.R; +import com.clock.study.animation.SimpleCustomAnimation; + +/** + * Android动画效果实现复习 + */ +public class AnimationActivity extends AppCompatActivity implements View.OnClickListener { + + private Button mBtnTranslate; + private Button mBtnScale; + private Button mBtnRotate; + private Button mBtnAlpha; + private Button mBtnSet; + private Button mBtnSimpleCustom; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_android_anim); + + mBtnTranslate = (Button) findViewById(R.id.btn_translate); + mBtnTranslate.setOnClickListener(this); + + mBtnScale = (Button) findViewById(R.id.btn_scale); + mBtnScale.setOnClickListener(this); + + mBtnRotate = (Button) findViewById(R.id.btn_rotate); + mBtnRotate.setOnClickListener(this); + + mBtnAlpha = (Button) findViewById(R.id.btn_alpha); + mBtnAlpha.setOnClickListener(this); + + mBtnSet = (Button) findViewById(R.id.btn_set); + mBtnSet.setOnClickListener(this); + + mBtnSimpleCustom = (Button) findViewById(R.id.btn_simple_custom_anim); + mBtnSimpleCustom.setOnClickListener(this); + + } + + @Override + public void onClick(View v) { + int viewId = v.getId(); + if (viewId == R.id.btn_translate) {//偏移动画 + //TranslateAnimation translateAnim = new TranslateAnimation(0, 500, 0, 500); + /*TranslateAnimation translateAnim = new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 500, Animation.ABSOLUTE, 0, + Animation.ABSOLUTE, 500);*/ + TranslateAnimation translateAnim = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT, 1.0f, Animation.RELATIVE_TO_PARENT, 0, + Animation.RELATIVE_TO_PARENT, 1.0f); + translateAnim.setDuration(2000); + mBtnTranslate.startAnimation(translateAnim); + //translateAnim.setFillAfter(true);//保持动画效果 + + } else if (viewId == R.id.btn_scale) {//缩放动画 + + //ScaleAnimation scaleAnim = new ScaleAnimation(0.5f, 1, 0.5f, 1); + //ScaleAnimation scaleAnim = new ScaleAnimation(0.5f, 1, 0.5f, 1, 300, 300); + ScaleAnimation scaleAnim = new ScaleAnimation(0.5f, 1, 0.5f, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); + scaleAnim.setDuration(1000); + mBtnScale.startAnimation(scaleAnim); + + } else if (viewId == R.id.btn_rotate) {//旋转动画 + + //RotateAnimation rotateAnim = new RotateAnimation(0, 360); + //RotateAnimation rotateAnim = new RotateAnimation(0, 360, 100, 100); + RotateAnimation rotateAnim = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); + rotateAnim.setDuration(2000); + mBtnRotate.startAnimation(rotateAnim); + + } else if (viewId == R.id.btn_alpha) {//透明度动画 + + AlphaAnimation alphaAnim = new AlphaAnimation(0, 1); + alphaAnim.setDuration(2000); + mBtnAlpha.startAnimation(alphaAnim); + + } else if (viewId == R.id.btn_set) {//动画合集 + + AnimationSet animSet = new AnimationSet(true); + animSet.setDuration(2000); + RotateAnimation rotateAnim = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); + ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); + animSet.addAnimation(rotateAnim); + animSet.addAnimation(scaleAnimation); + AlphaAnimation alphaAnim = new AlphaAnimation(0, 1); + animSet.addAnimation(alphaAnim); + mBtnSet.startAnimation(animSet); + + } else if (viewId == R.id.btn_simple_custom_anim) { + + SimpleCustomAnimation simpleCustomAnim = new SimpleCustomAnimation();//简单的自定义动画效果 + simpleCustomAnim.setDuration(1000); + mBtnSimpleCustom.startAnimation(simpleCustomAnim); + + } + + } +} diff --git a/src/main/java/com/clock/study/activity/AnimationTestActivity.java b/src/main/java/com/clock/study/activity/AnimationTestActivity.java new file mode 100644 index 0000000..24c2eac --- /dev/null +++ b/src/main/java/com/clock/study/activity/AnimationTestActivity.java @@ -0,0 +1,67 @@ +package com.clock.study.activity; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.BaseAdapter; +import android.widget.ListView; + +import com.clock.study.R; +import com.clock.study.animation.SimpleCustomAnimation; + +/** + * 测试Animation动画效果 + */ +public class AnimationTestActivity extends AppCompatActivity implements View.OnClickListener { + + private ListView mTestListView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_animation_test); + + mTestListView = (ListView) findViewById(R.id.list_view_test); + mTestListView.setAdapter(new SimpleTestListAdapter()); + + findViewById(R.id.btn_test_anim).setOnClickListener(this); + } + + @Override + public void onClick(View v) { + int viewId = v.getId(); + if (viewId == R.id.btn_test_anim) { + Animation animation = new SimpleCustomAnimation(); + animation.setDuration(1000); + mTestListView.startAnimation(animation); + } + } + + private class SimpleTestListAdapter extends BaseAdapter { + + @Override + public int getCount() { + return 20; + } + + @Override + public Object getItem(int position) { + return position; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = View.inflate(parent.getContext(), R.layout.author_info_layout, null); + } + return convertView; + } + } +} diff --git a/src/main/java/com/clock/study/activity/AnimatorActivity.java b/src/main/java/com/clock/study/activity/AnimatorActivity.java new file mode 100644 index 0000000..91d4622 --- /dev/null +++ b/src/main/java/com/clock/study/activity/AnimatorActivity.java @@ -0,0 +1,266 @@ +package com.clock.study.activity; + +import android.animation.AnimatorInflater; +import android.animation.AnimatorSet; +import android.animation.Keyframe; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.graphics.Color; +import android.os.Bundle; +import android.support.v4.view.ViewCompat; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.view.animation.BounceInterpolator; + +import com.clock.study.R; + +/** + * About Android Animator + * + * @author Clock + * @since 2016-07-21 + */ +public class AnimatorActivity extends AppCompatActivity implements View.OnClickListener { + + private static final String TAG = AnimatorActivity.class.getSimpleName(); + + private View mTarget; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_animator); + + mTarget = findViewById(R.id.anim_target); + + findViewById(R.id.btn_value_anim).setOnClickListener(this); + findViewById(R.id.btn_object_anim_alpha).setOnClickListener(this); + findViewById(R.id.btn_object_anim_rotation).setOnClickListener(this); + findViewById(R.id.btn_object_anim_set).setOnClickListener(this); + findViewById(R.id.btn_object_anim_xml).setOnClickListener(this); + findViewById(R.id.btn_simple_value_animator).setOnClickListener(this); + findViewById(R.id.btn_value_animator_argb).setOnClickListener(this); + findViewById(R.id.btn_bounce_interpolator).setOnClickListener(this); + findViewById(R.id.btn_simple_key_frame).setOnClickListener(this); + findViewById(R.id.btn_shake_key_frame).setOnClickListener(this); + + } + + @Override + public void onClick(View v) { + int viewId = v.getId(); + if (viewId == R.id.btn_value_anim) { + ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100); + valueAnimator.setDuration(3000); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int currentValue = (int) animation.getAnimatedValue(); + Log.i(TAG, "currentValue: " + currentValue); + } + }); + valueAnimator.start(); + + } else if (viewId == R.id.btn_object_anim_alpha) { + ObjectAnimator alphaObjectAnimator = ObjectAnimator.ofFloat(mTarget, "alpha", 1f, 0f, 1f); + alphaObjectAnimator.setDuration(3000); + alphaObjectAnimator.start(); + + } else if (viewId == R.id.btn_object_anim_rotation) { + ObjectAnimator rotationObjectAnimator = ObjectAnimator.ofFloat(mTarget, "rotation", 0f, 360f); + rotationObjectAnimator.setDuration(3000); + rotationObjectAnimator.start(); + + } else if (viewId == R.id.btn_object_anim_set) { + AnimatorSet animatorSet = new AnimatorSet(); + ObjectAnimator alphaObjectAnimator = ObjectAnimator.ofFloat(mTarget, "alpha", 1f, 0f, 1f); + ObjectAnimator rotationObjectAnimator = ObjectAnimator.ofFloat(mTarget, "rotation", 0f, 360f); + animatorSet.play(alphaObjectAnimator).with(rotationObjectAnimator); + //animatorSet.play(alphaObjectAnimator).after(rotationObjectAnimator); + //animatorSet.playTogether(alphaObjectAnimator, rotationObjectAnimator); + animatorSet.setDuration(3000); + animatorSet.start(); + + } else if (viewId == R.id.btn_object_anim_xml) { + AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.simple_animator); + animatorSet.setTarget(mTarget); + animatorSet.start(); + + } else if (viewId == R.id.btn_simple_value_animator) { + displayColorAnimation(mTarget, "#0000ff", "#ff0000"); + + } else if (viewId == R.id.btn_value_animator_argb) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + int startColor = 0x00000000; + int centerColor = 0xff00ff89; + int endColor = 0x00000000; + ValueAnimator valueAnimator = ValueAnimator.ofArgb(startColor, centerColor, endColor); + valueAnimator.setDuration(6000); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int color = (int) animation.getAnimatedValue(); + mTarget.setBackgroundColor(color); + } + }); + valueAnimator.start(); + } + + } else if (viewId == R.id.btn_bounce_interpolator) { + ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mTarget, "translationY", 0f, 1500f); + objectAnimator.setInterpolator(new BounceInterpolator()); + objectAnimator.setDuration(4000); + objectAnimator.start(); + + } else if (viewId == R.id.btn_simple_key_frame) { + Keyframe kf0 = Keyframe.ofFloat(0f, 0f); + Keyframe kf1 = Keyframe.ofFloat(.5f, 360f); + Keyframe kf2 = Keyframe.ofFloat(1f, 0f); + PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2); + ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(mTarget, pvhRotation); + rotationAnim.setDuration(5000); + rotationAnim.start(); + + } else if (viewId == R.id.btn_shake_key_frame) { + displayShakeAnimator(mTarget, 1f); + + } + } + + /** + * 创建一个旋转动画 + * + * @param target + * @param shakeFactor + * @return + */ + private void displayShakeAnimator(View target, float shakeFactor) { + Keyframe scaleXkf0 = Keyframe.ofFloat(0f, 1f); + Keyframe scaleXkf1 = Keyframe.ofFloat(0.1f, 0.9f); + Keyframe scaleXkf2 = Keyframe.ofFloat(0.2f, 0.9f); + Keyframe scaleXkf3 = Keyframe.ofFloat(0.3f, 0.9f); + Keyframe scaleXkf4 = Keyframe.ofFloat(0.4f, 1.1f); + Keyframe scaleXkf5 = Keyframe.ofFloat(0.5f, 1.1f); + Keyframe scaleXkf6 = Keyframe.ofFloat(0.6f, 1.1f); + Keyframe scaleXkf7 = Keyframe.ofFloat(0.7f, 1.1f); + Keyframe scaleXkf8 = Keyframe.ofFloat(0.8f, 1.1f); + Keyframe scaleXkf9 = Keyframe.ofFloat(0.9f, 1.1f); + Keyframe scaleXkf10 = Keyframe.ofFloat(1f, 1f); + + PropertyValuesHolder scaleXHolder = PropertyValuesHolder.ofKeyframe("scaleX", scaleXkf0, scaleXkf1, scaleXkf2, scaleXkf3, scaleXkf4, + scaleXkf5, scaleXkf6, scaleXkf7, scaleXkf8, scaleXkf9, scaleXkf10); + + Keyframe scaleYkf0 = Keyframe.ofFloat(0f, 1f); + Keyframe scaleYkf1 = Keyframe.ofFloat(0.1f, 0.9f); + Keyframe scaleYkf2 = Keyframe.ofFloat(0.2f, 0.9f); + Keyframe scaleYkf3 = Keyframe.ofFloat(0.3f, 0.9f); + Keyframe scaleYkf4 = Keyframe.ofFloat(0.4f, 1.1f); + Keyframe scaleYkf5 = Keyframe.ofFloat(0.5f, 1.1f); + Keyframe scaleYkf6 = Keyframe.ofFloat(0.6f, 1.1f); + Keyframe scaleYkf7 = Keyframe.ofFloat(0.7f, 1.1f); + Keyframe scaleYkf8 = Keyframe.ofFloat(0.8f, 1.1f); + Keyframe scaleYkf9 = Keyframe.ofFloat(0.9f, 1.1f); + Keyframe scaleYkf10 = Keyframe.ofFloat(1f, 1f); + PropertyValuesHolder scaleYHolder = PropertyValuesHolder.ofKeyframe("scaleY", scaleYkf0, scaleYkf1, scaleYkf2, scaleYkf3, scaleYkf4, + scaleYkf5, scaleYkf6, scaleYkf7, scaleYkf8, scaleYkf9, scaleYkf10); + + + PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofKeyframe("rotation", + Keyframe.ofFloat(0f, 0), + Keyframe.ofFloat(0.1f, -3 * shakeFactor), + Keyframe.ofFloat(0.2f, -3 * shakeFactor), + Keyframe.ofFloat(0.3f, 3 * shakeFactor), + Keyframe.ofFloat(0.4f, -3 * shakeFactor), + Keyframe.ofFloat(0.5f, 3 * shakeFactor), + Keyframe.ofFloat(0.6f, -3 * shakeFactor), + Keyframe.ofFloat(0.7f, -3 * shakeFactor), + Keyframe.ofFloat(0.8f, 3 * shakeFactor), + Keyframe.ofFloat(0.9f, -3 * shakeFactor), + Keyframe.ofFloat(1f, 0)); + + + ObjectAnimator.ofPropertyValuesHolder(target, scaleXHolder, scaleYHolder, rotationHolder).setDuration(1000).start(); + } + + /** + * 显示颜色变化的动画 + * + * @param target + * @param startColor + * @param endColor + */ + private void displayColorAnimation(final View target, final String startColor, final String endColor) { + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 100f); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) { + float fraction = animation.getAnimatedFraction(); + if (target != null) { + int startRed = Integer.parseInt(startColor.substring(1, 3), 16); + int startGreen = Integer.parseInt(startColor.substring(3, 5), 16); + int startBlue = Integer.parseInt(startColor.substring(5, 7), 16); + int endRed = Integer.parseInt(endColor.substring(1, 3), 16); + int endGreen = Integer.parseInt(endColor.substring(3, 5), 16); + int endBlue = Integer.parseInt(endColor.substring(5, 7), 16); + + int redDiff = Math.abs(endRed - startRed); + int greenDiff = Math.abs(endGreen - startGreen); + int blueDiff = Math.abs(endBlue - startBlue); + int colorDiff = redDiff + greenDiff + blueDiff; + + int currRed = getCurrentColor(startRed, endRed, colorDiff, fraction); + int currGreen = getCurrentColor(startGreen, endGreen, colorDiff, fraction); + int currBlue = getCurrentColor(startBlue, endBlue, colorDiff, fraction); + + String colorString = "#" + getHexString(currRed) + getHexString(currGreen) + getHexString(currBlue); + int color = Color.parseColor(colorString); + target.setBackgroundColor(color); + + } + } + + } + }); + valueAnimator.setDuration(3000); + valueAnimator.start(); + + } + + /** + * 获取当前新颜色 + * + * @param startColor + * @param endColor + * @param colorDiff + * @param fraction + * @return + */ + private int getCurrentColor(int startColor, int endColor, int colorDiff, float fraction) { + int currentColor = 0; + if (startColor > endColor) { + currentColor = (int) (startColor - fraction * colorDiff); + } else { + currentColor = (int) (startColor + fraction * colorDiff); + } + if (currentColor >= 0 && currentColor <= 256) {//最终的色值要确保在0到256之间 + return currentColor; + } else { + currentColor = endColor; + } + return currentColor; + } + + /** + * 将10进制颜色值转换成16进制。 + */ + private String getHexString(int value) { + String hexString = Integer.toHexString(value); + if (hexString.length() == 1) { + hexString = "0" + hexString; + } + return hexString; + } +} diff --git a/src/main/java/com/clock/study/activity/AuthorActivity.java b/src/main/java/com/clock/study/activity/AuthorActivity.java new file mode 100644 index 0000000..9d47ff6 --- /dev/null +++ b/src/main/java/com/clock/study/activity/AuthorActivity.java @@ -0,0 +1,32 @@ +package com.clock.study.activity; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.clock.study.R; +import com.clock.study.helper.DayNightHelper; + +public class AuthorActivity extends AppCompatActivity { + + private DayNightHelper mDayNightHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initData(); + initTheme(); + setContentView(R.layout.activity_author); + } + + private void initData() { + mDayNightHelper = new DayNightHelper(this); + } + + private void initTheme() { + if (mDayNightHelper.isDay()) { + setTheme(R.style.DayTheme); + } else { + setTheme(R.style.NightTheme); + } + } +} diff --git a/src/main/java/com/clock/study/activity/CapturePhotoActivity.java b/src/main/java/com/clock/study/activity/CapturePhotoActivity.java index 9cbe5d1..f0d813b 100644 --- a/src/main/java/com/clock/study/activity/CapturePhotoActivity.java +++ b/src/main/java/com/clock/study/activity/CapturePhotoActivity.java @@ -1,10 +1,20 @@ package com.clock.study.activity; +import android.Manifest; +import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; +import android.widget.Toast; import com.clock.study.R; import com.clock.study.helper.CapturePhotoHelper; @@ -23,6 +33,11 @@ public class CapturePhotoActivity extends AppCompatActivity implements View.OnCl private final static String TAG = CapturePhotoActivity.class.getSimpleName(); private final static String EXTRA_RESTORE_PHOTO = "extra_restore_photo"; + /** + * 运行时权限申请码 + */ + private final static int RUNTIME_PERMISSION_REQUEST_CODE = 0x1; + private CapturePhotoHelper mCapturePhotoHelper; private File mRestorePhotoFile; @@ -32,8 +47,6 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera_take_photo); - mCapturePhotoHelper = new CapturePhotoHelper(this, FolderManager.getPhotoFolder()); - findViewById(R.id.iv_take_photo).setOnClickListener(this); } @@ -42,29 +55,62 @@ protected void onCreate(Bundle savedInstanceState) { protected void onSaveInstanceState(Bundle outState) { Log.i(TAG, "onSaveInstanceState"); super.onSaveInstanceState(outState); - mRestorePhotoFile = mCapturePhotoHelper.getPhoto(); - Log.i(TAG, "onSaveInstanceState , mRestorePhotoFile: " + mRestorePhotoFile); - if (mRestorePhotoFile != null) { - outState.putSerializable(EXTRA_RESTORE_PHOTO, mRestorePhotoFile); + if (mCapturePhotoHelper != null) { + mRestorePhotoFile = mCapturePhotoHelper.getPhoto(); + Log.i(TAG, "onSaveInstanceState , mRestorePhotoFile: " + mRestorePhotoFile); + if (mRestorePhotoFile != null) { + outState.putSerializable(EXTRA_RESTORE_PHOTO, mRestorePhotoFile); + } } - } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { Log.i(TAG, "onRestoreInstanceState"); super.onRestoreInstanceState(savedInstanceState); - mRestorePhotoFile = (File) savedInstanceState.getSerializable(EXTRA_RESTORE_PHOTO); - Log.i(TAG, "onRestoreInstanceState , mRestorePhotoFile: " + mRestorePhotoFile); - mCapturePhotoHelper.setPhoto(mRestorePhotoFile); + if (mCapturePhotoHelper != null) { + mRestorePhotoFile = (File) savedInstanceState.getSerializable(EXTRA_RESTORE_PHOTO); + Log.i(TAG, "onRestoreInstanceState , mRestorePhotoFile: " + mRestorePhotoFile); + mCapturePhotoHelper.setPhoto(mRestorePhotoFile); + } } @Override public void onClick(View v) { int viewId = v.getId(); if (viewId == R.id.iv_take_photo) { - mCapturePhotoHelper.capture(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //Android M 处理Runtime Permission + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {//检查是否有写入SD卡的授权 + Log.i(TAG, "granted permission!"); + turnOnCamera(); + } else { + Log.i(TAG, "denied permission!"); + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + Log.i(TAG, "should show request permission rationale!"); + } + requestPermission(); + } + } else { + turnOnCamera(); + } + } + } + + /** + * 开启相机 + */ + private void turnOnCamera() { + if (mCapturePhotoHelper == null) { + mCapturePhotoHelper = new CapturePhotoHelper(this, FolderManager.getPhotoFolder()); } + mCapturePhotoHelper.capture(); + } + + /** + * 申请写入sd卡的权限 + */ + private void requestPermission() { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RUNTIME_PERMISSION_REQUEST_CODE); } @Override @@ -88,4 +134,58 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } } + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == RUNTIME_PERMISSION_REQUEST_CODE) { + for (int index = 0; index < permissions.length; index++) { + String permission = permissions[index]; + if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) { + if (grantResults[index] == PackageManager.PERMISSION_GRANTED) { + Log.i(TAG, "onRequestPermissionsResult: permission is granted!"); + turnOnCamera(); + + } else { + showMissingPermissionDialog(); + + } + } + } + } + } + + /** + * 显示打开权限提示的对话框 + */ + private void showMissingPermissionDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.help); + builder.setMessage(R.string.help_content); + + builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Toast.makeText(CapturePhotoActivity.this, R.string.camera_open_error, Toast.LENGTH_SHORT).show(); + finish(); + } + }); + + builder.setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + turnOnSettings(); + } + }); + + builder.show(); + } + + /** + * 启动系统权限设置界面 + */ + private void turnOnSettings() { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + getPackageName())); + startActivity(intent); + } } diff --git a/src/main/java/com/clock/study/activity/DayNightActivity.java b/src/main/java/com/clock/study/activity/DayNightActivity.java new file mode 100644 index 0000000..396b427 --- /dev/null +++ b/src/main/java/com/clock/study/activity/DayNightActivity.java @@ -0,0 +1,269 @@ +package com.clock.study.activity; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.clock.study.R; +import com.clock.study.adapter.SimpleAuthorAdapter; +import com.clock.study.helper.DayNightHelper; +import com.clock.study.type.DayNight; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * 夜间模式实现方案 + * + * @author Clock + * @since 2016-08-11 + */ +public class DayNightActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener { + + private final static String TAG = DayNightActivity.class.getSimpleName(); + + private DayNightHelper mDayNightHelper; + + private RecyclerView mRecyclerView; + + private LinearLayout mHeaderLayout; + private List mLayoutList; + private List mTextViewList; + private List mCheckBoxList; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); + initData(); + initTheme(); + setContentView(R.layout.activity_day_night); + initView(); + } + + private void initView() { + mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); + RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); + mRecyclerView.setLayoutManager(layoutManager); + mRecyclerView.setAdapter(new SimpleAuthorAdapter()); + + mHeaderLayout = (LinearLayout) findViewById(R.id.header_layout); + + mLayoutList = new ArrayList<>(); + mLayoutList.add((RelativeLayout) findViewById(R.id.jianshu_layout)); + mLayoutList.add((RelativeLayout) findViewById(R.id.zhihu_layout)); + + mTextViewList = new ArrayList<>(); + mTextViewList.add((TextView) findViewById(R.id.tv_jianshu)); + mTextViewList.add((TextView) findViewById(R.id.tv_zhihu)); + + mCheckBoxList = new ArrayList<>(); + CheckBox ckbJianshu = (CheckBox) findViewById(R.id.ckb_jianshu); + ckbJianshu.setOnCheckedChangeListener(this); + mCheckBoxList.add(ckbJianshu); + CheckBox ckbZhihu = (CheckBox) findViewById(R.id.ckb_zhihu); + ckbZhihu.setOnCheckedChangeListener(this); + mCheckBoxList.add(ckbZhihu); + + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + int viewId = buttonView.getId(); + if (viewId == R.id.ckb_jianshu) { + changeThemeByJianShu(); + + } else if (viewId == R.id.ckb_zhihu) { + changeThemeByZhiHu(); + + } + } + + private void initData() { + mDayNightHelper = new DayNightHelper(this); + } + + private void initTheme() { + if (mDayNightHelper.isDay()) { + setTheme(R.style.DayTheme); + } else { + setTheme(R.style.NightTheme); + } + } + + /** + * 切换主题设置 + */ + private void toggleThemeSetting() { + if (mDayNightHelper.isDay()) { + mDayNightHelper.setMode(DayNight.NIGHT); + setTheme(R.style.NightTheme); + } else { + mDayNightHelper.setMode(DayNight.DAY); + setTheme(R.style.DayTheme); + } + } + + /** + * 使用简书的实现套路来切换夜间主题 + */ + private void changeThemeByJianShu() { + toggleThemeSetting(); + refreshUI(); + } + + /** + * 使用知乎的实现套路来切换夜间主题 + */ + private void changeThemeByZhiHu() { + showAnimation(); + toggleThemeSetting(); + refreshUI(); + } + + /** + * 刷新UI界面 + */ + private void refreshUI() { + TypedValue background = new TypedValue();//背景色 + TypedValue textColor = new TypedValue();//字体颜色 + Resources.Theme theme = getTheme(); + theme.resolveAttribute(R.attr.clockBackground, background, true); + theme.resolveAttribute(R.attr.clockTextColor, textColor, true); + + mHeaderLayout.setBackgroundResource(background.resourceId); + for (RelativeLayout layout : mLayoutList) { + layout.setBackgroundResource(background.resourceId); + } + for (CheckBox checkBox : mCheckBoxList) { + checkBox.setBackgroundResource(background.resourceId); + } + for (TextView textView : mTextViewList) { + textView.setBackgroundResource(background.resourceId); + } + + Resources resources = getResources(); + for (TextView textView : mTextViewList) { + textView.setTextColor(resources.getColor(textColor.resourceId)); + } + + int childCount = mRecyclerView.getChildCount(); + for (int childIndex = 0; childIndex < childCount; childIndex++) { + ViewGroup childView = (ViewGroup) mRecyclerView.getChildAt(childIndex); + childView.setBackgroundResource(background.resourceId); + View infoLayout = childView.findViewById(R.id.info_layout); + infoLayout.setBackgroundResource(background.resourceId); + TextView nickName = (TextView) childView.findViewById(R.id.tv_nickname); + nickName.setBackgroundResource(background.resourceId); + nickName.setTextColor(resources.getColor(textColor.resourceId)); + TextView motto = (TextView) childView.findViewById(R.id.tv_motto); + motto.setBackgroundResource(background.resourceId); + motto.setTextColor(resources.getColor(textColor.resourceId)); + } + + //让 RecyclerView 缓存在 Pool 中的 Item 失效 + //那么,如果是ListView,要怎么做呢?这里的思路是通过反射拿到 AbsListView 类中的 RecycleBin 对象,然后同样再用反射去调用 clear 方法 + Class recyclerViewClass = RecyclerView.class; + try { + Field declaredField = recyclerViewClass.getDeclaredField("mRecycler"); + declaredField.setAccessible(true); + Method declaredMethod = Class.forName(RecyclerView.Recycler.class.getName()).getDeclaredMethod("clear", (Class[]) new Class[0]); + declaredMethod.setAccessible(true); + declaredMethod.invoke(declaredField.get(mRecyclerView), new Object[0]); + RecyclerView.RecycledViewPool recycledViewPool = mRecyclerView.getRecycledViewPool(); + recycledViewPool.clear(); + + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + refreshStatusBar(); + } + + /** + * 刷新 StatusBar + */ + private void refreshStatusBar() { + if (Build.VERSION.SDK_INT >= 21) { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = getTheme(); + theme.resolveAttribute(R.attr.colorPrimary, typedValue, true); + getWindow().setStatusBarColor(getResources().getColor(typedValue.resourceId)); + } + } + + /** + * 展示一个切换动画 + */ + private void showAnimation() { + final View decorView = getWindow().getDecorView(); + Bitmap cacheBitmap = getCacheBitmapFromView(decorView); + if (decorView instanceof ViewGroup && cacheBitmap != null) { + final View view = new View(this); + view.setBackgroundDrawable(new BitmapDrawable(getResources(), cacheBitmap)); + ViewGroup.LayoutParams layoutParam = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + ((ViewGroup) decorView).addView(view, layoutParam); + ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f); + objectAnimator.setDuration(300); + objectAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + ((ViewGroup) decorView).removeView(view); + } + }); + objectAnimator.start(); + } + } + + /** + * 获取一个 View 的缓存视图 + * + * @param view + * @return + */ + private Bitmap getCacheBitmapFromView(View view) { + final boolean drawingCacheEnabled = true; + view.setDrawingCacheEnabled(drawingCacheEnabled); + view.buildDrawingCache(drawingCacheEnabled); + final Bitmap drawingCache = view.getDrawingCache(); + Bitmap bitmap; + if (drawingCache != null) { + bitmap = Bitmap.createBitmap(drawingCache); + view.setDrawingCacheEnabled(false); + } else { + bitmap = null; + } + return bitmap; + } +} diff --git a/src/main/java/com/clock/study/activity/MainActivity.java b/src/main/java/com/clock/study/activity/MainActivity.java index 0637f11..4b0b1f8 100644 --- a/src/main/java/com/clock/study/activity/MainActivity.java +++ b/src/main/java/com/clock/study/activity/MainActivity.java @@ -15,6 +15,10 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); findViewById(R.id.btn_camera_take_photo).setOnClickListener(this); + findViewById(R.id.btn_animation).setOnClickListener(this); + findViewById(R.id.btn_animator).setOnClickListener(this); + findViewById(R.id.btn_day_night).setOnClickListener(this); + } @Override @@ -23,6 +27,15 @@ public void onClick(View v) { if (viewId == R.id.btn_camera_take_photo) { Intent takePhotoIntent = new Intent(this, CapturePhotoActivity.class); startActivity(takePhotoIntent); + } else if (viewId == R.id.btn_animation) { + Intent animationIntent = new Intent(this, AnimationActivity.class); + startActivity(animationIntent); + } else if (viewId == R.id.btn_animator) { + Intent animatorIntent = new Intent(this, AnimatorActivity.class); + startActivity(animatorIntent); + } else if (viewId == R.id.btn_day_night) { + Intent animatorIntent = new Intent(this, DayNightActivity.class); + startActivity(animatorIntent); } } } diff --git a/src/main/java/com/clock/study/adapter/SimpleAuthorAdapter.java b/src/main/java/com/clock/study/adapter/SimpleAuthorAdapter.java new file mode 100644 index 0000000..f8f2d5b --- /dev/null +++ b/src/main/java/com/clock/study/adapter/SimpleAuthorAdapter.java @@ -0,0 +1,51 @@ +package com.clock.study.adapter; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.clock.study.R; +import com.clock.study.activity.AuthorActivity; + +/** + * Just Simple Author Adapter + *

+ * Created by Clock on 2016/8/24. + */ +public class SimpleAuthorAdapter extends RecyclerView.Adapter { + + private final View.OnClickListener mSimpleClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Context context = v.getContext(); + Intent intent = new Intent(context, AuthorActivity.class); + context.startActivity(intent); + } + }; + + @Override + public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View rootView = inflater.inflate(R.layout.author_info_layout, parent, false); + return new SimpleViewHolder(rootView); + } + + @Override + public void onBindViewHolder(SimpleViewHolder holder, int position) { + holder.itemView.setOnClickListener(mSimpleClickListener); + } + + @Override + public int getItemCount() { + return 20; + } + + static class SimpleViewHolder extends RecyclerView.ViewHolder { + public SimpleViewHolder(View itemView) { + super(itemView); + } + } +} diff --git a/src/main/java/com/clock/study/animation/SimpleCustomAnimation.java b/src/main/java/com/clock/study/animation/SimpleCustomAnimation.java new file mode 100644 index 0000000..12d45d9 --- /dev/null +++ b/src/main/java/com/clock/study/animation/SimpleCustomAnimation.java @@ -0,0 +1,44 @@ +package com.clock.study.animation; + +import android.graphics.Matrix; +import android.util.Log; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +/** + * Created by Clock on 2016/7/9. + */ +public class SimpleCustomAnimation extends Animation { + + private final static String TAG = SimpleCustomAnimation.class.getSimpleName(); + + private int mWidth, mHeight; + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + Log.i(TAG, "-------------initialize-------------"); + Log.i(TAG, "width:" + width); + Log.i(TAG, "height:" + height); + Log.i(TAG, "parentWidth:" + parentWidth); + Log.i(TAG, "parentHeight:" + parentHeight); + Log.i(TAG, "-------------initialize-------------"); + super.initialize(width, height, parentWidth, parentHeight); + this.mWidth = width; + this.mHeight = height; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + Matrix matrix = t.getMatrix(); + matrix.preScale(interpolatedTime, interpolatedTime);//缩放 + matrix.preRotate(interpolatedTime * 360);//旋转 + //下面的Translate组合是为了将缩放和旋转的基点移动到整个View的中心,不然系统默认是以View的左上角作为基点 + matrix.preTranslate(-mWidth / 2, -mHeight / 2); + matrix.postTranslate(mWidth / 2, mHeight / 2); + Log.i(TAG, "-------------applyTransformation-------------"); + Log.i(TAG, "interpolatedTime:" + interpolatedTime);//动画持续的时间,时间比例系数(0.0 到 1.0)之间 + Log.i(TAG, "transformation:" + t);//控制动画效果,Transformation包含两个信息,一个Alpha值,一个Matrix矩阵,这里的Matrix默认是一个单位矩阵 + Log.i(TAG, "-------------applyTransformation-------------"); + + } +} diff --git a/src/main/java/com/clock/study/animator/ColorEvaluator.java b/src/main/java/com/clock/study/animator/ColorEvaluator.java new file mode 100644 index 0000000..22e1328 --- /dev/null +++ b/src/main/java/com/clock/study/animator/ColorEvaluator.java @@ -0,0 +1,31 @@ +package com.clock.study.animator; + +import android.animation.TypeEvaluator; + +/** + * 仿照了 ArgbEvaluator 的系统实现,用于产生颜色过度渐变效果 + *

+ * Created by Clock on 2016/7/22. + */ +public class ColorEvaluator implements TypeEvaluator { + + @Override + public Object evaluate(float fraction, Object startValue, Object endValue) { + int startInt = (Integer) startValue; + int startA = (startInt >> 24) & 0xff; + int startR = (startInt >> 16) & 0xff; + int startG = (startInt >> 8) & 0xff; + int startB = startInt & 0xff; + + int endInt = (Integer) endValue; + int endA = (endInt >> 24) & 0xff; + int endR = (endInt >> 16) & 0xff; + int endG = (endInt >> 8) & 0xff; + int endB = endInt & 0xff; + + return (int) ((startA + (int) (fraction * (endA - startA))) << 24) | + (int) ((startR + (int) (fraction * (endR - startR))) << 16) | + (int) ((startG + (int) (fraction * (endG - startG))) << 8) | + (int) ((startB + (int) (fraction * (endB - startB)))); + } +} diff --git a/src/main/java/com/clock/study/helper/DayNightHelper.java b/src/main/java/com/clock/study/helper/DayNightHelper.java new file mode 100644 index 0000000..602cb72 --- /dev/null +++ b/src/main/java/com/clock/study/helper/DayNightHelper.java @@ -0,0 +1,61 @@ +package com.clock.study.helper; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.clock.study.type.DayNight; + +/** + * Created by Clock on 2016/8/24. + */ +public class DayNightHelper { + + private final static String FILE_NAME = "settings"; + private final static String MODE = "day_night_mode"; + + private SharedPreferences mSharedPreferences; + + public DayNightHelper(Context context) { + this.mSharedPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); + } + + /** + * 保存模式设置 + * + * @param mode + * @return + */ + public boolean setMode(DayNight mode) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString(MODE, mode.getName()); + return editor.commit(); + } + + /** + * 夜间模式 + * + * @return + */ + public boolean isNight() { + String mode = mSharedPreferences.getString(MODE, DayNight.DAY.getName()); + if (DayNight.NIGHT.getName().equals(mode)) { + return true; + } else { + return false; + } + } + + /** + * 日间模式 + * + * @return + */ + public boolean isDay() { + String mode = mSharedPreferences.getString(MODE, DayNight.DAY.getName()); + if (DayNight.DAY.getName().equals(mode)) { + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/com/clock/study/manager/FolderManager.java b/src/main/java/com/clock/study/manager/FolderManager.java index 34f1851..19038e8 100644 --- a/src/main/java/com/clock/study/manager/FolderManager.java +++ b/src/main/java/com/clock/study/manager/FolderManager.java @@ -1,6 +1,13 @@ package com.clock.study.manager; +import android.Manifest; +import android.app.AlertDialog; +import android.content.Context; +import android.content.pm.PackageManager; import android.os.Environment; +import android.support.v4.app.ActivityCompat; + +import com.clock.study.R; import java.io.File; diff --git a/src/main/java/com/clock/study/type/DayNight.java b/src/main/java/com/clock/study/type/DayNight.java new file mode 100644 index 0000000..15e338e --- /dev/null +++ b/src/main/java/com/clock/study/type/DayNight.java @@ -0,0 +1,34 @@ +package com.clock.study.type; + +/** + * Created by Clock on 2016/8/24. + */ +public enum DayNight { + + DAY("DAY", 0), + NIGHT("NIGHT", 1); + + private String name; + private int code; + + private DayNight(String name, int code) { + this.name = name; + this.code = code; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/res/animator/simple_animator.xml b/src/main/res/animator/simple_animator.xml new file mode 100644 index 0000000..87a4808 --- /dev/null +++ b/src/main/res/animator/simple_animator.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/activity_android_anim.xml b/src/main/res/layout/activity_android_anim.xml new file mode 100644 index 0000000..facc04c --- /dev/null +++ b/src/main/res/layout/activity_android_anim.xml @@ -0,0 +1,46 @@ + + + +