├── Android GridLayout.md ├── Android Studio自定义文件模板.md ├── Android启动优化.md ├── Android项目结构的另外一种方式.md ├── Drawable Resources一览.md ├── Gradle依赖.md ├── Gradle的高级技巧.md ├── MVP+Dagger2+Retrofit实现更清晰的架构.md ├── README.md ├── RecycleView 列表显示更多.md ├── RecyclerView之ItemDecoration由浅入深.md ├── SharedPreferences的最佳实践.md ├── Support Library一览.md ├── android内存泄露.md ├── as的个人使用记录.md ├── dagger2:组件依赖和子组件的区别.md ├── images ├── ItemDecoration_2.png ├── ItemDecoration_3.png ├── ItemDecoration_4.png ├── ItemDecoration_5.png ├── ItemDecoration_6.png ├── ItemDecoration_7.png ├── drawable_bitmap.png ├── drawable_clip.png ├── drawable_inset.png ├── drawable_layer.png ├── drawable_level.png ├── drawable_mix.png ├── drawable_nine.png ├── drawable_scale.png ├── drawable_shape.png ├── drawable_state.png ├── drawable_transition.png ├── drawable_transition2.png ├── format01.png ├── format02.png ├── format03.png ├── format04.png ├── format05.png ├── memory_leaks.png ├── plu01.png ├── plu02.png ├── plu03.png ├── plu04.png ├── reuse01.png ├── reuse02.png ├── reuse03.png ├── reuse04.png ├── sep01.png ├── share.gif ├── share_gwl.gif ├── stickyheader.gif ├── wh01.png ├── wh02.png └── xml_name.png ├── todo └── todo.txt ├── 一种成功的xml资源命名规范.md ├── 低版本实现共享元素动画.md ├── 低版本实现共享元素动画(二):格瓦拉动画.md ├── 使用Gradle构建多个不同applicationId包.md ├── 使用Gradle管理Debug和Release版本的Key.md ├── 使用retrofit+okhttp实现无缝网络状态监测.md ├── 关于Android strings.xml你要记住的一些事.md ├── 写出高效清晰Layout布局文件的一些技巧.md ├── 在AndroidStudio中给你的代码添加版权声明.md ├── 实现淘宝和QQ ToolBar透明渐变效果.md ├── 实现知乎和简书快速返回效果.md ├── 小总结.md ├── 快速清除app数据.md ├── 教你实现别人家的动画3(淘宝,简书动画效果).md ├── 更新到Retrofit2的一些技巧.md ├── 每个Android开发者都应该知道的开源库(2014版).md ├── 每个android开发者都应该知道的Top 5三方库(2015版).md └── 用xml drawable替代png.md /Android GridLayout.md: -------------------------------------------------------------------------------- 1 | >原文:[Android Grid Layout](https://medium.com/google-developer-experts/android-grid-layout-1faf0df8d6f2#.ldtru2yec) 2 | 3 | >译文的GitHub地址:[Android GridLayout](https://github.com/thinkSky1206/android-blog/blob/master/Android%20GridLayout.md) 4 | 5 | >译者注:说实话 我确实没用过GridLayout 好好认识一下吧! 6 | 7 | ###*android开发者每天都在问自己一个问题:我到底应该用哪个layout* 8 | 9 | GridLayout发布已经有一段时间了-[New Layout Widgets: Space and GridLayout](http://android-developers.blogspot.jp/2011/11/new-layout-widgets-space-and-gridlayout.html) 10 | 11 | 然而GridLayout在当前开发中的情况如下: 12 | 13 | - 大多数开发者并不知道这个布局 14 | - 一些开发者知道GridLayout但是因为某些原因没有使用 15 | - 只有少部分开发者花时间了解和积极使用 16 | 17 | 这是我为什么要写这篇文章的原因,因为我觉得这个布局被不公平遗忘了 18 | 19 | #为什么我们需要Grid Layout 20 | GridLayout可以让你用一个简单的根view创建一个表格系统布局 21 | >我可以用LinearLayout嵌套来实现 22 | 23 | 是可以做到,但是你会有性能问题当布局层次太深 24 | 25 | >我可以用RelativeLayout来创建 26 | 27 | 也行,但是RelativeLayout有一些限制,例如: 28 | 29 | - 没法同时控制2个轴线对齐 30 | - 当组件需要的空间超出你预期的时候会跑出屏幕或发生重叠因为你不能使用weight等等 31 | 32 | 换一句话说就是RelativeLayout不够灵活和响应性不足。 33 | 34 | #例子 35 | 36 | 让我们实现一个简单的布局包含一个大图片,2个小图标和跟在图标后面的文本 37 | 38 | ![Preview](https://cdn-images-1.medium.com/max/800/1*hm-KJs7FJG5qtHglpvWYSQ.png) 39 | 40 | ###*RelativeLayout* 41 | 42 | 用RelatieveLayout实现起来非常简单,通过关键属性*layout_below*,*layout_toRightOf*和*layout_alignTop* 43 | 44 | ![Code](https://cdn-images-1.medium.com/max/800/1*orH45OZ2t_qeoEfSHzaZtA.png) 45 | 46 | 一眼看上去好像很完美,等你用不同字体size进行布局测试就呵呵了 47 | 48 | *问题 1*没法同时控制基于2个轴对齐 49 | 50 | 单行文本应该相对于图标垂直居中,不幸的是RelativeLayout没有提供这个可能性 51 | 52 | ![Preview](https://cdn-images-1.medium.com/max/800/1*1pxJm-XLyhFHoIzZf65CdQ.png) 53 | 54 | *问题 2* 组件重叠 55 | 56 | 多行文本会引起重叠,因为text用了layout_alignTop对图标进行对齐 57 | 58 | ![Preview](https://cdn-images-1.medium.com/max/800/1*uemGANLvCQv-tdfyadqnqA.png) 59 | 60 | ###*GridLayout* 61 | 62 | 如你看到的下面图片一样,GridLayout提供更好的表现结果: 63 | 64 | - 文本垂直居中于图标 65 | - 多行文本不会向下移动组件 66 | 67 | ![Preview](https://cdn-images-1.medium.com/max/800/1*Dz8SX4ju0NEW4OL6sHeI5g.png) 68 | 69 | 那么怎么实现这个效果呢?首先定义GridLayout为根布局。然后计算你要多少列并通过android:columnCount属性定义,在我们的例子中我们有2列。 70 | 71 | 因为GridLayout里面的views是一个接一个被放置的,所以没必要明确定义row和column 72 | 73 | 如果你想撑开view让它占用2行或2列,你可以用*layout_columnSpan*/*layout_rowSpan*属性 74 | 75 | 还有一件重要的事要记住-如果你想你的view使用所有可用的空间,不要设置width为match_parent,应该设置成0dp同时设置属性layout_gravity="fill" 76 | 77 | ![Code](https://cdn-images-1.medium.com/max/800/1*lSg-UAbQTJkG4pVMM34YaA.png) 78 | 79 | 80 | #总结 81 | 82 | GridLayout一方面是一个非常强大的工具,它提供了很好的灵活性和性能,另外一方面它需要一些时间来学习了解它如何工作,你通常需要花更多的时间来开发和维护这样的布局。 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Android Studio自定义文件模板.md: -------------------------------------------------------------------------------- 1 | >原文链接:[How to make your own File Templates in Android Studio](http://riggaroo.co.za/custom-file-templates-android-studio/) 2 | 3 | >译文的GitHub地址:[Android Studio自定义文件模板](https://github.com/thinkSky1206/android-blog/blob/master/Android%20Studio%E8%87%AA%E5%AE%9A%E4%B9%89%E6%96%87%E4%BB%B6%E6%A8%A1%E6%9D%BF.md) 4 | 5 | >译者注:还是挺实用的 6 | 7 | ![image](http://i1.wp.com/riggaroo.co.za/wp-content/uploads/2016/05/androidstudiofiletemplates.png) 8 | 9 | 我最近发现一个可以让我生活更简单的东东:自定义文件模板。什么是文件模板?文件模板是一开始就已经包含了一些代码的源文件。 10 | 11 | 在下面的例子中,我们将为用到时经常要查找的RecycleView adapter实现创建模板文件。 12 | 13 | #How to create your own file template in Android Studio: 14 | 15 | 1.右键代码源文件夹,选择“New” 然后再点击“Edit File Templates” 16 | 17 | ![step 1](http://i0.wp.com/riggaroo.co.za/wp-content/uploads/2016/05/CustomFileTemplates1.png) 18 | 19 | 2.点击添加按钮 新建一个新模板 20 | 21 | ![step 2](http://i0.wp.com/riggaroo.co.za/wp-content/uploads/2016/05/CreateNewTemplate.png) 22 | 23 | 3.你需要给模板文件命名,因为我打算创建RecycleView adapter的模板,所以命名成RecyclerViewAdapter. 24 | 25 | 4.然后你要粘贴或填写你的模板代码,这里你可以使用一些变量。下面是一些预定义变量: 26 | 27 | - ${NAME} 文件名字 28 | - ${PACKAGE_NAME} 包名 29 | - ${DATE} 系统当前时间 30 | 31 | 你也可以自定义一些变量让用户输入,在这个例子中,我想要用户提供ViewHolder class所以用${VIEWHOLDER_CLASS} 列表item class用${ITEM_CLASS} 32 | 33 | 现在下面的代码用来创建Recycler Adapter实现模板: 34 | 35 | #if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end 36 | 37 | import android.content.Context; 38 | import android.support.v7.widget.RecyclerView; 39 | import android.view.LayoutInflater; 40 | import android.view.View; 41 | import android.view.ViewGroup; 42 | import java.util.List; 43 | 44 | #parse("File Header.java") 45 | public class ${NAME} extends RecyclerView.Adapter<${VIEWHOLDER_CLASS}> { 46 | private final Context context; 47 | private List<${ITEM_CLASS}> items; 48 | 49 | public ${NAME}(List<${ITEM_CLASS}> items, Context context) { 50 | this.items = items; 51 | this.context = context; 52 | } 53 | 54 | @Override 55 | public ${VIEWHOLDER_CLASS} onCreateViewHolder(ViewGroup parent, 56 | int viewType) { 57 | View v = LayoutInflater.from(parent.getContext()) 58 | .inflate(R.layout.${LAYOUT_RES_ID}, parent, false); 59 | return new ${VIEWHOLDER_CLASS}(v); 60 | } 61 | 62 | @Override 63 | public void onBindViewHolder(${VIEWHOLDER_CLASS} holder, int position) { 64 | ${ITEM_CLASS} item = items.get(position); 65 | //TODO Fill in your logic for binding the view. 66 | } 67 | 68 | @Override 69 | public int getItemCount() { 70 | if (items == null){ 71 | return 0; 72 | } 73 | return items.size(); 74 | } 75 | } 76 | 77 | 当你使用这个模板的时候,会被提示输入${VIEWHOLDER_CLASS},${ITEM_CLASS}的值,这些值会替代我们定义的变量占位符,非常方便。 78 | 79 | 80 | 5.想使用你定义好的模板,点击右键 点击“New” 然后就会看到你模板名字出现在列表中。 81 | 82 | ![image](http://i2.wp.com/riggaroo.co.za/wp-content/uploads/2016/05/Selecting-custom-template.png) 83 | 84 | 点击模板名字,然后填写你的变量值: 85 | 86 | ![submit value](http://i2.wp.com/riggaroo.co.za/wp-content/uploads/2016/05/FillInCustomTemplateVariables.png) 87 | 88 | 然后你就会看到一个漂亮的生成好的class 89 | 90 | ![generated class](http://i1.wp.com/riggaroo.co.za/wp-content/uploads/2016/05/GeneratedClassFromTemplate.png) 91 | 92 | 现在我再也不用去查找RecyclerView adapters了!耶! 93 | 94 | 你有其他在用的好用模板吗?分享给我吧。 95 | -------------------------------------------------------------------------------- /Android启动优化.md: -------------------------------------------------------------------------------- 1 | >原文:[Android strings.xml — things to remember](https://medium.com/@dmytrodanylyk/android-strings-xml-things-to-remember-c155025bb8bb#.jjmb7gqpq) 2 | 3 | >译文的GitHub地址:[关于Android strings.xml你要记住的一些事](https://github.com/thinkSky1206/android-blog/blob/master/%E5%85%B3%E4%BA%8EAndroid%20strings.xml%E4%BD%A0%E8%A6%81%E8%AE%B0%E4%BD%8F%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BA%8B.md) 4 | 5 | >译者注:都是一些很实用的技巧 ,尤其是对于多语言国际化开发,感同身受 。 6 | 7 | [Android应用启动优化:一种DelayLoad的实现和原理(上篇)](http://androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load.html) 8 | 9 | 10 | [Android端应用秒开优化体验](http://zhengxiaoyong.me/2016/07/18/Android%E7%AB%AF%E5%BA%94%E7%94%A8%E7%A7%92%E5%BC%80%E4%BC%98%E5%8C%96%E4%BD%93%E9%AA%8C/) 11 | 12 | 13 | [Android性能优化之Splash页应该这样设计](http://zhengxiaoyong.me/2016/07/18/Android%E7%AB%AF%E5%BA%94%E7%94%A8%E7%A7%92%E5%BC%80%E4%BC%98%E5%8C%96%E4%BD%93%E9%AA%8C/) 14 | 15 | 16 | main()->Application:attachBaseContext()->onCreate()->Activity:onCreate()->onStart()->onResume()->performTraversals(layout mesure)->performTraversals(draw绘制)(显示第一帧) 17 | 18 | 19 | 1.点击图标——application.onCreate 20 | 21 | 2.oncreate-activity第一帧 22 | 23 | -------------------------------------------------------------------------------- /Android项目结构的另外一种方式.md: -------------------------------------------------------------------------------- 1 | >原文:[Android Project Structure — alternative way](https://medium.com/@dmytrodanylyk/android-project-structure-alternative-way-29ce766682f0#.onofequfx) 2 | 3 | >译者注:我很喜欢工工整整的摆放哪些图片什么的,看的舒服,因为真的是太多了 要找或者替换的时候很烦,所以项目中我用了Android File Grouping这个插件 很好用 但是它只是解决了layout文件,没有解决其他资源文件分组,这篇文章提供了非常好的方法,不说了 去把我的项目重构一遍。 4 | 5 | 我们都知道android项目结构长成下面这样:所有的图片放在drawble相应文件夹,所有的布局文件放在layout文件夹。但是随的项目开发文件越来越多,慢慢的会发现导航和查找文件变得非常困难。 6 | 7 | ![Typical android project structure](https://cdn-images-1.medium.com/max/800/1*VbZ-FXtEPpcsCZMdDXof3A.png) 8 | 9 | ###一个界面一个资源文件夹 10 | 11 | 在这种情况下你的界面包含大量的layout,drawable,demension,那么有理由给你的每个界面建立一个单独的资源文件夹 12 | 13 | ![Resource folder — per screen android project structure](https://cdn-images-1.medium.com/max/800/1*qBwnEgU3Mmjwfy9yIM4NiQ.png) 14 | 15 | 从上面的图片我们可以看到2个根文件夹包含在main目录中: 16 | 17 | - *res-main* 包含那些在多个界面中用到的公共资源 18 | - *res-screen* 包含各个界面的资源文件夹 例如:about,chat,event,details,event list,home,login等界面资源文件夹 19 | 20 | 让我们看一下chat界面的资源文件夹 21 | 22 | ![Resource folder](https://cdn-images-1.medium.com/max/800/1*ZF5Qi_OuNPHglNg21RYGaA.png) 23 | 24 | chat本身包含几个layout xml文件,所以我们创建了chat->layout文件夹并把它们都移到里面。如果还有很多只在chat界面用到的png图片,我们也把它们移到chat->drawable-hdpi,chat->drawable-xhdpi,chat->drawable-xxhdpi和chat->drawable-xxxhdpi对应文件夹里面。 25 | 26 | 当以后chat界面需要实现水平布局或者支持平板的时候,我们会创建layout-land和layout-sw720文件夹放在chat目录下。 27 | 28 | ### 怎么让gradle识别我们的资源文件夹 29 | 30 | 打开app.gradle文件在android块中声明sourceSets.更多资源合并信息请查看[here](http://tools.android.com/tech-docs/new-build-system/resource-merging) 31 | 32 | sourceSets { 33 | main { 34 | res.srcDirs = [ 35 | 'src/main/res-main', 36 | 'src/main/res-screen/about', 37 | 'src/main/res-screen/chat', 38 | 'src/main/res-screen/event-detail', 39 | 'src/main/res-screen/event-list', 40 | 'src/main/res-screen/home', 41 | 'src/main/res-screen/login', 42 | ] 43 | } 44 | } 45 | 46 | > **注意**:上面所有的操作确保你在Project模式下进行 47 | 48 | ###总结 49 | 50 | 如果你的项目很大,并且想要组织你的文件夹以便快速查看相关界面的layout,drawables,values等等,最好尝试使用每屏一个资源文件夹的android项目结构。 -------------------------------------------------------------------------------- /Drawable Resources一览.md: -------------------------------------------------------------------------------- 1 | 2 | >文章的GitHub地址:[Drawable Resources一览](https://github.com/thinkSky1206/android-blog/blob/master/Drawable%20Resources%E4%B8%80%E8%A7%88.md) 3 | 4 | >译者注:之前某个国外开发者大会释放出来的,整理一下记录下来,看看你都会么? 5 | 6 | 7 | #Bitmap File 8 | 9 | ![bitmap](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_bitmap.png) 10 | 11 | #Nine-Path File 12 | 13 | ![.9 图片](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_nine.png) 14 | 15 | #Layer List 16 | 17 | 18 | 24 | 29 | 34 | 35 | 36 | ![layer list](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_layer.png) 37 | 38 | #State List 39 | 40 | 41 | 46 | 49 | 51 | 52 | 53 | ![state list](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_state.png) 54 | 55 | #Level List 56 | 57 | 要实现一个多状态显示的效果,像下面这样写? 58 | 59 | ImageView ivScore = (ImageView)findViewById(R.id.iv_asd); 60 | int score = ...; 61 | if(score == 0) { 62 | ivScore.setImageResource(R.drawable.ic_score_bad); 63 | } else if(score == 1) { 64 | ivScore.setImageResource(R.drawable.ic_score_ok); 65 | } else if(score == 2) { 66 | ivScore.setImageResource(R.drawable.ic_score_good); 67 | } 68 | ![level list](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_level.png) 69 | 70 | No 下面才是优雅的方式 71 | 72 | 73 | 74 | 78 | 81 | 84 | 85 | 86 | 87 | ImageView ivScore = (ImageView)findViewById(R.id.iv_score); 88 | int score = ...; 89 | ivScore.setImageLevel(score); 90 | 91 | #Transition Drawable 92 | >用来设置2个图片的渐变效果 93 | 94 | 95 | 98 | 100 | 101 | 102 | ImageView ivScore = (ImageView) findViewById(R.id.iv_score); 103 | TransitionDrawable drawable =(TransitionDrawable) ivScore.getDrawable(); 104 | drawable.startTransition(1000); 105 | 106 | ![transition](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_transition.png) 107 | 108 | 109 | drawable.reverseTransition(1000); 110 | 111 | ![transition2](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_transition2.png) 112 | 113 | #Inset Drawable 114 | 115 | 116 | 122 | 123 | ![inset](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_inset.png) 124 | 125 | #Clip Drawable 126 | >图片裁剪,可以实现很多很好玩的效果,裁剪范围0-10000,5000也就是裁剪一半 127 | 128 | 129 | 130 | 134 | 135 | ImageView ivRate = (ImageView) findViewById(R.id.iv_rate); 136 | ClipDrawable drawable = (ClipDrawable) ivRate.getDrawable(); 137 | ... 138 | drawable.setLevel(5000); 139 | 140 | ![clip](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_clip.png) 141 | 142 | #Scale Drawable 143 | 144 | 145 | 150 | 151 | ![scale](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_scale.png) 152 | 153 | 154 | #Shape Drawable 155 | 156 | 157 | 163 | 165 | 167 | 168 | 169 | 170 | ![shape](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_shape.png) 171 | 172 | 173 | #Drawable Mixing 174 | >结合使用 175 | 176 | 177 | 180 | 181 | 183 | 185 | 186 | 187 | 188 | ![mix](https://github.com/thinkSky1206/android-blog/blob/master/images/drawable_mix.png) 189 | -------------------------------------------------------------------------------- /Gradle依赖.md: -------------------------------------------------------------------------------- 1 | >原文:[Gradle dependencies](https://applidium.com/en/news/gradle_dependencies/) 2 | 3 | >译者:个别地方翻译可能不是很好,反复读了几遍,对自己帮助还是挺大的。 4 | 5 | 前段时间我们谈了[Gradle](http://www.jianshu.com/p/cc98a6b4f52e),今天我们将会讨论Gradle的依赖管理,对于Android来说是一个伟大的功能。 6 | 7 | ![gradle](https://applidium.com/en/news/gradle_dependencies/android_studio.png?14582343) 8 | 9 | 使用Gradle你可以用类似Maven的方式轻松管理一些依赖像Play Services,support libraries,或者其他库。既然这样,那么你可以反问自己一些关于管理依赖的问题: 10 | 11 | - 当一个项目中的2个依赖同时依赖某个依赖,而它们的各自依赖的版本号有冲突的时候,怎么进行管理?(a,b同时依赖c ,a依赖c1.1,b依赖c2.1) 12 | - 什么是传递依赖解析? 13 | - 可选依赖的作用? 14 | 15 | 本文会用一些我们熟悉的例子清晰得来解决这些问题。 16 | 17 | ###演示项目 18 | 19 | 本文讨论的例子都放在了[Github](https://github.com/applidium/gradle-dependencies-demo)上面,你可以clone下来自己尝试一下。 20 | 21 | ###怎么添加依赖 22 | 23 | ####例子 24 | [Calligraphy]()是一个知名的管理字体的android库,为了用它,你需要在项目build.gradle添加它作为项目的一个依赖: 25 | 26 | compile 'uk.co.chrisjenx:calligraphy:2.1.0' 27 | 28 | 这是一个很典型用来添加依赖的例子,但是它自身有其他依赖呢?为了列出你项目中所有的依赖(包含依赖的子依赖)你可以用下面这个命令: 29 | 30 | ./gradlew dependencies app:dependencies 31 | 32 | 这个命令会列出app module每一个“configuration”(https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html)的依赖,为了减少输出内容我们只对compile configuration感兴趣。 33 | 34 | ./gradlew dependencies app:dependencies --configuration compile 35 | 36 | 我们获取到下面的结果(译者:自己可以查看下自己控制台输出更清晰): 37 | 38 | compile - Classpath for compiling the main sources. 39 | --- uk.co.chrisjenx:calligraphy:2.1.0 40 | \--- com.android.support:appcompat-v7:22.1.1 41 | \ --- com.android.support:support-v4:22.1.1 42 | \ --- com.android.support:support-annotations:22.1.1 43 | 44 | 我们可以看到Calligraphy依赖appcompat-v7,而v7本身又依赖appcompat-v4,v4反过来又依赖support-annotations,这些依赖不用我们添加默认就解决好了,它们被传递解决了。 45 | 46 | ###可传递性依赖 47 | ####例子 48 | 要是我们的app module已经使用了appcompat-v7,但是,是更高版本23.1.1,那么最终app使用的是哪个版本的appcompat-v7,是Calligraphy依赖的22.1.1吗?让我们运行命令再来分析下: 49 | 50 | compile - Classpath for compiling the main sources. 51 | +--- uk.co.chrisjenx:calligraphy:2.1.0 52 | | --- com.android.support:appcompat-v7:22.1.1 -> 23.1.1 53 | | --- com.android.support:support-v4:23.1.1 54 | | --- com.android.support:support-annotations:23.1.1 55 | --- com.android.support:appcompat-v7:23.1.1 (*) 56 | 57 | calligraphy依赖的appcompat-v7:22.1.1已经变成了appcompat-v7:23.1.1,默认,依赖解析是可传递的,也就是以递归的方式解析所有依赖(包括依赖的依赖),如果有冲突则取高的版本号。这就是为什么support-v4和support-annotations会自动引入到我的app module(我们没有必要在build.gradle进行添加它们了) 58 | 59 | ###依赖解析策略 60 | ####例子 61 | 我们可以添加下面的代码块到我们的build.gradle中用来通知我们是否项目的同一个依赖有2个不同版本: 62 | 63 | configurations.all { 64 | resolutionStrategy { 65 | failOnVersionConflict() 66 | } 67 | } 68 | 再build时上面的代码会触发错误,强制我们手动解决版本冲突。默认情况下,Gradle用它[自己的依赖解析策略](https://docs.gradle.org/current/userguide/dependency_management.html#sub:version_conflicts),当发生版本冲突时,它会使用高版本号,你也可以定义自己的解析策略,类似上面这个([文档](https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html)) 69 | 70 | ###过滤依赖 71 | ####例子 72 | 此外,我们可以直接配置Gradle怎样建立依赖,例如,如果查看[simple-xml](http://simple.sourceforge.net/home.php)库,依赖分析结果如下: 73 | 74 | compile - Classpath for compiling the main sources. 75 | --- org.simpleframework:simple-xml:2.7.1 76 | +--- stax:stax-api:1.0.1 77 | +--- stax:stax:1.2.0 78 | | \--- stax:stax-api:1.0.1 79 | \--- xpp3:xpp3:1.1.3.3 80 | 81 | 配置是无法通过编译,因为下面的原因: 82 | 83 | - xpp3本身就直接被包含在Android framework中,我们不能包含同包名,同类名的另外一个版本的xpp3 84 | - stax用的是android framework受保护的包名(java.xml.*),然而不用担忧simple-xml可以工作在Android上因为它的依赖stax是可选的 85 | (stax的可用性是在[运行时被检查](http://grepcode.com/file/repo1.maven.org/maven2/org.simpleframework/simple-xml/2.7.1/org/simpleframework/xml/stream/ProviderFactory.java/)) 86 | 87 | 但是我们需要修改build.gradle以便编译期间忽略stax(顺便说一下,编译系统足够聪明会自动忽略xpp3,只是会显示一个警告),我们可以用下面的语法排除stax 88 | 89 | compile('org.simpleframework:simple-xml:2.7.1'){ 90 | exclude module: 'stax' 91 | exclude module: 'stax-api' 92 | exclude module: 'xpp3' // optional 93 | } 94 | 95 | 相应的DSL文档请查看[这里](https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html) 96 | ###给library开发者 97 | 有了前面的例子我们可以看到没有stax,simple-xml也可以工作,如果我们看一下它的pom.xml,我们会发现stat不是一个可选依赖。但是它却可以定义为一个可选依赖。让我们细想一下:一个library L依赖一个可选的library O,L传递依赖解析的时候不会包含O依赖。 98 | 99 | 在simple-xml的例子中,如果stax和xpp3是可选依赖,我们就可以直接像下面声明simple-xml依赖: 100 | 101 | compile 'org.simpleframework:simple-xml:2.7.1' 102 | 103 | 实际上,Retrofit1就是这种情况,OKHttp是一个可选的依赖。那么在运行时,如果OkHttp可用,Retrofit会使用OkHttp作为默认的http client,否则将使用Retrofit用到的平台提供的默认http client,我们还注意到Retrofit 1没有使用Gradle而是Maven。我们可以解释这个选择是因为声明可选依赖还没有被Gradle原生支持。但是目前有一些解决方案可以让你实现: 104 | 105 | - 如[这里](https://issues.gradle.org/browse/GRADLE-1749)解释,可以手动使用可选依赖 106 | - 这个插件([gradle-extra-configurations-plugin](https://github.com/nebula-plugins/gradle-extra-configurations-plugin))可以给Gradle build system带来可选依赖 107 | ###总结 108 | 通过Gradle进行依赖管理是非常有帮助的。清晰的理解怎么配置这些依赖可以让你以一种有意义的方式管理依赖关系在android项目周期的演变。 109 | 不仅如此,一个非常精确的库依赖配置对开发人员帮助很大,可以很容易地包括在其他项目,如Retrofit. 110 | 111 | 特别感谢Adrien Couque给予我的帮助。 112 | 113 | ###相关资源 114 | [Gradle的高级技巧](http://www.jianshu.com/p/cc98a6b4f52e#) -------------------------------------------------------------------------------- /Gradle的高级技巧.md: -------------------------------------------------------------------------------- 1 | >原文:[Gradle, the Applidium way](http://applidium.com/en/news/gradle_tips/) 2 | 3 | 让我们继续谈论android生态中的Gradle 4 | 5 | ![gradle](http://upload-images.jianshu.io/upload_images/186157-65031426d1340c36.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 6 | 7 | Gradle是Android studio用到的一个自动构建系统,基于Groovy语法,用来管理和构建Android项目,它可以精细的处理构建过程的各个步骤和简化持续集成(CI),接下来让我们看看Gradle中一些有用的技巧。 8 | 9 | #1.The dependency, mother of all dependencies 10 | 11 | 开始之前,这里有一个重要的工具可以让你处理依赖更简单方便:SDK Manager Plugin,它会自动下载和更新你的一些依赖(例如:API,support library,emulator),非常好用,尤其是当你下载了别人的代码项目时,什么也干不了,这时只需打开项目,等待下载或更新依赖,然后你可以启动应用程序了! 12 | 13 | 使用这个插件,只需要在你Project的根目录的build.gradle加上一行就可以了 14 | 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:1.3.0' 17 | classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' //添加该行 18 | } 19 | 20 | 更多该插件相关信息 [github](https://github.com/JakeWharton/sdk-manager-plugin) 21 | 22 | #2.Variants opportunities 23 | 24 | #####productFlavor and buildConfigField 25 | 26 | 另一个Gradle有用的功能是productFlavor,你可以在你的build.gradle定义不同的productFlavor,这将会生成不同版本的应用程序包 27 | 28 | 一个简单例子:我想生成2个不同分支的包 29 | 30 | productFlavors { 31 | branchOne { 32 | applicationId "com.example.branchOne" 33 | buildConfigField "String", "CONFIG_ENDPOINT", "http://branchOne.com/android" 34 | } 35 | branchTwo { 36 | applicationId "com.example.branchTwo" 37 | buildConfigField "String", "CONFIG_ENDPOINT", "http://branchTwo.org" 38 | } 39 | } 40 | 41 | 你可以在variants里面定义buildConfigFields,有了这些字段,这样你就可以让你的整个配置都写在一个文件里面。可以很容易的用来定义我们的常量(而不是分开写在几个xml文件),例如,我们刚才就它来定义了http请求的根url. 42 | 43 | 你可以在代码中使用它们:BuildConfig.FIELD_NAME(上面的的例子就是BuildConfig.CONFIG\_ENDPOINT) 44 | 45 | 一个有用的技巧:你可以单独只为某一个variants添加一些依赖,只需要在Compile加上对应的variant名字前缀就可以了 46 | 47 | dependencies { 48 | compile 'com.android.support:support-v4:22.2.0' 49 | branchOneCompile 'com.android.support:appcompat-v7:22.2.0'//只为branchOne添加这个依赖 50 | } 51 | 52 | 53 | ####buildType 54 | 55 | 和productFlavors类似的,还有buildTypes(debug和release是默认的),它们都也可以为你的应用程序生成variants,合并一起形成buildVariant,brancheOne和debug会生成branchOneDebug. 56 | 57 | 差别在于改变buildType不会改变应用程序的代码,它们只是处理的东西不同,你可以通过buildType来获取更多的技术细节(例如:build optimization,log level等等),但是app的内容不会改变,相反的,使用productFlavor配置可以改变app的内容(ps:内容可以设想成package理解,buildType没法改applicationId)。 58 | 59 | ####flavorDimension 60 | 61 | 更进一步如果我们想基于多个标准构建多个版本,可以用flavorDimensions,看下面的例子: 62 | 63 | flavorDimensions "brand", "environment" 64 | productFlavors { 65 | branchOne { 66 | flavorDimension "brand" 67 | applicationId "com.example.branchOne" 68 | buildConfigField "String", "CONFIG_ENDPOINT", "http://branchOne.com/android" 69 | } 70 | branchTwo { 71 | flavorDimension "brand" 72 | applicationId "com.example.branchTwo" 73 | buildConfigField "String", "CONFIG_ENDPOINT", "http://branchTwo.org" 74 | } 75 | stubs { 76 | flavorDimension "environment" 77 | } 78 | preprod { 79 | flavorDimension "environment" 80 | } 81 | distrib { 82 | flavorDimension "environment" 83 | } 84 | } 85 | 86 | 生成的variants(2个buildType\*2个brand维度\*3个environment维度=12): 87 | 88 | ![variants](http://upload-images.jianshu.io/upload_images/186157-40c4893bd06b88a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 89 | 90 | 通过上面修改applicationId,每个版本的app都可以在你的手机上面安装共存。 91 | 92 | ####A dynamic Manifest 93 | 94 | 得感谢${name},使得你可以在你的Manifest插入一个占位符。看下面的例子: 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 通过上面的代码,${applicationId}会被替换成真实的applicationId,例如对于branchOne这个variant,它会变成: 104 | 105 | 106 | 107 | 这是非常有用的,因为我们要根据variant用不同的applicationId填充Manifest. 108 | 109 | 如果你想创建自己的占位符,你可以在manifestPlaceholders定义,语法是: 110 | 111 | productFlavors { 112 | branchOne { 113 | manifestPlaceholders = [branchCustoName :"defaultName"] 114 | } 115 | branchTwo { 116 | manifestPlaceholders = [branchCustoName :"otherName"] 117 | } 118 | } 119 | 120 | ####To go further 121 | 122 | 如果你想做更复杂的事情,你可以applicationVariants.all这个task中添加代码进行执行。 123 | 124 | 思考一下,假设,我想设置一个特定的applicationId给branchTwo和distrib结合的variant,我可以在build.gradle里面这样写: 125 | 126 | applicationVariants.all { variant -> 127 | def mergedFlavor = variant.mergedFlavor 128 | switch (variant.flavorName) { 129 | case "brancheTwoDistrib": 130 | mergedFlavor.setApplicationId("com.example.oldNameBranchTwo") 131 | break 132 | } 133 | } 134 | 135 | 有时某些buildTypes-flavor结合没有意义,我们想告诉Gradle不要生成这些variants,没有问题,只需要用variant filter就可以做到 136 | 137 | variantFilter { variant -> 138 | if (variant.buildType.name.equals('release')) { 139 | variant.setIgnore(!variant.getFlavors().get(1).name.equals('distrib')); 140 | } 141 | if (variant.buildType.name.equals('debug')) { 142 | variant.setIgnore(variant.getFlavors().get(1).name.equals('distrib')); 143 | } 144 | } 145 | 146 | 在上面的代码中,我们告诉Gradle buildType=debug不要和flavor=distrib结合而buildType=release只和flavor=distrib结合,生成的Variants 147 | 148 | ![btFla](http://upload-images.jianshu.io/upload_images/186157-40e360cf1eed2fbc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 149 | 150 | #3.Facilitate continuous integration(简化持续集成) 151 | 152 | 持续集成对于Applidium是一个大挑战,它是一种可以在开发的各个阶段保证你的代码质量的方法。下面是一些让集成CI更简单的技巧。 153 | 154 | ####Thanks to buildVariants 155 | 156 | 从上面的例子我们可以看到buildVariants(buildType+productFlavors)允许存在多个拥有各自资源和配置的开发环境。有了这些不同的开发环境,你可以很容易在假数据之间(stubs,suitable for tests)或者一个接近生产条件的环境(preprod)中进行切换。 157 | ####With a plug-in 158 | 159 | classpath 'com.novoda:gradle-android-command-plugin:1.4.0' 160 | 161 | 这个插件可以在Gradle tasks中执行adb命令,事实上,把所有的部署步骤集中在一个工具中是很重要的。这样,所有的部署链可以用一个单一的命令执行和最小化错误机率。当我们在Android Studio build+run构建运行一个应用程序时,Android Studio隐藏的事实是它使用了几个工具:首先执行Gradle assemble task然后通过adb安装和启动app。在CI Server或者其他你没有Android Studio的地方。你可以通过这个插件构建build和运行app 162 | 163 | 只需要在根目录的build.gradle添加一行即可 164 | 165 | dependencies { 166 | classpath 'com.android.tools.build:gradle:1.3.0' 167 | classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' 168 | classpath 'com.novoda:gradle-android-command-plugin:1.4.0' //添加该行 169 | } 170 | 171 | ####Automatically increase the versionCode(版本号自增长) 172 | 173 | 为了容易发现问题,推荐让version code自增长,避免问题,你可以创建一个让version code以1递增的Gradle task。 174 | 175 | 下面是代码,添加到build.gradle里面 176 | 177 | void bumpVersionCodeInFile(File file) { 178 | def text = file.text 179 | def matcher = (text =~ /versionCode ([0-9]+)/) 180 | if (matcher.size() != 1 || !matcher.hasGroup()) { 181 | throw new GradleException("Could not find versionCode in app/build.gradle") 182 | } 183 | def String versionCodeStr = matcher[0][1] 184 | def versionCode = Integer.valueOf(versionCodeStr) 185 | def newVersionCode = versionCode + 1 186 | def newContent = matcher.replaceFirst("versionCode " + newVersionCode) 187 | file.write(newContent) 188 | } 189 | task(bumpVersionCode) << { 190 | def appGradleFile = file('app/build.gradle') 191 | if (appGradleFile.canRead()) { 192 | bumpVersionCodeInFile(appGradleFile) 193 | } else { 194 | throw new GradleException("Could not read app/build.gradle"); 195 | } 196 | def wearGradleFile = file('wear/build.gradle') 197 | if (wearGradleFile.canRead()) { 198 | bumpVersionCodeInFile(wearGradleFile) 199 | } 200 | // No exception here since projects are not required to have a wearable app 201 | } 202 | 203 | #4.Tell us your interests(告诉我们你对哪些感兴趣) 204 | 205 | 这篇关于Gradle的文章到此结束了。接下来的主题还没确定,给我们分享你的想法或者你对Android还有哪些问题。我们将会尽力帮助你。 206 | 207 | 208 | #5.其他资源 209 | 210 | 211 | 212 | 213 | [使用Gradle管理Debug/Release版本的Key](http://www.jianshu.com/p/3f25bef975e2) 使用了文中的动态占位符技术 214 | 215 | [使用Gradle构建多个不同applicationId包](http://www.jianshu.com/p/037327da4893)使用了文中的productFlavor and buildConfigField同时打包多个开发环境包 -------------------------------------------------------------------------------- /MVP+Dagger2+Retrofit实现更清晰的架构.md: -------------------------------------------------------------------------------- 1 | 这个架构已经有不少文章介绍了,今天打算自己实践下。 2 | 3 | MVP概念不多说了 相关介绍已经很多了 4 | 5 | Dagger2:依赖注入框架,用来解决依赖 除了基本依赖 ,mvp的V-->P-->M的之间依赖也轻松解决 方便不少 6 | 7 | Retrofit:用来解决M的RestApi数据获取, 天然支持Rxjava 不过这里我没用到Rxjava 其自带的Callback已经足够用了 8 | 9 | 估计这个架构的难点在于Dagger2 理解它的工作方式需要方式需要点时间,我收集了一些资料,可以查看我博客的相关文章。 10 | 11 | 12 | ok,开始写demo,简单看下项目结构 13 | 14 | ![项目结构](http://img.blog.csdn.net/20150613224358959) 15 | 16 | - data:数据来源 也就是MVP的M 17 | - model:实体bean,本来也应该放在data中,为了方便查找就抽出了,毕竟很多地方用到 18 | - ui:视图,V和P都在里面,View层是最难组织的,有人喜欢以功能划分 eg:main,login 有人喜欢以组件划分 19 | 20 | 21 | ![完整项目结构](http://img.blog.csdn.net/20150613225224892) 22 | 23 | 24 | 25 | 来简单看一下dagger2提供的依赖结构 26 | 27 | ![dagger依赖结构](http://img.blog.csdn.net/20150613230845776) 28 | 29 | AppComponent通常提供全局的对象以便于其他的组件依赖使用,比如context,rest api接口等,这些都是全局的单例对象 30 | 31 | MainActivityComponent特意针对MainActivity,所以它只提供其对应的MainActivityPresenter,因为其依赖AppComponent,所以它可以使用AppComponent提供的对象 32 | 33 | **AppComponent** 34 | 可以看到其向外公布了Application,ApiService,User三个对象,供其他component都可以使用 35 | 36 | @Singleton 37 | @Component(modules = {AppModule.class, ApiServiceModule.class, AppServiceModule.class}) 38 | public interface AppComponent { 39 | 40 | 41 | Application getApplication(); 42 | 43 | ApiService getService(); 44 | 45 | User getUser(); 46 | } 47 | 48 | 49 | **MainActivityComponent** 50 | 它依赖了AppComponent,本身只提供了MainActivityPrresenter,并且只针对MainActivity 51 | 52 | @ActivityScope 53 | @Component(modules = MainActivityModule.class,dependencies = AppComponent.class) 54 | public interface MainActivityComponent { 55 | 56 | MainActivity inject(MainActivity mainActivity); 57 | 58 | MainActivityPresenter presenter(); 59 | 60 | 61 | } 62 | 63 | 最后看一下Activity怎么使用 64 | 65 | 66 | public class MainActivity extends BaseActivity { 67 | 68 | @InjectView(R.id.tv) 69 | TextView textView; 70 | 71 | @Inject 72 | MainActivityPresenter presenter;//注入其所需要的presenter 73 | 74 | 75 | @Override 76 | protected void onCreate(Bundle savedInstanceState) { 77 | super.onCreate(savedInstanceState); 78 | setContentView(R.layout.activity_main); 79 | ButterKnife.inject(this); 80 | presenter.showUserName(); 81 | 82 | } 83 | 84 | @Override 85 | protected void setupActivityComponent(AppComponent appComponent) { 86 | DaggerMainActivityComponent.builder() 87 | .appComponent(appComponent) 88 | .mainActivityModule(new MainActivityModule(this)) 89 | .build() 90 | .inject(this); 91 | 92 | } 93 | 94 | //只做界面逻辑 95 | public void setTextView(String username){ 96 | textView.setText(username); 97 | } 98 | 99 | 100 | 101 | 102 | } 103 | 104 | 105 | 代码就不全贴了 后面会放到github上面去 106 | 显而易见,整个项目和结构会非常舒服,各个模块各司其事,有了dagger在扩展和测试会非常方便,分别提供DebugComponent和ReleaseComponent随意切换 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 之前一直主要在CSDN,简书发布文章(好多csdn文章搬不动 哭瞎),但是写的多了以后发现,想要那么一个目录页,可以让读者和自己根据知识点进行更好的进行阅读,还有一个原因把这里当做一个备份。后面文章会同时发布在github和简书,喜欢就点一下star哦! 2 | 3 | 喜欢简书阅读体验的也可以去 [**我的简书**](http://www.jianshu.com/users/ba6770f3f858/latest_articles) 4 | 5 | 6 | # 知识点 7 | 8 | [Drawable Resources一览](https://github.com/thinkSky1206/android-blog/blob/master/Drawable%20Resources%E4%B8%80%E8%A7%88.md) 9 | 10 | # UI 11 | #### *CoordinatorLayout* 12 | 13 | [自定义CoordinatorLayout的Behavior(2):实现淘宝和QQ ToolBar透明渐变效果](https://github.com/thinkSky1206/android-blog/blob/master/%E5%AE%9E%E7%8E%B0%E6%B7%98%E5%AE%9D%E5%92%8CQQ%20ToolBar%E9%80%8F%E6%98%8E%E6%B8%90%E5%8F%98%E6%95%88%E6%9E%9C.md) 14 | 15 | [自定义CoordinatorLayout的Behavior实现知乎和简书快速返回效果](https://github.com/thinkSky1206/android-blog/blob/master/%E5%AE%9E%E7%8E%B0%E7%9F%A5%E4%B9%8E%E5%92%8C%E7%AE%80%E4%B9%A6%E5%BF%AB%E9%80%9F%E8%BF%94%E5%9B%9E%E6%95%88%E6%9E%9C.md) 16 | 17 | #### *RecyclerView* 18 | 19 | [RecyclerView之ItemDecoration由浅入深](https://github.com/thinkSky1206/android-blog/blob/master/RecyclerView%E4%B9%8BItemDecoration%E7%94%B1%E6%B5%85%E5%85%A5%E6%B7%B1.md) 20 | 21 | ### *动画* 22 | 23 | [低版本实现共享元素动画(二):格瓦拉动画](https://github.com/thinkSky1206/android-blog/blob/master/%E4%BD%8E%E7%89%88%E6%9C%AC%E5%AE%9E%E7%8E%B0%E5%85%B1%E4%BA%AB%E5%85%83%E7%B4%A0%E5%8A%A8%E7%94%BB%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E6%A0%BC%E7%93%A6%E6%8B%89%E5%8A%A8%E7%94%BB.md) 24 | 25 | [低版本实现共享元素动画](https://github.com/thinkSky1206/android-blog/blob/master/%E4%BD%8E%E7%89%88%E6%9C%AC%E5%AE%9E%E7%8E%B0%E5%85%B1%E4%BA%AB%E5%85%83%E7%B4%A0%E5%8A%A8%E7%94%BB.md) 26 | 27 | [教你实现别人家的动画3(淘宝,简书动画效果)](https://github.com/thinkSky1206/android-blog/blob/master/%E6%95%99%E4%BD%A0%E5%AE%9E%E7%8E%B0%E5%88%AB%E4%BA%BA%E5%AE%B6%E7%9A%84%E5%8A%A8%E7%94%BB3(%E6%B7%98%E5%AE%9D%2C%E7%AE%80%E4%B9%A6%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C).md) 28 | 29 | # 性能优化 30 | 31 | [android内存泄露](https://github.com/thinkSky1206/android-blog/blob/master/android%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2.md) 32 | 33 | 34 | # 重构,技巧 35 | >梦里寻它千百度,蓦然回首,它在灯火阑珊处,总有那么一些技巧让你相见恨晚! 36 | 37 | 38 | [关于Android strings.xml你要记住的一些事](https://github.com/thinkSky1206/android-blog/blob/master/%E5%85%B3%E4%BA%8EAndroid%20strings.xml%E4%BD%A0%E8%A6%81%E8%AE%B0%E4%BD%8F%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BA%8B.md) 39 | 40 | [Android GridLayout](https://github.com/thinkSky1206/android-blog/blob/master/Android%20GridLayout.md) 41 | 42 | [一种成功的xml资源命名规范](https://github.com/thinkSky1206/android-blog/blob/master/%E4%B8%80%E7%A7%8D%E6%88%90%E5%8A%9F%E7%9A%84xml%E8%B5%84%E6%BA%90%E5%91%BD%E5%90%8D%E8%A7%84%E8%8C%83.md) 43 | 44 | [Android项目结构的另外一种方式](https://github.com/thinkSky1206/android-blog/blob/master/Android%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84%E7%9A%84%E5%8F%A6%E5%A4%96%E4%B8%80%E7%A7%8D%E6%96%B9%E5%BC%8F.md) 45 | 46 | [用xml drawable替代png](https://github.com/thinkSky1206/android-blog/blob/master/%E7%94%A8xml%20drawable%E6%9B%BF%E4%BB%A3png.md) 47 | 48 | [写出高效清晰Layout布局文件的一些技巧](https://github.com/thinkSky1206/android-blog/blob/master/%E5%86%99%E5%87%BA%E9%AB%98%E6%95%88%E6%B8%85%E6%99%B0Layout%E5%B8%83%E5%B1%80%E6%96%87%E4%BB%B6%E7%9A%84%E4%B8%80%E4%BA%9B%E6%8A%80%E5%B7%A7.md) 49 | 50 | [SharedPreferences的最佳实践](https://github.com/thinkSky1206/android-blog/blob/master/SharedPreferences%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5.md) 51 | 52 | [MVP+Dagger2+Retrofit实现更清晰的架构](https://github.com/thinkSky1206/android-blog/blob/master/MVP%2BDagger2%2BRetrofit%E5%AE%9E%E7%8E%B0%E6%9B%B4%E6%B8%85%E6%99%B0%E7%9A%84%E6%9E%B6%E6%9E%84.md) 53 | 54 | # 三方库 55 | 56 | 57 | [使用retrofit+okhttp实现无缝网络状态监测](https://github.com/thinkSky1206/android-blog/blob/master/Android%20Studio%E8%87%AA%E5%AE%9A%E4%B9%89%E6%96%87%E4%BB%B6%E6%A8%A1%E6%9D%BF.md) 58 | 59 | 60 | [dagger2:组件依赖和子组件的区别](https://github.com/thinkSky1206/android-blog/blob/master/dagger2%EF%BC%9A%E7%BB%84%E4%BB%B6%E4%BE%9D%E8%B5%96%E5%92%8C%E5%AD%90%E7%BB%84%E4%BB%B6%E7%9A%84%E5%8C%BA%E5%88%AB.md) 61 | 62 | [更新到Retrofit2的一些技巧](https://github.com/thinkSky1206/android-blog/blob/master/%E6%9B%B4%E6%96%B0%E5%88%B0Retrofit2%E7%9A%84%E4%B8%80%E4%BA%9B%E6%8A%80%E5%B7%A7.md) 63 | 64 | [每个android开发者都应该知道的Top 5三方库(2015版)](https://github.com/thinkSky1206/android-blog/blob/master/%E6%AF%8F%E4%B8%AAandroid%E5%BC%80%E5%8F%91%E8%80%85%E9%83%BD%E5%BA%94%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84Top%205%E4%B8%89%E6%96%B9%E5%BA%93(2015%E7%89%88).md) 65 | 66 | [每个Android开发者都应该知道的开源库(2014版)](https://github.com/thinkSky1206/android-blog/blob/master/%E6%AF%8F%E4%B8%AAAndroid%E5%BC%80%E5%8F%91%E8%80%85%E9%83%BD%E5%BA%94%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E5%BC%80%E6%BA%90%E5%BA%93(2014%E7%89%88).md) 67 | 68 | 69 | 70 | # Gradle 71 | 72 | [Gradle依赖](https://github.com/thinkSky1206/android-blog/blob/master/Gradle%E4%BE%9D%E8%B5%96.md) 73 | 74 | [Gradle的高级技巧](https://github.com/thinkSky1206/android-blog/blob/master/Gradle%E7%9A%84%E9%AB%98%E7%BA%A7%E6%8A%80%E5%B7%A7.md) 75 | 76 | [使用Gradle构建多个不同applicationId包](https://github.com/thinkSky1206/android-blog/blob/master/%E4%BD%BF%E7%94%A8Gradle%E6%9E%84%E5%BB%BA%E5%A4%9A%E4%B8%AA%E4%B8%8D%E5%90%8CapplicationId%E5%8C%85.md) 77 | 78 | [使用Gradle管理Debug/Release版本的Key](https://github.com/thinkSky1206/android-blog/blob/master/%E4%BD%BF%E7%94%A8Gradle%E7%AE%A1%E7%90%86Debug%E5%92%8CRelease%E7%89%88%E6%9C%AC%E7%9A%84Key.md) 79 | 80 | # Android Studio 81 | 82 | [Android Studio自定义文件模板](https://github.com/thinkSky1206/android-blog/blob/master/Android%20Studio%E8%87%AA%E5%AE%9A%E4%B9%89%E6%96%87%E4%BB%B6%E6%A8%A1%E6%9D%BF.md) 83 | 84 | [给你的代码添加版权声明](https://github.com/thinkSky1206/android-blog/blob/master/%E5%9C%A8AndroidStudio%E4%B8%AD%E7%BB%99%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%A0%81%E6%B7%BB%E5%8A%A0%E7%89%88%E6%9D%83%E5%A3%B0%E6%98%8E.md) 85 | 86 | [快速清除数据](https://github.com/thinkSky1206/android-blog/blob/master/%E5%BF%AB%E9%80%9F%E6%B8%85%E9%99%A4app%E6%95%B0%E6%8D%AE.md) 87 | 88 | 89 | 90 | # 工具 91 | 92 | [阴影背景生成器](http://inloop.github.io/shadow4android/) 93 | 94 | [jadx](https://github.com/skylot/jadx) 95 | 96 | 97 | -------------------------------------------------------------------------------- /RecycleView 列表显示更多.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | Adapter实现 14 | 15 | public class DiscoverAdapter extends RecyclerView.Adapter { 16 | 17 | private SparseBooleanArray expandStates = new SparseBooleanArray(); 18 | private List data; 19 | private RvItemClickListener itemClickListener; 20 | 21 | 22 | public DiscoverAdapter(RvItemClickListener itemClickListener) { 23 | this.data = new ArrayList<>(); 24 | this.itemClickListener = itemClickListener; 25 | } 26 | 27 | public void addHead(List newData) { 28 | data.addAll(0, newData); 29 | notifyItemRangeInserted(0, newData.size()); 30 | } 31 | 32 | public void addFooter(List newData) { 33 | data.addAll(newData); 34 | int size = data.size() - 1; 35 | notifyItemInserted(size); 36 | } 37 | 38 | public void setData(List data) { 39 | this.data = data; 40 | notifyDataSetChanged(); 41 | } 42 | 43 | public List getData() { 44 | return data; 45 | } 46 | 47 | @Override 48 | public ItemView onCreateViewHolder(ViewGroup parent, int viewType) { 49 | this.context = parent.getContext(); 50 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.share_discover_item, parent, false); 51 | return new ItemView(view); 52 | } 53 | 54 | @Override 55 | public void onBindViewHolder(ItemView holder, int position) { 56 | ShareInfo item = data.get(position); 57 | holder.contentTv.setText(item.getTitle()); 58 | holder.contentTv.setTag(position); 59 | holder.contentTv.setOnClickListener(new View.OnClickListener() { 60 | @Override 61 | public void onClick(View v) { 62 | TextView expandTv = (TextView) v; 63 | int pos = (int) expandTv.getTag(); 64 | expandTv.setMaxLines(100); 65 | expandStates.put(pos, true); 66 | } 67 | }); 68 | if (expandStates.get(position)) { 69 | holder.contentTv.setMaxLines(100); 70 | } 71 | } 72 | 73 | @Override 74 | public int getItemCount() { 75 | return data.size(); 76 | } 77 | 78 | public class ItemView extends RecyclerView.ViewHolder implements View.OnClickListener { 79 | 80 | private TextView contentTv; 81 | 82 | 83 | public ItemView(View itemView) { 84 | super(itemView); 85 | contentTv = (TextView) itemView.findViewById(R.id.tv_share_item_content); 86 | itemView.setOnClickListener(this); 87 | } 88 | 89 | @Override 90 | public void onClick(View v) { 91 | itemClickListener.onItemClick(v, this.getLayoutPosition()); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /RecyclerView之ItemDecoration由浅入深.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | >译文的GitHub地址:[RecyclerView之ItemDecoration由浅入深](https://github.com/thinkSky1206/android-blog/blob/master/RecyclerView%E4%B9%8BItemDecoration%E7%94%B1%E6%B5%85%E5%85%A5%E6%B7%B1.md) 4 | 5 | >译者注:RecyclerView第一篇,希望后面坚持下来 6 | 7 | RecyclerView没有像之前ListView提供divider属性,而是提供了方法 8 | 9 | recyclerView.addItemDecoration() 10 | 其中ItemDecoration需要我们自己去定制重写,一开始可能有人会觉得麻烦不好用,最后你会发现这种可插拔设计不仅好用,而且功能强大。 11 | 12 | 13 | ItemDecoration类主要是三个方法: 14 | 15 | public void onDraw(Canvas c, RecyclerView parent, State state) 16 | public void onDrawOver(Canvas c, RecyclerView parent, State state) 17 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) 18 | 19 | 20 | 官方源码虽然都写的很清楚,但还不少小伙伴不知道怎么理解,怎么用或用哪个方法,下面我画个简单的图来帮你们理解一下。 21 | 22 | ![ItemDecoration](http://upload-images.jianshu.io/upload_images/186157-1ab2f9304503506c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 23 | 24 | 图画的丑请见谅,首先我们假设绿色区域代表的是我们的内容,红色区域代表我们自己绘制的装饰,可以看到: 25 | 26 | 图1:代表了getItemOffsets(),可以实现类似padding的效果 27 | 28 | 图2:代表了onDraw(),可以实现类似绘制背景的效果,内容在上面 29 | 30 | 图3:代表了onDrawOver(),可以绘制在内容的上面,覆盖内容 31 | 32 | 注意上面是我个人从应用角度的看法,事实上实现上面的效果可能三个方法每个方法都可以实现。只不过这种方法更好理解。 33 | 34 | 下面是我们没有添加任何ItemDecoration的界面 35 | 36 | ![activity](http://upload-images.jianshu.io/upload_images/186157-e8eafc2e1dd17110.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 37 | 38 | 主页布局界面很简单,背景设成灰色 39 | 40 | 41 | //灰色背景 48 | 49 | 50 | 53 | 54 | 60 | 61 | 62 | 63 | 69 | 70 | 71 | 72 | ok 接下来,让我们来实现实际开发中常遇到的场景。 73 | 74 | 75 | #padding 76 | 从前面的图可以看到实现这个效果,需要重写getItemOffsets方法。 77 | 78 | public class SimplePaddingDecoration extends RecyclerView.ItemDecoration { 79 | 80 | private int dividerHeight; 81 | 82 | 83 | public SimplePaddingDecoration(Context context) { 84 | dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height); 85 | } 86 | 87 | @Override 88 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 89 | super.getItemOffsets(outRect, view, parent, state); 90 | outRect.bottom = dividerHeight;//类似加了一个bottom padding 91 | } 92 | } 93 | 94 | 没错,就这么2行代码,然后添加到RecyclerView 95 | 96 | recyclerView.addItemDecoration(new SimplePaddingDecoration(this)); 97 | 98 | 实现效果: 99 | 100 | ![Padding ItemDecoration.png](http://upload-images.jianshu.io/upload_images/186157-5404e1be7bf508c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 101 | 102 | #分割线 103 | 分割线在app中是经常用到的,用ItemDecoration怎么实现呢,其实上面padding改成1dp就实现了分割线的效果,但是分割线的颜色只能是背景灰色,所以不能用这种方法。 104 | 105 | 要实现分割线效果需要 getItemOffsets()和 onDraw()2个方法,首先用 getItemOffsets给item下方空出一定高度的空间(例子中是1dp),然后用onDraw绘制这个空间 106 | 107 | public class SimpleDividerDecoration extends RecyclerView.ItemDecoration { 108 | 109 | private int dividerHeight; 110 | private Paint dividerPaint; 111 | 112 | public SimpleDividerDecoration(Context context) { 113 | dividerPaint = new Paint(); 114 | dividerPaint.setColor(context.getResources().getColor(R.color.colorAccent)); 115 | dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height); 116 | } 117 | 118 | 119 | @Override 120 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 121 | super.getItemOffsets(outRect, view, parent, state); 122 | outRect.bottom = dividerHeight; 123 | } 124 | 125 | @Override 126 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 127 | int childCount = parent.getChildCount(); 128 | int left = parent.getPaddingLeft(); 129 | int right = parent.getWidth() - parent.getPaddingRight(); 130 | 131 | for (int i = 0; i < childCount - 1; i++) { 132 | View view = parent.getChildAt(i); 133 | float top = view.getBottom(); 134 | float bottom = view.getBottom() + dividerHeight; 135 | c.drawRect(left, top, right, bottom, dividerPaint); 136 | } 137 | } 138 | } 139 | 140 | 实现效果: 141 | 142 | 143 | ![Divider ItemDecoration.png](http://upload-images.jianshu.io/upload_images/186157-11a6344281993a69.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 144 | 145 | #标签 146 | 147 | 现在很多电商app会给商品加上一个标签,比如“推荐”,“热卖”,“秒杀”等等,可以看到这些标签都是覆盖在内容之上的,这就可以用onDrawOver()来实现,我们这里简单实现一个有趣的标签 148 | 149 | public class LeftAndRightTagDecoration extends RecyclerView.ItemDecoration { 150 | private int tagWidth; 151 | private Paint leftPaint; 152 | private Paint rightPaint; 153 | 154 | public LeftAndRightTagDecoration(Context context) { 155 | leftPaint = new Paint(); 156 | leftPaint.setColor(context.getResources().getColor(R.color.colorAccent)); 157 | rightPaint = new Paint(); 158 | rightPaint.setColor(context.getResources().getColor(R.color.colorPrimary)); 159 | tagWidth = context.getResources().getDimensionPixelSize(R.dimen.tag_width); 160 | } 161 | 162 | @Override 163 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 164 | super.onDrawOver(c, parent, state); 165 | int childCount = parent.getChildCount(); 166 | for (int i = 0; i < childCount; i++) { 167 | View child = parent.getChildAt(i); 168 | int pos = parent.getChildAdapterPosition(child); 169 | boolean isLeft = pos % 2 == 0; 170 | if (isLeft) { 171 | float left = child.getLeft(); 172 | float right = left + tagWidth; 173 | float top = child.getTop(); 174 | float bottom = child.getBottom(); 175 | c.drawRect(left, top, right, bottom, leftPaint); 176 | } else { 177 | float right = child.getRight(); 178 | float left = right - tagWidth; 179 | float top = child.getTop(); 180 | float bottom = child.getBottom(); 181 | c.drawRect(left, top, right, bottom, rightPaint); 182 | 183 | } 184 | } 185 | } 186 | } 187 | 188 | 实现效果: 189 | 190 | ![Tag ItemDecoration](http://upload-images.jianshu.io/upload_images/186157-252aa2ed0a0ef103.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 191 | 192 | #组合 193 | 不要忘记的是ItemDecoration是可以叠加的 194 | 195 | recyclerView.addItemDecoration(new LeftAndRightTagDecoration(this)); 196 | recyclerView.addItemDecoration(new SimpleDividerDecoration(this)); 197 | 198 | 我们把上面2个ItemDecoration同时添加到RecyclerView看下什么效果 199 | 200 | ![ItemDecoration](http://upload-images.jianshu.io/upload_images/186157-00d3652a11399844.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 201 | 202 | 是不是有种狂拽炫酷吊炸天的赶脚。。。 203 | 204 | 三个方法都用了一遍,你以为这就结束了?呵呵 并没有 205 | 206 | #section 207 | 208 | 这个是什么呢,先看下我们实现的效果 209 | 210 | 211 | ![Section ItemDecoration](http://upload-images.jianshu.io/upload_images/186157-0339858aeebc23ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 212 | 213 | 一看这个就很熟悉吧,手机上面的通讯录联系人,知乎日报都是这样效果,可以叫分组,也可以叫section分块 先不管它叫什么。 214 | 215 | 这个怎么实现呢? 其实和实现分割线是一样的道理 ,只是不是所有的item都需要分割线,只有同组的第一个需要。 216 | 217 | 我们首先定义一个接口给activity进行回调用来进行数据分组和获取首字母 218 | 219 | public interface DecorationCallback { 220 | 221 | long getGroupId(int position); 222 | 223 | String getGroupFirstLine(int position); 224 | } 225 | 226 | 然后再来看我们的ItemDecoration 227 | 228 | public class SectionDecoration extends RecyclerView.ItemDecoration { 229 | private static final String TAG = "SectionDecoration"; 230 | 231 | private DecorationCallback callback; 232 | private TextPaint textPaint; 233 | private Paint paint; 234 | private int topGap; 235 | private Paint.FontMetrics fontMetrics; 236 | 237 | 238 | public SectionDecoration(Context context, DecorationCallback decorationCallback) { 239 | Resources res = context.getResources(); 240 | this.callback = decorationCallback; 241 | 242 | paint = new Paint(); 243 | paint.setColor(res.getColor(R.color.colorAccent)); 244 | 245 | textPaint = new TextPaint(); 246 | textPaint.setTypeface(Typeface.DEFAULT_BOLD); 247 | textPaint.setAntiAlias(true); 248 | textPaint.setTextSize(80); 249 | textPaint.setColor(Color.BLACK); 250 | textPaint.getFontMetrics(fontMetrics); 251 | textPaint.setTextAlign(Paint.Align.LEFT); 252 | fontMetrics = new Paint.FontMetrics(); 253 | topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);//32dp 254 | 255 | 256 | } 257 | 258 | 259 | @Override 260 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 261 | super.getItemOffsets(outRect, view, parent, state); 262 | int pos = parent.getChildAdapterPosition(view); 263 | Log.i(TAG, "getItemOffsets:" + pos); 264 | long groupId = callback.getGroupId(pos); 265 | if (groupId < 0) return; 266 | if (pos == 0 || isFirstInGroup(pos)) {//同组的第一个才添加padding 267 | outRect.top = topGap; 268 | } else { 269 | outRect.top = 0; 270 | } 271 | } 272 | 273 | @Override 274 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 275 | super.onDraw(c, parent, state); 276 | int left = parent.getPaddingLeft(); 277 | int right = parent.getWidth() - parent.getPaddingRight(); 278 | int childCount = parent.getChildCount(); 279 | for (int i = 0; i < childCount; i++) { 280 | View view = parent.getChildAt(i); 281 | int position = parent.getChildAdapterPosition(view); 282 | long groupId = callback.getGroupId(position); 283 | if (groupId < 0) return; 284 | String textLine = callback.getGroupFirstLine(position).toUpperCase(); 285 | if (position == 0 || isFirstInGroup(position)) { 286 | float top = view.getTop() - topGap; 287 | float bottom = view.getTop(); 288 | c.drawRect(left, top, right, bottom, paint);//绘制红色矩形 289 | c.drawText(textLine, left, bottom, textPaint);//绘制文本 290 | } 291 | } 292 | } 293 | 294 | 295 | private boolean isFirstInGroup(int pos) { 296 | if (pos == 0) { 297 | return true; 298 | } else { 299 | long prevGroupId = callback.getGroupId(pos - 1); 300 | long groupId = callback.getGroupId(pos); 301 | return prevGroupId != groupId; 302 | } 303 | } 304 | 305 | public interface DecorationCallback { 306 | 307 | long getGroupId(int position); 308 | 309 | String getGroupFirstLine(int position); 310 | } 311 | } 312 | 313 | 可以看到和divider实现一样,都是重写getItemOffsets()和onDraw()2个方法,不同的是根据数据做了处理。 314 | 315 | 在Activity中使用 316 | 317 | recyclerView.addItemDecoration(new SectionDecoration(this, new SectionDecoration.DecorationCallback() { 318 | @Override 319 | public long getGroupId(int position) { 320 | return Character.toUpperCase(dataList.get(position).getName().charAt(0)); 321 | } 322 | 323 | @Override 324 | public String getGroupFirstLine(int position) { 325 | return dataList.get(position).getName().substring(0, 1).toUpperCase(); 326 | } 327 | })); 328 | 329 | 干净舒服,不少github类似的库都是去adapter进行处理 侵入性太强 或许ItemDecoration是个更好的选择,可插拔,可替换。 330 | 331 | 到这里细心的人就会发现了,header不会动啊,我手机上的通讯录可是会随的滑动而变动呢,这个可以实现么? 332 | 333 | #StickyHeader 334 | 335 | 这个东西怎么叫我也不知道啊 粘性头部?英文也有叫 pinned section 取名字真是个麻烦事。 336 | 337 | 先看下我们简单实现的效果 338 | 339 | 340 | ![stickyheader](http://upload-images.jianshu.io/upload_images/186157-06489058260d58d5.gif?imageMogr2/auto-orient/strip) 341 | 342 | 首先一看到图,我们就应该想到header不动肯定是要绘制item内容之上的,需要重写onDrawOver()方法,其他地方和section实现一样。 343 | 344 | 345 | public class PinnedSectionDecoration extends RecyclerView.ItemDecoration { 346 | private static final String TAG = "PinnedSectionDecoration"; 347 | 348 | private DecorationCallback callback; 349 | private TextPaint textPaint; 350 | private Paint paint; 351 | private int topGap; 352 | private Paint.FontMetrics fontMetrics; 353 | 354 | 355 | public PinnedSectionDecoration(Context context, DecorationCallback decorationCallback) { 356 | Resources res = context.getResources(); 357 | this.callback = decorationCallback; 358 | 359 | paint = new Paint(); 360 | paint.setColor(res.getColor(R.color.colorAccent)); 361 | 362 | textPaint = new TextPaint(); 363 | textPaint.setTypeface(Typeface.DEFAULT_BOLD); 364 | textPaint.setAntiAlias(true); 365 | textPaint.setTextSize(80); 366 | textPaint.setColor(Color.BLACK); 367 | textPaint.getFontMetrics(fontMetrics); 368 | textPaint.setTextAlign(Paint.Align.LEFT); 369 | fontMetrics = new Paint.FontMetrics(); 370 | topGap = res.getDimensionPixelSize(R.dimen.sectioned_top); 371 | 372 | 373 | } 374 | 375 | 376 | @Override 377 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 378 | super.getItemOffsets(outRect, view, parent, state); 379 | int pos = parent.getChildAdapterPosition(view); 380 | long groupId = callback.getGroupId(pos); 381 | if (groupId < 0) return; 382 | if (pos == 0 || isFirstInGroup(pos)) { 383 | outRect.top = topGap; 384 | } else { 385 | outRect.top = 0; 386 | } 387 | } 388 | 389 | 390 | @Override 391 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 392 | super.onDrawOver(c, parent, state); 393 | int itemCount = state.getItemCount(); 394 | int childCount = parent.getChildCount(); 395 | int left = parent.getPaddingLeft(); 396 | int right = parent.getWidth() - parent.getPaddingRight(); 397 | float lineHeight = textPaint.getTextSize() + fontMetrics.descent; 398 | 399 | long preGroupId, groupId = -1; 400 | for (int i = 0; i < childCount; i++) { 401 | View view = parent.getChildAt(i); 402 | int position = parent.getChildAdapterPosition(view); 403 | 404 | preGroupId = groupId; 405 | groupId = callback.getGroupId(position); 406 | if (groupId < 0 || groupId == preGroupId) continue; 407 | 408 | String textLine = callback.getGroupFirstLine(position).toUpperCase(); 409 | if (TextUtils.isEmpty(textLine)) continue; 410 | 411 | int viewBottom = view.getBottom(); 412 | float textY = Math.max(topGap, view.getTop()); 413 | if (position + 1 < itemCount) { //下一个和当前不一样移动当前 414 | long nextGroupId = callback.getGroupId(position + 1); 415 | if (nextGroupId != groupId && viewBottom < textY ) {//组内最后一个view进入了header 416 | textY = viewBottom; 417 | } 418 | } 419 | c.drawRect(left, textY - topGap, right, textY, paint); 420 | c.drawText(textLine, left, textY, textPaint); 421 | } 422 | 423 | } 424 | 425 | } 426 | 427 | 好了,现在发现ItemDecoration有多强大了吧! 当然还有更多就需要你自己去发现了。 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | -------------------------------------------------------------------------------- /SharedPreferences的最佳实践.md: -------------------------------------------------------------------------------- 1 | >原文:[Best practices for SharedPreferences](http://blog.yakivmospan.com/best-practices-for-sharedpreferences/) 2 | 3 | Android提供了很多种保存应用程序数据的方法。其中一种就是用SharedPreferences对象来保存我们私有的键值(key-value)数据。 4 | 5 | 所有的逻辑都是基于下面三个类: 6 | 7 | - SharedPreferences 8 | - SharedPreferences.Editor 9 | - SharedPreferences.OnSharedPreferenceChangeListener 10 | 11 | ###SharedPreferences 12 | 13 | SharedPreferences是其中最重要的。它负责获取(解析)存储的数据,提供获取Editor对象的接口和添加或移除OnSharedPreferenceChangeListener的接口。 14 | 15 | - 创建SharedPreferences你需要Context对象(也可以是application Context) 16 | - getSharedPreferences方法会解析Preference文件并为它创建一个Map对象 17 | - 你可以用Context提供的几个模式创建它,强烈建议使用MODE_PRIVATE模式因为创建全局可读写的文件是比较危险的,可能会导致app的安全漏洞。 18 | 19 | / parse Preference file 解析Preference文件 20 | SharedPreferences preferences = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE); 21 | 22 | // get values from Map 23 | preferences.getBoolean("key", defaultValue) 24 | preferences.get..("key", defaultValue) 25 | 26 | // you can get all Map but be careful you must not modify the collection returned by this 27 | // method, or alter any of its contents. 28 | //(Preference文件转换成map)你可以获取到一个map但是小心点最好不要修改map或它的内容 29 | Map all = preferences.getAll(); 30 | 31 | // get Editor object 32 | SharedPreferences.Editor editor = preferences.edit(); 33 | 34 | //add on Change Listener 添加监听器 35 | preferences.registerOnSharedPreferenceChangeListener(mListener); 36 | 37 | //remove on Change Listener 取消监听器 38 | preferences.unregisterOnSharedPreferenceChangeListener(mListener); 39 | 40 | // listener example 监听器例子 41 | SharedPreferences.OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener 42 | = new SharedPreferences.OnSharedPreferenceChangeListener() { 43 | @Override 44 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 45 | } 46 | }; 47 | 48 | ###Editor 49 | 50 | SharedPreferences.Editor是一个用来修改SharedPreferences对象值的接口。所有在Editor的修改会进行批处理,同时只有你调用了commit()或者apply()的时候才会复制到原来的SharedPreferences。 51 | 52 | - 用Editor的简单接口添加值 53 | - 用同步的commit()或速度更快异步的apply()来保存值。实际上在不同的线程使用commit()时会更安全。这是为什么我喜欢用commit() 54 | - 删除单个值用remove,删除所有值用clear() 55 | 56 | // get Editor object 57 | SharedPreferences.Editor editor = preferences.edit(); 58 | 59 | // put values in editor 60 | editor.putBoolean("key", value); 61 | editor.put..("key", value); 62 | 63 | // remove single value by key 64 | editor.remove("key"); 65 | 66 | // remove all values 67 | editor.clear(); 68 | 69 | // commit your putted values to the SharedPreferences object synchronously 70 | // returns true if success 同步提交保存 成功返回true 71 | boolean result = editor.commit(); 72 | 73 | // do the same as commit() but asynchronously (faster but not safely) 74 | // returns nothing 异步保存 不返回结果 75 | editor.apply(); 76 | 77 | ###性能和技巧 78 | 79 | - SharedPreferences是一个单例对象所以你可以轻易的获取它的多个引用,它只有在第一次调用getSharedPreferences的时候打开文件,只为它创建一个引用。(ps:真啰嗦,其实就是只实例化一次,后面调用会越来越快,看下面的例子) 80 | 81 | // There are 1000 String values in preferences 82 | 83 | SharedPreferences first = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE); 84 | // call time = 4 milliseconds第一次读取文件花了4毫秒 85 | 86 | SharedPreferences second = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE); 87 | // call time = 0 milliseconds第二次0 88 | 89 | SharedPreferences third = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE); 90 | // call time = 0 milliseconds第三次0 91 | 92 | - 因为是单例对象你可以随意更改它多个实例的内容不用担心他们的数据会不同 93 | 94 | first.edit().putInt("key",15).commit(); 95 | 96 | int firstValue = first.getInt("key",0)); // firstValue is 15 97 | int secondValue = second.getInt("key",0)); // secondValue is also 15 98 | 99 | - 当你第一次调用get方法时,它会通过key解析获取到value然后会把它加到map中,第二次调用get会直接从map拿出,不用解析。 100 | 101 | first.getString("key", null) 102 | // call time = 147 milliseconds 第一次拿需要解析比较慢,然后会放到map中 103 | 104 | first.getString("key", null) 105 | // call time = 0 milliseconds 第二次直接从map中拿 ,不用解析 很快 106 | 107 | second.getString("key", null) 108 | // call time = 0 milliseconds 和第二次一样 109 | 110 | third.getString("key", null) 111 | // call time = 0 milliseconds 112 | 113 | - 记住越大的Preference对象它的get,commit,apply,remove和clear等操作时间越长。所以推荐把你的数据分割成不同的小对象。 114 | - 你的Preference不会在你的app更新后移除,所以有时候你需要创建一个迁移方案。例如你的app需要在启动的时候解析本地的JSON,只有在第一次启动执行然后保存boolean的标识wasLocalDataLoaded,一段时间后你更新了JSON发布了一个新版本,用户会更新app但是他们不会加载新的JSON因为他们在第一个版本已经做了。 115 | 116 | public class MigrationManager { 117 | private final static String KEY_PREFERENCES_VERSION = "key_preferences_version"; 118 | private final static int PREFERENCES_VERSION = 2; 119 | 120 | public static void migrate(Context context) { 121 | SharedPreferences preferences = context.getSharedPreferences("pref", Context.MODE_PRIVATE); 122 | checkPreferences(preferences); 123 | } 124 | 125 | private static void checkPreferences(SharedPreferences thePreferences) { 126 | final double oldVersion = thePreferences.getInt(KEY_PREFERENCES_VERSION, 1); 127 | 128 | if (oldVersion < PREFERENCES_VERSION) { 129 | final SharedPreferences.Editor edit = thePreferences.edit(); 130 | edit.clear(); 131 | edit.putInt(KEY_PREFERENCES_VERSION, currentVersion); 132 | edit.commit(); 133 | } 134 | } 135 | } 136 | 137 | - SharedPreferences保存在app的data文件夹下xml文件里面 138 | 139 | // yours preferences 我们自己创建的 140 | /data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PREFS_NAME.xml 141 | 142 | // default preferences 默认 143 | /data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PACKAGE_NAME_preferences.xml 144 | 145 | ###示例代码 146 | 147 | public class PreferencesManager { 148 | 149 | private static final String PREF_NAME = "com.example.app.PREF_NAME"; 150 | private static final String KEY_VALUE = "com.example.app.KEY_VALUE"; 151 | 152 | private static PreferencesManager sInstance; 153 | private final SharedPreferences mPref; 154 | 155 | private PreferencesManager(Context context) { 156 | mPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); 157 | } 158 | 159 | public static synchronized void initializeInstance(Context context) { 160 | if (sInstance == null) { 161 | sInstance = new PreferencesManager(context); 162 | } 163 | } 164 | 165 | public static synchronized PreferencesManager getInstance() { 166 | if (sInstance == null) { 167 | throw new IllegalStateException(PreferencesManager.class.getSimpleName() + 168 | " is not initialized, call initializeInstance(..) method first."); 169 | } 170 | return sInstance; 171 | } 172 | 173 | public void setValue(long value) { 174 | mPref.edit() 175 | .putLong(KEY_VALUE, value) 176 | .commit(); 177 | } 178 | 179 | public long getValue() { 180 | return mPref.getLong(KEY_VALUE, 0); 181 | } 182 | 183 | public void remove(String key) { 184 | mPref.edit() 185 | .remove(key) 186 | .commit(); 187 | } 188 | 189 | public boolean clear() { 190 | return mPref.edit() 191 | .clear() 192 | .commit(); 193 | } 194 | } -------------------------------------------------------------------------------- /Support Library一览.md: -------------------------------------------------------------------------------- 1 | >文章的GitHub地址:[Drawable Resources](https://github.com/thinkSky1206/android-blog/blob/master/android%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2.md) 2 | 3 | >译者注:之前某个国外开发者大会释放出来的,整理一下记录下来,看看你都会么? 4 | 5 | -------------------------------------------------------------------------------- /android内存泄露.md: -------------------------------------------------------------------------------- 1 | 2 | >文章的GitHub地址:[android内存泄露](https://github.com/thinkSky1206/android-blog/blob/master/android%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2.md) 3 | 4 | >译者注:之前看到内存泄露标题的文章都是一扫而过,私以为要么华而不实,要么晦涩难懂,最近自己学习实践了一下,算是有所收获了吧。 5 | 6 | ![memory leaks](https://github.com/thinkSky1206/android-blog/blob/master/images/memory_leaks.png) 7 | 8 | 本文主要是总结一下自己项目实践过程中遇到的情况。 9 | 10 | 如何发现和解决内存泄露,大家去阅读下面相关文章,基本都给出了答案了。 11 | 12 | 13 | - [Memory leaks in Android](https://medium.com/freenet-engineering/memory-leaks-in-android-identify-treat-and-avoid-d0b1233acc8#.43c1t9l8w) 14 | - [常见的八种导致 APP 内存泄漏的问题](http://blog.nimbledroid.com/2016/05/23/memory-leaks-zh.html) 15 | - [基于Android Studio的内存泄漏检测与解决全攻略](http://wetest.qq.com/lab/view/?id=99) 16 | 17 | 18 | #1.监听器 19 | 20 | 当你使用系统服务SensorManager,LocationManager的时候,你需要注册监听器来被通知当它们获取信息的时候,这样它们就持有了activity的引用,当你销毁activiy的时候没有取消监听器的话,就会导致泄露了 21 | 22 | 解决方法:onDestroy的时候取消注册监听器 23 | 24 | #2.静态变量引用context 25 | 26 | 这种情况很常见的就是:单例持有activity的context 27 | 28 | 因为单例的生命周期比activity的长,所以activity一直被引用没法回收,导致泄露。 29 | 30 | 例如:有人会用ActivitieManager之类的用List保存activity来处理退出app,页面跳转操作,你会发现占用内存会一直居高不下。 31 | 32 | 解决方法:生命周期短的不要引用生命周期长的 33 | 34 | 1. activity context替换成application context 35 | 2. 静态变量避免引用activity context 36 | 37 | 38 | #3.内部类 39 | 40 | 内部类用的地方非常多,因为可以增加封装性和可读性,使用也很方便。不幸的是内部类能够访问外部类这一优势就是通过持有外部类的引用来实现的。 41 | 42 | 例如:activity里面的各种adapter 43 | 44 | 解决方法: 45 | 46 | 1. 声明成静态内部类 47 | 2. 如果要访问外部类组件,声明成WeakReference 48 | 49 | #4.匿名内部类 50 | 51 | 这个情况就很多了, 比如AsyncTask,Handlers,Threads,Timer Tasks 52 | 53 | 匿名内部类和内部类的情况差不多都是默认持有外部类的强引用,注意:大部分匿名内部类都用于异步执行,执行完成更新UI的时候不要忘了判断VIEW是否为空。 54 | 55 | 例如:Retrofit的CallBack 56 | 57 | 解决方法:和内部类一样 58 | 59 | 贴个上面文章提到的AsyncTask小例子看一下就一目了然: 60 | 61 | **修改之前** 62 | 63 | 64 | public class AsyncActivity extends Activity { 65 | 66 | TextView textView; 67 | 68 | @Override 69 | protected void onCreate(Bundle savedInstanceState) { 70 | super.onCreate(savedInstanceState); 71 | setContentView(R.layout.activity_async); 72 | textView = (TextView) findViewById(R.id.textView); 73 | 74 | new BackgroundTask().execute(); 75 | } 76 | 77 | private class BackgroundTask extends AsyncTask { 78 | 79 | @Override 80 | protected String doInBackground(Void... params) { 81 | // Do background work. Code omitted. 82 | return "some string"; 83 | } 84 | 85 | @Override 86 | protected void onPostExecute(String result) { 87 | textView.setText(result); 88 | } 89 | } 90 | } 91 | 92 | 93 | 94 | **修改之后** 95 | 96 | 97 | public class AsyncActivity extends Activity { 98 | 99 | TextView textView; 100 | AsyncTask task; 101 | 102 | @Override 103 | protected void onCreate(Bundle savedInstanceState) { 104 | super.onCreate(savedInstanceState); 105 | setContentView(R.layout.activity_async); 106 | textView = (TextView) findViewById(R.id.textView); 107 | 108 | task = new BackgroundTask(textView).execute(); 109 | } 110 | 111 | @Override 112 | protected void onDestroy() { 113 | task.cancel(true); 114 | super.onDestroy(); 115 | } 116 | 117 | private static class BackgroundTask extends AsyncTask { 118 | 119 | private final WeakReference textViewReference; 120 | 121 | public BackgroundTask(TextView resultTextView) { 122 | this.textViewReference = new WeakReference<>(resultTextView); 123 | } 124 | 125 | @Override 126 | protected void onCancelled() { 127 | // Cancel task. Code omitted. 128 | } 129 | 130 | @Override 131 | protected String doInBackground(Void... params) { 132 | // Do background work. Code omitted. 133 | return "some string"; 134 | } 135 | 136 | @Override 137 | protected void onPostExecute(String result) { 138 | TextView view = textViewReference.get(); 139 | if (view != null) { 140 | view.setText(result); 141 | } 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /as的个人使用记录.md: -------------------------------------------------------------------------------- 1 | 2 | >译文的GitHub地址:[低版本实现共享元素动画(二):格瓦拉动画](https://github.com/thinkSky1206/android-blog/blob/master/%E4%BD%8E%E7%89%88%E6%9C%AC%E5%AE%9E%E7%8E%B0%E5%85%B1%E4%BA%AB%E5%85%83%E7%B4%A0%E5%8A%A8%E7%94%BB%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E6%A0%BC%E7%93%A6%E6%8B%89%E5%8A%A8%E7%94%BB.md) 3 | 4 | >译者注: 5 | 6 | 7 | 8 | ![]() 9 | 10 | #基本设置和功能 11 | 12 | ###字体设置 13 | 14 | 1. 菜单字体设置:Appearrance & Beahavior->Appearance 15 | 2. 编辑器代码字体设置:Editor->Color & Fonts->Font 16 | 17 | ###Log颜色 18 | Editor->Color & Fonts->Android Logcat 19 | 20 | ###禁用Instant Run 21 | 22 | Build,Execution,Deployment->Instant Run->不勾选Enable Instant Run和Restart activity on code changes 23 | 24 | ###屏幕模式 25 | 26 | View->Enter Presentation Mode 演示模式 27 | 28 | View->Enter Distraction Free Mode 免打扰模式 29 | 30 | View->Enter Full Screen 全屏模式 31 | 32 | ###layout文件默认使用Text模式 33 | 34 | 每次新建一个布局都是打开Design编辑器,都要手动切换 很烦有没有? 35 | 36 | Text模式下 找到设置按钮->prefer xml editor 37 | 38 | ###重新运行自动清除log 39 | 40 | Run->Edit Configurations->app->Miscellaneous->勾选Clear log before lauch 41 | 42 | ###设置日志打印信息 43 | 44 | logcat->设置 45 | 46 | ###显示代码本地历史 47 | 48 | 选择代码段->右键Local History 49 | 50 | ###代码声明,版权设置 51 | 52 | 53 | #快捷键 54 | 55 | 搜索所有命令快捷键:ctrl+shift+a 56 | 57 | 预览字符串文本:ctrl+ - 58 | 59 | 格式化:ctrl+alt+L 60 | 61 | 文件导航:ctrl+tab ctrl+shift+tab 62 | 63 | 64 | 65 | 生成sett/gett :alt+insert 66 | 67 | 选择下一个和当前选中内容一样的 :alt+j 68 | 69 | 70 | 71 | 重构命名:shift+F6 72 | 73 | 新行: shift+enter 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /dagger2:组件依赖和子组件的区别.md: -------------------------------------------------------------------------------- 1 | >原文:[Component Dependency vs Submodules in Dagger 2](http://jellybeanssir.blogspot.my/2015/05/component-dependency-vs-submodules-in.html) 2 | 3 | >译文的GitHub地址:[dagger2:组件依赖和子组件的区别](https://github.com/thinkSky1206/android-blog/blob/master/dagger2%EF%BC%9A%E7%BB%84%E4%BB%B6%E4%BE%9D%E8%B5%96%E5%92%8C%E5%AD%90%E7%BB%84%E4%BB%B6%E7%9A%84%E5%8C%BA%E5%88%AB.md) 4 | 5 | >译者注:发现很多人都忽略了这个重要的内容,没有完全翻译原文,只简单翻译了重点。有兴趣的可以查看原文 6 | 7 | #Component Dependency(组件依赖) 8 | 9 | 假如,你有一个ApplicationComponent持有一个Module里面包含LocationService,Resources,LayoutInflater等等,它们都需要Context参数。 10 | 11 | 你可能也想有一个DataComponent用来管理持久化WebService拿到的数据。 12 | 13 | 可DataComponent唯一缺的就是ApplicationComponent里面的Context.这就是Component Dependecy发挥作用的地方。你只需要给DataComponent声明依赖Application Component然后你就可以拿到Context了。 14 | 15 | 记住,你必须要在ApplicationComponent显示声明Context 因为这是消费父组件对象的唯一方法。它所有modules里面的对象不能用于child module 16 | 17 | 例子: 18 | 19 | @Singleton 20 | @Component(modules = ApplicationModule.class) 21 | public interface ApplicationComponent { 22 | 23 | //显示声明 对子组件可见 24 | Application application(); 25 | } 26 | 27 | @PerActivity 28 | //声明依赖 29 | @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) 30 | public interface ActivityComponent { 31 | 32 | void inject(MainActivity mainActivity); 33 | 34 | } 35 | 36 | #Subcomponents(子组件) 37 | 38 | 和组件依赖最大的不同是当你接入父组件的时候,你可以访问它所有模块的所有对象而无需显示声明依赖父组件。 39 | 40 | 让我们假设你的LoginFragment需要一个组件:LoginComponent,它包含了一个用于处理业务逻辑的Presenter。Presenter想要调用WebService的方法获取数据,所以它需要依赖DataComponent里面的WebService. 41 | 42 | 这次你不用在LoginComponent中指定任何东西,但是在ApplicationComponent,你需要声明ApplicationComponent可以被LoginComponent继承如下: 43 | 44 | LoginComponent plus(LoginModule module); 45 | 46 | 然后你需要手动hook into ApplicationComponent来获取它所有的对象: 47 | 48 | getApplicationComponent().plus(new LoginModule(this)); 49 | 50 | 不要忘了把你的子组件@Component替换成@Subcomponent。 51 | 52 | 例子: 53 | 54 | @Singleton 55 | @Component(modules = AppModule.class) 56 | public interface AppComponent { 57 | 58 | //activy组件继承父组件 59 | ActivityComponent plus(ActivityModule module); 60 | 61 | } 62 | 63 | @ActivityScope 64 | //声明Subcomponent 65 | @Subcomponent(modules = ActivityModule.class) 66 | public interface ActivityComponent { 67 | 68 | void inject(MainActivity activity); 69 | 70 | void inject(SearchActivity activity); 71 | 72 | FragmentComponent plus(FragmentModule module); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /images/ItemDecoration_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/ItemDecoration_2.png -------------------------------------------------------------------------------- /images/ItemDecoration_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/ItemDecoration_3.png -------------------------------------------------------------------------------- /images/ItemDecoration_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/ItemDecoration_4.png -------------------------------------------------------------------------------- /images/ItemDecoration_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/ItemDecoration_5.png -------------------------------------------------------------------------------- /images/ItemDecoration_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/ItemDecoration_6.png -------------------------------------------------------------------------------- /images/ItemDecoration_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/ItemDecoration_7.png -------------------------------------------------------------------------------- /images/drawable_bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_bitmap.png -------------------------------------------------------------------------------- /images/drawable_clip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_clip.png -------------------------------------------------------------------------------- /images/drawable_inset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_inset.png -------------------------------------------------------------------------------- /images/drawable_layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_layer.png -------------------------------------------------------------------------------- /images/drawable_level.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_level.png -------------------------------------------------------------------------------- /images/drawable_mix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_mix.png -------------------------------------------------------------------------------- /images/drawable_nine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_nine.png -------------------------------------------------------------------------------- /images/drawable_scale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_scale.png -------------------------------------------------------------------------------- /images/drawable_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_shape.png -------------------------------------------------------------------------------- /images/drawable_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_state.png -------------------------------------------------------------------------------- /images/drawable_transition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_transition.png -------------------------------------------------------------------------------- /images/drawable_transition2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/drawable_transition2.png -------------------------------------------------------------------------------- /images/format01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/format01.png -------------------------------------------------------------------------------- /images/format02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/format02.png -------------------------------------------------------------------------------- /images/format03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/format03.png -------------------------------------------------------------------------------- /images/format04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/format04.png -------------------------------------------------------------------------------- /images/format05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/format05.png -------------------------------------------------------------------------------- /images/memory_leaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/memory_leaks.png -------------------------------------------------------------------------------- /images/plu01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/plu01.png -------------------------------------------------------------------------------- /images/plu02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/plu02.png -------------------------------------------------------------------------------- /images/plu03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/plu03.png -------------------------------------------------------------------------------- /images/plu04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/plu04.png -------------------------------------------------------------------------------- /images/reuse01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/reuse01.png -------------------------------------------------------------------------------- /images/reuse02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/reuse02.png -------------------------------------------------------------------------------- /images/reuse03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/reuse03.png -------------------------------------------------------------------------------- /images/reuse04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/reuse04.png -------------------------------------------------------------------------------- /images/sep01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/sep01.png -------------------------------------------------------------------------------- /images/share.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/share.gif -------------------------------------------------------------------------------- /images/share_gwl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/share_gwl.gif -------------------------------------------------------------------------------- /images/stickyheader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/stickyheader.gif -------------------------------------------------------------------------------- /images/wh01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/wh01.png -------------------------------------------------------------------------------- /images/wh02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/wh02.png -------------------------------------------------------------------------------- /images/xml_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3citizen/android-blog/633978c2dc3cf78f4cf502a677fab4b11088755c/images/xml_name.png -------------------------------------------------------------------------------- /todo/todo.txt: -------------------------------------------------------------------------------- 1 | 待完成的文章 -------------------------------------------------------------------------------- /一种成功的xml资源命名规范.md: -------------------------------------------------------------------------------- 1 | >原文:[A successful XML naming convention](http://jeroenmols.com/blog/2016/03/07/resourcenaming/) 2 | 3 | >译文的GitHub地址:[一种成功的xml资源命名规范](https://github.com/thinkSky1206/android-blog/blob/master/%E4%B8%80%E7%A7%8D%E6%88%90%E5%8A%9F%E7%9A%84xml%E8%B5%84%E6%BA%90%E5%91%BD%E5%90%8D%E8%A7%84%E8%8C%83.md) 4 | 5 | >译者注:命名是一个项目的小细节,也是一个项目的基石。虽然我们懂得了各种命名规范,但是我必须承认最后我每次都失败了,实践到最后就会发现做好真的很难,除了疯涨的资源文件,还涉及到不同的团队成员,就算同一个控件,有可能每个人都能给上不同的名字。这篇文章给了一个不错的模式,可以结合自己的情况进行改造,结合之前那篇项目结构的文章会有不错的效果。命名规范重要,但是每个人做到遵循更为重要。 6 | 7 | 8 | ![xml](http://jeroenmols.com/img/blog/resourcenaming/resourcenaming.png) 9 | 10 | 你还记得最后一次你钻进去strings.xml里面查找你正在使用的字符,或手动浏览所有drawables找到你要的那个是什么时候吗? 11 | 12 | 当我们开始一个新项目时,我们总是花费很多时间在架构,CI(持续集成),build flavors...上面,但是对于资源命名你准备好了策略么? 13 | 14 | 你应该准备!因为缺乏xml命名空间,让管理android资源非常麻烦,特别是在大项目,容易失控。 15 | 16 | 17 | 所以让我们引入一个简单的模式来解决你的痛苦。 18 | 19 | - 容易查找任何资源(借助ide的自动完成) 20 | - 逻辑,可预测的名字 21 | - 资源清晰有序 22 | - 强类型资源 23 | 24 | 25 | 这篇文章会解释它的机制,它的优点,局限性,最后会提供一个简单易用的速查表。 26 | 27 | #基本原理 28 | 所有资源命名都遵循一个简单的规则 29 | 30 | ![basic](http://jeroenmols.com/img/blog/resourcenaming/whatwheredescriptionsize.jpg) 31 | 32 | 33 | 让我们先来描述每一个元素,再看完了优点之后,我们将会看到怎么把它应用到每一种资源类型。 34 | 35 | ###WHAT(是什么) 36 | 表明资源实际代表什么,通常是一个标准的android view,资源类型选项有限。 37 | 38 | (例如:MainActivity->activity) 39 | 40 | ###WHERE(在何处) 41 | 描述它在app的逻辑模块,如果在多个页面用到使用*all*,其他的就用 使用该资源的页面所处的逻辑模块 42 | 43 | (例如:MainActiviy->main,ArticleDetailFragment->articledetail) 44 | ###DESCRIPTION(描述) 45 | 用来区分一个页面中多个相同元素 46 | 47 | (例如:title) 48 | ###SIZE(大小)可选 49 | 50 | 一个精确的大小或尺寸,可用于drawables和dimensions 51 | 52 | (例如:24dp,small) 53 | 54 | ![cheat-sheet](https://github.com/thinkSky1206/android-blog/blob/master/images/xml_name.png) 55 | 56 | 下载和打印这个[速查表](http://jeroenmols.com/img/blog/resourcenaming/resourcenaming_cheatsheet.pdf)方便参考 57 | 58 | #优点 59 | 60 | **1.资源根据页面排序** 61 | 62 | where部分描述了一个资源属于哪个页面,所以对于一个特定的页面,可以获取到它的所有ids,drawables,dimensions...等等 63 | >译者注:例如main页面,你只要输入main就可以找到所有的资源了。 64 | 65 | **2. 强类型资源ids** 66 | 67 | 对于资源的ids,what描述了xml元素所属的类名。这样你调用findViewById()的时候知道转换成什么。 68 | >译者注:textview_main这个id你马上就知道它的控件class是TextView了 69 | 70 | **3. 更好的资源组织** 71 | 72 | 文件浏览器或项目导航通常把文件安装字母排序,这意味的 73 | layouts和drawables分别按照他们的what(activity,fragment)和where进行分组,现在Android studio本身和一些插件已经支持了。 74 | 75 | **4. 更有效的自动补全** 76 | 77 | 因为资源名称足够预测,所以用IDE的自动补全变得更加简单,通常只需要输入what或者where就可以缩小可选项。 78 | 79 | **5. 减少名字冲突** 80 | 81 | 不同页面用到的相同的资源用all,其他的用各自的where,固定的命名模式避免了所有的命名冲突 82 | 83 | **6. 清晰的资源名称** 84 | 85 | 整体上把所有的资源命名更加逻辑,让整个项目非常清晰。 86 | 87 | **7. 工具支持** 88 | 89 | 这个命名模式可以轻松被Android Studio提供的一些功能支持,例如:强制执行lint规则,支持当你改变what或where进行重构时,在project view更好的资源可视化。。。 90 | 91 | 92 | #layouts 93 | Layouts相对简单,通常一个页面只有几个layouts,所以规则可以简化为: 94 | ![layouts](http://jeroenmols.com/img/blog/resourcenaming/layouts.png) 95 | what是下面表格中的一种 96 | 97 | 前缀 | 用法 98 | ---------|------ 99 | activity | activity的内容视图 100 | fragment | fragment的view 101 | view | 用于inflate的自定义View 102 | item | 用于list/recycle/gridview的item布局 103 | layout | 用于include标签使用的布局 104 | 105 | 例子: 106 | 107 | - activiy_main:MainActivity的content view 108 | - fragment_articledetail:ArticleDetailFragment的view 109 | - view_menu:在自定义MenuView类中被inflated的布局 110 | - layout_actionbar_backbutton:一个包含返回按钮的actionbar工具栏布局(过于简单没有必要封装成一个自定义view,直接include) 111 | 112 | #strings 113 | Strings的what部分不重要,没有什么用处的(译者注:因为string.xml已经固定了它的what,drawables是一样的道理),所以我们要么用**where**来指明这个字符在只在某个页面被用到: 114 | ![strings_where](http://jeroenmols.com/img/blog/resourcenaming/strings.png) 115 | 要么用**all**表示在app中多个地方会重用: 116 | ![strings_all](http://jeroenmols.com/img/blog/resourcenaming/strings2.png) 117 | 例子: 118 | 119 | - articledetail_title:ArticleDetailFragment页面的标题 120 | - feedback_explanation:FeedbackFragment反馈页面的说明 121 | - feedback_namehint:FeedbackFragment页面的name输入框的提示语 122 | - all_done:通用的“done”字符 123 | 124 | 很明显在同一个view里面所有资源的where是一样的。 125 | 126 | #drawables 127 | 128 | drawables的what部分也是没用的,所以我们也是要么用where指明在哪个页面用到: 129 | ![drawables_where](http://jeroenmols.com/img/blog/resourcenaming/drawables.png) 130 | 要么用all表示在app中重用的: 131 | ![drawables_all](http://jeroenmols.com/img/blog/resourcenaming/drawables2.png) 132 | 你也可以添加可选的size部分,可以用来表示实际大小24dp或尺寸small。 133 | 134 | 例子: 135 | 136 | - articledetail_placeholder:ArticleDetailFragment的占位图 137 | - all_infoicon:通用info图标 138 | - all_infoicon_large:通用info图标的大图 139 | - all_infoicon_24dp:24dp大小的通用info图标 140 | 141 | 142 | #ids 143 | 对于ids,what是xml元素所属类名。第二个部分是id所在的页面,紧接后面的是一个可选的描述用来区分一个页面的多个相同元素。 144 | ![ids](http://jeroenmols.com/img/blog/resourcenaming/ids.png) 145 | 例子: 146 | 147 | - tablayout_main:MainActivity里面的TabLayout控件 148 | - imageview_menu_profile:自定义MenuView中的用户ImageView 149 | - textview_articledetail_title:ArticleDetailFragment中的标题TextView 150 | 151 | >译者注:这里的what可以自己换一下,改成控件类名的首字母 例如:textview->tv会简洁一点,不然有时候会真的很长。 152 | 153 | #dimensions 154 | app应该只定义一组有限的diemens,它们必须要被不断重复使用。这样大多数deimensions默认就是all 155 | 156 | 所以你应该多用: 157 | ![dimens_all](http://jeroenmols.com/img/blog/resourcenaming/dimensions2.png) 158 | 当然特定页面也可以使用: 159 | ![demens_spec](http://jeroenmols.com/img/blog/resourcenaming/dimensions.png) 160 | 161 | 这里的what是下面表格中的一种: 162 | 163 | 前缀 | 用法 164 | ---------|------ 165 | width | 宽度 166 | height | 高度 167 | size | 宽度=高度 168 | margin | margin外边距 169 | padding | padding内边距 170 | elevation| 角度 171 | keyline | 边缘对齐位置 172 | textsize | 文本大小的sp 173 | 174 | 注意上面的列表包含了大部分会用到的what,还有一些dimension限定符类似:rotation,scale...通常只是在drawables被用到而且不能重用。 175 | 176 | 例子: 177 | >译者注:all不用声明,所以默认是all 178 | 179 | - height_toolbar:所有toolbar的高度 180 | - keyline_listtext:listitem的文本边缘对齐 181 | - textsize_medium:所有文本的中等大小 182 | - size_menu_icon:menu里面图标size 183 | - height_menu_profileimage:menu的头像图片高度 184 | 185 | 186 | 187 | 188 | 189 | 190 | #需要知道的限制 191 | **1. 页面命名必须唯一** 192 | 193 | 为了避免where冲突,View类必须有一个唯一的名字,例如你不能有"MainActivity"和"MainFragment",因为这个"main"前缀就不是一个唯一的where了 194 | >译者:个人觉得这个问题有一个小方法可以解决, 195 | 如果你的MainFragment比MainActivity的id多的话,MainActivity->maina,MainMainFragment->main,反之MainActivity->main,MainMainFragment->mainf 196 | 197 | **2. 不支持重构** 198 | 199 | 当进行重构时改变class名不会改变相关的资源名,所以如果你把"MainActivity"重命名成"ContentActivity"的时候,layout文件"activity_main"不会重命名成"activity_content",希望Android Studio有一天会支持。 200 | 201 | **3. 不是所有的资源类型都支持** 202 | 203 | 该方案当前还没有支持所有的资源类型。有些资源因为很少用(例如:raw和asset),还有一些资源因为他们很难组织(例如:themes/styles/colors/animations)(译者注:color和animation都是偏向使用术语来进行命名,而theme和style已经有了命名规则,并且允许你隐性继承属性) 204 | 205 | 206 | 207 | 208 | #总结 209 | 好了!一个清晰简单和易于使用的资源命名模式,不要忘了下载[速查表](http://jeroenmols.com/img/blog/resourcenaming/resourcenaming_cheatsheet.pdf)方便参考哦! 210 | 211 | 尽管这个模式没法覆盖所有的资源类型,但是它确实给当前的命名之痛提供了一个易于使用的解决方法。也许在以后,我会给其他资源类型提供一些建议。 212 | 213 | 上twitter联系@molsjeroen告诉我你的想法,或者在下面留言! 214 | -------------------------------------------------------------------------------- /低版本实现共享元素动画.md: -------------------------------------------------------------------------------- 1 | 2 | >译文的GitHub地址:[低版本实现共享元素动画](https://github.com/thinkSky1206/android-blog/blob/master/%E4%BD%8E%E7%89%88%E6%9C%AC%E5%AE%9E%E7%8E%B0%E5%85%B1%E4%BA%AB%E5%85%83%E7%B4%A0%E5%8A%A8%E7%94%BB.md) 3 | 4 | 5 | 6 | ![Share ElementTransition](https://github.com/thinkSky1206/android-blog/blob/master/images/share.gif) 7 | 8 | 共享元素过渡动画是material设计重要的一部分,activity切换动画是一个让用户记住我们app的好方法。在切换动画中,共享元素动画可能是最常用的,在淘宝,各种外卖app都能看到这种动画(ios)。 9 | 10 | 11 | 共享元素动画在Lollipop+很容易实现,但是如果你想应用在老版本上,有点麻烦,但也没那么可怕,下面我会告诉你怎么实现它。 12 | 13 | #主要步骤 14 | 15 | 简单起见,我们的例子是从Activity A->Activity B并且他们共享一个元素(图片内容)。 16 | 17 | Activity A是一个RecycleView列表页面,点击会item会跳转到B页面 18 | 19 | Activity B是一个只显示图片的页面 20 | 21 | 下面是几个重要的步骤。 22 | 23 | 1. Activity A捕捉共享元素的初始值(位于屏幕的坐标,view的宽高),然后通过Intent传递给Activity B 24 | 2. Activity B初始设成全透明 25 | 3. Activity B读取传递过来的值并准备动画场景 26 | 4. Activity B开始执行共享元素动画 27 | 28 | 下面我会详解这些步骤并给出示范代码。 29 | 30 | 首先,我们把Activity A的共享view称为origin view,Activity B的共享view叫destination view,记住:这两个view 虽然它们被称为共享,但它们实际上是两个独立的视图对象,只不过内容相同。 31 | 32 | 让我们开始吧。 33 | 34 | #1.Activity A 捕捉和传递初始值 35 | 36 | 在RecycleView的点击事件相应中,我们获取图片ImageView的相关信息,并传递给B 37 | 38 | 39 | itemClickListener = new MainAdapter.RecycleItemClickListener() { 40 | @Override 41 | public void onItemClick(View view, int position) { 42 | Intent intent = new Intent(); 43 | intent.setClass(A.this, B.class); 44 | intent.putExtra(VIEW_INFO_EXTRA, createViewInfoBundle(view)); 45 | startActivity(intent); 46 | overridePendingTransition(0, 0); 47 | } 48 | }; 49 | 50 | 51 | 52 | 要记住调用overridePendingTransition(0, 0)禁用默认的过渡动画。 53 | 54 | 55 | 方法createViewInfoBundle主要返回view的位置和宽高 56 | 57 | 58 | private Bundle createViewInfoBundle(View view) { 59 | int[] screenLocation = new int[2]; 60 | view.getLocationOnScreen(screenLocation); 61 | Bundle b = new Bundle(); 62 | int left = screenLocation[0]; 63 | int top = screenLocation[1]; 64 | int width = view.getWidth(); 65 | int height = view.getHeight(); 66 | b.putInt("left", left); 67 | b.putInt("top", top); 68 | b.putInt("width", width); 69 | b.putInt("height", height); 70 | return b; 71 | } 72 | 73 | #2.Activity B 背景设成透明 74 | 75 | B页面的布局很简单,ImageView默认隐藏 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | Activity B背景设成透明 95 | 96 | 100 | 101 | 102 | 103 | #3.Activity B 接收信息并准备动画场景 104 | 105 | @Override 106 | protected void onCreate(Bundle savedInstanceState) { 107 | super.onCreate(savedInstanceState); 108 | setContentView(R.layout.activity_article_image); 109 | ... 110 | destinationView = findViewById(R.id.iv_detail); 111 | containerView = (LinearLayout) findViewById(R.id.container_detail); 112 | // 取出传递过来的originView信息 113 | extractViewInfoFromBundle(getIntent()); 114 | //简单起见,我们直接设置本地图片 不使用网络加载图片 115 | destinationView.setBackgroundDrawable(getResources().getDrawable(R.drawable.ic)); 116 | onUiReady(); 117 | ... 118 | } 119 | 120 | 121 | private void onUiReady() { 122 | destinationView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 123 | @Override 124 | public boolean onPreDraw() { 125 | // remove previous listener 126 | destinationView.getViewTreeObserver().removeOnPreDrawListener(this); 127 | //准备场景 128 | prepareScene(); 129 | //播放动画 130 | runEnterAnimation(); 131 | return true; 132 | } 133 | }); 134 | } 135 | 136 | private void prepareScene() { 137 | int[] screenLocation = new int[2]; 138 | destinationView.getLocationOnScreen(screenLocation); 139 | //移动到起始view位置 140 | deltaX = originViewLeft - screenLocation[0]; 141 | deltaY = originViewTop - screenLocation[1]; 142 | destinationView.setTranslationX(deltaX); 143 | destinationView.setTranslationY(deltaY); 144 | //缩放到起始view大小 145 | scaleX = (float) originViewWidth / destinationView.getWidth(); 146 | scaleY = (float) originViewHeight / destinationView.getHeight(); 147 | destinationView.setScaleX(scaleX); 148 | destinationView.setScaleY(scaleY); 149 | } 150 | 151 | 152 | 153 | 154 | #4.Activity B 执行动画 155 | 156 | 157 | private void runEnterAnimation() { 158 | destinationView.setVisibility(View.VISIBLE); 159 | //获取图片的颜色,设置背景色 160 | Bitmap bitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.ic)).getBitmap(); 161 | Palette p = Palette.generate(bitmap); 162 | Palette.Swatch swatch = p.getDarkVibrantSwatch(); 163 | containerView.setBackgroundColor(swatch.getRgb()); 164 | //执行动画 165 | destinationView.animate() 166 | .setDuration(DEFAULT_DURATION) 167 | .setInterpolator(DEFAULT_INTERPOLATOR) 168 | .scaleX(1f) 169 | .scaleY(1f) 170 | .translationX(0) 171 | .translationY(0) 172 | .start(); 173 | 174 | } 175 | 176 | 别忘了点击返回键的退出动画 177 | 178 | 179 | @Override 180 | public void onBackPressed() { 181 | runExitAnimation(); 182 | } 183 | 184 | 185 | 186 | private void runExitAnimation() { 187 | destinationView.animate() 188 | .setDuration(DEFAULT_DURATION) 189 | .setInterpolator(DEFAULT_INTERPOLATOR) 190 | .scaleX(scaleX) 191 | .scaleY(scaleY) 192 | .translationX(deltaX) 193 | .translationY(deltaY) 194 | .withEndAction(new Runnable() { 195 | @Override 196 | public void run() { 197 | finish(); 198 | overridePendingTransition(0, 0); 199 | } 200 | }).start(); 201 | } 202 | 203 | 204 | 好了,完事 205 | 206 | 但是如果应用在产品上的话,你看淘宝或其他app,就会发现要A和B的图片宽高比一定是相同,不然动画是不对的,我这里没有处理。 207 | 208 | **相关参考** 209 | 210 | [Android Shared-Element Transitions for all](https://medium.com/@aitorvs/android-shared-element-transitions-for-all-b90e9361507d#.sk2z0p1hh) 211 | 212 | [Android — Smooth shared transitions in all android versions](https://medium.com/@Sserra90/android-smooth-shared-transitions-in-all-android-versions-9cd27fd3c80f#.28my4o4qk) 213 | 214 | -------------------------------------------------------------------------------- /低版本实现共享元素动画(二):格瓦拉动画.md: -------------------------------------------------------------------------------- 1 | 2 | >译文的GitHub地址:[低版本实现共享元素动画(二):格瓦拉动画](https://github.com/thinkSky1206/android-blog/blob/master/%E4%BD%8E%E7%89%88%E6%9C%AC%E5%AE%9E%E7%8E%B0%E5%85%B1%E4%BA%AB%E5%85%83%E7%B4%A0%E5%8A%A8%E7%94%BB%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E6%A0%BC%E7%93%A6%E6%8B%89%E5%8A%A8%E7%94%BB.md) 3 | 4 | >译者注:好久没折腾动画了,一开始就停不下来了 5 | 6 | 7 | 8 | ![Share ElementTransition](https://github.com/thinkSky1206/android-blog/blob/master/images/share_gwl.gif) 9 | 10 | 刚写完[低版本实现共享元素动画](http://www.jianshu.com/p/b0c1aec41900),ios的同事丢过来手机告诉我格瓦拉app的一个动画很好看,我一看还真不错,就问android版本的实现呢,打开他的香槟金小米5以为是和ios一样的实现,可惜只实现了一半,盯得动画看了一会,发现有点像是android material设计动画,然后他又给我看了好几个app的动画,一看我就发现了,ios好多app动画都是android material设计里面的,而且不少是material的错误示范,哈哈 11 | 12 | 像格瓦拉app这个动画,看过google的material设计,会很熟悉,但是这个动画 用户等待内容的时间太长了,体验并不是那么的好,google不推荐。先不讨论对不对了,我们来简单实现一下吧 13 | 14 | 15 | #主要步骤 16 | 17 | 主要流程其实和上一篇动画差不多,只不过B页面多了一个Reveal动画,这个动画我们用一个三方库来实现 18 | 19 | compile ('com.github.ozodrukh:CircularReveal:2.0.1@aar') { 20 | transitive = true; 21 | } 22 | 23 | 我们来分析一下B页面,可以看到有好几层,在android中这个可以用RelativeLayout来实现 24 | 25 | ![Activity B](http://upload-images.jianshu.io/upload_images/186157-db599f5515929f61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 26 | 27 | 28 | 29 | 33 | 34 | 35 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 62 | 63 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 81 | 82 | 83 | 84 | 85 | 动画的流程是 86 | 87 | 1. layer 2默认不可见 88 | 2. layer 3 共享元素移动 89 | 3. layer 2 开始reveal动画 90 | 91 | 也就是在上一篇的基础上再加个动画 92 | 93 | private void runEnterAnimation() { 94 | destinationView.setVisibility(View.VISIBLE); 95 | destinationView.animate() 96 | .setDuration(DEFAULT_DURATION) 97 | .setInterpolator(DEFAULT_INTERPOLATOR) 98 | .scaleX(1f) 99 | .scaleY(1f) 100 | .translationX(0) 101 | .translationY(0) 102 | .setListener(new AnimatorListenerAdapter() { 103 | @Override 104 | public void onAnimationEnd(Animator animation) { 105 | revealOn();//添加reval动画 106 | } 107 | }) 108 | .start(); 109 | 110 | } 111 | 112 | 113 | private void revealOn() { 114 | int cx = destinationView.getRight() - destinationView.getWidth() / 2; 115 | int cy = destinationView.getTop() + destinationView.getHeight() / 2; 116 | float radius = (float) Math.hypot(containerView.getWidth(), containerView.getHeight()); 117 | Animator animator = ViewAnimationUtils.createCircularReveal(containerView, cx, cy, 0, radius); 118 | animator.setInterpolator(DEFAULT_INTERPOLATOR); 119 | animator.setDuration(900); 120 | animator.addListener(new AnimatorListenerAdapter() { 121 | @Override 122 | public void onAnimationStart(Animator animation) { 123 | containerView.setVisibility(View.VISIBLE); 124 | } 125 | }); 126 | animator.start(); 127 | } 128 | 129 | 130 | 退出的话 以进入动画相反顺序执行 131 | 132 | @Override 133 | public void onBackPressed() { 134 | revealOff(); 135 | } 136 | 137 | public void revealOff() { 138 | int cx = destinationView.getRight() - destinationView.getWidth() / 2; 139 | int cy = destinationView.getTop() + destinationView.getHeight() / 2; 140 | float radius = (float) Math.hypot(containerView.getWidth(), containerView.getHeight()); 141 | Animator animator = ViewAnimationUtils.createCircularReveal(containerView, cx, cy, radius, 0); 142 | animator.setInterpolator(DEFAULT_INTERPOLATOR); 143 | animator.setDuration(900); 144 | animator.addListener(new AnimatorListenerAdapter() { 145 | @Override 146 | public void onAnimationEnd(Animator animation) { 147 | containerView.setVisibility(View.INVISIBLE); 148 | runExitAnimation(); 149 | } 150 | }); 151 | animator.start(); 152 | } 153 | 154 | 155 | private void runExitAnimation() { 156 | destinationView.animate() 157 | .setDuration(DEFAULT_DURATION) 158 | .setInterpolator(DEFAULT_INTERPOLATOR) 159 | .scaleX(scaleX) 160 | .scaleY(scaleY) 161 | .translationX(deltaX) 162 | .translationY(deltaY) 163 | /* .setListener(new AnimatorListenerAdapter() { 164 | @Override 165 | public void onAnimationEnd(Animator animation) { 166 | bgView.setVisibility(View.INVISIBLE); 167 | finish(); 168 | overridePendingTransition(0, R.anim.fade_exit); 169 | } 170 | })*/ 171 | .withEndAction(new Runnable() { 172 | @Override 173 | public void run() { 174 | bgView.setVisibility(View.INVISIBLE); 175 | finish(); 176 | overridePendingTransition(0, R.anim.fade_exit); 177 | } 178 | }) 179 | .start(); 180 | } 181 | 182 | 183 | 184 | 最后效果会发现 185 | 186 | 1. activity b退出的时候效果还是不理想 这可能也是格瓦拉android不实现退出动画的原因,我觉得这个真想实现的话,通过alpha退出是可以实现和ios一样效果的 187 | 2. 动画差值器尤其重要,上面退出动画其实控制好也可以实现 我全部用的AccelerateDecelerateInterpolator 效果很生硬 188 | 3. RecycleView Item里面获取在屏幕位置好像会有偏差 189 | 190 | 当然只是粗糙简单实现一下,细节没有去深究,效果比原版差不少,重要的是看到别人的动画时,培养如何思考的这个过程。 191 | -------------------------------------------------------------------------------- /使用Gradle构建多个不同applicationId包.md: -------------------------------------------------------------------------------- 1 | 最近和Gradle打交道的时间挺多的,很多在构建打包过程中的不少奇奇怪怪的需求都用gradle解决了,给开发过程节省了不少时间,也让我对gradle刮目相看啊,看来得找时间得好好深入了解下。 2 | 3 | 好了,废话不多说了,看下我们的需求。 4 | 5 | app是针对海外开发的,所以在开发的时候一般都是基于本地后台服务接口开发,到了某个时间或某个版本的时候,再把后台服务接口更换成海外服务接口打包给本地测试同事或海外测试同事进行安装测试。本来这也没太问题,你要哪个版本测试我就给你改下服务接口地址重新打个包就好了。 6 | 7 | 假设下面是我们的目前的代码,想要哪个地址就改下API_URL重新打个包就ok了 8 | 9 | //public static final String API_URL = "http://www.google.com";//美国us 10 | //public static final String API_URL = "http://www.nanfei.com";//南非za 11 | public static final String API_URL = "http://www.baidu.com";//本地local 12 | 13 | Retrofit retrofit = new Retrofit.Builder() 14 | .baseUrl(API_URL) 15 | .client(client) 16 | .addConverterFactory(GsonConverterFactory.create()) 17 | .build(); 18 | 19 | 20 | 可是有一天,测试同事跑来说,每次测试不同版本只能安装一个app(applicationId是唯一的,会进行覆盖),他想在同一台手机上安装多个app,app之间的区别只是它们的后台服务接口地址API_URL不同,当然最好app的桌面名字能区分出来是哪个服务接口地址,这样测试就方便了 21 | 22 | 如何实现呢,同时打多个包肯定会想到用productFlavors,同时API_URL和app\_name需要动态改变。 23 | 24 | ##1.移除strings的app_name 25 | 由于app\_name是动态的所以肯定不能写死了,把它删掉 26 | 27 | 28 | 29 | 30 | ##2.设置build.gradle的productFlavors 31 | 32 | productFlavors { 33 | local { 34 | applicationId "com.lwp.app" 35 | buildConfigField 'String', 'API_URL', '"http://www.baidu.com"' 36 | resValue "string", "app_name", "app" 37 | } 38 | us { 39 | applicationId "com.lwp.app.us" 40 | buildConfigField 'String', 'API_URL', '"http://www.google.com"' 41 | resValue "string", "app_name", "app_us" 42 | 43 | } 44 | za { 45 | applicationId "com.lwp.app.za" 46 | buildConfigField 'String', 'API_URL', '"http://www.nanfei.com"' 47 | resValue "string", "app_name", "app_za" 48 | } 49 | } 50 | 51 | 上面我们分别设置三个版本各自的applicationId,API_URL,app\_name 52 | 53 | ##3.代码中引用动态API_URL 54 | 我们设置了不同版本的对应API\_URL,代码肯定是要用它的,build完毕后。 55 | 56 | 直接可以用**BuildConfig.API\_URL**,这个是动态的,不同版本会自动生成不同的值。 57 | 58 | Retrofit retrofit = new Retrofit.Builder() 59 | .baseUrl(BuildConfig.API_URL) 60 | .client(client) 61 | .addConverterFactory(GsonConverterFactory.create()) 62 | .build(); 63 | 64 | 65 | ##4.执行gradlew assembleDebug 66 | 67 | 执行命令后会生成类似 68 | 69 | app_local_debug.apk 70 | app_us_debug.apk 71 | app_za_debug.apk 72 | 73 | 安装完成后,桌面会显示app,app\_us,app\_za三个图标一样的app 74 | 75 | 他们功能完全一样,只是后台服务接口地址API\_URL不一样。 76 | 77 | 这样就可以愉快的同时进行三个不同版本app测试同时互相比对数据的正确性了。 -------------------------------------------------------------------------------- /使用Gradle管理Debug和Release版本的Key.md: -------------------------------------------------------------------------------- 1 |   在开发过程中经常会遇到debug/release版本中某个值需要动态改变方便开发和测试,就像BuildConfig的DEBUG一样,在debug版本中为true,release版本中为false,这样不用我们手动每次去修改,在开发过程中还是比较方便的。 2 | 3 |  最近的工作中由于使用到了百度地图SDK,使用过百度地图SDK的人可能知道百度给我的Key是根据我们的秘钥sha1和包名生成的,所以这样就产生了一个问题,当我们打包debug和release包时需要不同的key,或者使用gradle productFlavors修改applicationId同时打多个不同版本的包时,每个版本包的key是不同的,这就需要我们动态设置key 4 | 5 | 所以我们的需求是 6 | 7 | debug:key=1234 8 | release:key=56789 9 | 10 | 这样我们就不用每次修改Key再去打包了,那如何实现呢? 11 | 12 | ##1.在AndroidManifest.xml设置占位 13 | 16 | 17 | ${baiduMapKey} baiduMapKey这个值是动态的,需要我们设置 18 | 19 | ##2.在build.gradle中设置Debug/Release的baiduMapKey 20 | 21 | 22 | buildTypes { 23 | debug { 24 | manifestPlaceholders = [baiduMapKey: "1234"] 25 | } 26 | 27 | release { 28 | manifestPlaceholders = [baiduMapKey: "5678"] 29 | 30 | } 31 | } 32 | 33 | ##3.执行gradlew assembleDebug/assembleRelease 34 | 这个时候的debug版本key=1234 35 | release版本key=5678 36 | 37 | ####就这么简单的完事了,是不是很简单方便 -------------------------------------------------------------------------------- /使用retrofit+okhttp实现无缝网络状态监测.md: -------------------------------------------------------------------------------- 1 | >原文:[SEAMLESS NETWORK STATE MONITORING WITH RETROFIT + OKHTTP](http://blog.stablekernel.com/seamless-network-state-monitoring-with-retrofit-okhttp?utm_campaign=stable%7Ckernel+Developer+Blog+&utm_source=hs_email&utm_medium=email&utm_content=34158359&_hsenc=p2ANqtz-93ZBGz1S-0ajswYTtZ7-00zxDWnSw-juNh8FebmXWTnvf4CxLxAM4AuQpjSN7b47_8Glc4F4JxY1Q6C0J0Xb_n0mff-g&_hsmi=34158359) 2 | 3 | >译文的GitHub地址:[使用retrofit+okhttp实现无缝网络状态监测](https://github.com/thinkSky1206/android-blog/blob/master/%E4%BD%BF%E7%94%A8retrofit%2Bokhttp%E5%AE%9E%E7%8E%B0%E6%97%A0%E7%BC%9D%E7%BD%91%E7%BB%9C%E7%8A%B6%E6%80%81%E7%9B%91%E6%B5%8B.md) 4 | 5 | >译者注:我得去补补Application Interceptor和Network Interceptor 6 | 7 | 8 | 在依赖web服务的android app中,我们通常要在发送web请求前检查网络的状态。这样我们就可以弹出提示告诉用户问题而不是等待网络请求超时。网络状态通常需要调用ConnectivityManager。然而每个请求都要这样做有点繁琐,现在最流行的网络请求解决方案是使用Retrofit。当使用Retrofit时处理异步结果最简单的方式是用RxJava Observables。如果你正在使用Retrofit和RxJava 那么就有一种简单的方式来实现监测网络连接状态。让我们来看看吧! 9 | 10 | 首先我们创建一个监测网络接口,这样我们就可以使用依赖注入在测试的时候进行切换: 11 | 12 | 13 | public interface NetworkMonitor { 14 | boolean isConnected(); 15 | } 16 | 17 | 18 | 然后实现这个接口并调用系统的ConnectivityManager。 19 | 20 | 21 | public class LiveNetworkMonitor implements NetworkMonitor { 22 | 23 | private final Context applicationContext; 24 | 25 | public LiveNetworkMonitor(Context context) { 26 | applicationContext = context.getApplicationContext(); 27 | } 28 | 29 | public boolean isConnected() { 30 | ConnectivityManager cm = 31 | (ConnectivityManager) applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); 32 | 33 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 34 | return activeNetwork != null && 35 | activeNetwork.isConnectedOrConnecting(); 36 | } 37 | } 38 | 39 | 假设你已经配置好了Retrofit,在我们这个例子中我们打算用github api获取public events,如下: 40 | 41 | 42 | public interface GithubWebService { 43 | 44 | @GET("events") 45 | Observable> getPublicEvents(); 46 | } 47 | 48 | 然后在我们的activity中调用这个请求接口: 49 | 50 | 51 | public class MainActivity extends AppCompatActivity { 52 | 53 | 54 | protected GithubWebService githubWebService = ...; 55 | 56 | @Override 57 | protected void onCreate(Bundle savedInstanceState) { 58 | super.onCreate(savedInstanceState); 59 | setContentView(R.layout.activity_main); 60 | 61 | githubWebService.getPublicEvents() 62 | .observeOn(AndroidSchedulers.mainThread()) 63 | .subscribe(events -> { 64 | // TODO onNext 65 | }, throwable -> { 66 | // TODO onError 67 | }); 68 | } 69 | } 70 | 71 | 额 那我们在哪里调用这个方法NetworkMonitor.isConnected() 才能实现每次网络请求进行无缝网络检查呢?让我们看一下创建Retrofit对象的dagger module代码: 72 | 73 | @Provides 74 | @Singleton 75 | GithubWebService provideWebService() { 76 | 77 | String baseUrl = "https://api.github.com"; 78 | 79 | Retrofit.Builder builder = new Retrofit.Builder() 80 | .baseUrl(baseUrl) 81 | .addConverterFactory(GsonConverterFactory.create()) 82 | .addCallAdapterFactory(RxJavaCallAdapterFactory 83 | .createWithScheduler(Schedulers.io())); 84 | 85 | OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder(); 86 | 87 | return builder.client(okHttpClientBuilder.build()) 88 | .build() 89 | .create(GithubWebService.class); 90 | } 91 | 92 | 为了监测网络状态我们将使用OkHttp Interceptor! 拦截器可以让我们拦截网络请求并在把请求交给okhttp管道前做一些我们自己工作,我们可以监测网络是否可连接然后决定继续请求还是抛出一个自定义RunTimeError异常。我们更新一下代码 provide方法参数并添加一个拦截器: 93 | 94 | @Provides 95 | @Singleton 96 | GithubWebService provideWebService(NetworkMonitor networkMonitor) { 97 | 98 | String baseUrl = "https://api.github.com"; 99 | 100 | Retrofit.Builder builder = new Retrofit.Builder() 101 | .baseUrl(baseUrl) 102 | .addConverterFactory(GsonConverterFactory.create()) 103 | .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io())); 104 | 105 | OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder(); 106 | 107 | // 看这里 !!! 我们添加了一个网络监听拦截器 108 | okHttpClientBuilder.addInterceptor(chain -> { 109 | boolean connected = networkMonitor.isConnected(); 110 | if (networkMonitor.isConnected()) { 111 | return chain.proceed(chain.request()); 112 | } else { 113 | throw new NoNetworkException(); 114 | } 115 | }); 116 | 117 | return builder.client(okHttpClientBuilder.build()) 118 | .build() 119 | .create(GithubWebService.class); 120 | } 121 | 122 | 我们在拦截器链中添加了我们的拦截器。这样在okhttp其他chain完成前会进行网络检查。我们要么继续处理这个chain要么抛出自定义 NoNetworkException. 123 | 124 | 我们最后要做的事(并且不幸的是每个地方都要这样做)是在onError方法中捕获这个异常: 125 | 126 | 127 | githubWebService.getPublicEvents() 128 | .subscribeOn(AndroidSchedulers.mainThread() 129 | .subscribe(events -> { 130 | // TODO onNext 131 | }, throwable -> { 132 | // on Error 133 | if (throwable instanceof NoNetworkException) { 134 | // TODO handle 'no network' 135 | } else { 136 | // TODO handle some other error 137 | } 138 | }); 139 | 140 | 在这个例子中,我决定使用的是Application Interceptor.我会把这个决定留给你是使用Application Interceptor还是Network Interceptor。如果你要Network Interceptor你只需要用 addNetworkInterceptor 替换 addInterceptor。 141 | 142 | 用 Application Interceptor,你不需要担心每个重定向和中间响应的网络检查。然而,当okhttp从缓存中检索到你的请求时依然会执行没必要的网络检查,这让缓存的作用大打折扣。 143 | 144 | 用 Network Interceptor,则相反:okhttp提供缓存响应时不会进行网络检查。然而,你却需要为重定向和重试进行网络检查(译者:没理解好望指正 原文:you'll have the network check firing for redirects and retries),这有点矫枉过正。 145 | 146 | 147 | 最后例子代码 [android-okhttp-network-monitor](https://github.com/tir38/android-okhttp-network-monitor) 148 | 149 | -------------------------------------------------------------------------------- /关于Android strings.xml你要记住的一些事.md: -------------------------------------------------------------------------------- 1 | >原文:[Android strings.xml — things to remember](https://medium.com/@dmytrodanylyk/android-strings-xml-things-to-remember-c155025bb8bb#.jjmb7gqpq) 2 | 3 | >译文的GitHub地址:[关于Android strings.xml你要记住的一些事](https://github.com/thinkSky1206/android-blog/blob/master/%E5%85%B3%E4%BA%8EAndroid%20strings.xml%E4%BD%A0%E8%A6%81%E8%AE%B0%E4%BD%8F%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BA%8B.md) 4 | 5 | >译者注:都是一些很实用的技巧 ,尤其是对于多语言国际化开发,感同身受 。 6 | 7 | 这篇文章是关于android开发再平常不过的东西--strings.xml 8 | 9 | #不要复用 10 | 11 | >不要在多个页面复用字符串 12 | 13 | **1.**假设你在登录和注册页面都有一个loading加载框,你打算让2个加载框使用同一个字符-R.string.loading. 14 | 15 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/reuse01.png) 16 | 17 | 不久后你决定让其中一个加载框换个字符,然后你不得不创建2个新的字符串并修改你的java代码,如果你一开始就用了2个string,你就只需要修改一个strings.xml文件就可以了 18 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/reuse02.png) 19 | 20 | **2.**你永远不知道你的app将来会支持哪种语言,在某个语言-你可能可以用同一个单词表达不同的内容(译者:done可以表达中文的确定,下一步等等),但是在另外一个语言-你不得不用多个单词表达不同的内容。 21 | 22 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/reuse03.png) 23 | 24 | ![res/values-UA/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/reuse04.png) 25 | 26 | 可以看到英语版本的strings.xml用了同样的单词 “YES”给R.string.download_file_yes 和 R.string.terms_of_use_yes 27 | 28 | 但是乌克兰版本的strings.xml使用了2个不同单词 “Гаразд”给R.string.download_file_yes,“Так”给R.string.terms_of_use_yes。 29 | 30 | 31 | #分开 32 | >用前缀和注释让不同页面的字符串分开 33 | 34 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/sep01.png) 35 | 36 | **1.**给每个字符添加页面前缀用于帮助识别当前字符串属于哪个界面 37 | 38 | **2.**清晰的strings.xml可以让维护变的简单,同时可以一个页面接一个页面翻译成其他语言 39 | 40 | #Format格式化 41 | >使用Resources#getString(int id, Object… formatArgs)格式化字符串 42 | 43 | 千万别用“+”这种操作符,因为在不同语言单词排列顺序可能是不一样的 44 | 45 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/format01.png) 46 | 47 | 48 | ![java code](https://github.com/thinkSky1206/android-blog/blob/master/images/format02.png) 49 | 50 | 51 | 正确的方式是使用 Resources#getString(int id, Object… formatArgs) 52 | 53 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/format03.png) 54 | 55 | 56 | ![res/values-UA/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/format04.png) 57 | 58 | 59 | ![java code](https://github.com/thinkSky1206/android-blog/blob/master/images/format05.png) 60 | 61 | 62 | #Plurals复数 63 | >使用Resources#getQuantityString (int id, int quantity)获取数量字符串 64 | 65 | 不要在你的java代码里面手动解析复数,因为不同语言有不同的规则和数量语法协议。 66 | 67 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/plu01.png) 68 | 69 | 70 | ![java code](https://github.com/thinkSky1206/android-blog/blob/master/images/plu02.png) 71 | 72 | 正确的方式是使用Resources#getQuantityString (int id, int quantity) 73 | 74 | 75 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/plu03.png) 76 | 77 | 78 | ![java code](https://github.com/thinkSky1206/android-blog/blob/master/images/plu04.png) 79 | 80 | 81 | #字符高亮 82 | >使用html文本高亮静态字符 83 | 84 | 如果你想改变TextView字符串的颜色-*ForegroundColorSpan*并不是一个任何时候都好用的一个选择,因为高亮是通过下标index来实现的,在多语言app里面并不安全(译者:你要手动计算不同语言同一个单词的index,容易数组下标越界)。更好的做法是使用strings.xml里面的html font颜色标签。 85 | 86 | 87 | 假设你有一个文本“Discover and play games.” 然后你想高亮“Discover”单词和“play”单词成蓝色。 88 | 89 | ![res/values/strings.xml](https://github.com/thinkSky1206/android-blog/blob/master/images/wh01.png) 90 | 91 | ![java code](https://github.com/thinkSky1206/android-blog/blob/master/images/wh02.png) 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /写出高效清晰Layout布局文件的一些技巧.md: -------------------------------------------------------------------------------- 1 | >原文:[Android – How to write Batman like xml layout](http://ivankocijan.xyz/android-batman_like_layout/) 2 | 3 | 当人们谈论Android性能的时候总是习惯讨论怎么写出清晰高效的Java代码,却忽略了layout布局文件。layout布局缓慢的渲染速度对app性能也有的很大的影响。充满不必要的views和可读性差的layout文件会让你的app运行缓慢。在本文中我会分享5个技巧来帮你写出高效清晰的layout布局文件。(ps:下面的技巧都非常实用,开发过程中很常见,感动哭!) 4 | 5 | ###1. Use compound drawable on a TextView 6 | >用TextView本身的属性同时显示图片和文字 7 | 8 | (ps:难以理解现在还有很多人不懂得用这个,实实在在的减少很多view啊,哎!) 9 | 10 | 通常你需要在文本旁边添加一张图片,假设你需要添加图片在文字的左边,像下面这样: 11 | 12 | ![TextView](http://upload-images.jianshu.io/upload_images/186157-7d1534318daf150e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 13 | 14 | 不少人首先想到的就是用一个LinearLayout或RelativeLayou来包含一个TextView和ImageView,最后你用了三个UI元素和一大坨代码。用TextView的compound drawable是一个更好更清晰的解决方案。你只需要一个属性就可以搞定。 15 | 16 | 23 | 24 | 25 | 用到的主要属性: 26 | 27 | **drawableLeft**- 指定drawable放在文本的左边 28 | 29 | **drawableStart**- 作用和drawableLeft相同但是它基于新的API(android 4.2)支持[RTL](http://android-developers.blogspot.hr/2013/03/native-rtl-support-in-android-42.html) 30 | 31 | **drawablePadding**- 指定文本和drawable之间padding 32 | 33 | ###2. ImageView has src and background attribute 34 | >同时使用ImageView的src和background属性实现点击效果 35 | 36 | 你应该同时使用它们,在很多情况下你会想要给ImageView添加点击效果,然后我看到很多人用LinearLayout来包裹一个ImageView来实现。添加另一个view是没必要的。下面的代码可以让你做的更好: 37 | 38 | 45 | 46 | 显示图片用"src"属性,drawable selector 图片点击效果用"background"属性实现,上面用的是android默认提供的selector,当然你也可以换成你自己实现的。下面是最后的效果:(ps:哈哈,效果自己copy上面几行代码就可以看到了,实在需要看请翻墙查看原文)。 47 | 48 | ###3. Use LinearLayout divider 49 | >用LinearLayout自带的分割线 50 | 51 | 分割线在app经常会用到的,使用频率高到让你惊讶。但是LinearLayout有一个属性可以帮你添加分割线。下面的例子中,LinearLayout包含2个TextView和基于他们中间的分割线。 52 | 53 | ![divider](http://upload-images.jianshu.io/upload_images/186157-c908d64cefdc08c3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 54 | 55 | ######1.Create divider shape(创建shape) 56 | 下面是一个简单的shape divider_horizontal.xml用来当做分割线。 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ######2.Add shape to LinearLayout 67 | //居中显示 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 上面用到了三个xml属性: 93 | 94 | **divider** -用来定义一个drawable或者color作为分割线 95 | 96 | **showDividers** -指定分割线在哪里显示,它们可以显示在开始位置,中间,末尾或者选择不显示 97 | 98 | **dividerPadding** -给divider添加padding 99 | 100 | ###4.Use the Space view 101 | >使用Space控件 102 | 103 | 当你需要在2个UI控件添加间距的时候,你可能会添加padding或margin。有时最终的layout文件是非常混乱,可读性非常差。当你需要解决问题时,你突然意识到这里有一个5dp的paddingTop,那里有一个2dp的marginBottom,还有一个4dp的paddingBottom在第三个控件上然后你很难弄明白到底是哪个控件导致的问题。还有我发现有些人在2个控件之间添加LinearLayout或View来解决这个问题,看起来是一个很简单解决方案但是对App的性能有很大的影响。 104 | 105 | 这里有一个更简单更容易的方法那就是Space,看下官方的文档:*“Space is a lightweight View subclass that may be used to create gaps between components in general purpose layouts.”* 他们没有说谎,确实很轻量。如果你看过Space的实现会发现Space继承View但是没有绘制任何东西在canvas。 106 | 107 | /** 108 | * Draw nothing. 109 | * 110 | * @param canvas an unused parameter. 111 | */ 112 | @Override 113 | public void draw(Canvas canvas) { 114 | } 115 | 116 | 使用方法很简单,看下面的图片,我们想要在在标题和描述之间添加间距。 117 | 118 | ![space](http://upload-images.jianshu.io/upload_images/186157-a4b03fba72448034?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 119 | 120 | 你只需要简单的在2个TextView之间添加一个Space就可以了 121 | 122 | //添加间距 128 | 129 | 133 | 134 | ###5.Use and 135 | >使用标签 136 | 137 | 重用布局是一个保持app一致的好方法,这样以后有改变的话只要改一个文件就可以了,Android提供了标签帮你重用布局。 138 | 139 | 例如你现在决定创建有一个logo图片居中的酷炫Toolbar工具栏,然后你想要添加到每个页面中,下面是Toolbar效果: 140 | 141 | ![toolbar](http://upload-images.jianshu.io/upload_images/186157-741b82214cf2be0e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 142 | 143 | 下面是batman_toolbar.xml代码 144 | 145 | 152 | 153 | 154 | 158 | 159 | 160 | 161 | 你可以复制粘贴这些代码到每个Activity,但是不要这么做,在编程中有一个重要的规则:当你复制粘贴,那你就是在做错误的事。在这种情况下你可以用标签在多个界面重用这个布局。 162 | 163 | 164 | 172 | 173 | 174 | 175 | 176 | 177 | 用标签你可以只用一行代码在app的每个页面添加你的toolbar,任何改变都会自动更新到所有页面。 178 | 179 | 除了,也常用来从你的view层次结构中减不必要的view,它会移除没必要的嵌套的layouts,例如,如果被包含布局文件的根是一个LinearLayout,然后你把它include包含在另外一个LinearLayout里面,2个嵌套的LinearLayouts是没有必要的,这个嵌套的layout没有任何作用除了影响UI性能。在这种情况下可以用来替换被包含布局的根LinarLayout 移除不必要的view. 180 | 181 | 关于的更多信息你可以查看[官方文档](http://developer.android.com/training/improving-layouts/reusing-layouts.html) 182 | 183 | ###Don’t always play by the rules 184 | >上面的技巧不用当做规则 185 | 186 | 我希望这5个技巧可以帮你写出更好更简单的布局layout,但是不要把这些技巧当是规则,它们更像是指南。总有一些情况下你没法使用这些技巧,只能通过增加布局的复杂性来解决。在这种情况下,在添加更多view之前,你可以考虑自定义View试的找到更简单的解决方法。只要记住一件事,在你的视图层次添加一个view代价是不可小嘘的,它会影响你的app加载速度。 187 | 188 | 谢谢Ana和oFca对文章进行校对。 -------------------------------------------------------------------------------- /在AndroidStudio中给你的代码添加版权声明.md: -------------------------------------------------------------------------------- 1 | >原文:[Add a copyright notice to your code](https://antoniocappiello.com/2015/12/08/add-a-copyright-notice-to-your-code/) 2 | 3 | >译文的GitHub地址:[给你的代码添加版权声明](https://github.com/thinkSky1206/android-blog/blob/master/%E5%9C%A8AndroidStudio%E4%B8%AD%E7%BB%99%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%A0%81%E6%B7%BB%E5%8A%A0%E7%89%88%E6%9D%83%E5%A3%B0%E6%98%8E.md) 4 | 5 | >译者注:一个规范的项目无论是开源的还是公司内部,版权声明和协议声明都是不可少的,在Android Studio中非常简单,没加上的快去给你的项目加上吧。 6 | 7 | ![image](https://antoniocappiellocomblog.files.wordpress.com/2015/12/how-i-did-it-last-time-omer.jpg?w=322&h=239) 8 | 9 | 写这个“上次我是怎么做的”系列,是为了下次再遇到不需要去google了。 10 | 11 | ##怎么给我的android项目添加自定义的版权声明呢? 12 | 13 | 其实非常简单的,你只需要记住哪个menu和修改哪个设置就可以了。 14 | 15 | 首先,你需要确定版权是要给android studio中所有项目还是只是给当前打开的项目。 16 | 17 | 要是给所有的项目,选择File->Default Settings 18 | 19 | ![all_project](https://antoniocappiellocomblog.files.wordpress.com/2015/12/preferences-global.png?w=676) 20 | 21 | 要是只是给当前的项目,只需点击如下图的Preference图标 22 | 23 | ![current_project](https://antoniocappiellocomblog.files.wordpress.com/2015/12/preferences-current-project.png?w=676) 24 | 25 | 接下来的步骤都是一样,除了上面开头那段 26 | 27 | 找到Editor->Copyright,点击Copyright Profiles 28 | ![step 2](https://antoniocappiellocomblog.files.wordpress.com/2015/12/preferences-current-project-2.png?w=676) 29 | 30 | 点击打开面板左侧的“+”图标,新建一个你自己的版权声明 31 | 32 | ![step 3](https://antoniocappiellocomblog.files.wordpress.com/2015/12/new.png?w=676) 33 | 34 | 输入你的版权文本,还有你可以你的文本中使用一些变量 例如: 35 | 36 | - $today:当前的日期和时间 37 | - $today.year:当前年 38 | - $username:android studio当前用户名 39 | - 还有[其他](https://www.jetbrains.com/help/idea/2016.1/copyright-profiles.html?origin=old_help) 40 | 41 | ![step 4](https://antoniocappiellocomblog.files.wordpress.com/2015/12/formatting.png?w=676) 42 | 43 | 填写完后,点击验证*Validate*按钮确保它可以符合velocity模板,如果合法,会弹出下面的提示,然后点击Apply 44 | 45 | ![step 5](https://antoniocappiellocomblog.files.wordpress.com/2015/12/validation.png?w=676) 46 | 47 | 完成后再回来点击*Copyright*,现在你可以在下拉框里面选择你刚新建的版权,然后点击*Apply/Ok* 48 | 49 | ![step 6](https://antoniocappiellocomblog.files.wordpress.com/2015/12/apply.png?w=676) 50 | 51 | 现在你的版权信息已经可以用了,如果你新建一个文件,它会自动添加到文件的顶部。 52 | 53 | ![result](https://antoniocappiellocomblog.files.wordpress.com/2015/12/result.png?w=676) 54 | 55 | 但是有种情况怎么给现有的文件手动添加呢?,在文件顶部右键点击弹出菜单->选择*Generata...*如下图所示: 56 | 57 | ![generata](https://antoniocappiellocomblog.files.wordpress.com/2015/12/generate.png?w=676) 58 | 59 | 将会出现一个小窗口 60 | 61 | ![mini](https://antoniocappiellocomblog.files.wordpress.com/2015/12/generate-2.png?w=676) 62 | 63 | 点击*Copyright*然后版权信息会自动添加到文件的最上面! 64 | 65 | 66 | 显然你不会想要给所有现有的文件一个一个手动添加,所以我建议在项目任何文件夹右键点击出现下图,然后选择*Update Copyright...* 67 | 68 | ![update all](https://antoniocappiellocomblog.files.wordpress.com/2015/12/update-all.png?w=676) 69 | 70 | 这时候你可以选择把你的版权声明添加到整个项目中。 71 | 72 | ![all project](https://antoniocappiellocomblog.files.wordpress.com/2015/12/update-all-2.png?w=676) 73 | 74 | 好啦! 75 | 76 | 非常简单,写下来之后 后面再遇到就知道怎么做了。 77 | -------------------------------------------------------------------------------- /实现淘宝和QQ ToolBar透明渐变效果.md: -------------------------------------------------------------------------------- 1 | >[自定义CoordinatorLayout的Behavior实现知乎和简书快速返回效果 2 | ](http://www.jianshu.com/p/d372d37e8640) 3 | 4 | 每天用淘宝和QQ 会发现淘宝的商品详情页和qq的好友动态页都不约而同的用了工具栏透明渐变效果,淘宝是为了不挡住商品图片,qq设置为了不挡住header image背景,效果感觉还挺好看,老有人问怎么做的。 5 | 6 | 那我们来分析一下他们的效果 7 | 8 | **淘宝效果:**图片向上滑动的时候,toolbar慢慢的由透明变成完全不透明,向下滑动则相反 9 | 10 | ![淘宝效果](http://upload-images.jianshu.io/upload_images/186157-3ebabd7564a4d264?imageMogr2/auto-orient/strip) 11 | 12 | **QQ效果:**(请自行查看手机,模拟器打死都安装不了,录不了gif) qq的效果类似不过有点不同 下面会分析 13 | 14 | ![QQ效果](http://upload-images.jianshu.io/upload_images/186157-59b644d4978dadec?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 15 | 16 | 看完他们的效果我们可以发现: 17 | 18 | - 页面进来 toolbar开始都是透明的 19 | - 向上滑动 toolbar的透明度由透明变成透明,再向下滑动时,又由不透明变成透明 20 | 21 | 但是仔细体验会发现它们还是有一点点不同,qq的好友动态是要滑到快要出现文字时才会瞬间变成不透明,看下面的图大概能理解了(数字代表alpha 0为完全透明 0-255表示0-100的透明度 255表示完全不透明) 22 | 23 | ![效果区分](http://upload-images.jianshu.io/upload_images/186157-7912905e04746727?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | 可以看到QQ的渐变区间小的多 也就是一瞬间就变了透明度 26 | 27 | 好了 简单看了别人家的效果 下面我们要自己来简单实现一下了 28 | 29 | ###1.布局 30 | 31 | 淘宝和QQ的布局总体看都是一样的 toolbar +可滑动内容区(header+列表) 32 | 33 | 34 | 40 | 41 | 44 | 45 | 49 | 50 | 56 | 57 | 62 | 63 | 64 | 65 | 66 | 73 | 74 | 75 | 76 | 77 | 上面可滑动部分为了简单起见用了NestedScrollView,当然你可以可以换成RecyleView,里面包含了一个固定高度的图片和文字列表 很简单的布局。 78 | 79 | ###2.实现自定义的ToolbarAlphaBehavior 80 | 81 | 原理就是根据滑动的偏移值来设置对应的toolbar透明度(看上面的渐变区间分析图一目了然) 实现也就几行代码(就喜欢几行代码的感觉) 82 | 83 | public class ToolbarAlphaBehavior extends CoordinatorLayout.Behavior { 84 | private static final String TAG = "ToolbarAlphaBehavior"; 85 | private int offset = 0; 86 | private int startOffset = 0; 87 | private int endOffset = 0; 88 | private Context context; 89 | 90 | public ToolbarAlphaBehavior(Context context, AttributeSet attrs) { 91 | super(context, attrs); 92 | this.context = context; 93 | } 94 | 95 | @Override 96 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, Toolbar child, View directTargetChild, View target, int nestedScrollAxes) { 97 | return true; 98 | } 99 | 100 | 101 | @Override 102 | public void onNestedScroll(CoordinatorLayout coordinatorLayout, Toolbar toolbar, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 103 | startOffset = 0; 104 | endOffset = context.getResources().getDimensionPixelOffset(R.dimen.header_height) - toolbar.getHeight(); 105 | offset += dyConsumed; 106 | if (offset <= startOffset) { //alpha为0 107 | toolbar.getBackground().setAlpha(0); 108 | } else if (offset > startOffset && offset < endOffset) { //alpha为0到255 109 | float precent = (float) (offset - startOffset) / endOffset; 110 | int alpha = Math.round(precent * 255); 111 | toolbar.getBackground().setAlpha(alpha); 112 | } else if (offset >= endOffset) { //alpha为255 113 | toolbar.getBackground().setAlpha(255); 114 | } 115 | } 116 | 117 | } 118 | 119 | 没错就这么几行代码,主要代码都在onNestedScroll方法里面 120 | 121 | ###3.配置Behavior 122 | 123 | 给Toolbar配置Behavior 124 | 125 | 132 | 133 | 因为一开始页面进来toolbar就是透明的 别忘了初始化透明度 134 | 135 | @Override 136 | protected void onCreate(Bundle savedInstanceState) { 137 | super.onCreate(savedInstanceState); 138 | setContentView(R.layout.activity_behavior); 139 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_behavior); 140 | toolbar.getBackground().setAlpha(0);//toolbar透明度初始化为0 141 | } 142 | 143 | 然后就ok了 144 | 145 | ###4.结束 146 | 147 | 最后看下我们的效果 148 | 149 | ![toolbar渐变效果](http://upload-images.jianshu.io/upload_images/186157-f45022413ab28ba8?imageMogr2/auto-orient/strip) 150 | 151 | 恩 还不错 是不是觉得分分钟就搞定了 当然这里只是简易实现 给大家一个思路,用在实际产品开发中 亲 可能要考虑更多东西哦。 -------------------------------------------------------------------------------- /实现知乎和简书快速返回效果.md: -------------------------------------------------------------------------------- 1 | Design lib里面的CoordinatorLayout是一个非常强大的控件,它接管了child组件之间的交互。让你滑动交互使用更加方便简单,效果也更加强大,不需要向以前那样自己处理一坨什么乱七八槽的滑动 事件传递之类的恶心东西了。 2 | 3 | 比如常见的顶部工具栏随内容滑动消失和显示,这个官方已经支持了Toolbar,但是有时候我们想让自己的组件也可以和滑动交互,这个时候我们就需要自定义一个我们自己的Behavior了 4 | 5 | 6 | 7 | **知乎效果** 8 | 9 | 知乎的效果是顶部不动,底部随内容滑动 显示和隐藏 10 | 可以先看一下知乎的底部快速返回效果(sorry 静态图) 11 | ![知乎效果](http://img.blog.csdn.net/20150912142700784) 12 | 13 | 看下我们实现的效果 14 | 15 | ![模仿知乎效果](http://img.blog.csdn.net/20150912142922383) 16 | 17 | 18 | 先看下我们的布局 19 | 20 | ``` 21 | 22 | 26 | 27 | 30 | 31 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 54 | 55 | 62 | 63 | 64 | 69 | 70 | 71 | 72 | 73 | 74 | ``` 75 | 76 | 布局很简单就三个内容 toolbar,RecyclerView,footer(LinearLayout) 77 | 注意底部操作栏最外层的 LinearLayout我们加上了 78 | > app:layout_behavior="com.example.lwp.design.behavior.FooterBehavior" 79 | 80 | 81 | FooterBehavior就是我们要自定义的behavior,让它和滑动交互,内容向上滑动时消失,向下滑动时显示 82 | 实现我们自己的Behavior其实很简单 ,就是几行代码的事,主要就是根据滑动距离来显示和隐藏footer 83 | 84 | ``` 85 | public class FooterBehavior extends CoordinatorLayout.Behavior { 86 | 87 | private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); 88 | 89 | 90 | private int sinceDirectionChange; 91 | 92 | 93 | public FooterBehavior(Context context, AttributeSet attrs) { 94 | super(context, attrs); 95 | } 96 | 97 | //1.判断滑动的方向 我们需要垂直滑动 98 | @Override 99 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { 100 | return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 101 | } 102 | 103 | //2.根据滑动的距离显示和隐藏footer view 104 | @Override 105 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { 106 | if (dy > 0 && sinceDirectionChange < 0 || dy < 0 && sinceDirectionChange > 0) { 107 | child.animate().cancel(); 108 | sinceDirectionChange = 0; 109 | } 110 | sinceDirectionChange += dy; 111 | if (sinceDirectionChange > child.getHeight() && child.getVisibility() == View.VISIBLE) { 112 | hide(child); 113 | } else if (sinceDirectionChange < 0 && child.getVisibility() == View.GONE) { 114 | show(child); 115 | } 116 | } 117 | 118 | 119 | private void hide(final View view) { 120 | ViewPropertyAnimator animator = view.animate().translationY(view.getHeight()).setInterpolator(INTERPOLATOR).setDuration(200); 121 | animator.setListener(new Animator.AnimatorListener() { 122 | @Override 123 | public void onAnimationStart(Animator animator) { 124 | 125 | } 126 | 127 | @Override 128 | public void onAnimationEnd(Animator animator) { 129 | view.setVisibility(View.GONE); 130 | } 131 | 132 | @Override 133 | public void onAnimationCancel(Animator animator) { 134 | show(view); 135 | } 136 | 137 | @Override 138 | public void onAnimationRepeat(Animator animator) { 139 | 140 | } 141 | }); 142 | animator.start(); 143 | } 144 | 145 | 146 | private void show(final View view) { 147 | ViewPropertyAnimator animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(200); 148 | animator.setListener(new Animator.AnimatorListener() { 149 | @Override 150 | public void onAnimationStart(Animator animator) { 151 | 152 | } 153 | 154 | @Override 155 | public void onAnimationEnd(Animator animator) { 156 | view.setVisibility(View.VISIBLE); 157 | } 158 | 159 | @Override 160 | public void onAnimationCancel(Animator animator) { 161 | hide(view); 162 | } 163 | 164 | @Override 165 | public void onAnimationRepeat(Animator animator) { 166 | 167 | } 168 | }); 169 | animator.start(); 170 | 171 | } 172 | ``` 173 | 174 | 175 | 176 | 177 | **简书效果** 178 | 179 | 简书效果有点类似全屏 就是滑动的时候顶部和底部的都消失,提供更好的阅读体验 180 | 181 | ![简书效果](http://img.blog.csdn.net/20150912145040567) 182 | 183 | 看下我们实现的效果 184 | ![模仿简书效果](http://img.blog.csdn.net/20150912145224441) 185 | 186 | 布局还是原来的布局,只需要改2个小地方,一个是让Toolbar支持隐藏,第二个底部的behavior改成我们接下来实现的behavior名称: FooterBehaviorDependAppBar 187 | 188 | ``` 189 | ... 190 | 195 | 196 | ... 197 | 198 | 208 | ... 209 | 210 | 211 | ``` 212 | 213 | 从效果来看,第一眼感觉会比实现知乎效果要麻烦,其实比第一个behavior实现更加简单,几乎只用了一行代码实现 child.setTranslationY(translationY); 214 | 215 | ``` 216 | public class FooterBehaviorDependAppBar extends CoordinatorLayout.Behavior { 217 | 218 | 219 | public FooterBehaviorDependAppBar(Context context, AttributeSet attrs) { 220 | super(context, attrs); 221 | } 222 | 223 | @Override 224 | public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { 225 | return dependency instanceof AppBarLayout; 226 | } 227 | 228 | 229 | @Override 230 | public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { 231 | float translationY = Math.abs(dependency.getTranslationY()); 232 | child.setTranslationY(translationY); 233 | return true; 234 | } 235 | } 236 | ``` 237 | 238 | 现在可以看到实现自己的Behavior是多么的方便和简单了吧,这样你就可以在滑动交互中实现更复杂和酷炫的交互效果了 239 | 赶紧去试试吧! -------------------------------------------------------------------------------- /小总结.md: -------------------------------------------------------------------------------- 1 | >反编译工具:我一般用来快速progurd 2 | 3 | jadx 4 | 5 | >gif录制 6 | 7 | LICEcap+GIF Movice Gear 8 | 9 | >adb 连接问题 10 | 11 | *1.*netstat -aon|findstr "5037" 12 | 13 | *2.*tasklist|findstr "8156" 14 | 15 | *3.*adb kill-server 16 | 17 | *4.*adb start-server 18 | 19 | >svn+as 设置ignore 20 | 21 | 新建项目->设置ignore file->share project->commit 22 | 23 | >drawble着色 24 | 25 | Drawable tintLeftIcon = DrawableCompat.wrap(icon); 26 | DrawableCompat.setTint(tintLeftIcon, color); 27 | 28 | 29 | >获取字符宽度 30 | 31 | Rect bounds = new Rect(); 32 | paint.getTextBounds(text, 0, text.length(), bounds); 33 | 34 | >获取屏幕内容高度 35 | 36 | @Override 37 | public void onWindowFocusChanged(boolean hasFocus) { 38 | super.onWindowFocusChanged(hasFocus); 39 | Rect rect = new Rect(); 40 | getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(rect); 41 | int toolbarHeight = getResources().getDimensionPixelSize(R.dimen.toolbar); 42 | L.ii("view height:" + rect.height() + ",toolbar:" + toolbarHeight); 43 | ViewGroup.LayoutParams params = bottomLayout.getLayoutParams(); 44 | params.height = rect.height() - toolbarHeight; 45 | bottomLayout.setLayoutParams(params); 46 | } 47 | 48 | 49 | >TextView添加圆形背景span 50 | 51 | public class CircleSpan implements LineBackgroundSpan { 52 | 53 | public static final float DEFAULT_RADIUS = 3; 54 | 55 | 56 | private float radius; 57 | private int color; 58 | 59 | 60 | public CircleSpan(float radius, int color) { 61 | this.radius = radius; 62 | this.color = color; 63 | } 64 | 65 | @Override 66 | public void drawBackground(Canvas c, Paint p, int left, int right, int top, int baseline, int bottom, CharSequence text, int start, int end, int lnum) { 67 | int oldColor = p.getColor(); 68 | if (color != 0) { 69 | p.setColor(color); 70 | } 71 | c.drawCircle((left + right) / 2, (bottom + top) / 2, Math.min(bottom, right) / 2, p); 72 | p.setColor(oldColor); 73 | } 74 | } 75 | 76 | >google地图文字marker 77 | 78 | private Bitmap generateTextBitmap(String text, int textSize) { 79 | Paint paint = new Paint(); 80 | paint.setTextSize(textSize); 81 | paint.setStyle(Paint.Style.FILL); 82 | paint.setAntiAlias(true); 83 | Rect bounds = new Rect(); 84 | paint.getTextBounds(text, 0, text.length(), bounds); 85 | int x = 20; 86 | int y = 20; 87 | Bitmap.Config conf = Bitmap.Config.ARGB_4444; 88 | Bitmap bmp = Bitmap.createBitmap(bounds.width() + x, bounds.height() + y, conf); 89 | Canvas canvas = new Canvas(bmp); 90 | paint.setColor(Color.parseColor(Constants.MATERIAL)); 91 | canvas.drawText(text, x / 2, bounds.height() + y / 2, paint); 92 | return bmp; 93 | } 94 | 95 | >FragmentPagerAdapter更新问题 96 | 97 | >notity-getItemPos-return NONE-instanceItem-(有缓存set数据)-(无缓存new fragment) 98 | 99 | public class DevicePagerAdapter extends FragmentPagerAdapter { 100 | private List data; 101 | private Context context; 102 | 103 | 104 | public DevicePagerAdapter(FragmentManager fm, Context context, List list) { 105 | super(fm); 106 | this.context = context; 107 | this.data = list; 108 | } 109 | 110 | public void update(List list) { 111 | data.clear(); 112 | data.addAll(list); 113 | this.notifyDataSetChanged(); 114 | } 115 | 116 | public List getAll() { 117 | return this.data; 118 | } 119 | 120 | public DeviceInfo getItemData(int pos) { 121 | return data.get(pos); 122 | } 123 | 124 | @Override 125 | public Fragment getItem(int position) { 126 | DeviceTabFragment fragment = new DeviceTabFragment(); 127 | fragment.setListener((HomeBaseActivity) context); 128 | return fragment; 129 | } 130 | 131 | 132 | @Override 133 | public Object instantiateItem(ViewGroup container, int position) { 134 | DeviceTabFragment f = (DeviceTabFragment) super.instantiateItem(container, position); 135 | f.setInfo(data.get(position)); 136 | return f; 137 | } 138 | 139 | @Override 140 | public int getCount() { 141 | return data.size(); 142 | } 143 | 144 | @Override 145 | public int getItemPosition(Object object) { 146 | return POSITION_NONE; 147 | } 148 | 149 | public View getTabView(int position) { 150 | DeviceInfo deviceInfo = data.get(position); 151 | View view = LayoutInflater.from(context).inflate(R.layout.common_tab_header, null); 152 | ImageView icon = (ImageView) view.findViewById(R.id.header_icon); 153 | TextView title = (TextView) view.findViewById(R.id.header_title); 154 | icon.setImageResource(deviceInfo.getSelectorDrawable()); 155 | title.setText(deviceInfo.getShortName()); 156 | return view; 157 | } 158 | } 159 | 160 | 161 | >TabLayout 162 | 163 | 1.修改拦截tab点击事件 164 | 165 | onTabClickListener = new View.OnClickListener() { 166 | @Override 167 | public void onClick(View v) { 168 | int pos = (int) v.getTag(); 169 | TabLayout.Tab tab = tabLayout.getTabAt(pos); 170 | if (tab != null) { 171 | tab.select(); 172 | DeviceInfo info = data.get(pos); 173 | moveCenter(info.getUid()); 174 | } 175 | } 176 | }; 177 | 178 | 179 | tabLayout.setupWithViewPager(viewPager); 180 | for (int i = 0; i < tabLayout.getTabCount(); i++) { 181 | TabLayout.Tab tab = tabLayout.getTabAt(i); 182 | tab.setCustomView(adapter.getTabView(i)); 183 | View tabView = (View) tab.getCustomView().getParent(); 184 | tabView.setTag(i); 185 | tabView.setOnClickListener(onTabClickListener); 186 | } 187 | if (!isDataNull) { 188 | tabLayout.getTabAt(lastSelectedPos).getCustomView().setSelected(true); 189 | viewPager.setCurrentItem(lastSelectedPos); 190 | loadAddress(list.get(0)); 191 | } 192 | 193 | 194 | >日常项目配置 195 | 196 | **1.gradle.properties** 197 | 198 | # Project-wide Gradle settings. 199 | 200 | # IDE (e.g. Android Studio) users: 201 | # Gradle settings configured through the IDE *will override* 202 | # any settings specified in this file. 203 | 204 | # For more details on how to configure your build environment visit 205 | # http://www.gradle.org/docs/current/userguide/build_environment.html 206 | 207 | # Specifies the JVM arguments used for the daemon process. 208 | # The setting is particularly useful for tweaking memory settings. 209 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 210 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 211 | 212 | # When configured, Gradle will run in incubating parallel mode. 213 | # This option should only be used with decoupled projects. More details, visit 214 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 215 | org.gradle.parallel=true 216 | 217 | android.useDeprecatedNdk=true 218 | #keystore 219 | KEYSTORE_FILE=D\:\\devWork\\keystore\\jzb_release.jks 220 | KEYSTORE_PASSWORD=123456 221 | KEY_ALIAS=key 222 | KEY_PASSWORD=123456 223 | 224 | **2.build.gradle** 225 | 226 | apply plugin: 'com.android.application' 227 | 228 | android { 229 | compileSdkVersion 23 230 | buildToolsVersion "23.0.3" 231 | 232 | defaultConfig { 233 | applicationId "com.uu.uu" 234 | minSdkVersion 15 235 | targetSdkVersion 22 236 | versionCode 15 237 | versionName "2.2.0" 238 | // manifestPlaceholders = [app_label: "@string/app_name"] 239 | 240 | ndk { 241 | moduleName "protocolParser" 242 | stl "c++_static" 243 | setcFlags "-std=c++11" 244 | ldLibs "atomic" 245 | } 246 | 247 | 248 | } 249 | 250 | /* productFlavors { 251 | local { 252 | applicationId "com.ybit.jzb" 253 | buildConfigField 'String', 'API_URL', '"http://192.168.2.177:9900/assets/"' 254 | resValue "string", "app_name", "Jzb" 255 | } 256 | us { 257 | applicationId "com.ybit.jzb.us" 258 | buildConfigField 'String', 'API_URL', '"http://183.233.129.45:9900/assets/"' 259 | resValue "string", "app_name", "Jzb_us" 260 | 261 | } 262 | za { 263 | applicationId "com.ybit.jzb.za" 264 | buildConfigField 'String', 'API_URL', '"http://gissetsa.gigaiot.com:9900/assets/"' 265 | resValue "string", "app_name", "Jzb_za" 266 | } 267 | }*/ 268 | 269 | 270 | signingConfigs { 271 | debug { 272 | storeFile file("D:/devWork/keystore/debug.keystore") 273 | storePassword "android" 274 | keyAlias "androiddebugkey" 275 | keyPassword "android" 276 | } 277 | 278 | release { 279 | storeFile file(KEYSTORE_FILE) 280 | storePassword KEYSTORE_PASSWORD 281 | keyAlias KEY_ALIAS 282 | keyPassword KEY_PASSWORD 283 | } 284 | } 285 | 286 | 287 | buildTypes { 288 | debug { 289 | buildConfigField "boolean", "LOG_DEBUG", "true" 290 | manifestPlaceholders = [baiduMapKey: "lGOttbnLskUmWPTHHahoI8bz", googleMapKey: "AIzaSyCvl4cEojxrrD-oH_hMPkUHY_6FU0POohM"] 291 | versionNameSuffix "-debug" 292 | minifyEnabled false 293 | zipAlignEnabled true 294 | shrinkResources false 295 | signingConfig signingConfigs.debug 296 | applicationVariants.all { variant -> 297 | variant.outputs.each { output -> 298 | def date = new Date(); 299 | def formattedDate = date.format('MMdd') 300 | output.outputFile = new File(output.outputFile.parent, 301 | output.outputFile.name.replace("-debug", "-debug-" + formattedDate) 302 | ) 303 | } 304 | } 305 | 306 | } 307 | 308 | release { 309 | buildConfigField "boolean", "LOG_DEBUG", "false" 310 | manifestPlaceholders = [baiduMapKey: "sPCYeXc7uc4BRaGc0Flewh4Y", googleMapKey: "AIzaSyCvl4cEojxrrD-oH_hMPkUHY_6FU0POohM"] 311 | zipAlignEnabled true 312 | shrinkResources true //去除无用的资源 313 | minifyEnabled true 314 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 315 | signingConfig signingConfigs.release 316 | applicationVariants.all { variant -> 317 | variant.outputs.each { output -> 318 | def date = new Date(); 319 | def formattedDate = date.format('MMdd') 320 | output.outputFile = new File(output.outputFile.parent, 321 | output.outputFile.name.replace("-release", "-release-" + formattedDate) 322 | ) 323 | } 324 | } 325 | } 326 | 327 | 328 | } 329 | 330 | sourceSets { 331 | main { 332 | jniLibs.srcDirs = ['libs'] 333 | } 334 | } 335 | packagingOptions { 336 | exclude 'META-INF/LICENSE.txt' 337 | exclude 'META-INF/NOTICE.txt' 338 | } 339 | 340 | dexOptions { 341 | jumboMode = true 342 | } 343 | 344 | lintOptions { 345 | abortOnError false 346 | } 347 | 348 | } 349 | 350 | 351 | 352 | repositories { 353 | jcenter() 354 | mavenCentral() 355 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 356 | flatDir { 357 | dirs 'libs' 358 | } 359 | } 360 | 361 | 362 | 363 | dependencies { 364 | compile fileTree(dir: 'libs', include: ['*.jar']) 365 | compile project(':materialCalendarView') 366 | compile project(':seekBarHint') 367 | compile 'com.android.support:recyclerview-v7:23.3.0' 368 | compile 'com.android.support:appcompat-v7:23.3.0' 369 | compile 'com.android.support:support-v4:23.3.0' 370 | compile 'com.android.support:design:23.3.0' 371 | compile 'com.android.support:percent:23.3.0' 372 | compile 'com.jakewharton:butterknife:7.0.1' 373 | compile 'com.squareup.retrofit:retrofit:2.0.0-beta1' 374 | compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1' 375 | compile 'com.squareup.okhttp:okhttp:2.5.0' 376 | compile 'com.google.code.gson:gson:2.3.1' 377 | compile 'com.orhanobut:logger:1.11' 378 | // compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT' 379 | //osm 380 | compile(name: 'osmbonuspack_v5.3', ext: 'aar') 381 | compile 'org.osmdroid:osmdroid-android:4.3' 382 | compile 'org.slf4j:slf4j-android:1.6.1-RC1' 383 | compile 'org.apache.commons:commons-lang3:3.3.2' 384 | //baidu 385 | compile files('libs/BaiduLBS_Android.jar') 386 | compile 'net.danlew:android.joda:2.8.2' 387 | compile 'de.greenrobot:eventbus:2.4.0' 388 | //network sqlite3 debug 389 | compile 'com.facebook.stetho:stetho:1.2.0' 390 | //google map 391 | compile 'com.google.android.gms:play-services-maps:8.4.0' 392 | compile 'com.google.android.gms:play-services-gcm:8.4.0' 393 | compile 'com.github.jakob-grabner:Circle-Progress-View:v1.2.9' 394 | 395 | } 396 | 397 | apply plugin: 'com.google.gms.google-services' 398 | -------------------------------------------------------------------------------- /快速清除app数据.md: -------------------------------------------------------------------------------- 1 | >原文链接:[Clear the app data quickly](https://medium.com/sebs-top-tips/clear-the-app-data-quickly-android-studio-protips-1-ebc47ea06286#.cj23k5wx8) 2 | 3 | 我们在开发app的时候,debug时经常会遇到需要清除app数据的场景,比如清除登录用户名重新进行登录测试,清除某个列表页面的数据库数据等等。 4 | 5 | 这个操作还是挺麻烦的,需要在我们手机的文件管理或者app管理找到对应的app 然后进行数据清除操作,一次还好 要是每次都要清除数据进行调试就有点麻烦了。 6 | 7 | 有一个插件可以帮助你解决这个烦扰:**[ADB IDEA](https://github.com/pbreault/adb-idea)**,as的一个adb插件 8 | 9 | ![adb idea](http://upload-images.jianshu.io/upload_images/186157-ce350416050cca39?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 10 | 11 | 从上面的图片可以看到操作:Tools->Android->ADB idea->ADB Clear App Data And Restart 12 | 然后你的app就会清楚数据重新启动了,比手动操作可快多了。 13 | 14 | 当然还有更快的方法: 15 | 16 | **1**:使用快捷键: Ctrl+Shift+A (Ctrl+Alt+Shift+A Linux和 Windows)呼出弹出窗口 17 | 18 | **2**:输入**cr** (Clear Restart) 19 | 20 | ![adb dialog](http://upload-images.jianshu.io/upload_images/186157-af883f4ac68adb2c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 21 | 22 | 然后点击或者使用快捷键6就可以了。 23 | 24 | 不错 还是挺实用的插件。 -------------------------------------------------------------------------------- /教你实现别人家的动画3(淘宝,简书动画效果).md: -------------------------------------------------------------------------------- 1 | >之前的文章都写上csdn上,主要觉得简书是分享文字的地方,最近发现关注我的开发专题人挺多的,就打算把这篇技术文章也发下简书上吧,希望对一些朋友有帮助。 2 | 3 | >前2篇的地址,有兴趣也可以去看看。 4 | 5 | >http://blog.csdn.net/tiankong1206/article/details/44901601 6 | 7 | >http://blog.csdn.net/tiankong1206/article/details/44947495 8 | 9 | 10 | 11 | 这篇文章我们来实现个稍微简单点的动画效果 12 | 每天在iphone上用淘宝和简书发现他们有个共同的弹出效果(ios自带的?),今天我们就来看看他们吧 13 | 14 | ![这里写图片描述](http://img.blog.csdn.net/20150418115638623) 15 | 16 | ![这里写图片描述](http://img.blog.csdn.net/20150418115610458) 17 | 18 | 好吧 我不知道怎么录屏ios手机动态gif 19 | 没关系,看我们实现后的效果就可以大概明白了。 20 | ![这里写图片描述](http://img.blog.csdn.net/20150418121839876) 21 | ok 看完了效果图 我们还是老规矩,首先来简单分析下。 22 | 23 | - 首先有2个视图,一个是主内容视图,第二个视图有点类似popopWindow,它默认是不显示的 24 | - 第一个动画,popopWindow从下面弹出的时候,window的动画很简单,就是从下面移动出来显示。主视图的动画也不难,包含x,y比例缩小,半透明,还有一个倾斜的效果 25 | - 第二个动画就很明了,和第一个相反就ok了 26 | 27 | 分析下来发现挺简单的,就是scale,alpha,translation几个普通动画组合 28 | 好了,开始码代码了 29 | 首先是布局文件,很简单,里面就2个LinerLayout,各自包含一个按钮。 30 | 31 | 32 | 33 | 36 | //主视图 37 | 43 |