├── 设计模式相关 ├── 策略模式.md ├── 代理设计模式.md └── 单例模式.md ├── README.md ├── 面试相关 ├── 2018最新BAT java经典必考面试题.docx ├── 2018-06 卖好车电话面试.md ├── 面试题详解.md └── Android基础题目.md ├── Android杂记 ├── Service详解.md ├── Android和H5交互——坑.md ├── 自定义View套路分析.md ├── Android一步一步带你了解跨进程通信.md ├── 内存泄漏.md ├── 是时候好好管理一下啊你的RxJava2.x.md ├── 浅谈Activity、Window、View之间的关系.md ├── 一个Android程序猿的北漂之路.md ├── Android仿微信文章悬浮窗效果.md ├── ConstraintLayout最强布局解析.md ├── Kotlin学习笔记.md ├── DataBinding入门学习笔记.md └── Android屏幕分辨率.md ├── Android源码相关 ├── RxJava源码解析.md ├── xutils3源码阅读.md ├── ViewPager源码解析.md ├── EventBus源码详解.md ├── ButterKnife源码解析.md ├── View的Touch事件分发.md ├── View的事件分发机制.md ├── Fragment源码解析.md ├── Activity的启动流程.md ├── setContentView源码.md ├── 带着问题去看源码——TextView篇.md ├── LayoutInflater源码解析.md ├── Handler源码分析.md └── Retrofit源码解析.md ├── Java相关 ├── JVM内存模型.md ├── Http与Https.md └── Java面向对象OOP.md ├── Web前端相关 ├── React笔记.md ├── Html+Css实现Tab选项卡功能(一).md ├── 初识Weex.md ├── 初识JavaScript.md ├── Web实现手风琴效果.md ├── Web实现Tab选项卡功能(二).md ├── Android开发人员不得不学习的CSS3基础.md ├── JavaScript基础(二).md ├── JavaScript基础(一).md ├── 微信小程序学习笔记.md ├── 初识前端.md └── 初识Vuejs.md └── ButterKnife.md /设计模式相关/策略模式.md: -------------------------------------------------------------------------------- 1 | ## 策略模式 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-Knowledge 2 | 持续分享自己平时总结的知识以及看的Android源码,也欢迎大家上传自己平时的总结或者自己看的Android源码。 3 | 4 | 5 | #### 未完待续。。。。 6 | -------------------------------------------------------------------------------- /面试相关/2018最新BAT java经典必考面试题.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/24Kshign/Android-Knowledge/HEAD/面试相关/2018最新BAT java经典必考面试题.docx -------------------------------------------------------------------------------- /Android杂记/Service详解.md: -------------------------------------------------------------------------------- 1 | ## Service详解 2 | 3 | `Service`作为四大组件之一,相信还是有很多人在日常开发中很少用到,所以对于它的理解肯定不是很深(LZ也是),所以在此想通过一个小demo来深入了解`Service` -------------------------------------------------------------------------------- /Android源码相关/RxJava源码解析.md: -------------------------------------------------------------------------------- 1 | ## RxJava源码解析 2 | 3 | ### 1、介绍 4 | RxJava是一个用于处理一步任务的库,它的思想是响应式编程(基于异步数据流概念的编程模式) 5 | 6 | #### 未完待续。。。。 -------------------------------------------------------------------------------- /Android杂记/Android和H5交互——坑.md: -------------------------------------------------------------------------------- 1 | ## Android和H5交互——坑 2 | 3 | ### 1、WebView打开某个H5时白屏 4 | Android的WebView打开H5的某个页面白屏,刷新一下WebView就好了,H5上来就是这肯定是你们移动端的缓存。 5 | 6 | #### 未完待续。。。。。 -------------------------------------------------------------------------------- /Android杂记/自定义View套路分析.md: -------------------------------------------------------------------------------- 1 | ### 自定义View套路分析 2 | 3 | #### 1、自定义View套路 4 | 5 | **(1)、自定义属性,获取自定义属性(达到配置的效果)** 6 | 7 | **(2)、onMeasure()方法用于测量自己的宽高,前提是继承View,如果是继承系统已有的控件(如TextView或Button等),已经给你计算好了宽高。** 8 | 9 | **(3)、onDraw()方法用于绘制显示的内容** 10 | 11 | **(4)、onTouch()方法用于与用户交互** 12 | 13 | 14 | #### 2、自定义ViewGroup套路 15 | 16 | **(1)、自定义属性,获取自定义属性(达到配置的效果)** 17 | 18 | **(2)、onMeasure()方法,for循环测量子View宽高,根据子View的宽高来计算自己的宽高。** 19 | 20 | **(3)、onDraw()一般不需要,默认情况下是不会调用,如果你要绘制,则需要实现dispatchDraw()** 21 | 22 | **(4)、onLayout()用来摆放子View,前提是View可见** -------------------------------------------------------------------------------- /Android源码相关/xutils3源码阅读.md: -------------------------------------------------------------------------------- 1 | ### xutils3源码阅读 2 | 3 | ``` 4 | //handleType-->class 获取setContentView方法 5 | Method setContentViewMethod = handlerType.getMethod("setContentView", int.class); 6 | //反射执行该方法 7 | setContentViewMethod.invoke(activity, viewId); 8 | ``` 9 | 10 | ``` 11 | View view = finder.findViewById(viewInject.value(), viewInject.parentId()); 12 | if (view != null) { 13 | //可以操作所有修饰 14 | field.setAccessible(true); 15 | //反射注入属性 16 | field.set(handler, view); 17 | } 18 | ``` 19 | **属性注入流程:** 20 | 21 | 利用反射去获取Annotation--->value--->findViewById--->反射注入属性 22 | 23 | **事件注入流程:** 24 | 25 | 利用反射去获取Annotation--->value--->setOnClickListener--->动态代理反射执行方法 -------------------------------------------------------------------------------- /Java相关/JVM内存模型.md: -------------------------------------------------------------------------------- 1 | ## JVM内存模型 2 | 3 | 在Java中,有六种存储地址: 4 | 5 | ### 寄存器 6 | >这是最快的存储区,它位于不同于其他存储区的地方(处理器内部)。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。 7 | 8 | ### 栈区 9 | >位于通用RAM中,这是一种快速有效的分配存储方法,仅次于寄存器。每个线程包含一个栈,栈中只保存基础数据类型的值和对象以及基础数据的引用;每个栈中的数据(基础数据类型和对象引用)都是私有的,其他栈不能访问。 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。 10 | 11 | ### 堆区 12 | >一种通用性的内存池(也存在于RAM中)。堆中存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) ;jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。 13 | 14 | **注意:实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针!** 15 | 16 | ### 方法区(静态区) 17 | >这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。 方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。 18 | 19 | ### 常量存储 20 | >常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中。 21 | 22 | 23 | ### 非RAM存储 24 | >如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。 25 | 26 | 我们日常开发过程中比较常见的也就三种,堆、栈和方法区。下面通过一张图片来了解他们: 27 | ![](http://ooaap25kv.bkt.clouddn.com/18-8-8/45764560.jpg) 28 | -------------------------------------------------------------------------------- /Android杂记/Android一步一步带你了解跨进程通信.md: -------------------------------------------------------------------------------- 1 | ## Android一步一步带你了解跨进程通信 2 | 3 | ### 什么是进程? 4 | 5 | 在我们日常开发中,好像和线程打交道多一点,大部分人还是没有和进程打过交道的,那什么是进程呢?进程一般指一个执行单元吗,我们`Android`应用程序就是一个进程,一个进程可以只包含一个线程,在`Android`中我们称之为主线程或者`UI`线程。 6 | 7 | ![](http://ooaap25kv.bkt.clouddn.com/18-10-11/61560334.jpg) 8 | 9 | ### 为什么要跨进程通信? 10 | 11 | 跨进程通信,简言之,就得有多个进程,才能实现跨进程通信;那么在 `Android`开发中,我们使用的最多的还是多线程,应该绝大多数人都没有使用过多进程,那么为什么要使用多进程呢?有两种情况,第一就是一个应用因为某些原因自身需要多进程来实现,比如有些模块由于特殊原因需要运行在单独的进程中,又比如为了加大一个应用可使用的内存所以需要通过多进程的方式来获取多份内存空间;还有一种情况就是当前应应用需要向其他应用获取数据,由于是两个应用,所以必须采用跨进程通信的方式来获取数据。 12 | 13 | ### 如何使用多进程? 14 | 15 | 在 Android中使用多进程的方法只有一个,就是在`AndroidMenifest`中给四大组件指定`android:process`属性就可以了,但是使用多进程之后会出现很多问题,例如数据,线程等: 16 | 17 | - **1、静态成员和单例模式完全失效** 18 | - **2、线程同步机制完全失效** 19 | - **3、SharePrefrfence的可靠性下降** 20 | - **4、Application会被多次创建** 21 | 22 | ### 实现跨进程通信的方法 23 | 24 | 上面说到使用多进程会出现很多问题,为了解决这些问题,系统提供了很多跨进程通信的方法,能让我们实现数据交互,文件共享等操作,例如基于`Binder`的`Messenger`和`AIDL`,`Socket`等都是可以实现跨进程通信的;在学习这些方法之前我们需要先了解一个知识,序列化,什么是序列化呢?就是指把Java对象保存为二进制字节码的过程,那怎么取出被序列化过的数据呢,在Java中,将二进制字节码重新转换成对象的过程叫做反序列化,这两个过程可以在完全不同的系统下进行。 25 | 26 | -------------------------------------------------------------------------------- /Android源码相关/ViewPager源码解析.md: -------------------------------------------------------------------------------- 1 | ## ViewPager源码解析 2 | 3 | ### 1、setAdapter方法 4 | ``` 5 | public void setAdapter(@Nullable PagerAdapter adapter) { 6 | ... 7 | 8 | populate(); 9 | 10 | ... 11 | } 12 | ``` 13 | 调用setAdapter方法将setAdapter方法里面主要调了populate方法,这个方法是用来创建和销毁View的: 14 | 15 | 创建View:mAdapter.instantiateItem() 16 | 17 | 销毁View:mAdapter.destroyItem() 18 | 19 | 从源码中可以看出,ViewPager中放多少页面都不会导致内存溢出问题,因为会不断的创建和销毁View 20 | 21 | ![ViewPager工作原理](https://upload-images.jianshu.io/upload_images/4314397-07dc5f93c8ef12ce?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 22 | 23 | ### 2、setCurrentItem()方法 24 | ``` 25 | public void setCurrentItem(int item, boolean smoothScroll) { 26 | setCurrentItemInternal(item, smoothScroll, false); 27 | } 28 | 29 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { 30 | scrollToItem(item, smoothScroll, velocity, dispatchSelected); 31 | } 32 | 33 | private void scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected) { 34 | smoothScrollTo(destX, 0, velocity); 35 | } 36 | 37 | void smoothScrollTo(int x, int y, int velocity) { 38 | mScroller.startScroll(sx, sy, dx, dy, duration); 39 | } 40 | ``` 41 | mScroller.startScroll方法是切换页面并且执行动画 42 | 43 | 44 | -------------------------------------------------------------------------------- /面试相关/2018-06 卖好车电话面试.md: -------------------------------------------------------------------------------- 1 | ## 2018-06 卖好车电话面试 2 | 3 | ### 1、说说你对handler机制的理解 4 | 5 | 一般你从源码去讲就可以了,handler的初始化需要注意什么,message的存储,messageQueue的结构,怎么轮训的。 6 | 7 | 详细参考: 8 | ##### [Handler源码分析](https://www.jianshu.com/p/27ffef014dfa) 9 | 10 | ### 2、Android中事件分发是怎么进行的(一次完整的分发)? 11 | 12 | Activity-->ViewGroup-->View-->Viewgroup--Activity 13 | 14 | 详细参考: 15 | ##### [View的Touch事件分发机制](https://github.com/24Kshign/Android-Knowledge/blob/master/View%E7%9A%84Touch%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91.md) 16 | ##### [Android的事件分发机制](https://github.com/24Kshign/Android-Knowledge/blob/5f7380c2096fa6fdabdd6245fbfeb75ec17af884/View%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E6%9C%BA%E5%88%B6.md) 17 | 18 | ### 3、什么是内存泄漏?在Android开发中,有哪些情况会发生内存泄漏?你是通过什么方法来解决内存泄漏的? 19 | 20 | 详细参考: 21 | ##### [内存泄漏](https://github.com/24Kshign/Android-Knowledge/blob/cf8494109030b81200f8422dc686c0e3f044c07e/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md) 22 | 23 | ### 4、如何优化布局的?根布局为什么会测量两遍? 24 | 25 | ### 5、垃圾回收机制?算法? 26 | 27 | ### 6、类加载机制 28 | 29 | ### 7、Retrofit的原理 30 | 31 | 详细参考 32 | ##### [Retrofit源码解析](https://github.com/24Kshign/Android-Knowledge/blob/5f7380c2096fa6fdabdd6245fbfeb75ec17af884/Retrofit%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md) 33 | 34 | ### 8、开发过程中用过哪些设计模式 35 | 36 | ### 9、RxJava是用了什么设计模式?观察者是怎么和被观察者绑定的 -------------------------------------------------------------------------------- /Java相关/Http与Https.md: -------------------------------------------------------------------------------- 1 | ## Http与Https 2 | 3 | ### 1、两者的概念 4 | 5 | #### Http 6 | Http是互联网上应用最为广泛的一种网络协议,是一个客户端和服务端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。 7 | 8 | #### Https 9 | Https是以安全为目标的Http通道,简单讲就是Https更加安全,即Http下加入SSL层,Https的安全基础是SSL,因此加密的详细内容就需要SSL。HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。 10 | 11 | ### 2、两者的区别 12 | Http协议传输的数据都是未加密的,也就是明文的,因此使用Http协议传输隐私信息非常不安全,为了保证这些隐私数据都能加密传输,于是就有了SSL(Secure Sockets Layer)协议用于对Http协议传输的数据进行加密,从而就有了Https。 13 | 14 | (1)、Https协议需要到ca申请证书,一般免费的证书比较少,大多都是付费的。 15 | 16 | (2)、Http是超文本传输协议,信息是明文传输,Http是具有安全性的SSL加密传输协议 17 | 18 | (3)、Http和Https使用的完全不同的连接方式,用的端口也不一样,前者端口是80,后者是443。 19 | 20 | (4)、Http的连接很简单,是无状态的;Https协议是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议,比Http协议安全。 21 | 22 | ### 3、Https的工作原理 23 | 由于Https能够加密信息,所以现在绝大部分网站,后台接口都会使用Https传输协议。 24 | 25 | ![](http://www.mahaixiang.cn/uploads/allimg/1507/1-150H120343I41.jpg) 26 | 27 | (1)、客户端使用Https的Url访问Web服务器,要求与Web服务器建立SSL连接 28 | 29 | (2)、Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。 30 | 31 | (3)、客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密等级。 32 | 33 | (4)、客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。 34 | 35 | (5)、Web服务器利用自己的私钥解密出会话密钥。 36 | 37 | (6)、Web服务器利用会话密钥加密与客户端之间的通信。 38 | 39 | ![](https://pic002.cnblogs.com/images/2012/339704/2012071410212142.gif) 40 | 41 | -------------------------------------------------------------------------------- /Android杂记/内存泄漏.md: -------------------------------------------------------------------------------- 1 | ## 内存泄漏 2 | 3 | ### 1、定义 4 | 5 | 一个无用对象仍然被其他对象对象持有引用,造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间的浪费,这就是内存泄漏。 6 | 7 | ### 2、在Android开发中,有哪些情况会发生内存泄漏? 8 | 9 | ##### 2.1 Context 10 | 11 | 我们都知道Activity的生命周期是比较短的,如果我们在一个生命周期更长的对象中引用了Activity的Context,那么在Activity销毁的时候内存就不会去回收Activity所占用的内存,因为它还在被使用,所以就回导致内存泄漏。 12 | 13 | ###### 解决办法:尽量使用Application的Context来代替Activity的Context。 14 | 15 | ##### 2.2 静态变量 16 | 17 | 静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化之后,它所持有的引用只有等到进程结束之后才会释放,就会导致内存泄漏。 18 | 19 | ###### 解决办法:在用不到它的地方将其置空就好了。 20 | 21 | ##### 2.3 非静态内部类 22 | 23 | 非静态内部类默认持有外部类的引用,当非静态内部类对象的生命周期比外部类的生命周期长时,就会导致内存泄漏。 24 | 25 | ###### 解决办法:使用静态内部类来代替非静态,或者也可以将该类封装成一个单例。 26 | 27 | ##### 2.4 线程+Handler 28 | 29 | 在Activity中使用Handler的时候,Handler会自动与主线程Looper的MessageQueue关联起来,所有发送到MessageQueue的Message都持有Handler的引用,相当于间接的持有Activity的引用,而主线程的Looper对象会伴随该应用程序的整个生命周期,当Activity销毁之后,不能被回收。我们在Activity中使用线程的时候一般会使用匿名内部类,而匿名内部类也会对外部类持有默认的引用,当Activity关闭后如果线程中的任务还没执行完,也会导致Activity不能被回收,就会造成内存泄漏。 30 | 31 | ###### 解决办法:Handler的创建使用静态内部类的方式,然后适当的配合弱引用,最后在Activity销毁的时候remove掉消息;线程的话,创建一个静态内部类,实现Runnable接口,在使用的时候实例化它。 32 | 33 | ##### 2.5 资源未关闭 34 | 35 | 比如使用Bitmap的时候,使用数据库游标Cursor的时候,使用文件File的时候,等等这些都可能会造成内存泄漏。 36 | 37 | ###### 解决办法:在Activity销毁的时候,在适当的时候关闭或置空这些资源。 38 | 39 | ##### 2.6 未取消注册或回调 40 | 41 | 我们在Activity中注册广播时,如果不在Activity销毁时取消注册,那么这个广播就一直伴随应用程序,可能就会发生内存泄漏;使用观察者模式时如果不及时取消的话也会造成内存泄漏;使用RxJava的时候如果不在Activity销毁时取消绑定的话也会发生内存泄漏。 42 | 43 | ###### 解决方法:在需要取消的时候记得取消就行了。 44 | 45 | 46 | -------------------------------------------------------------------------------- /Android杂记/是时候好好管理一下啊你的RxJava2.x.md: -------------------------------------------------------------------------------- 1 | ## 是时候好好管理一下啊你的RxJava2.x 2 | 3 | ### 序言,最近在测试的时候发现项目中有的网络请求没有带上项目中封装的RxJava生命周期管理器,然后就导致了某个Activity在销毁之后,网络请求依然在,等到响应的时候View已经为空了(Unbinder.unbind),这个时候就报空指针了,吓得我立马去检查还有没有某些漏网之鱼也没有加的。这个就是我们常说的RxJava内存泄漏。 4 | 5 | ![](http://ooaap25kv.bkt.clouddn.com/18-6-22/24237331.jpg) 6 | 7 | 如果你还不知道RxJava2.0会发生内存泄漏或者你还不知道网络请求会出现上述情况的话,那么,你还是装死好了,本篇博客将针对Retrofit2.0+RxJava2.0来进行介绍,有两种方法可以避免内存泄漏问题。 8 | 9 | ### 一、Disposable / CompositeDisposable 10 | 11 | 首先我们来写一段代码,在Activity中每隔一段时间发送一个事件,然后用TextView来显示: 12 | 13 | ``` 14 | Observable.interval(2, TimeUnit.SECONDS) 15 | .subscribeOn(Schedulers.io()) 16 | .observeOn(AndroidSchedulers.mainThread()) 17 | .subscribe(new Observer() { 18 | @Override 19 | public void onSubscribe(Disposable d) { 20 | 21 | } 22 | 23 | @Override 24 | public void onNext(Long aLong) { 25 | textView.setText(String.valueOf(aLong)); 26 | } 27 | 28 | @Override 29 | public void onError(Throwable e) { 30 | 31 | } 32 | 33 | @Override 34 | public void onComplete() { 35 | 36 | } 37 | }); 38 | ``` 39 | 40 | ![](http://ooaap25kv.bkt.clouddn.com/18-6-22/78624525.jpg) 41 | 42 | 相信大家都看到了,退出activity的时候,我们把做了unbind操作,但是rxjava发送事件还没有停止,然后就发生了上述悲剧。接下来我们使用官方回调给我们的Disposable这个东西来让Rxjava的事件停止,只需要在onDestroy中切断Observable(被观察者)和Observer(观察者)的联系,使用Disposable.dispose()方法即可,那么在 -------------------------------------------------------------------------------- /设计模式相关/代理设计模式.md: -------------------------------------------------------------------------------- 1 | ## 代理设计模式 2 | ### 1、定义 3 | 为其他对象提供一种代理,以控制对这个对象的访问,分为静态代理和动态代理。 4 | ### 2、静态代理设计模式 5 | 6 | **银行办卡——角色划分** 7 | 8 | (1)、目标接口 9 | 10 | (2)、代理对象,银行工作人员 11 | 12 | (3)、被代理对象,我 13 | 14 | ### 3、动态代理设计模式 15 | 16 | InvocationHandler,Java提供的一套动态代理机制 17 | 18 | ### 4、实现Retrofit的create 19 | 20 | ``` 21 | public T create(final Class service) { 22 | Utils.validateServiceInterface(service); 23 | if (validateEagerly) { 24 | eagerlyValidateMethods(service); 25 | } 26 | return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service }, 27 | new InvocationHandler() { 28 | private final Platform platform = Platform.get(); 29 | 30 | @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) 31 | throws Throwable { 32 | // If the method is a method from Object then defer to normal invocation. 33 | if (method.getDeclaringClass() == Object.class) { 34 | return method.invoke(this, args); 35 | } 36 | if (platform.isDefaultMethod(method)) { 37 | return platform.invokeDefaultMethod(method, service, proxy, args); 38 | } 39 | ServiceMethod serviceMethod = 40 | (ServiceMethod) loadServiceMethod(method); 41 | OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); 42 | return serviceMethod.callAdapter.adapt(okHttpCall); 43 | } 44 | }); 45 | } 46 | ``` 47 | 48 | (1)、首先通过`Utils.validateServiceInterface(service)`来判断入参是否是一个接口。 49 | 50 | (2)、然后是判断是否需要做缓存 51 | 52 | (3)、通过动态代理的模式去创建接口的实例(`Proxy.newProxyInstance()`方法) 53 | 54 | **总结:使用动态代理模式来解耦** 55 | 56 | -------------------------------------------------------------------------------- /Java相关/Java面向对象OOP.md: -------------------------------------------------------------------------------- 1 | ## Java面向对象OOP 2 | 3 | ### 1、面向对象六大基本原则 4 | 5 | #### (1)、单一职责原则(Single Responsibility Principle) 6 | 就一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该是一组相关性很高的函数、数据的封装。两个完全不一样的功能就不应该放在同一个类中,单一指责所表达出的用意就是“单一”二字。 7 | 8 | **注意:链式调用,解决参数多这种问题,链式调用只是一种方式,而builder是设计模式,builder设计模式可能会用到链式调用这种方式。** 9 | 10 | #### (2)、开闭原则(Open Close Principle) 11 | 软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的。对于原来写好的代码里面是不可修改,但是对于外部又是可扩展的。 12 | 13 | 开闭原则指导我们,当软件需要变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。我们尽量不要通过继承等方式添加新的实现,这会导致类型的膨胀以及历史遗留代码的冗余。但是在实际开发过程中,只通过继承的方式来升级并维护原有系统只是一个理想化的场景,所以,在实际开发过程中,修改原有代码和扩展代码往往是同时存在的, 14 | 15 | #### (3)、里氏替换原则(Liskov Substitution Principle) 16 | 里氏替换原则简单来说就是,所有引用基类的地方必须能透明地使用其子类的对象。通俗点讲,只要父类能出现的地方子类就可以出现。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。主要体现在实现和继承上。 17 | 18 | 比如,RecyclerView.setLayoutManager(new LinearLaoutManager())和RecyclerView.setLayoutManager(new GridLayoutManager());Retrofit动态的设置不同的解析器。 19 | 20 | #### (4)、依赖倒置原则(Dependence Inversion Principle) 21 | 依赖反转原则指代了一种特定的解耦形式,高层模块不依赖低层次模块的细节,说白了高层次就是不依赖细节而是依赖抽象。 22 | 23 | **关键点:** 24 | 25 | (1)、高层模块不应该依赖底层模块,两者都应该依赖其抽象 26 | 27 | (2)、抽象不应该依赖细节 28 | 29 | (3)、细节应该依赖抽象 30 | 31 | 也就是说,具体实现的类就是低层,被调用的以及接口是高层。 32 | 33 | #### (5)、接口隔离原则(InterfaceSegregation Principles) 34 | 它的定义是:客户端不应该依赖它不需要的接口。另一种定义是:类间的依赖关系应该建立在最小的接口上。 35 | 36 | 接口隔离原则将非常庞大、臃肿的接口拆分成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署,让客户端依赖的接口尽可能地小。 37 | 38 | #### (6)、迪米特原则《最少知识原则》(Law of Demeter) 39 | 一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现、如何复杂都与调用者或者依赖者没关系,调用者或者依赖者只需要知道他需要的方法即可,其他的我一概不关心。说白了,就是一个类要尽可能少的依赖其他类,只需要与之有直接关系的类联系即可。 40 | 41 | ### 2、面向对象继承的优缺点 42 | #### 优点: 43 | 44 | (1)、代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性 45 | 46 | (2)、子类和父类基本相似,但又与父类有区别 47 | 48 | (3)、提高代码的可扩展性 49 | 50 | #### 缺点: 51 | 52 | (1)、继承是侵入性的,只要继承就必须拥有父类的所有属性和方法 53 | 54 | (2)、可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类的所有属性和方法 55 | 56 | -------------------------------------------------------------------------------- /Android源码相关/EventBus源码详解.md: -------------------------------------------------------------------------------- 1 | ## EventBus源码详解 2 | 3 | ### 1、介绍 4 | 一个个事件(event)发送到总线上,然后EventBus根据已注册的订阅者(subscribers)来匹配相应的事件,进而把事件传递给订阅者 5 | 6 | ### 2、描述 7 | **(1)、**onCreate方法中注册EventBus——>`EventBus.getDefault().register(this)` 8 | 9 | **(2)、**onDestroy方法中取消注册EventBus——>`EventBus.getDefault().unregister(this)` 10 | 11 | **(3)、**把需要接收的方法采用注解Subscribe 12 | 13 | **(4)、**在任意的地方只要调用EventBus.post,就会执行Subscribe注解方法(前提是同类型的对象) 14 | 15 | ### 3、步骤 16 | #### register()注册 17 | 18 | **第一步:findUsingReflectionInSingleClass()** 19 | 20 | 去解析注册者对象的所有方法并且找出带有Subscriber注解的方法,让通过Annotation解析所有细节参数(线程,优先级,粘性事件,参数类型),把这些参数封装成一个SubscriberMethod对象,添加到集合并返回。 21 | 22 | **第二步:调用subscribe()方法进行订阅** 23 | 24 | 解析所有SubscriberMethod的eventType(参数的class),然后按照要求解析成Map, CopyOnWriteArrayList>格式,key是指eventType,value就是Subscription的列表,Subscription包含两个属性Subscriber和SubscriberMethod 25 | 26 | #### post发送消息 27 | **第一步:postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();** 28 | 29 | 首先获取是否在主线程post消息的标志(这里稍后会用到),然后循环调用postSingleEvent()方法,然后再调用postSingleEventForEventType()方法,通过参数event和参数的class来获取一个CopyOnWriteArrayList集合,然后遍历这个集合,拿到相应的信息去进行发送操作(调用postToSubscription()方法),然后通过判断threadMode的类型,通过反射去执行。 30 | 31 | #### ThreadMode类型 32 | 33 | **(1)、POSTING** 34 | 35 | 同一个线程;在哪个线程发送事件那么该方法就在哪个线程执行,EventBus默认就是这种类型 36 | 37 | **(2)、MAIN** 38 | 39 | 主线程;如果发布者线程是主线程,那么直接在发布者线程(主线程)里边调用事件处理方法;如果发布者线程不是主线程,就把此事件送到主线程消息循环处理队列,在主线程中处理此事件 40 | 41 | **(3)、BACKGROUND** 42 | 43 | 子线程;如果发布者线程是主线程,那么把此事件发送到一个专门处理后台线程的消息循环处理队列,该队列管理多个后台线程;如果发布者不是主线程,那么在发布者线程中直接调用事件处理方法 44 | 45 | **(4)、ASYNC** 46 | 47 | 异步线程;并不使用队列管理多个事件,也不管发布者处在主线程与否,为每一个事件单独开辟一个线程处理 48 | 49 | #### unregister()移除 50 | 将订阅者从集合中移除,根据传入的参数(Activity),然后通过之前存的Map, CopyOnWriteArrayList>集合,挨个移除即可。 51 | 52 | #### 粘性事件 53 | 简单讲,就是在发送事件之后再订阅该事件也能收到该事件。它的使用场景是我们要把一个Event发送到一个还没有初始化的Activity/Fragment,即尚未订阅事件。那么如果只是简单的post一个事件,那么是无法收到的,这时候,你需要用到粘性事件,它可以帮你解决这类问题。 54 | -------------------------------------------------------------------------------- /Android源码相关/ButterKnife源码解析.md: -------------------------------------------------------------------------------- 1 | ### ButterKnife源码解析 2 | 3 | #### 1、介绍 4 | butterknife主要是为了解决findViewById和setOnClickListener等 5 | 6 | #### 2、原理 7 | 主要采用编译时注解,就是采用APT生成代码。在编译源文件时,会分析扫描注解,当扫描到butterknife定义的@BindView、@OnClick等注解时,会使用JavaPoet来生成代码。生成后的文件会再次分析,直到没有分析到需要处理的注解位置。关键是AbstractProcessor(编译时注解处理器) 8 | 9 | #### 4、什么是AbstractProcessor 10 | 在程序编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了该处理器支持注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理。 11 | 12 | #### 3、什么是APT 13 | APT(Annotation Processing Tool 的简称),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入。 14 | 15 | #### 4、源码解析 16 | 17 | ``` 18 | @Target(FIELD) 19 | @Retention(CLASS) 20 | public @interface BindView { 21 | /** View ID to which the field will be bound. */ 22 | int[] value(); 23 | } 24 | ``` 25 | @Retention(CLASS)表示编译时注解,在编译的时候所使用的。 26 | 27 | 28 | ``` 29 | Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); 30 | //noinspection unchecked 31 | bindingCtor = (Constructor) bindingClass.getConstructor(cls, View.class); 32 | ``` 33 | 34 | 编译的时候通过上述代码生成一个.java文件--->.class文件 35 | 36 | #### 5、定义注解的元注解 37 | 38 | **`@Target`**:注解的作用目标 39 | 40 | ``` 41 | @Target(ElementType.TYPE) //接口、类、枚举、注解 42 | @Target(ElementType.FIELD) //字段、枚举的常量 43 | @Target(ElementType.METHOD) //方法 44 | @Target(ElementType.PARAMETER) //方法参数 45 | @Target(ElementType.CONSTRUCTOR) //构造函数 46 | @Target(ElementType.LOCAL_VARIABLE)//局部变量 47 | @Target(ElementType.ANNOTATION_TYPE)//注解 48 | @Target(ElementType.PACKAGE) ///包 49 | ``` 50 | 51 | **`@ Reteniton `**:定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中 52 | 53 | ``` 54 | public enum RetentionPolicy { 55 | SOURCE, 56 | CLASS, 57 | RUNTIME 58 | } 59 | ``` 60 | 61 | **1、SOURCE**:被编译器忽略 62 | 63 | **2、CLASS**:(编译时)注解将会被保留在Class文件中,但在运行时并不会被VM保留。这是默认行为,所有没有用Retention注解的注解,都会采用这种策略。 64 | 65 | **3、RUNTIME**:(运行时)保留至运行时。所以我们可以通过反射去获取注解信息。 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Android源码相关/View的Touch事件分发.md: -------------------------------------------------------------------------------- 1 | ### View的Touch事件分发 2 | 3 | #### 1、现象 4 | OnTouch,OnTouchEvent,OnClickListener三者都有时: 5 | 6 | **(1)、OnTouch返回false** 7 | 8 | 执行顺序为:OnTouch.DOWN---->OnTouchEvent.DOWN---->OnTouch.MOVE---->OnTouchEvent.MOVE---->OnTouch.UP---->OnTouchEvent.UP---->OnClickListener 9 | 10 | **(2)、OnTouch返回true** 11 | 12 | 执行顺序为:OnTouch.DOWN---->OnTouch.MOVE---->OnTouch.UP,不会执行OnTouchEvent和OnClickListener方法 13 | 14 | **(3)、没有OnTouch,OnTouchEvent返回true** 15 | 执行顺序为:OnTouchEvent.DOWN---->OnTouchEvent.MOVE---->OnTouchEvent.UP,不会执行OnClickListener 16 | 17 | #### 2、View中有两个非常重要的与Touch相关的方法 18 | 19 | **(1)、dispatchTouchEvent** 20 | 21 | ``` 22 | boolean result = false; 23 | ListenerInfo li = mListenerInfo; 24 | ``` 25 | ListenerInfo存放了关于View的所有Listener信息,如OnTouchListener,OnClickListener 26 | 27 | ``` 28 | if (li != null && li.mOnTouchListener != null 29 | && (mViewFlags & ENABLED_MASK) == ENABLED 30 | && li.mOnTouchListener.onTouch(this, event)) { 31 | result = true; 32 | } 33 | ``` 34 | ENABLE表示该View是否enable;若mOnTouchListener.onTouch(this, event)返回false,则result = false,否则result = true 35 | 36 | ``` 37 | if (!result && onTouchEvent(event)) { 38 | result = true; 39 | } 40 | ``` 41 | 42 | 如果result为false,则View会执行onTouchEvent事件,如果result为false,则View不会执行onYouchEvent事件 43 | 44 | ``` 45 | if (!post(mPerformClick)) { 46 | performClick(); 47 | } 48 | ``` 49 | 在View的onTouchEvent()方法的ACTION_UP中调用了performClick()方法,然后调用li.mOnClickListener.onClick(this)方法(OnClickListener方法),所以上述分析可以解释1中的现象 50 | 51 | **(2)、onTouchEvent** 52 | 53 | 一般都会被我们复写 54 | 55 | #### 3、在ViewGroup中还有一个和OnTouch相关的方法 56 | 57 | **onInterceptTouchEvent方法** 58 | 59 | ``` 60 | if (!disallowIntercept) { 61 | intercepted = onInterceptTouchEvent(ev); 62 | ev.setAction(action); // restore action in case it was changed 63 | } else { 64 | intercepted = false; 65 | } 66 | ``` 67 | 68 | 每次有事件时,都会按照Activity---->ViewGroup---->View这样的线路开始分发,而且最先调用的肯定是dispatchTouchEvent()方法,在ViewGroup的dispatchTouchEvent方法中会调用onInterceptTouchEvent()方法询问是否拦截事件(返回false表示不拦截事件,事件会继续往下一层传递;true表示拦截,事件不会往下传了,这时会调用ViewGroup的OnTouchEvent方法,自己消费,而且在同一个事件列的其他事件中也都直接自己消费,不会往下传递),还会遍历该ViewGroup下的所有子View。 -------------------------------------------------------------------------------- /Web前端相关/React笔记.md: -------------------------------------------------------------------------------- 1 | ## React笔记 2 | 3 | ### 1、注意 4 | 在代码中任何使用到JavaScriptXML(JSX)的地方,最后一个script标签都要加上type="text/babel"属性,这是因为React独有的JSX语法,跟JavaScript不兼容: 5 | 6 | ``` 7 | 8 | ``` 9 | 10 | ### 2、语法 11 | 12 | #### 2.1、ReactDOM.render方法 13 | ReactDOM.render是React的最基本方法,用于将模板转为HTML语言,并插入指定的DOM节点。 14 | 15 | ``` 16 | ReactDOM.render( 17 |

Hello, world!

, 18 | document.getElementById('example') 19 | ); 20 | ``` 21 | 上述代码是将一个h1标签插入到example节点下。 22 | 23 | ### 3、JSX语法 24 | 25 | ``` 26 | var names = ['Alice', 'Emily', 'Kate']; 27 | 28 | ReactDOM.render( 29 |
30 | { 31 | names.map(function (name) { 32 | return
Hello, {name}!
33 | }) 34 | } 35 |
, 36 | document.getElementById('example') 37 | ); 38 | ``` 39 | 40 | 语法规则:遇到HTML标签(以<开头),就用HTML规则解析;遇到代码块(以{开头),就用JavaScript规则解析。 41 | 42 | ### 4、组件 43 | ``` 44 | var HelloMessage = React.createClass({ 45 | render: function() { 46 | return

Hello {this.props.name}

; 47 | } 48 | }); 49 | 50 | ReactDOM.render( 51 | , 52 | document.getElementById('example') 53 | ); 54 | ``` 55 | 使用React.createClass方法来生成一个组件类,每一个组件类都必须有自己的render方法,用于输出组件,这里需要注意的地方有: 56 | 57 | >(1)、组件类的第一个字母必须大写,否则会报错 58 | > 59 | >(2)、组件类只能包含一个顶层标签,否则也会报错 60 | 61 | 添加组件属性时需要注意,class属性需要写成className,for属性要写成htmlFor 62 | ### 5、PropTypes 63 | #### 5.1 组件类的PropTypes属性,就是用来验证组件实例的属性是否符合要求 64 | 65 | ``` 66 | var MyTitle = React.createClass({ 67 | propTypes: { 68 | title: React.PropTypes.string.isRequired, 69 | }, 70 | 71 | render: function() { 72 | return

{this.props.title}

; 73 | } 74 | }); 75 | ``` 76 | 上面例子中就是验证属性的值是否是字符串,如果不是那么就会在控制台报错 77 | 78 | #### 5.2 getDefaultProps 79 | 用来设置组件属性的默认值,在没有接收到传过来值的情况下,会有一个默认值 80 | 81 | ``` 82 | var MyTitle = React.createClass({ 83 | getDefaultProps : function () { 84 | return { 85 | title : 'Hello World' 86 | }; 87 | }, 88 | 89 | render: function() { 90 | return

{this.props.title}

; 91 | } 92 | }); 93 | 94 | ReactDOM.render( 95 | , 96 | document.body 97 | ); 98 | ``` 99 | 上述默认为Hello World 100 | -------------------------------------------------------------------------------- /Web前端相关/Html+Css实现Tab选项卡功能(一).md: -------------------------------------------------------------------------------- 1 | ## Html+Css实现Tab选项卡功能(一) 2 | 3 | ### 序言 4 | 5 | 前段时间学习了一下Html+Css+Js相关知识,感觉不用的话很容易忘记,因此打算每周有空的时候都会写一些小型的demo,也希望能给想入门Web前端的Android程序猿们一些建议,好了话不多说,代码为敬: 6 | 7 | ![](http://ooaap25kv.bkt.clouddn.com/18-10-24/37644265.jpg) 8 | 9 | ### 实现 10 | 11 | 就是这样一个简单的效果,来看一下html代码: 12 | 13 | ``` 14 | 15 |
16 | 23 |
24 | 25 | ``` 26 | 27 | 是不是很简单,`html`代码里面只管放东西进去就好了,需要什么就放什么,在`html`中,通常用`ul`标签来实现无需列表,而`li`标签通常被用来定义列表项目,所以它们俩是配套使用的,塞完东西之后我们要去摆放和设置一些样式 28 | 29 | ![没有加css样式](http://ooaap25kv.bkt.clouddn.com/18-10-24/85361508.jpg) 30 | 31 | 要想好看,接下来看看`css`代码: 32 | 33 | ``` 34 | .tabBar{ 35 | width: 600px; 36 | height: 50px; 37 | background-color: #ffffff; 38 | margin: 10px auto; 39 | } 40 | 41 | .tabBar ul { 42 | width:510px; 43 | margin: 0 auto; 44 | } 45 | 46 | .tabBar ul li { 47 | width: 100px; 48 | height: 50px; 49 | float: left; 50 | list-style: none; 51 | } 52 | 53 | .tabBar ul li a{ 54 | width: 100px; 55 | height: 50px; 56 | text-align: center; 57 | margin: 0 1px; 58 | display: block; 59 | background-color: #e1e1e1; 60 | line-height: 50px; 61 | text-decoration: none; 62 | color: #000000; 63 | } 64 | 65 | .tabBar ul li a:hover{ 66 | color: #ffffff; 67 | background-color: #f60; 68 | } 69 | ``` 70 | 71 | 貌似看起来也很简单,解释一下,`list-style: none;`这句代码是去除`
  • `前面的圆点的,`text-decoration: none;`这句是去除``标签的下划线的,然后我们对`
  • `标签使用`float: left;`属性,这样就能使得列表横向排列了。代码中有一些居中的写法,我们来看看: 72 | 73 | - `margin: 0 auto;` || `margin: 10px auto;`这样是让布局水平居中,这其实是一个缩写,想了解更多的可以翻到底下去看我以前写的文章 74 | 75 | - `line-height: 50px;`这种可以让布局垂直居中,这个高度设置为父布局的高度就行了 76 | 77 | - `text-align: center;`这种一般是文本居中的方式。 78 | 79 | 80 | ### 小白请先看这里 81 | 82 | #### [1、初识前端](https://www.jianshu.com/p/e3021fb2d85c) 83 | 84 | #### [2、初识JavaScript](https://www.jianshu.com/p/d84b66839570) 85 | 86 | #### [3、Android开发人员不得不学习的JavaScript基础(一)](https://www.jianshu.com/p/4a1c75990f84) 87 | 88 | #### [4、Android开发人员不得不学习的JavaScript基础(二)](https://www.jianshu.com/p/b33068c297be) 89 | 90 | #### [5、Android开发人员不得不学习的Vue.js基础](https://www.jianshu.com/p/1d8e33406fe9) 91 | 92 | 93 | ### 公众号 94 | 95 | 欢迎关注我的个人公众号【IT先森养成记】,专注大前端技术分享,包含Android,Java基础,Kotlin,HTML,CSS,JS等技术;在这里你能得到的不止是技术上的提升,还有一些学习经验以及志同道合的朋友,赶快加入我们,一起学习,一起进化吧!!! 96 | 97 | ![公众号:IT先森养成记](http://ooaap25kv.bkt.clouddn.com/18-10-24/75189748.jpg) -------------------------------------------------------------------------------- /Android源码相关/View的事件分发机制.md: -------------------------------------------------------------------------------- 1 | ### Android的事件分发机制 2 | #### 1 事件分发 3 | 4 | * 定义: 将点击事件(MotionEvent)传递给某个View或者处理&整个过程. 5 | * 解决的问题: 点击事件由那个对象发出,经过哪些对象,最终达到哪些对象&最终得到处理. 6 | * 顺序: Activity->ViewGroup->View. 7 | * 核心方法: dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent. 8 | * 还有一个设置setOnTouchListener的方法. 9 | 10 | #### 2 事件的总线有两条 11 | * 事件从上到下分发,也就是最外层的ViewGroup1->View1... 12 | 如果中间没有任何View或者ViewGroup消费掉次事件的话,事件将一直传下去. 13 | 方法的传递顺序就是: 14 | ``` 15 | ViewGroup:dispatchTouchEvent-> 16 | ViewGroup:onInterceptTouchEvent-> 17 | View:dispatchTouchEvent-> 18 | View:onInterceptTouchEvent-> 19 | View:onTouchEvent... 20 | ``` 21 | * 时间从下往上分发,当事件向下传递的过程中没有被任何View消费,那么事件会传到最后的那个View里面,然后再返回到最上层的View中.最后的终点就是一开始传递事件的那个View.View2->ViewGroup2...方法的传递顺序就是: 22 | ``` 23 | View:onTouchEvent -> ViewGroup:onTouchEvent... 24 | ``` 25 | 26 | 27 | #### 3 涉及到的方法 28 | 29 | **(1)、dispatchTouchEvent**
    30 | 该方法是View和ViewGroup共有的方法,当点击事件传递过来的时候是最先接收到事件的.该方法决定了该View是否继续往下分发事件.也就是它的onInterceptTouchEvent和onTouchEvent是否会执行到.返回值有三种: 31 | * true
    32 | 当返回true的时候,此次事件将不会再往下分发.也就是此次事件消费结束. 33 | 34 | * false
    35 | 当返回false的时候,本身不会再处理任何事件,事件将被返回到上一级的onTouchEvent方法里面.也就是本身的onInterceptTouchEvent或者onTouchEvent都不会执行. 36 | 37 | * super
    38 | 将事件分发到自己的onInterceptTouchEvent方法里面. 39 | 40 | 41 | 42 | **(2)、onInterceptTouchEvent** 43 | 该方法是ViewGroup特有的,它可以决定你手否拦截次事件.再dispatchTouchEvent方法里面会调用.如果拦截事件(返回值为true),则该事件将交给自己的onTouchEvent方法处理,不再继续分发事件.返回值有三种: 44 | * true
    45 | 当返回true的时候,表明拦截此事件,把事件交给自己的onTouchEvent处理,事件将不会再往子View分发. 46 | 47 | * false
    48 | 当返回false的时候,不拦截事件,事件将继续分发下去. 49 | 50 | * super
    51 | 等同于返回值为false的时候,super.onInterceptTouchEvent(ev)该方法返回的也是false.不拦截事件,事件将继续分发下去. 52 | 53 | **(3)、onTouchEvent** 54 | 55 | 该方法一般是消费事件的.可以再该方法中处理很多事情.该方法的返回值有三种: 56 | 57 | * true
    58 | 当返回true的时候,表示消费事件,一旦事件被消费了则事件就会停止了,不会继续往上分发.同样返回true的话也表示消费任何事件. 59 | 60 | * false
    61 | 当返回false的时候,说明不消费事件,事件将继续往上分发,直到被消费&直到最顶层.如果返回false的话,此事件以后的事件就不会再接收. 62 | 63 | * super
    64 | 等同于返回值为false的时候. 65 | 66 | **(4)、setOnTouchListener** 67 | 该方法再dispatchTouchEvent中调用,再ViewGroup中是在onInterceptTouchEvent之后调用.该方法决定了onTouchEvent方法是否会执行到. 68 | 69 | #### 4 事件的类型 70 | 71 | 常用的事件有以下几种: 72 | * ACTION_DOWN :一个按下的手势已经启动,该事件里面包含初始的起始位置信息. 73 | * ACTION_UP :一个按下的手势已经结束,该事件里面包含最后的位置信息. 74 | * ACTION_MOVE :发生在DOWN和UP之间的时间,该事件包含了移动中的任何一个点. 75 | * ACTION_CANCEL :当前的事件中止,可以当成一个UP事件. 76 | * ACTION_OUTSIDE :当移动事件已经滑出该View的边界的事件. 77 | * ACTION_POINTER_DOWN :双指按下. 78 | * ACTION_POINTER_UP :双指抬起. 79 | ``` 80 | 81 | 这里要注意的是当双指按下的时候事件的走向:ACTION_DOWN->ACTION_POINTER_DOWN. 82 | 双指抬起的时候是:ACTION_POINTER_UP->ACTION_UP. 83 | 当我们想要拦截到双指事件的时候必须加上一个添加判断, 84 | switch (event.getAction() & event.getActionMasked()). 85 | ``` 86 | 87 | 88 | #### 问题1 只拦截点击事件? 89 | #### 问题2 只拦截滑动事件? 90 | #### 问题3 requestDisallowInterceptTouchEvent的原理 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Web前端相关/初识Weex.md: -------------------------------------------------------------------------------- 1 | ## 初识Weex 2 | 3 | Weex 是一个使用 Web 开发体验来开发高性能原生应用的框架。 4 | 5 | ### 环境搭建 6 | 7 | 环境搭建在这里就不详细讲述了,大家可以去官网瞅瞅。 8 | 9 | [Weex环境搭建](http://weex.apache.org/cn/guide/set-up-env.html) 10 | 11 | ### Weex 通用样式 12 | 13 | #### 1、适配和缩略 14 | 15 | 1.1、Weex 对于长度值目前只支持像素值,不支持相对单位(em,rem);适配以 750px 标准,详细可以参考:[weex-html5 组件进阶](https://yq.aliyun.com/articles/61067) 16 | 17 | 1.2、设定边框,border 目前不支持简写,类似`border: 1px solid #ff0000;`的组合写法,需要单独分开写。 18 | 19 | 1.3、设定背景颜色,background 目前不支持类似这样`background: red`的写法,需要写全。 20 | 21 | #### 2、定位属性 22 | 23 | 2.1、weex 支持 position 定位,`relative | absolute | fixed | sticky`,默认值为 relative。 24 | 25 | 2.2、weex 目前不支持 z-index 设置元素的层级关系,但靠后的元素层级更高,因此,对于层级高的元素,可将其排列在后面。 26 | 27 | 2.3、如果定位元素超过容器边界,在 Android 下,超出部分将不可见,原因在于 Android 端元素 overflow 默认值为 hidden。 28 | 29 | #### 3、文本属性 30 | 31 | `color;` `font-size;` `font-weight;` `font-style;` `font-family;` `text-decoration;` `text-align;` `text-overflow`(内容超长时的省略样式); lines(指定文本行数) 32 | 33 | #### 4、其他属性 34 | 35 | 4.1、weex 支持线性渐变(linear-gradient),不支持径向渐变(radial-gradient) 36 | 37 | 4.2、weex 中 box-shadow 仅仅支持 iOS。 38 | 39 | 4.3、目前组件无法定义一个或几个角的border-radius,只对 iOS 有效,对 Android 无效。 40 | 41 | 4.4、weex 中,flexbox 是默认并且唯一的布局模型,每个元素都默认拥有了`display:flex`属性。 42 | 43 | ### Weex 内建组件 44 | 45 | #### 1、a 组件 46 | 47 | 1.1、Weex 中 a 组件定义了指向 weex 页面打包后的一个 js 地址。 48 | 49 | 1.2、组件中无法添加文本,需要在其中 text 组件才能添加文本。 50 | 51 | 1.3、此组件支持除了自己外的所有 Weex 组件做为子组件。 52 | 53 | 1.4、支持所有通用样式。 54 | 55 | 1.5、请不要为 a 组件添加 click 事件。 56 | 57 | ``` 58 |
    59 | 点击跳转 60 | 61 | 62 | 注意:在weex中,a标签中直接写文字是没用的,需要嵌套一个标签,而且a标签不能跳转 63 | 一个html链接。 64 | ``` 65 | 66 | #### 2、web 组件 67 | 68 | 2.1、web 组件用于在页面中嵌入一张网页;src 用以指定资源地址。 69 | 70 | 2.2、不支持任何子组件 71 | 72 | 2.3、pagestart,web 组件开始加载时执行。 73 | 74 | 2.4、pagefinish,web 组件完成加载时执行。 75 | 76 | 2.5、error,web 组件加载错误时执行。 77 | 78 | ``` 79 | 89 | 90 | 108 | 109 | 120 | ``` 121 | 122 | #### 3、weex 内建模块 webview 模块 123 | 124 | 3.1、一系列 web 组件的操作接口。可以通过调用 this.$refs.el 来获取元素的引用。 125 | 126 | 3.2、goBack(webElement),加载历史记录里的前一个资源地址。 127 | 128 | 3.2、goForward(webElement),加载历史记录里的下一个资源地址。 129 | 130 | 3.3、reload(webElement),刷新当前页面。 -------------------------------------------------------------------------------- /面试相关/面试题详解.md: -------------------------------------------------------------------------------- 1 | ## 面试题详解 2 | 3 | ### 1、APK包含了哪些东西,打包过程是什么? 4 | #### APK组成: 5 | 6 | **(1)、classes.dex:**这是java源码编译后生成的java字节码文件 7 | 8 | **(2)、resources.arsc:**这是编译后的二进制资源文件 9 | 10 | **(3)、AndroidManifest.xml:**这个Manifest文件里面都是编译之后的,都是二进制,看不到什么信息 11 | 12 | **(4)、proguard.cfg:**这是代码混淆配置文件 13 | 14 | **(5)、**META-INF:该目录下存放的是签名信息,用来保证apk包的完整性和系统的安全 15 | 16 | **(6)、res:**该目录下存放的是资源文件,但是也是编译之后的,二进制的。 17 | 你试过就会知道,真正容量大的就是classes.dex、资源文件,当然了有时候也会有lib目录,lib目录下放的是一些jar包和so包,容量也是挺大的。 18 | 19 | #### 打包过程: 20 | (1)、打包资源文件,生成R.java文件 21 | 22 | (2)、处理aidl文件,生成相应java文件 23 | 24 | (3)、编译工程源代码,生成相应class文件 25 | 26 | (4)、转换所有class文件,生成classes.dex文件 27 | 28 | (5)、打包生成apk 29 | 30 | (6)、对apk文件进行签名 31 | 32 | (7)、对签名后的apk文件进行处理 33 | 34 | ### 2、Android 内存优化和布局优化 35 | 36 | 首先我们要了解Java的内存机制,堆区,栈区和方法区: 37 | 38 | #### 堆区(共享) 39 | 存储的全部是对象,每个对象都包含一个与之对应的class的信息(class的目的是得到操作指令jvm);只有一个heap区,被所有线程共享,不存放基本类型和对象引用,只存放对象本身。 40 | 41 | 堆的优劣势:堆的优势是可以动态的分配内存大小,生存期也不必事先告诉编译器,java的垃圾收集器会自动收取这些不在使用的数据,但缺点是,由于要在运行时动态分配内存,存取速度慢。 42 | 43 | #### 栈区(私有) 44 | 每一个线程包含一个stack区,只保存基本数据类型的对象和自定义对象的引用(不是对象),对象都存放在共享heap中;每个栈中的数据(基本数据类型和对象引用)都是私有的,其他栈不能访问;栈分为3部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令) 45 | 46 | 栈的优势劣势:存取速度比堆要快,仅次于直接位于CPU的寄存器,但必须确定的是存在stack中的数据大小与生存期必须是确定的,缺乏灵活性。单个stack的数据可以共享。 47 | 48 | **stack:**是一个先进后出的数据结构,通常保存方法中的参数,局部变量。在java中,所有基本类型和引用类型都在stack中储存,栈中数据的生存空间一般在当前scopes内。 49 | 50 | #### 方法区(共享) 51 | 又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量;方法区中包含的都是在程序中永远的唯一的元素 52 | 53 | #### 内存优化: 54 | 55 | **(1)、单例导致内存泄漏:**因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。 56 | 57 | **(2)、静态变量导致内存泄露:**静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。 58 | 59 | **(3)、非静态内部类导致内存泄露:**非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。 60 | 61 | **(4)、未取消注册或回调导致内存泄露:**比如我们在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个刚播会一直存在系统中,同上面所说的非静态内部类一样持有Activity引用,导致内存泄露。 62 | 63 | #### 布局优化: 64 | 布局的优化其实说白了就是减少层级,越简单越好,减少overdraw,就能更好的突出性能。下面介绍几种布局优化的方式: 65 | 66 | **(1)、首先是善用相对布局Relativelayout(尽量用):**一般情况下用LinearLayout的时候总会比RelativeLayout多一个View的层级。而每次往应用里面增加一个View,或者增加一个布局管理器的时候,都会增加运行时对系统的消耗,因此这样就会导致界面初始化、布局、绘制的过程变慢。 67 | 68 | **(2)、布局优化的另外一种手段就是使用抽象布局标签include、merge、ViewStub:**include标签常用于将布局中的公共部分提取出来;merge标签是作为include标签的一种辅助扩展来使用,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套;viewstub是view的子类。他是一个轻量级View, 隐藏的,没有尺寸的View。 69 | 70 | **(3)、Android最新的布局方式ConstaintLayout:**ConstraintLayout允许你在不适用任何嵌套的情况下创建大型而又复杂的布局。它与RelativeLayout非常相似,所有的view都依赖于兄弟控件和父控件的相对关系。但是,ConstraintLayout比RelativeLayout更加灵活,目前在AndroidStudio中使用也十分方便,就和以前的拖拉控件十分相似。 71 | 72 | **(4)、利用Android Lint工具寻求可能优化布局的层次** 73 | 74 | 75 | ### 3、Error和Exception的区别 76 | 77 | #### Error 78 | Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。如java.lang.StackOverFlowError和Java.lang.OutOfMemoryError。对于这类错误,Java编译器不去检查他们。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。 79 | 80 | #### Exception 81 | Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。 82 | 83 | #### try-catch-finally-return执行顺序 84 | (1)、不管是否有异常产生,finally块中代码都会执行; 85 | 86 | (2)、当try和catch中有return语句时,finally块仍然会执行; 87 | 88 | (3)、finally是在return后面的表达式运算后执行的,所以函数返回值是在finally执行前确定的。无论finally中的代码怎么样,返回的值都不会改变,仍然是之前return语句中保存的值; 89 | 90 | (4)、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。 -------------------------------------------------------------------------------- /面试相关/Android基础题目.md: -------------------------------------------------------------------------------- 1 | ## Android基础题目 2 | 3 | ### 选择题 4 | 5 | #### 1、Android四大组件中哪一个主要功能是负责与用户交互? ([B](https://blog.csdn.net/m0_37989980/article/details/78681367)) 6 | 7 | - A. Servce 8 | 9 | - B. Activity 10 | 11 | - C. Content Provider 12 | 13 | - D. BroadCast Receiver 14 | 15 | #### 2、以下哪种不是Android数据存储的方式? ([C](https://www.cnblogs.com/pxsbest/p/5068482.html)) 16 | 17 | - A. Shared Preferences 18 | 19 | - B. Content Provider 20 | 21 | - C. Bundle 22 | 23 | - D. SQLite 24 | 25 | #### 3、下列关于Activity的启动模式描述错误的是 ([C](https://www.cnblogs.com/lwbqqyumidi/p/3771542.html)) 26 | 27 | - A. 设置属性为singleTop时,若目标Activity位于task栈顶,则不再重复生成,直接使用栈顶Activity。 28 | 29 | - B. 设置属性为singleTask时,若task栈中已经存在目标Activity,则将其之上的Acticity全部出栈,并使其变为栈顶。 30 | 31 | - C. android:taskAffinity与singleTask搭配使用可以使Activity运行在指定的栈中。 32 | 33 | - D. 通过Intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)能实现android:launchMode=”singleTop”相同的效果。 34 | 35 | #### 4、通过 startService() 和 bindService(),以下说法错误的是 ([A](https://blog.csdn.net/dfskhgalshgkajghljgh/article/details/51471108)) 36 | 37 | - A. 如果是调用 bindService() 启动服务:会调用如下生命周期方法:onCreate() —> onBind —> onDestory() —> onUnBind() 38 | 39 | - B. 通过 startService() 启动服务:会调用如下生命周期方法:onCreate() —> onStart() —> onDestory() 40 | 41 | - C. 当采用 startService() 方法启动服务,访问者与服务之间是没有绑定在一起的,访问者退出,服务还在运行 42 | 43 | - D. 采用 bindService() 方法启动服务时,访问者与服务是绑定在一起的,即访问者退出,服务也就终止,解除绑定。 44 | 45 | #### 5、下面关于 Android dvm 的进程和 Linux 的进程 , 应用程序的进程说法正确的是? ([B](https://blog.csdn.net/lin111000713/article/details/52459710)) 46 | 47 | - A. DVM 指 Dalvik 的虚拟机。每一个 Android 应用程序都在它自己的进程中运行,都拥有一个独立的 Dalvik 虚拟机实例。而每一个 DVM 不一定都是在 Linux 中的一个进程,所以说不是一个概念。 48 | 49 | - B. DVM 指 Dalvik 的虚拟机。每一个 Android 应用程序都在它自己的进程中运行,都拥有一个独立的 Dalvik 虚拟机实例。而每一个 DVM 都是在 Linux 中的一个进程,所以说可以认为是同一个概念。 50 | 51 | - C. DVM 指 Dalvik 的虚拟机,每一个 Android 应用程序都在它自己的进程中运行,不一定拥有一个独立的 Dalvik 虚拟机实例。而每一个 DVM 都是在 Linux 中的一个进程,所以说可以认为是同一个概念。 52 | 53 | - D. DVM 指 Dalvik 的虚拟机,每一个 Android 应用程序都在它自己的进程中运行,不一定拥有一个独立的 Dalvik 虚拟机实例。而每一个DVM不一定都是在 Linux 中的一个进程,所以说不是一个概念。 54 | 55 | #### 6、以下哪种场景不会导致内存泄漏? ([D](https://www.jianshu.com/p/65f914e6a2f8)) 56 | 57 | - A. 查询数据库时未关闭游标Cursor。 58 | 59 | - B. 使用计时器时,未在不使用的时候关闭。 60 | 61 | - C. 动态注册的广播未在不使用的时候unregister。 62 | 63 | - D. 在Activity被destory的时候未将全局变量设成null。 64 | 65 | #### 7、在滴滴打车APP中点击支付到支付宝APP中进行支付操作,出现密码输入框,到此时相关的Activity会发生的生命周期回调依次为? ([C](https://www.cnblogs.com/lwbqqyumidi/p/3769113.html)) 66 | 67 | - A. onpause() —> ondestroy() —> oncreate() —> onresume() 68 | 69 | - B. ondestroy() —> oncreate() —> onstart() —> onresume() 70 | 71 | - C. onpause() —> oncreate() —> onstart() —> onresume() 72 | 73 | - D. onstop() —> ondestroy() —> oncreate() —> onstart() 74 | 75 | #### 8、以下关于内存回收的说明正确的是? ([B](https://www.cnblogs.com/ganchuanpu/p/8479518.html)) 76 | 77 | - A. 程序员必须创建一个线程来释放内存。 78 | 79 | - B. 内存回收程序负责释放无用内存。 80 | 81 | - C. 内存回收程序允许程序员直接释放内存。 82 | 83 | - D. 内存回收程序可以在指定的时间释放内存对象。 84 | 85 | #### 9、关于res/raw目录说法正确的是? ([A](https://blog.csdn.net/ztchun/article/details/60809640)) 86 | 87 | - A. 这里的文件是原封不动的存储到设备上不会转换为二进制的格式。 88 | 89 | - B. 这里的文件是原封不动的存储到设备上会转换为二进制的格式。 90 | 91 | - C. 这里的文件最终以二进制的格式存储到指定的包中。 92 | 93 | - D. 这里的文件最终不会以二进制的格式存储到指定的包中。 94 | 95 | #### 10、关于Handler的说法不正确的是? ([A](https://www.cnblogs.com/wlming/p/5553207.html)) 96 | 97 | - A. 它实现不同进程间通信的一种机制。 98 | 99 | - B. 它避免了在新线程中刷新UI的操作。 100 | 101 | - C. 它采用队列的方式来存储Message。 102 | 103 | - D. 它实现不同线程间通信的一种机制。 -------------------------------------------------------------------------------- /Web前端相关/初识JavaScript.md: -------------------------------------------------------------------------------- 1 | ## Android开发人员初识JavaScript 2 | 3 | JavaScript是一种脚本语言;网页,以及基于H5的手机app等都靠JavaScript来驱动;更简单的来说,JavaScript就像是一种运行在浏览器中的解释型语言。 4 | 5 | ### 变量 6 | 7 | 在JavaScript中,定义变量需要使用var关键字,语法如下: 8 | 9 | ``` 10 | var 变量名 11 | ``` 12 | 变量名要遵循命名规则: 13 | 14 | - 变量必须使用字母、下划线或者美元开始 15 | - 可以使用任意多个英文字母、数字、下划线或者美元符号组成 16 | - 不能使用JavaScript关键词与保留字作为变量名 17 | 18 | ![摘自慕课网](http://img.mukewang.com/529c07c000014f5103080447.jpg) 19 | 20 | ### 函数 21 | 和其他语言一样,JavaScript同样具有函数,在JavaScript中如何定义一个函数呢: 22 | 23 | ``` 24 | function 函数名() 25 | { 26 | 函数代码; 27 | } 28 | ``` 29 | 函数的定义遵循以下规则: 30 | 31 | - 一定要使用关键字function来定义函数 32 | - “函数名”不要使用中文 33 | 34 | ### 消息对话框 35 | 36 | 在JavaScript中,消息对话框有三种: 37 | 38 | ##### 1、alert警告框 39 | 40 | ![](http://ooaap25kv.bkt.clouddn.com/18-8-7/23074058.jpg) 41 | 42 | >**注意:** 43 | > 44 | >(1)、在点击对话框"确定"按钮前,不能进行任何其它操作。 45 | > 46 | >(2)、消息对话框通常可以用于调试程序。 47 | > 48 | >(3)、alert输出内容,可以是字符串或变量,与document.write 相似。 49 | 50 | ##### 2、confirm确认框 51 | 52 | confirm消息对话框通常用于允许用户做选择的动作,如:“你对吗?”等。弹出对话框(包括一个确定按钮和一个取消按钮)。 53 | 54 | ``` 55 | 用法: 56 | confirm(str); 57 | 58 | 参数说明: 59 | str:在消息对话框中要显示的文本 60 | 返回值: Boolean值 61 | 62 | 返回值: 63 | 当用户点击"确定"按钮时,返回true 64 | 当用户点击"取消"按钮时,返回false 65 | ``` 66 | ![](http://ooaap25kv.bkt.clouddn.com/18-8-7/66497542.jpg) 67 | 68 | ##### 3、prompt提问框 69 | 70 | prompt弹出消息对话框,通常用于询问一些需要与用户交互的信息。弹出消息对话框(包含一个确定按钮、取消按钮与一个文本输入框)。 71 | 72 | ``` 73 | 用法: 74 | prompt(str1, str2); 75 | prompt(str1); 76 | 77 | 参数说明: 78 | str1: 要显示在消息对话框中的文本,不可修改 79 | str2:文本框中的默认内容,可修改,也可为空 80 | 81 | 返回值: 82 | 当用户点击确定按钮时,文本框中的内容将作为函数返回值 83 | 当用户点击取消按钮时,将返回null 84 | ``` 85 | ![](http://ooaap25kv.bkt.clouddn.com/18-8-7/54850731.jpg) 86 | 87 | ### 打开新的窗口 88 | 89 | 使用window.open()方法可以打开一个已经存在或者新建的浏览器窗口。 90 | 91 | ``` 92 | window.open([URL], [窗口名称], [参数字符串]) 93 | ``` 94 | 95 | ##### 参数说明: 96 | 97 | **1、URL:** 98 | 99 | 可选参数,在窗口中要显示网页的网址或路径。如果省略这个参数,或者它的值是空字符串,那么窗口就不显示任何文档。 100 | 101 | **2、窗口名称:** 102 | 103 | 可选参数,被打开窗口的名称。 104 | 105 | >(1).该名称由字母、数字和下划线字符组成。 106 | > 107 | >(2)."_top"、"_blank"、"_self"具有特殊意义的名称。 108 | > 109 | _blank:在新窗口显示目标网页 110 | _self:在当前窗口显示目标网页 111 | _top:框架网页中在上部窗口中显示目标网页 112 | 113 | >(3).相同 name 的窗口只能创建一个,要想创建多个窗口则 name 不能相同。 114 | 115 | >(4).name 不能包含有空格。 116 | 117 | **3、参数字符串** 118 | 119 | ![摘自慕课网](http://img.mukewang.com/52e3677900013d6a05020261.jpg) 120 | 121 | ### 文档对象模型DOM 122 | 123 | ##### 1、通过ID来获取元素 124 | 125 | 在HTML中,元素的id是唯一的,那么我们可以通过id来获取某一元素,然后对标签进行动态操作。 126 | 127 | ``` 128 | document.getElementById(“id”) ; 129 | 130 | 获取的结果为null或者[object HTMLParagraphElement] 131 | ``` 132 | **注意:这里获取到的元素是一个对象,如果想对元素进行操作,可以通过元素的属性或者方法即可。** 133 | 134 | ##### 2、替换HTML元素内容 135 | 136 | 通过innerHTML属性就可以获取或替换 HTML 元素的内容。 137 | 138 | ``` 139 | Object.innerHTML = xxx; 140 | ``` 141 | Object是获取的元素对象,如通过document.getElementById("ID")获取的元素。 142 | 143 | ##### 3、改变HTML样式 144 | 145 | HTML DOM 允许 JavaScript 改变 HTML 元素的样式。 146 | 147 | ``` 148 | Object.style.元素属性 = new style; 149 | ``` 150 | Object是获取的元素对象,如通过document.getElementById("id")获取的元素。可以通过修改以下属性来改变HTML的样式: 151 | 152 | ![摘自慕课网](http://img.mukewang.com/52e4d4240001dd6c04850229.jpg) 153 | 154 | **注意:该表只是一小部分CSS样式属性,其它样式也可以通过该方法设置和修改。** 155 | 156 | ##### 4、显示与隐藏 157 | 158 | 在网页中,我们经常可以看到某个元素显示和隐藏的效果,是通过display属性来实现的。 159 | 160 | ``` 161 | Object.style.display = "none"; //隐藏元素 162 | 163 | bject.style.display = "block"; //显示元素 164 | ``` 165 | Object是获取的元素对象,如通过document.getElementById("id")获取的元素。 166 | 167 | ##### 5、控制类名 168 | 169 | 通过className属性设置或返回元素的class属性。 170 | 171 | ``` 172 | object.className = "css样式"; 173 | ``` 174 | 通常使用该属性为某个元素动态改变css样式。 -------------------------------------------------------------------------------- /设计模式相关/单例模式.md: -------------------------------------------------------------------------------- 1 | ## 单例模式 2 | 3 | ### 1、什么是单例模式? 4 | 单例对象必须确保只有一个实例存在,一个类有且只有一个实例,而且自行实例化并向整个系统提供这个实例。 5 | 6 | ### 2、使用场景 7 | 确保某个类只有一个对象(实例)的场景,避免产生多个对象消耗过多的资源,例如某个对象创建需要消耗的资源过多,如要访问IO和数据库等资源。 8 | 9 | ### 3、实现 10 | (1)、构造方法不对外开放,一般为private; 11 | 12 | (2)、通过一个静态方法或者枚举返回单例类对象; 13 | 14 | (3)、确保单例类的对象只有一个,尤其是在多线程环境下 15 | 16 | (4)、确保单例类对象在反序列化时不会重复构建对象 17 | 18 | ### 4、示例(列举4种实现单例模式的方法) 19 | 20 | #### (1)、饿汉单例模式(线程安全) 21 | ``` 22 | public class Singleton { 23 | 24 | private final static Singleton INSTANCE = new Singleton(); 25 | 26 | private Singleton(){} 27 | 28 | public static Singleton getInstance(){ 29 | return INSTANCE; 30 | } 31 | } 32 | ``` 33 | 饿汉单例模式解决了多线程并发的问题,因为在加载这个类的时候,就实例化了instance。 34 | 35 | **总结:饿汉单例模式的优点是写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题;缺点是在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。** 36 | 37 | #### (2)、懒汉模式(线程不安全) 38 | 懒汉模式是生命一个静态类对象,并且在用户第一次调用getInstance时进行初始化 39 | 40 | ``` 41 | public class Singleton{ 42 | 43 | private static Singleton INSTANCE; 44 | 45 | private Singleton(){} 46 | 47 | public static synchronized Singleton getInstance(){ 48 | if (null == INSTANCE){ 49 | INSTANCE = new Singleton(); 50 | } 51 | return INSTANCE; 52 | } 53 | } 54 | 55 | ``` 56 | 57 | 懒汉模式使用了一个synchronized关键字,所以getInstance是一个同步方法,这就是上述所说的在多线程情况下保证单例对象唯一的一种手段。 58 | 59 | **总结:懒汉单例模式的优点是单例只在使用时才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销,所以一般不建议使用** 60 | 61 | #### (3)、Double Check Lock(DCL)单例模式 62 | ``` 63 | public class Singleton { 64 | 65 | private static Singleton INSTANCE = null; 66 | 67 | private Singleton() { 68 | } 69 | 70 | public static Singleton getInstance() { 71 | if (null == INSTANCE) { 72 | synchronized (Singleton.class) { 73 | if (null == INSTANCE) { 74 | INSTANCE = new Singleton(); 75 | } 76 | } 77 | } 78 | return INSTANCE; 79 | } 80 | } 81 | ``` 82 | **总结:INSTANCE = new Singleton();这条语句并不是一个原子操作,这句代码最终会被编译成多条汇编指令,大致做了以下三件事:** 83 | 84 | (i)、给Singleton的实例分配内存 85 | 86 | (ii)、调用Singleton()的构造方法,初始化成员字段 87 | 88 | (iii)、将INSTANCE对象指向分配的内存空间(此时INSTANCE就不是null) 89 | 90 | **DCL单例模式的优点是资源利用率高,第一次执行getInstance时单例才会被实例化,效率高;缺点是第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败,在高并发环境下也有一定的缺陷。** 91 | 92 | #### (4)、静态内部类单例模式 93 | ``` 94 | public class Singleton { 95 | 96 | private Singleton() { 97 | } 98 | 99 | public static class SingletonInstance { 100 | private static final Singleton INSTANCE = new Singleton(); 101 | } 102 | 103 | public static Singleton getInstance() { 104 | return SingletonInstance.INSTANCE; 105 | } 106 | } 107 | ``` 108 | **总结:第一次加载Singleton类时并不会初始化INSTANCE,只有在第一次调用getInstance方法时才会导致INSTANCE被初始化。因此第一次调用INSTANCE调用getInstance方法会导致虚拟机加载SingletonInstance类,这种方式不仅能确保线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化,所以这才是被推荐使用的单例模式创建方法** 109 | 110 | 还有一些其他的方式可以用来创建单例类,比如使用枚举实现单例模式,使用容器实现单例模式 111 | 112 | ### 5、Android源码中的单例模式 113 | 在Android系统中,也有很多地方用到单例模式的,比如LayoutInflater类就是一个使用单例模式的类,还有很多的第三方库,也是用的单例模式,如EventBus等。 114 | 115 | ### 6、总结 116 | 单例模式是运用频率很高的模式,但是由于在客户端通常没有高并发的情况,因此选择这种实现方式不会有太大的影响,这里推荐使用上述说的静态内部类的方式去实现单例模式。 117 | #### 单例模式的优点: 118 | (1)、由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建和销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显 119 | 120 | (2)、由于单例模式只生产一个实例,所以减少了系统的性能开销,当一个对象的生产需要比较多的资源时,如读取配置、生产其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。 121 | 122 | (3)、单例模式可以避免对资源的多重占用,例如一个写文件的操作,由于只有一个实例存在内存,避免对同一个资源文件的同时写操作。 123 | 124 | (4)、单例模式可以在系统设置全局的访问点,优化和资源共享访问,例如,可以设计一个单例类,负责所有数据的映射关系。 125 | 126 | #### 单例模式的缺点: 127 | (1)、单例模式一般没有接口,扩展难度比较高,若要扩展出了修改基本代码基本上没有第二种途径可以实现。 128 | 129 | (2)、单例对象如果持有Context,那么很容易发生内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context。 -------------------------------------------------------------------------------- /Android源码相关/Fragment源码解析.md: -------------------------------------------------------------------------------- 1 | ## Fragment源码解析 2 | ### 1、简介 3 | Fragment是Android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑, 当然现在他仍然是平板APP UI设计的宠儿,而且我们普通手机开发也会加入这个Fragment,我们可以把他看成一个小型的Activity,又称Activity片段。 4 | 5 | ### 2、用法 6 | #### 2.1 Fragment添加 7 | ``` 8 | FragmentManager fragmentManager=getSupportFragmentManager(); 9 | //开启一个事物 10 | FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction(); 11 | fragmentTransaction.add(R.id.xxx,new Fragment()); 12 | //提交事物 13 | fragmentTransaction.commit(); 14 | ``` 15 | 16 | #### 2.2 Fragment替换 17 | ``` 18 | FragmentManager fragmentManager=getSupportFragmentManager(); 19 | //开启一个事物 20 | FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction(); 21 | fragmentTransaction.replace(R.id.xxx,new Fragment()); 22 | //提交事物 23 | fragmentTransaction.commit(); 24 | ``` 25 | 26 | #### 3、源码解析 27 | #### 3.1、add()方法 28 | ``` 29 | @Override 30 | public FragmentTransaction add(Fragment fragment, String tag) { 31 | doAddOp(0, fragment, tag, OP_ADD); 32 | return this; 33 | } 34 | 35 | private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { 36 | final Class fragmentClass = fragment.getClass(); 37 | final int modifiers = fragmentClass.getModifiers(); 38 | if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers) 39 | || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) { 40 | throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName() 41 | + " must be a public static class to be properly recreated from" 42 | + " instance state."); 43 | } 44 | 45 | fragment.mFragmentManager = mManager; 46 | 47 | if (tag != null) { 48 | if (fragment.mTag != null && !tag.equals(fragment.mTag)) { 49 | throw new IllegalStateException("Can't change tag of fragment " 50 | + fragment + ": was " + fragment.mTag 51 | + " now " + tag); 52 | } 53 | fragment.mTag = tag; 54 | } 55 | 56 | if (containerViewId != 0) { 57 | if (containerViewId == View.NO_ID) { 58 | throw new IllegalArgumentException("Can't add fragment " 59 | + fragment + " with tag " + tag + " to container view with no id"); 60 | } 61 | if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { 62 | throw new IllegalStateException("Can't change container ID of fragment " 63 | + fragment + ": was " + fragment.mFragmentId 64 | + " now " + containerViewId); 65 | } 66 | fragment.mContainerId = fragment.mFragmentId = containerViewId; 67 | } 68 | 69 | addOp(new Op(opcmd, fragment)); 70 | } 71 | 72 | void addOp(Op op) { 73 | mOps.add(op); 74 | op.enterAnim = mEnterAnim; 75 | op.exitAnim = mExitAnim; 76 | op.popEnterAnim = mPopEnterAnim; 77 | op.popExitAnim = mPopExitAnim; 78 | } 79 | ``` 80 | >add方法中又调用了doAddOp方法,这个方法主要就是获取你传进来的一些参数,然后再调用addOp方法设置一些参数;整体来说add方法主要就是设置一些参数,所以我们需要调用commit方法来改变,那么进到commit方法里面看一下 81 | 82 | #### 3.2、commit方法 83 | ``` 84 | @Override 85 | public int commit() { 86 | return commitInternal(false); 87 | } 88 | 89 | ...省略一些方法... 90 | 91 | void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { 92 | 这个方法会回调fragment的生命周期,最后会把fragment(调用onCreateView方法是会生成一个View)添加到container(也就是Activity)中 93 | } 94 | ``` 95 | 96 | #### 3.3、replace方法 97 | ``` 98 | @Override 99 | public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { 100 | if (containerViewId == 0) { 101 | throw new IllegalArgumentException("Must use non-zero containerViewId"); 102 | } 103 | 104 | doAddOp(containerViewId, fragment, tag, OP_REPLACE); 105 | return this; 106 | } 107 | ``` 108 | 109 | 我们可以看到,和add方法一样都是调用doAddOp方法,只不过传的最后一个参数是OP_REPLACE -------------------------------------------------------------------------------- /Web前端相关/Web实现手风琴效果.md: -------------------------------------------------------------------------------- 1 | ## Web实现手风琴效果 2 | 3 | ### 序言 4 | 5 | 今天给大家带来一个手风琴效果的图片,效果如下图所示: 6 | 7 | ![](http://ooaap25kv.bkt.clouddn.com/18-10-26/85041570.jpg) 8 | 9 | ### 实现 10 | 11 | 我们来简单分析一下,这里一共有两种效果,鼠标移动到某一列表项时,这个列表项会变大,里面的图片和文字也会变大,所以这个地方我们得写两套样式用来切换,然后就是监听鼠标的事件了,总体来说和之前写的 `tabBar` 功能也有点类似,下面来看看 `Html` 代码: 12 | 13 | ``` 14 | 32 | ``` 33 | 34 | 这里我只是写了列表项中的其中一项,剩下的几项复制粘贴一下就好了,接下来看看CSS的代码: 35 | 36 | ``` 37 | .container { 38 | width: 938px; 39 | height: 128px; 40 | margin: 0 auto; 41 | border: 1px solid #cccccc; 42 | } 43 | 44 | .container li{ 45 | width: 156px; 46 | height: 128px; 47 | float: left; 48 | overflow: hidden; 49 | } 50 | 51 | .container li a{ 52 | width: 156px; 53 | height: 128px; 54 | position: relative; 55 | display: block; 56 | overflow: hidden; 57 | text-decoration: none; 58 | } 59 | 60 | .container li img{ 61 | height: 72px; 62 | width: 117px; 63 | position: absolute; 64 | bottom: 0; 65 | right: -15px; 66 | } 67 | 68 | .description { 69 | width: 136px; 70 | padding: 4px 10px; 71 | position: absolute; 72 | top: 0; 73 | left: 0; 74 | } 75 | 76 | .description h3{ 77 | font-size: 14px; 78 | font-weight: 700; 79 | } 80 | 81 | .description p{ 82 | color:#868686; 83 | font-size: 12px; 84 | height: 22px; 85 | width: 136px; 86 | overflow: hidden; 87 | line-height: 22px; 88 | } 89 | 90 | .description .price { 91 | font-size: 14px; 92 | font-style: italic; 93 | color: #fa2a5d; 94 | height: 35px; 95 | } 96 | 97 | .container .line{ 98 | position: absolute; 99 | right: 0; 100 | width: 0; 101 | border: 1px dashed #cacaca; 102 | height: 128px; 103 | } 104 | 105 | .container .mask{ 106 | position: absolute; 107 | top: 0; 108 | left: 0; 109 | height: 128px; 110 | width: 156px; 111 | background-color: #000; 112 | opacity: 0; 113 | } 114 | 115 | .container:hover .mask { 116 | opacity: 0.15; 117 | } 118 | 119 | 120 | /* 以下是变大之后的 */ 121 | 122 | .container li.big, li.big a{ 123 | width: 314px; 124 | height: 128px; 125 | } 126 | 127 | .container li.big img { 128 | width: 195px; 129 | height: 120px; 130 | right: 0; 131 | bottom: 0; 132 | } 133 | 134 | .container li.big .description { 135 | width: 290px; 136 | } 137 | 138 | .container li.big h3 { 139 | font-size: 18px; 140 | } 141 | 142 | .container li.big .description p { 143 | font-size: 14px; 144 | width: 166px; 145 | } 146 | 147 | .container li.big p.price { 148 | font-size: 16px; 149 | padding-top: 7px; 150 | } 151 | 152 | .container li.big a:hover .mask{ 153 | opacity: 0; 154 | } 155 | .container li:hover{ 156 | width: 312px; 157 | } 158 | ``` 159 | 160 | 这里用到了一个新的布局,`relative` 相对定位布局,这个相对是相对于父布局的,可以设置 `top`,`left`,`bottom`,`right`是个方向上相对父布局的距离,然后再给子布局设置绝对定位布局,这样的话就表示子布局只会跟着父布局改动而改动了。然后还有一个遮罩层的概念,就是给某个元素设置 `opacity` 属性,这个的取值是0-1,一个透明度的变化,最后再来定义一个 `big` 的样式,就算完成了;那么我们怎么给布局动态的添加 `class`属性呢,来看看 `Js` 的实现: 161 | 162 | ``` 163 | window.onload = function () { 164 | var outer = document.getElementById('header'); 165 | var lis = outer.getElementsByTagName('li'); 166 | if (null != lis) { 167 | for (var i=0; i 先调用handleLauncherActivity 173 | 174 | ——> 再调用performLauncherActivity 175 | 176 | ——> 然后调Activity.onCreate方法 177 | 178 | ——> 然后调handleResumeActivity方法 179 | 180 | ——> 然后调performResumeActivity 181 | 182 | ——> Activity的onResume方法 183 | 184 | ——> wm.addView(decor,1); 这时候才开始把DecorView加载到WindowManager上来,这时候才开始View的绘制流程,才开始执行measure(),layout(),draw() 185 | 186 | ##### 之所以能拿到控件的宽高是因为调用了onMeasure方法,setContentView只是创建了DecorView 把我们的布局加载到DecorView 187 | 188 | 189 | ### View的绘制流程: 190 | 191 | ——> wm.addView(decor,1); 192 | 193 | ——> WindowmanagerImpl.addView(); 194 | 195 | ——> root.setView(view, wparams, panelParentView); 196 | 197 | ——> requestLayout(); 198 | 199 | ——> scheduleTraversals(); 200 | 201 | ——> 调用mTraversalRunnable里面的doTraversal(); 202 | 203 | ——> **performTraversals();** 这里才开始View的测量,摆放,绘制等操作 204 | 205 | 第一个调用的方法:performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 206 | 207 | ——> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 208 | 209 | ——> onMeasure(widthMeasureSpec, heightMeasureSpec);测量开始 210 | 211 | ——> for循环,拿ViewGroup的子布局挨个进行测量 212 | 213 | ——> wrap_content对应的测量模式为AT_MOST;match_parent fill_parent 100dp对应的测量模式为EXACTLY,该模式和大小是由父布局和自己决定的 214 | 215 | 1、如果父布局是wrap_content,就算子布局是match_parent,这个时候计算的测量模式还是AT_MOST 216 | 217 | 2、如果父布局是match_parent,子布局是match_parent,这个时候计算的测量模式还是EXACTLY 218 | 219 | ——> child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 220 | 221 | ——> 然后调setMeasureDimension()方法,这个时候布局控件才算真正有了宽高; 222 | 223 | ——> 接着执行ViewGroup的onMeasure()方法,这个时候要指定自己(外层布局)的宽高了,childWidth = child.getMeasuredWidth() + share; 224 | 225 | ——> 同样,在**performTraversals()**方法中,也分别调用了OnLayout()和OnDraw()方法 226 | 227 | ### getWidth和getMeasureWidth 228 | 1、getWidth在onLayout方法执行完之后才会有数据,getMeasureWidth在onMeasure方法执行完之后才会有数据,所以在onMeasure之后可以调用getMeasuredWidth获取宽度,在onLayout()之后,可以用getWitdth获取宽度 229 | 230 | 2、当布局继承ViewGroup时,重写onLayout方法时,getWidth和getMeasureWidth返回的值才会不一致 231 | 232 | ### measure,layout和draw 233 | measure和layout都是通过遍历的方式进行,而draw是通过递归的方式进行的 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /Web前端相关/Web实现Tab选项卡功能(二).md: -------------------------------------------------------------------------------- 1 | ## Web实现Tab选项卡功能(二) 2 | 3 | 4 | ##### [Web实现Tab选项卡功能(一)](https://www.jianshu.com/p/64fba48bd5cd) 5 | 6 | 7 | ### 序言 8 | 9 | 废话就不多说了,直接上图,今天带来一个`Tab`选项卡 + 二级菜单的功能,相信在各大网站上也很常见吧! 10 | 11 | ![](http://ooaap25kv.bkt.clouddn.com/18-10-25/19633381.jpg) 12 | 13 | ### 实现 14 | 15 | 先来看看 `html`代码: 16 | 17 | ``` 18 | 19 | 63 | 64 | ``` 65 | 66 | 是不是跟昨天差不多,只不过在每一个一级菜单里面添加了一个二级菜单,效果是一样的,同样,我们来看看没有经过 `CSS` 洗礼的原型图: 67 | 68 | ![](http://ooaap25kv.bkt.clouddn.com/18-10-25/76806867.jpg) 69 | 70 | 这样肯定不是我们想看到的,下面我们来看一下 `CSS` 部分的代码: 71 | 72 | ``` 73 | .tabBar{ 74 | width: 800px; 75 | height: 50px; 76 | background-color: #ffffff; 77 | margin: 10px auto; 78 | } 79 | 80 | .nav { 81 | width:760px; 82 | margin: 0 auto; 83 | } 84 | 85 | li { 86 | list-style: none; 87 | } 88 | 89 | a{ 90 | text-decoration: none; 91 | text-align: center; 92 | display: block; 93 | } 94 | 95 | .nav li { 96 | width: 150px; 97 | height: 50px; 98 | float: left; 99 | } 100 | 101 | .nav li a{ 102 | width: 150px; 103 | height: 50px; 104 | margin: 0 1px; 105 | background-color: #e1e1e1; 106 | line-height: 50px; 107 | color: #000000; 108 | } 109 | 110 | .nav li a:hover{ 111 | color: #ffffff; 112 | background-color: #f60; 113 | } 114 | 115 | .subNav{ 116 | height: 0px; 117 | overflow: hidden; 118 | } 119 | 120 | .subNav li a{ 121 | width: 150px; 122 | height: 50px; 123 | text-align: center; 124 | background-color: #f7f7f7; 125 | line-height: 50px; 126 | color: #666666; 127 | } 128 | 129 | .subNav li a:hover{ 130 | color: #ffffff; 131 | background-color: #f60; 132 | } 133 | ``` 134 | 135 | 大部分CSS代码还是和昨天写的一样,讲一下新用到的东西,我们在正常情况下是不显示二级菜单的,所以这里我们使用`overflow: hidden;`来实现,这个属性的意思就是当内容溢出元素框时,我就隐藏起来,它不止 `hidden` 这一个值可以设置,还有其他的;而且要将 `Subnav` 的高度设置成0,这样才能完全隐藏二级菜单,其他设置和之前一样;那么怎么让鼠标移动到某个 `tabBar` 元素之后,显示该元素下的二级菜单呢?我们都知道,在 `Web` 中,`Js` 被用来处理用户与网页客户端的交互,所以这就要借助我们的 `Js` 来完成这件事了,上代码: 136 | 137 | ``` 138 | window.onload = function() { 139 | var lis = document.getElementsByTagName('li'); 140 | for (var i=0; i= 200){ 149 | clearInterval(that.time); 150 | } 151 | }, 20) 152 | } 153 | } 154 | lis[i].onmouseleave = function () { 155 | var nav = this.getElementsByTagName('ul')[0]; 156 | if (null != nav) { 157 | var that = nav; 158 | clearInterval(that.time); 159 | that.time = setInterval(function () { 160 | that.style.height = that.offsetHeight - 16 + "px"; 161 | if (that.offsetHeight <= 0) { 162 | that.style.height = 0 +"px"; 163 | clearInterval(that.time); 164 | } 165 | }, 20) 166 | } 167 | } 168 | } 169 | } 170 | ``` 171 | 172 | `window.onload` 方法是页面在加载完之后就会被立即执行,这样避免代码中一些对象未被加载就被操作了,然后获取 `li` 元素,并对每个 `li` 元素设置鼠标监听事件,当鼠标经过的时候,循环执行一个函数,一个增加 `subNav` 的高度的函数,当增加的高度超过了二级菜单中的高度时,取消函数的执行,这样就能形成一个打开的动画,同理,在鼠标移除监听事件中,也循环执行减去 `subNav` 高度的函数,这样就能形成一个关闭动画。 -------------------------------------------------------------------------------- /Android杂记/浅谈Activity、Window、View之间的关系.md: -------------------------------------------------------------------------------- 1 | ## 浅谈Activity、Window、View之间的关系 2 | 3 | ### 序言:很多人都会用Activity、Window、View,但是你知道他们是怎样加载出来并呈现在你眼前的吗?你知道他们之间有着鲜为人知的关系吗? 4 | 5 | ![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537872442296&di=7411d17ac60eb7be6dcd9c99e134eda7&imgtype=0&src=http%3A%2F%2Fhimg2.huanqiu.com%2Fattachment2010%2F2017%2F0411%2F20170411011851880.jpg) 6 | 7 | 讲个很简单的例子,这一天天气甚好,小明外出写生,小明背了一包东西,画板啊,纸啊,笔啊什么的,然后小明找了一处风景甚好的地方,从包里拿出画板,纸,笔然后开始画画,不一会儿小明就画完了一幅风景图。在这个例子当中,画板就好比`Activity`,纸就好比`Window`,而笔就是`View`,我们所看到的就是这幅画,是通过笔一点一点画出来的,在哪里画呢?当然是纸上了,而最终承载这幅画的东西就是画板了。这么说可能不太生动,下面,我们从源码的角度来看看这三者的关系。 8 | 9 | ### Activity的创建过程 10 | 11 | 我们都知道,Activity启动的时候是从ActivityThread中的Handler中发起的,然后经过handlerLauncher等一系列方法,如果还不知道的话可以去参考我之前写的[一步一步带你探索Activity的启动流程](https://github.com/24Kshign/Android-Knowledge/blob/a8b38bf4765df13fee3a573249a1dc472f3a7e6c/Android%E6%BA%90%E7%A0%81%E7%9B%B8%E5%85%B3/Activity%E7%9A%84%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.md) 12 | 13 | ``` 14 | ActivityThread类: 15 | 16 | private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { 17 | 18 | ... 19 | WindowManagerGlobal.initialize(); 20 | Activity a = performLaunchActivity(r, customIntent); 21 | ... 22 | } 23 | ``` 24 | 25 | 在这里先调用了`WindowManagerGlobal`中的初始化方法初始化了`WindowManagerService`,看名字大概就能知道这是一个`WindowManager`的服务,通过这个服务可以对页面进行操作;然后通过调用`performLaunchActivity`方法生成了一个Activity。 26 | 27 | 28 | ### Window的创建过程 29 | 30 | 上面通过`performLaunchActivity`方法生成了一个Activity,我们来看看是怎样生成的: 31 | 32 | ``` 33 | ActivityThread类: 34 | 35 | private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { 36 | 37 | ... 38 | Activity activity = null; 39 | try { 40 | activity = mInstrumentation.newActivity(cl, 41 | component.getClassName(), r.intent); 42 | } catch (Exception e) { 43 | ... 44 | } 45 | ... 46 | 47 | if (activity != null) { 48 | activity.attach(appContext, this, getInstrumentation(), r.token, 49 | r.ident, app, r.intent, r.activityInfo, title, r.parent, 50 | r.embeddedID, r.lastNonConfigurationInstances, config, 51 | r.referrer, r.voiceInteractor, window, r.configCallback); 52 | } 53 | 54 | ... 55 | } 56 | ``` 57 | 在这个方法中,通过`newActivity`这个方法(反射)来生成了一个`Activity`,生成好了`Activity`之后就调用`Activity`中的`attach`方法,来看一下这个方法里面干了些什么: 58 | 59 | ``` 60 | final void attach(Context context, ActivityThread aThread, 61 | Instrumentation instr, IBinder token, int ident, 62 | Application application, Intent intent, ActivityInfo info, 63 | CharSequence title, Activity parent, String id, 64 | NonConfigurationInstances lastNonConfigurationInstances, 65 | Configuration config, String referrer, IVoiceInteractor voiceInteractor, 66 | Window window, ActivityConfigCallback activityConfigCallback) { 67 | 68 | mWindow = new PhoneWindow(this, window, activityConfigCallback); 69 | mWindow.setWindowControllerCallback(this); 70 | mWindow.setCallback(this); 71 | mWindow.setOnWindowDismissedCallback(this); 72 | mWindow.getLayoutInflater().setPrivateFactory(this); 73 | if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { 74 | mWindow.setSoftInputMode(info.softInputMode); 75 | } 76 | if (info.uiOptions != 0) { 77 | mWindow.setUiOptions(info.uiOptions); 78 | } 79 | } 80 | ``` 81 | 果然,在`Activity`的`attach`方法中创建了一个`Window`,这个`Window`就是我们经常听到的`PhoneWindow` 82 | 83 | ### View的创建过程 84 | 85 | 我们大胆的猜测一下,`View`应该是被添加到`Window`中的,那么我们来看一下,到底是怎样添加的呢?上面说到在`handlerLauncher`中调用了`performLaunchActivity`方法,源码中还调用了`handleResumeActivity`方法,这个方法是在生命周期`onCreate`之后,`onResume`之前调用的,我们来看一下在这个方法中干了些什么: 86 | 87 | ``` 88 | final void handleResumeActivity(IBinder token, 89 | boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { 90 | 91 | ... 92 | r = performResumeActivity(token, clearHide, reason); 93 | ... 94 | if (r.window == null && !a.mFinished && willBeVisible) { 95 | r.window = r.activity.getWindow(); 96 | View decor = r.window.getDecorView(); 97 | decor.setVisibility(View.INVISIBLE); 98 | ViewManager wm = a.getWindowManager(); 99 | WindowManager.LayoutParams l = r.window.getAttributes(); 100 | a.mDecor = decor; 101 | 102 | ... 103 | wm.addView(decor, l); 104 | ... 105 | } 106 | } 107 | ``` 108 | 这里会先获取一个`Window`和`DecorView`,然后拿到`ViewManager`(`WindowManager`的父类),然后调用`addView`方法,`ViewManager`和`WindowManager`都是接口,那么我们只要到他的实现类`WindowManagerImpl`中去找`addView`方法就可以了: 109 | 110 | ``` 111 | @Override 112 | public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { 113 | applyDefaultToken(params); 114 | mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); 115 | } 116 | ``` 117 | 这个`mGlobal`就是我们之前的`WindowManagerGlobal`,看到这里相信大家应该有点眉目了吧,最终是由这货负责把`DecorView`添加到Window中,在`WindowManagerGlobal`中的`addView`方法中还会初始化`ViewRootImpl`,有兴趣的可以自行看源码了解一下 118 | 119 | `XML`中的`View`是如何添加到`DecorView`中的这个也不在这里分析了,可以参考我之前写的[一步一步带你解析setContentView源码](https://github.com/24Kshign/Android-Knowledge/blob/a8b38bf4765df13fee3a573249a1dc472f3a7e6c/Android%E6%BA%90%E7%A0%81%E7%9B%B8%E5%85%B3/setContentView%E6%BA%90%E7%A0%81.md) 120 | 121 | ### 总结 122 | 123 | 啥也不说了,上图 124 | 125 | ![](http://ooaap25kv.bkt.clouddn.com/18-6-15/38756758.jpg) -------------------------------------------------------------------------------- /Web前端相关/Android开发人员不得不学习的CSS3基础.md: -------------------------------------------------------------------------------- 1 | ## Android开发人员不得不学习的CSS3基础 2 | 3 | `CSS3` 是 `CSS` 的升级版,和 `CSS` 一样都是控制网页的样式和布局的,但是新增了很多种属性便于我们更好的给 `Html` 元素设置样式。 4 | 5 | ### 边框 6 | 7 | 用 `CSS3`,你可以创建圆角边框,添加阴影框,边框有以下几个属性: 8 | 9 | - border-radius 10 | 11 | - box-shadow 12 | 13 | - border-image 14 | 15 | **1、CSS3的圆角边框** 16 | 17 | ``` 18 | div { 19 | // 语法 20 | // border-radius:top-left top-right bottom-right bottom-left; 21 | 22 | border:2px solid; 23 | border-radius:25px; 24 | } 25 | ``` 26 | 27 | `border-radius` 中有四个值,分别是左上角,右上角,右下角,左下角;你也可以单独设置: 28 | 29 | | 值 | 说明 | 30 | | ----------|:-----------:| 31 | | border-top-left-radius| 定义了左上角的弧度 | 32 | | border-top-right-radius| 定义了右上角的弧度 | 33 | | border-bottom-right-radius| 定义了右下角的弧度 | 34 | | border-bottom-left-radius| 定义了左下角的弧度 | 35 | 36 | 37 | **2、CSS3阴影** 38 | 39 | ``` 40 | // 语法: 41 | // box-shadow: h-shadow v-shadow blur spread color inset; 42 | 43 | div { 44 | box-shadow: 10px 10px 5px #888888; 45 | } 46 | ``` 47 | 48 | 你可以通过设置 `box-shadow` 属性来给元素设置阴影,以下是一些属性值: 49 | 50 | | 值 | 说明 | 51 | | ----------|:-----------:| 52 | | h-shadow | 必需的。水平阴影的位置。允许负值 | 53 | | v-shadow | 必需的。垂直阴影的位置。允许负值 | 54 | | blur | 可选。模糊距离 | 55 | | spread | 可选。阴影的大小 | 56 | | color | 可选。阴影的颜色。 | 57 | | inset | 可选。从外层的阴影(开始时)改变阴影内侧阴影 | 58 | 59 | **3、CSS3边界图片** 60 | 61 | ``` 62 | div { 63 | // 语法: 64 | // border-image: source slice width outset repeat|initial|inherit; 65 | 66 | border-image:url(border.png) 30 30 round; 67 | -webkit-border-image:url(border.png) 30 30 round; /* Safari 5 and older */ 68 | -o-border-image:url(border.png) 30 30 round; /* Opera */ 69 | } 70 | ``` 71 | 72 | 你还可以用图片给元素添加边框,需要注意的是,这个要适配不同的浏览器: 73 | 74 | | 值 | 说明 | 75 | | ----------|:-----------:| 76 | | source | 用于指定要用于绘制边框的图像的位置 | 77 | | slice | 图像边界向内偏移 | 78 | | width | 图像边界的宽度 | 79 | | outset | 用于指定在边框外部绘制 border-image-area 的量 | 80 | | repeat | 用于设置图像边界是否应重复(repeat)、拉伸(stretch)或铺满(round)。 | 81 | 82 | ### 过渡 83 | 84 | 在 `CSS3`中,我们为了给某个元素从某种样式转变到另一种样式添加效果,可以使用 `transition`属性即可: 85 | 86 | ``` 87 | div { 88 | transition-property: width; 89 | transition-duration: 1s; 90 | transition-timing-function: linear; 91 | transition-delay: 2s; 92 | /* Safari */ 93 | -webkit-transition-property:width; 94 | -webkit-transition-duration:1s; 95 | -webkit-transition-timing-function:linear; 96 | -webkit-transition-delay:2s; 97 | } 98 | ``` 99 | 这里同样需要做适配,还可以简写,把所有的属性写在一起,类似于: 100 | 101 | ``` 102 | div { 103 | transition: width 1s linear 2s; 104 | /* Safari */ 105 | -webkit-transition:width 1s linear 2s; 106 | } 107 | ``` 108 | 109 | | 值 | 说明 | 110 | | ----------|:-----------:| 111 | | transition-property | 规定应用过渡的 CSS 属性的名称。 | 112 | | transition-duration | 定义过渡效果花费的时间。默认是 0。 | 113 | | transition-timing-function | 规定过渡效果的时间曲线。默认是 "ease"。 | 114 | | transition-delay | 规定过渡效果何时开始。默认是 0。 | 115 | 116 | ### 弹性盒子 117 | 118 | 是一种当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。 119 | 120 | ``` 121 | div { 122 | display: flex; //将该div内部设置成弹性布局 123 | flex-diretion: row; //设置弹性布局的方向是横向的 124 | } 125 | ``` 126 | 127 | 弹性布局的方向有以下四种: 128 | 129 | - row:横向从左到右排列(左对齐),默认的排列方式。 130 | 131 | - row-reverse:反转横向排列(右对齐,从后往前排,最后一项排在最前面。 132 | 133 | - column:纵向排列。 134 | 135 | - column-reverse:反转纵向排列,从后往前排,最后一项排在最上面。 136 | 137 | 除此之外,弹性布局还有两种居中的方式: 138 | 139 | **1、justify-content** 140 | 141 | `justify-content` 内容对齐,把弹性项沿着弹性容器的主轴线(main axis)对齐。 142 | 143 | ``` 144 | justify-content: flex-start | flex-end | center | space-between | space-around 145 | ``` 146 | 147 | - **flex-start:** 148 | 弹性项目向行头紧挨着填充。这个是默认值。第一个弹性项的main-start外边距边线被放置在该行的main-start边线,而后续弹性项依次平齐摆放。 149 | 150 | - **flex-end:** 151 | 弹性项目向行尾紧挨着填充。第一个弹性项的main-end外边距边线被放置在该行的main-end边线,而后续弹性项依次平齐摆放。 152 | 153 | - **center:** 154 | 弹性项目居中紧挨着填充。(如果剩余的自由空间是负的,则弹性项目将在两个方向上同时溢出)。 155 | 156 | - **space-between:** 157 | 弹性项目平均分布在该行上。如果剩余空间为负或者只有一个弹性项,则该值等同于flex-start。否则,第1个弹性项的外边距和行的main-start边线对齐,而最后1个弹性项的外边距和行的main-end边线对齐,然后剩余的弹性项分布在该行上,相邻项目的间隔相等。 158 | 159 | - **space-around:** 160 | 弹性项目平均分布在该行上,两边留有一半的间隔空间。如果剩余空间为负或者只有一个弹性项,则该值等同于center。否则,弹性项目沿该行分布,且彼此间隔相等(比如是20px),同时首尾两边和弹性容器之间留有一半的间隔(1/2*20px=10px)。 161 | 162 | **2、align-items** 163 | 164 | `align-items` 设置或检索弹性盒子元素在侧轴(纵轴)方向上的对齐方式。 165 | 166 | ``` 167 | align-items: flex-start | flex-end | center | baseline | stretch 168 | ``` 169 | 170 | - **flex-start:** 171 | 弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴起始边界。 172 | 173 | - **flex-end:** 174 | 弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴结束边界。 175 | 176 | - **center:** 177 | 弹性盒子元素在该行的侧轴(纵轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度)。 178 | 179 | - **baseline:** 180 | 如弹性盒子元素的行内轴与侧轴为同一条,则该值与'flex-start'等效。其它情况下,该值将参与基线对齐。 181 | 182 | - **stretch:** 183 | 如果指定侧轴大小的属性值为'auto',则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照'min/max-width/height'属性的限制。 184 | 185 | 以下是 `CSS3` 弹性盒子常用的属性: 186 | 187 | | 值 | 说明 | 188 | | ----------|:-----------:| 189 | | flex-direction | 指定了弹性容器中子元素的排列方式 | 190 | | justify-content | 设置弹性盒子元素在主轴(横轴)方向上的对齐方式。 | 191 | | align-items | 设置弹性盒子元素在侧轴(纵轴)方向上的对齐方式。 | 192 | | flex-wrap | 设置弹性盒子的子元素超出父容器时是否换行。 | 193 | | align-content | 修改 flex-wrap 属性的行为,类似 align-items, 但不是设置子元素对齐,而是设置行对齐 | 194 | | flex-flow | flex-direction 和 flex-wrap 的简写 | 195 | | order | 设置弹性盒子的子元素排列顺序。 | 196 | | align-self | 在弹性子元素上使用。覆盖容器的 align-items 属性。 | 197 | | flex | 设置弹性盒子的子元素如何分配空间。 | 198 | -------------------------------------------------------------------------------- /Android杂记/一个Android程序猿的北漂之路.md: -------------------------------------------------------------------------------- 1 | ### 初衷 2 | 3 | 写这篇文章的初衷就是想告诫自己,一定要自律坚持做一些有意义的事,别把时间都浪费。说这简单,但做起来都很难。希望有缘看到次文章的朋友能好好把握规划下自己的人生,一辈子真的不长,不要让自己后悔。 4 | 5 | ### 个人经历背景 6 | 7 | 笔者今年24,目前就职在北京从事安卓开发,老家是安徽的,我没有上过大学,记得16岁就出入社会了。去过很多地方,干过很多工作例如: -摄影-服务员-工地-销售等等。每份工作周期最久的也就半年多,从来没有在一个行业待过一年,在家里听到最多的也是家长的唠叨,相信很多朋友和我一样,没有目标,做服务员嫌累,体面的嫌工资低,到最后什么都没有做,时间也全都被浪费。 8 | 9 | 很多时候我也经常在夜里问自己,想要什么,追求是什么,可能当时年轻,想的就是,体面点工资高点,父母跟外人说起我时能给他们长面子。也经常暗暗发誓,这次一定要认真做好好赚钱,但是总是一次次让自己失望让在乎的人失望,直到发生了一些事才让自己**大彻大悟**。 10 | 11 | ##### 关于感情 12 | 13 | 对于爱情我们脑子里浮现里的都是甜蜜温馨的画面,虽然总说一个人也可以活得很精彩,我想大家都一样都是用来安慰自己的。而我的感情可以说是遗憾,也怪自己太年轻在无能为力的年龄遇见了**她**。 14 | 15 | 说说我的故事,我的第一个女朋友是在做销售的时候认识的,当时19岁,那个时候我什么都不懂相比较她会洗衣服、做饭、做家务。而我只会坐在电脑面前玩游戏,好吃懒作。现在每每想到这些心就会痛。当时的我真的是渣到了极点。 16 | 17 | 我们相爱的这段时间我没有给她买过一件像样的东西,她每天起的特别早每天洗漱完喊醒赖床的我,我生病的时候会给我买药,上火给我买罐头而她犯胃病的时候我却只会傻呆呆的站在旁边却不知道要怎么办。第一次带她回家,我妈第一眼看见就非常喜欢她,说她非常干练把我照顾的特别好也能管住我,而我那时候却不懂珍惜,我经常打夜市骗她说我妈想我了今晚回家睡不回去了其实就是在通宵玩。 18 | 19 | 久而久之,她对我也越来越失望,那会碰到淡季工资越来越少,眼高手低的我又辞去了工作在家一歇又是几个月。而这次她终于提出了分手。记得分手那时我还在她家楼下发疯大呼小叫,还一直问她为什么 这么狠心 这么绝情现在想想当时自己多么可笑幼稚,连自己都照顾不了还怎么照顾别人。可能是第一份感情,对我的冲击很大。没有了她以后每天魂不守舍,抽着烟喝着啤酒学着电视的颓废情节。失恋过的应该都知道那种感觉感觉人生没继续下去的意义了。振作过来以后又暗暗给自己下决心要好好奋斗让她后悔,过后的每段日子我都会搜她的QQ号想在空间能知道她的一点消息,而今得知现在她已经是两个孩子的妈了,过得很幸福而我还是一个人,当初下的决心并没有让我洗心革面,时间并没有等我。 20 | 21 | ##### 转折 22 | 23 | 人总是在绝境的时候才会逼自己一把,22岁的某一天从小带我到大的外婆因为夏天务农天气太热导致了脑梗塞左边身体全部瘫痪。可以说外婆是我最亲的人,得知这件事以后整个人都快软下去了。记得那会在医院,外婆躺在病床看到我我喊她她却不认识我家里人告诉我她先在谁也不记得了也不能动弹,听到这些眼泪就掉出来。但是我妈没有放弃,在外婆恢复的期间内我妈一直给外婆看照片,“这是你孙子这是你大儿子”你好好想想,每天不断给她重复给她左半边做康复运动。慢慢的,外婆开始有意识了,记得那会他喊我虽然喊的是别的名字但是我心里清楚他喊得就是我我说你喊的是不是钟钟阿她点头笑着, 当时心里特比开心,再到后来基本都能认全家里人了而且能自己拄着拐杖走路了,虽然口齿没以前清楚了。后面外婆在我家小姨家轮住了一段时间以后又回乡下了外公怕给我们添麻烦决定自己照顾她,外公的年级也大了。而且以前都是外婆做饭做家务,现在全部由外公来了,做饭家务照顾奶奶洗漱日常... 24 | 25 | 有次回家看外公外婆,老远外婆看到我就对外公喊着大孙子回来了,自从上班以来很少回老家了以前每个暑假都会回乡下跟外婆外公过。外公忙活这给我做顿大餐,关心的问我好不好也不给我们打个电话。听着这些话 看着外公外婆的忙碌的背影,鼻子就是一酸。他们还能有多少时间等你给他们享福,小的时候他们辛苦照顾我把我带到大,而我现在逢年过节都没有拿得出手的东西去看他们,越想越多。也就在那会 我开始醒悟了,不能一次次在骗自己不能在该奋斗的年级选择安逸了,那些比你优秀的人还在努力那些比你条件还差的人没有找借口还在为生活拼搏你真的不能再给自己退路了,你的时间真的没有多少了。 26 | 27 | ##### 北漂之路开启 28 | 29 | 在那年新春之后我反复考虑最后还是决定了选择去北京北漂,离开了安徽。我知道我的选择很莽撞可能会很苦,这次我也明白如果混不好我是真的没脸再回去,但是不能在给自己找借口了一定要坚持住,我还有很多目标要去实现,我不能在逃避自己。不过理性告诉我 ,如果我来北京还是做一些所谓的那些职业,还是不会改变什么,好在当时我一个发小的爸爸是在某个培训机构当主任,在他的介绍 推荐下我去了北京的一个培训机构,学习软件,因为自己平时也爱玩电脑爱玩游戏 ,当时承诺好好学10个月出来包工作,一个月至少10K 。但是对我这个一个月最多拿三四千的人来说1W多,真的是很多的钱,很心动。我想了很久,借了学费2W8 ,开始了学习之旅 。 30 | 31 | ##### Android学习之旅 32 | 33 | 记得刚来北京那会还很冷,南方人不是很适应还有雾霾,那会心里真的说不出来的苦 。但是心里想着这是最后的出路 ,必须得坚强 咬咬牙一定要好好学。刚进班里培训机构的学生大多数都比我小,都是98 99年的 ,当时有四个专业" H5 、移动互联、大数据、 UI视觉设计" 我当时心里想的很简单,我喜欢玩手机,而且手机是每个人生活必备的,我想都没想就选择了安卓这个职业 事实证明我选的是对的。因为其他专业的就业真的很差,说白就是念完找不到工作,当然跟运气也有关 ,我看了课程, JAVA3个月WEB1个月Android四个月,最后三个月是实训写项目,都是从外面买的项目。 这边的规则就是每个月学完会有一次月考 过了就学第二个月 不过就是继续学第一个月 直到你考过升上第二个月不然一直卡在第一个月。 当时想的最多的就是 一定不要末 最快的时间出去找工作。 34 | 35 | 第一个月第一堂课,遇到了我人生中对我影响比较大的一个讲师,负责给我们讲JAVA 对我们很负责人,教的理念也很新颖。第一次黑窗口 运行helloworld 从记事本到notepad++ 。由于有计算机的底子,所以前期学起来特别容易,考试基本都是满分过,越到后面就没像开始之前那么认真 专心了,人都是这样 无法长时间保持专注 ,到了WEB学习, 我们换了讲师 。听学长说 WEB 跟后面Android的牵连没多大关系可以不用认真学只要保证过就行了不要在这里末,得不偿失 那时候开始用eclipse 学一些前端东西,换了讲师,真心没法适应 加上没之前认真, 很多东西都是半懂的状况。 尽管这样,我还是熬过去了,熬了四个月,终于开始学专业课的知识了,也是在这里遇到了人生的第二个好讲师,可以说真的很负责人 很尽责 我觉得他不是在工作,真的是在用心传授他直到的所以知识,讲的东西很细, 教材大纲没有的 他也会跟我们讲 会带着我们看源码,会给我们出各种变态题。 那时候学安卓感觉真的很美妙,原来手机上的软件就是这么诞生的阿,不过如此阿 。不过事实是我想的太天真了,越学到后面 感觉到了安卓的深奥,还有其体系的强大 ,对与我这个初出茅庐的人来说连入门都还没达标。 36 | 37 | 不过也在这时人生中第一次有了目标,就是一定要学会做一个自己的APP,四个月的时间很快就过去 从安卓入门-安卓中级-安卓高级, 我都没有末,考试的题目有写页面、有些数据库、也要一些逻辑交互总之都是很简单的题。就这样终于迎来了最后的阶段,实训 ,而我的挑战也随之开始了,从上实训以后发现 没有讲师了,完全自学 自己研究。而对于我这种没有自律的来说这简直没法学了,必须有人带才能管住自己,果不其然 实训的期间我末了。可能有点飘,觉得安卓很简单 ,到了实训开始接触第三方框架 一些更深入的东西了,而且没有人跟你讲解,只有自己去摸索,为了培养自己的自学能力,这恰恰是我的弱点 。对于当时来说,末一个月等于多交一个月宿舍费,多吃一个月饭 我身上还欠着贷款,还有期间念书吃饭买东西花呗借呗 ,当想到这些 ,不断告诫自己 不忘初心不能松懈。好在很快调整了状态 ,之后每天学习看资料 看代码 ,不懂的问人 问之前讲师。顺利度过了那个阶段,到了最后一个月,学校的项目经理让我们写毕业答辩,并且找一个APP,作为演讲主题,写一个PPT 。我记得当时找的APP是美术宝,有兴趣的可以安卓市场搜下这个app,说白了其实就是以后你出去找工作的一个说辞,让我们找个下载量低的 把别人的APP说成自己做的,不会被以后面试的发现。当时看了这APP,把项目里没个点击事件都过了一遍,用到的技术都去看了文章 ,把一些特色功能自己单独抽出来写了Demo,也鉴定了我出来找工作的技术 ,就这样很快答辩结束,我们开始投简历 找工作最后的阶段也是最期待的。 38 | 39 | ##### 找工作 40 | 41 | 当时工作经历我们都伪装的三年,简历的专业技能基本都是一样熟悉各种框架,有自己的独立开发的APP,也有团队开发的。这点培训的人应该都知道,伪装成大佬。 找工作不比培训轻松,记得第一天出去找工作 因为平常都不出门,都在学校宿舍或者周边活动, 出去了连做什么车都不知道,地铁几号几号到哪里都不清楚,也碰了不少壁 ,经常面试迟到。不过运气好,面试的第一家我就成了,当时给的9K是做一个类似滴滴打车的APP当时问的也是一些地图之内的在面试之前高德地图我也研究看过在学校也集成过他问的一些问题基本流利的都回答上了。老板看着我说可以挺不错直接就给我发了offer,当时心里想着9K ,哇这么多 肯定去干阿 。但是仔细心里盘算了下在北京,而且职业也不同 。当时我记得我们专业的平均薪资是12K,而且我们学校当时的项目经理也说你在学校学的还可以说我在北京找个15K的很Easy,于是我考虑了一会说到我回去考虑下给你答复。后面陆陆续续听到我们班别的人拿了offer,14K15K17k的都有,后面心里想着这么好拿么,后面出去也给足自己信心了要工资我都是以17K起步,不过事实告诉我 这东西看运气的,真的没那技术。就算进去了也干不了那活 ,记得最打脸的一次面试是一个搞阅读器的公司,公司名字我就不提了,当时面试问我你们项目现在版本是多少, 怎么个迭代,几周更新一次,你怎么处理apk压缩 gradle 怎么配置,我当时努力按照老师的话想办法把他往项目上引,但是无奈人家一直抓这这些问,我没有实际开发经验 当然回答不出来,出来后很受打击。之后安排的面试也被影响,都没戏,后面就开始后悔 拒绝了第一家了 导致现在找不到工作,但是心里想着没有钱怎么还贷款怎么生活 当初的目标怎么办,我告诉自己一定不能放弃得坚持。终于在第二周的礼拜五面试上了一家公司给的13K也是我现在就职的这家公司,包吃住 可以说福利很好了,当初要的15 被他砍价到13K 13薪 ,想到包吃住 我就想想砍的钱我就当交房租了 毕竟在北京没个几千一个月住房是个问题。 42 | 43 | ### 感触感悟 44 | 45 | 写到这我发现来北京之后的选择我都是幸运的,从选的专业遇到的讲师和选择的公司碰到的伯乐都对我之后有很大的帮助,所以人一定要有希望,一定要有目标不能放弃不能颓废不要非得到绝境才去挖掘自己的潜能 你永远不知道自己有多强,而如今我已经从当时那个渣男慢慢蜕化慢慢开始让自己变得更好更优秀,如今现在上班已经快一年,贷款已经全部还清,今年的节假日还给外婆妈妈买了礼物,手里有钱了真的不一样说什么干什么都硬气了。当然这些都是你辛苦付出才会得到回报的。上班的第一个月很辛苦基本每天熬夜到凌晨 很多东西需要学需要掌握,还有很多小插曲我就不一一叙述总之想得到自己想要的东西就要付出辛苦的汗水。 46 | 47 | 这篇文章到这其实大部分写的是我的经历,从懵懂无知到现在慢慢成熟努力奋斗,我知道还有很多和我经历相似的朋友,可能有的比我还要苦。环境比我还要差。但是一定不要轻易放弃,一定要积极向上。一定不要放弃自己心里暗暗许下的誓言,不能给自己找理由开脱一旦有了第一次就有第二次第三次。说到既做到,想想你的家人想想你还有多少时间可以挥霍,一定要朝着一个目标去努力你永远不知道自己有多强。 48 | 49 | 另外我还想对一些正在上班的同僚说一些话,人一定要有危机感我发现我上班稳定以后就开始过得有点空,没有活的时候就会分神,把时间都浪费在发呆看新闻这些无趣的事情上,我觉得时代都在快速的发展特别是IT这行各项技术年年更语言也不断替代,如果不去了解不去学习坐以待毙,很快你就会被淘汰 这样你又回到解放前,所以一定要定期给自己充电去学习一些新的技术看一些有深度的文章,或者动手去做一些有意义的事不要总把时间都浪费在一些没意义的事上,而今天写这篇文章的其中一个意义就是督促自己不要懒,也想把自己的经历和一些故事跟大家一起分享,希望我的故事能够帮助激励一些朋友度过难关。 50 | 51 | 感谢帮助我的所有朋友,也祝福所有朋友能够在以后的日子里越过越好实现自己的目标。 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Web前端相关/JavaScript基础(二).md: -------------------------------------------------------------------------------- 1 | ## JavaScript基础(二) 2 | 3 | ### 对象 4 | 5 | 书接上文,在JavaScript中,有很多常用的对象,这一节中我们继续讲对象。 6 | 7 | #### 1、window对象 8 | 9 | window对象是BOM的核心,window对象指当前的浏览器窗口,window对象中有很多种方法供开发者调用: 10 | 11 | | 方法 | 描述 | 12 | | :-: | :-: | 13 | | alert() | 显示带有一段消息和一个确认按钮的警告框 | 14 | | prompt() | 显示可提示用户输入的对话框 | 15 | | confirm() | 显示带有一段消息以及确认按钮和取消按钮的对话框 | 16 | | open() | 打开一个新的浏览器窗口,或者查找一个已命名的窗口 | 17 | | close() | 关闭浏览器窗口 | 18 | | print() | 打印当前窗口内容 | 19 | | focus() | 把键盘焦点给予一个窗口 | 20 | | blur() | 把键盘焦点从顶层窗口移开 | 21 | | movebBy() | 可相对窗口的当前坐标把它移动到指定的像素 | 22 | | moveTo() | 把窗口的左上角移动到一个指定的坐标 | 23 | | resizeBy() | 按照指定的像素调整窗口的大小 | 24 | | resizeTo() | 把窗口的大小调整到指定的宽和高 | 25 | | scrollBy() | 按照指定的像素值来滚动内容 | 26 | | scrollTo() | 把内容滚动到指定位置 | 27 | | setInterval() | 每隔指定的时间执行代码 | 28 | | setTimeOut() | 在指定的延迟时间之后来执行代码 | 29 | | clearInterval() | 取消setInterval的值 | 30 | | clearTimeout() | 取消setTimeOut的值 | 31 | 32 | #### 2、history对象 33 | 34 | history对象记录了用户曾经浏览过的页面(URL),并可以实现浏览器前进与后退相似导航的功能。需要注意的是从窗口被打开的那一刻开始记录,每个浏览器窗口、每个标签页乃至每个框架,都有自己的history对象与特定的window对象关联。 35 | 36 | | 方法/属性 | 描述 | 37 | | :-: | :-: | 38 | | length | 返回浏览器历史列表中的URL数量 | 39 | | back() | 加载history列表中的前一个URL | 40 | | forward() | 加载history列表中的下一个URL | 41 | | go() | 加载history列表中的某个具体的页面 | 42 | 43 | #### 3、location对象 44 | 45 | location用于获取或设置窗体的URL,并且可以用于解析URL。我们先看看location对象属性图示: 46 | 47 | ![摘自慕课网](http://img.mukewang.com/53605c5a0001b26909900216.jpg) 48 | 49 | 下面是location对象的一些属性以及方法: 50 | 51 | | 方法/属性 | 描述 | 52 | | :-: | :-: | 53 | | hash | 设置或返回从#号开始的URL(锚) | 54 | | host | 设置或返回主机名和当前URL的端口号 | 55 | | hostname | 设置或返回当前URL的主机名 | 56 | | href | 设置或返回完整的URL | 57 | | pathname | 设置或返回从#号开始的URL(锚) | 58 | | port | 设置或返回当前URL的端口号 | 59 | | protocol | 设置或返回当前URL的协议) | 60 | | search | 设置或返回从?号开始的URL(查询部分) | 61 | | assign() | 加载新的文档 | 62 | | reload() | 重新加载当前文档 | 63 | | replace() | 用心的文档替换当前文档 | 64 | 65 | #### 4、navigator对象 66 | 67 | navigator对象包含有关浏览器的信息,通常用于检测浏览器与操作系统的版本。以下是navigator对象的一些属性: 68 | 69 | | 属性 | 描述 | 70 | | :-: | :-: | 71 | | appCodeName | 浏览器代码名的字符串表示 | 72 | | appName | 返回浏览器名称 | 73 | | appVersion | 返回浏览器的平台和版本信息 | 74 | | platform | 返回运行浏览器的操作系统平台 | 75 | | userAgent | 返回由客户机发送服务器的user-agent头部值 | 76 | 77 | **4.1、userAgent** 78 | 79 | 返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串);几种浏览的user_agent,像360的兼容模式用的是IE、极速模式用的是chrom的内核。可以使用userAgent属性来判断使用的是什么浏览器: 80 | 81 | ![摘自慕课网](http://img.mukewang.com/535a3a4a0001e03f06870189.jpg) 82 | 83 | #### 5、screen对象 84 | 85 | screen对象用于获取用户的屏幕信息,以下是screen对象的属性 86 | 87 | | 属性 | 描述 | 88 | | :-: | :-: | 89 | | avaiHeight | 窗口可以使用的屏幕高度,单位为像素 | 90 | | avaiWidth | 窗口可以使用的屏幕宽度,单位为像素 | 91 | | colorDepth | 用户浏览器表示的颜色位数,通常为32位(每像素的位数)(IE浏览器不支持) | 92 | | pixelDepth | 窗口可以使用的屏幕高度,单位为像素 | 93 | | height | 屏幕的高度,单位为像素 | 94 | | width | 屏幕的宽度,单位为像素 | 95 | 96 | 97 | #### 5、DOM对象 98 | 99 | 文档对象模型DOM(Document Object Model)定义访问和处理HTML文档的标准方法。DOM 将HTML文档呈现为带有元素、属性和文本的树结构(节点树)。将HTML代码分解为DOM节点层次如图所示: 100 | 101 | ![](http://img.mukewang.com/5375ca7e0001dd8d04830279.jpg) 102 | 103 | HTML文档是由节点构成的集合,DOM节点有以下几种: 104 | 105 | >**5.1、元素节点:**上图中html、body、p等都是元素节点,即标签。 106 | > 107 | >**5.2、文本节点:**向用户展示的内容,入li中的JavaScript、DOM、CSS等文本。 108 | > 109 | >**5.3、属性节点:**元素属性,如a标签的链接属性href="http:xxx.xxx.xxx"。 110 | 111 | **节点属性如下表:** 112 | 113 | | 属性 | 说明 | 114 | | :-: | :-: | 115 | | nodeName | 返回一个字符串,其内容是给定节点的名字 | 116 | | nodeType | 返回一个整数,这个数值代表给定节点的类型 | 117 | | nodeValue | 返回给定节点的当前值 | 118 | 119 | - nodeName 属性: 节点的名称,是只读的。 120 | - 元素节点的 nodeName 与标签名相同 121 | - 属性节点的 nodeName 是属性的名称 122 | - 文本节点的 nodeName 永远是 #text 123 | - 文档节点的 nodeName 永远是 #document 124 | - nodeValue 属性:节点的值 125 | - 元素节点的 nodeValue 是 undefined 或 null 126 | - 文本节点的 nodeValue 是文本自身 127 | - 属性节点的 nodeValue 是属性的值 128 | - nodeType 属性: 节点的类型,是只读的。以下常用的几种结点类型: 129 | 130 | | 元素类型 | 节点类型 | 131 | | :-: | :-: | 132 | | 元素 | 1 | 133 | | 属性 | 2 | 134 | | 文本 | 3 | 135 | | 注释 | 8 | 136 | | 文档 | 9 | 137 | 138 | **遍历节点树:** 139 | 140 | | 方法 | 说明 | 141 | | :-: | :-: | 142 | | childNodes | 返回一个数组,这个数组由给定元素节点的子节点 | 143 | | firstChild | 返回第一个子节点 | 144 | | lastChild | 返回最后一个节点 | 145 | | parentNode | 返回一个给定节点的父节点 | 146 | | nextSibling | 返回给定节点的下一个节点 | 147 | | previousSibling | 返回给定节点的下一个节点 | 148 | 149 | **DOM操作:** 150 | 151 | | 方法 | 说明 | 152 | | :-: | :-: | 153 | | createElement(ele) | 创建一个新的元素节点 | 154 | | createTextNode() | 创建一个包含着给定文本的新文本节点 | 155 | | appendChild() | 指定节点的最后一个节点列表之后添加一个新的子节点 | 156 | | insertBefore() | 将一个给定节点插入到一个给定元素节点的给定子节点前面 | 157 | | removeChild() | 从一个给定元素中删除字子节点 | 158 | | replaceChild(ele) | 把一个给定元素里的一个子节点替换成另外一个节点 | 159 | 160 | >**5.4、getElementsByName()方法**,返回带有指定名称的节点对象的集合。 161 | > 162 | >- 因为文档中的name属性可能不唯一,所有getElementsByName() 方法返回的是元素的数组,而不是一个元素。 163 | > 164 | >- 和数组类似也有length属性,可以和访问数组一样的方法来访问,从0开始。 165 | > 166 | >**5.5、getElementsByTagName()方法**,返回带有指定标签名的节点对象的集合。返回元素的顺序是它们在文档中的顺序。 167 | > 168 | >- Tagname是标签的名称,如p、a、img等标签名。 169 | > 170 | >- 和数组类似也有length属性,可以和访问数组一样的方法来访问,所以从0开始。 -------------------------------------------------------------------------------- /ButterKnife.md: -------------------------------------------------------------------------------- 1 | ### ButterKnife 2 | 3 | **定义:** 4 | 5 | 这是一个帮助大家解决 `View` 的注入(代替 `View` 的操作)框架。 6 | 7 | **源码分析:** 8 | 9 | 首先 `ButterKnife` 采用的是 `CLASS` (编译时注解),然后使用 `APT` 编译时注解技术,在编译的过程中会扫描全部你所要处理的注解类,然后会生成 `_ViewBinding.java` 文件,这个文件会帮我们处理 `findViewById` 和 `onClick` 事件等操作,我们调用 `ButterKnife.bind()` 方法是通过反射注入的方式来调用 `_ViewBinding.java` 文件中的方法。 10 | 11 | ### EventBus 12 | 13 | **定义:** 14 | 15 | 这是一个用于Android的发布/订阅事件总线,能够简化应用组件间的通信,解藕事件的发送者和接受者。 16 | 17 | 首先 `EventBus` 采用的是的是 `RUNTIME` (运行时注解),`EventBus` 采用的是单例模式(DCL双检查法)来实例化 `EventBus` 对象,然后调用注册,发送等方法完成事件的发布和订阅。 18 | 19 | **EventBus.getDefault().register** 20 | 21 | 通过传入的注册对象参数来解析注册对象中所有的带有 `Subscriber` 注解的方法,然后注解中所有的细节参数(线程,优先级,粘性事件,参数类型)封装成一个 `SubscriberMethod` 对象并添加到集合中返回。 22 | 23 | ``` 24 | private void findUsingReflectionInSingleClass(FindState findState) { 25 | Method[] methods; 26 | try { 27 | // This is faster than getMethods, especially when subscribers are fat classes like Activities 28 | methods = findState.clazz.getDeclaredMethods(); 29 | } catch (Throwable th) { 30 | // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 31 | methods = findState.clazz.getMethods(); 32 | findState.skipSuperClasses = true; 33 | } 34 | for (Method method : methods) { 35 | int modifiers = method.getModifiers(); 36 | if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { 37 | Class[] parameterTypes = method.getParameterTypes(); 38 | if (parameterTypes.length == 1) { 39 | Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); 40 | if (subscribeAnnotation != null) { 41 | Class eventType = parameterTypes[0]; 42 | if (findState.checkAdd(method, eventType)) { 43 | ThreadMode threadMode = subscribeAnnotation.threadMode(); 44 | findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, 45 | subscribeAnnotation.priority(), subscribeAnnotation.sticky())); 46 | } 47 | } 48 | } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { 49 | String methodName = method.getDeclaringClass().getName() + "." + method.getName(); 50 | throw new EventBusException("@Subscribe method " + methodName + 51 | "must have exactly 1 parameter but has " + parameterTypes.length); 52 | } 53 | } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { 54 | String methodName = method.getDeclaringClass().getName() + "." + method.getName(); 55 | throw new EventBusException(methodName + 56 | " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | 参数都封装好之后就会调用 `subscribe()` 方法,这个方法是为了解析所有 `SubscriberMethod` 的 `eventType`(参数类型),然后将这些按照要求(优先级)的顺序保存起来,这样是为了之后订阅事件作准备。然后再判断是否是粘性事件,如果是粘性事件会再做一层处理。 63 | 64 | ``` 65 | private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { 66 | Class eventType = subscriberMethod.eventType; 67 | Subscription newSubscription = new Subscription(subscriber, subscriberMethod); 68 | CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType); 69 | if (subscriptions == null) { 70 | subscriptions = new CopyOnWriteArrayList<>(); 71 | subscriptionsByEventType.put(eventType, subscriptions); 72 | } else { 73 | if (subscriptions.contains(newSubscription)) { 74 | throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " 75 | + eventType); 76 | } 77 | } 78 | 79 | int size = subscriptions.size(); 80 | for (int i = 0; i <= size; i++) { 81 | if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { 82 | subscriptions.add(i, newSubscription); 83 | break; 84 | } 85 | } 86 | 87 | List> subscribedEvents = typesBySubscriber.get(subscriber); 88 | if (subscribedEvents == null) { 89 | subscribedEvents = new ArrayList<>(); 90 | typesBySubscriber.put(subscriber, subscribedEvents); 91 | } 92 | subscribedEvents.add(eventType); 93 | 94 | if (subscriberMethod.sticky) { 95 | if (eventInheritance) { 96 | // Existing sticky events of all subclasses of eventType have to be considered. 97 | // Note: Iterating over all events may be inefficient with lots of sticky events, 98 | // thus data structure should be changed to allow a more efficient lookup 99 | // (e.g. an additional map storing sub classes of super classes: Class -> List). 100 | Set, Object>> entries = stickyEvents.entrySet(); 101 | for (Map.Entry, Object> entry : entries) { 102 | Class candidateEventType = entry.getKey(); 103 | if (eventType.isAssignableFrom(candidateEventType)) { 104 | Object stickyEvent = entry.getValue(); 105 | checkPostStickyEventToSubscription(newSubscription, stickyEvent); 106 | } 107 | } 108 | } else { 109 | Object stickyEvent = stickyEvents.get(eventType); 110 | checkPostStickyEventToSubscription(newSubscription, stickyEvent); 111 | } 112 | } 113 | } 114 | ``` 115 | 116 | **EventBus.getDefault().post** 117 | 118 | 通过 `post` 方法来发送一个事件,然后会根据当前传入的参数来查询之前注册时所存的数据,通过这个参数获得类名,方法名等,然后再用反射的方法去执行某个类的某个方法,这样就能响应式的实现我们想要的功能。 119 | -------------------------------------------------------------------------------- /Android杂记/Android仿微信文章悬浮窗效果.md: -------------------------------------------------------------------------------- 1 | ## Android仿微信文章悬浮窗效果 2 | 3 | ### 序言 4 | 5 | 前些日子跟朋友聊天,朋友Z果粉,前些天更新了微信,说微信出了个好方便的功能啊,我问是啥功能啊,看看我大Android有没有,他说现在阅读公众号文章如果有人给你发微信你可以把这篇文章当作悬浮窗悬浮起来,方便你聊完天不用找继续阅读,听完是不是觉得这叫啥啊,我大Android微信版不是早就有这个功能了吗,我看文章的时候看到过有这个悬浮按钮,但是我一直没有使用过,试了一下还是挺方便的,就想着自己实现一下这个功能,下面看图,大家都习惯了无图言X 6 | 7 | ![](http://ooaap25kv.bkt.clouddn.com/18-10-9/44021423.jpg) 8 | 9 | ### 原理 10 | 11 | 看完动图我们来分析一下,如何在每个页面上都存在一个`View`呢,有些人可能会说,写在base里面,这样每次启动一个新的`Activity`都要往页面上`addView`一次,性能不好,再说了,我们作为一个优秀的程序员能干这种重复的事吗,这种方案果断打回去;既然这样的话那我们肯定要在全局加了,那么全局是哪呢?相信了解过`Activity`源码的朋友肯定知道,全局可以在`Window`层加啊,这样既能一次性搞定,又不影响性能,说干就干。 12 | 13 | ### 实现 14 | 15 | #### 1、权限 16 | 17 | 首先我们要考虑的一个问题就是权限问题,因为要适配`Android 7.0 8.0`,添加悬浮窗是需要申请权限的,这里参考了[ 18 | Android 悬浮窗权限各机型各系统适配大全](https://blog.csdn.net/self_study/article/details/52859790)这篇文章,适配的比较全,可以直接拿来用。这里需要注意的是,为了适配`Android 8.0`,`Window`的类型需要配置一下: 19 | 20 | ``` 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 22 | //Android 8.0 23 | mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 24 | } else { 25 | //其他版本 26 | mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; 27 | } 28 | ``` 29 | 30 | #### 2、添加ViewGroup到Window 31 | 32 | 判断好权限之后,直接添加就可以了 33 | 34 | ``` 35 | @SuppressLint("CheckResult") 36 | private void showWindow(Context context) { 37 | mWindowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE); 38 | mView = LayoutInflater.from(context).inflate(R.layout.article_window, null); 39 | 40 | ImageView ivImage = mView.findViewById(R.id.aw_iv_image); 41 | String imageUrl = SPUtil.getStringDefault(ARTICLE_IMAGE_URL, ""); 42 | RequestOptions requestOptions = RequestOptions.circleCropTransform(); 43 | requestOptions.placeholder(R.mipmap.ic_launcher_round).error(R.mipmap.ic_launcher_round); 44 | Glide.with(context).load(imageUrl).apply(requestOptions).into(ivImage); 45 | 46 | initListener(context); 47 | 48 | mLayoutParams = new WindowManager.LayoutParams(); 49 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 50 | mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 51 | } else { 52 | mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; 53 | } 54 | mLayoutParams.format = PixelFormat.RGBA_8888; //窗口透明 55 | mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; //窗口位置 56 | mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 57 | mLayoutParams.width = 200; 58 | mLayoutParams.height = 200; 59 | mLayoutParams.x = mWindowManager.getDefaultDisplay().getWidth() - 200; 60 | mLayoutParams.y = 0; 61 | mWindowManager.addView(mView, mLayoutParams); 62 | } 63 | ``` 64 | 65 | #### 3、View的拖拽实现 66 | 67 | 借助`WindowManager.LayoutParams`来实现,`mLayoutParams.x`和`mLayoutParams.y`分别表示`mView`左上角的横纵坐标,所以我们只需要改动这两个值就行了,当`ACTION_UP`时,计算当前`mView`的中心点相对窗口的位置,然后将`mView`动态滑动到窗口左边或者右边: 68 | 69 | ``` 70 | //设置触摸滑动事件 71 | mView.setOnTouchListener(new View.OnTouchListener() { 72 | int startX, startY; //起始点 73 | boolean isMove; //是否在移动 74 | long startTime; 75 | int finalMoveX; //最后通过动画将mView的X轴坐标移动到finalMoveX 76 | 77 | @Override 78 | public boolean onTouch(View v, MotionEvent event) { 79 | switch (event.getAction()) { 80 | case MotionEvent.ACTION_DOWN: 81 | startX = (int) event.getX(); 82 | startY = (int) event.getY(); 83 | startTime = System.currentTimeMillis(); 84 | isMove = false; 85 | return false; 86 | case MotionEvent.ACTION_MOVE: 87 | mLayoutParams.x = (int) (event.getRawX() - startX); 88 | mLayoutParams.y = (int) (event.getRawY() - startY); 89 | updateViewLayout(); //更新mView 的位置 90 | return true; 91 | case MotionEvent.ACTION_UP: 92 | long curTime = System.currentTimeMillis(); 93 | isMove = curTime - startTime > 100; 94 | 95 | //判断mView是在Window中的位置,以中间为界 96 | if (mLayoutParams.x + mView.getMeasuredWidth() / 2 >= mWindowManager.getDefaultDisplay().getWidth() / 2) { 97 | finalMoveX = mWindowManager.getDefaultDisplay().getWidth() - mView.getMeasuredWidth(); 98 | } else { 99 | finalMoveX = 0; 100 | } 101 | 102 | //使用动画移动mView 103 | ValueAnimator animator = ValueAnimator.ofInt(mLayoutParams.x, finalMoveX).setDuration(Math.abs(mLayoutParams.x - finalMoveX)); 104 | animator.addUpdateListener((ValueAnimator animation) -> { 105 | mLayoutParams.x = (int) animation.getAnimatedValue(); 106 | updateViewLayout(); 107 | }); 108 | animator.start(); 109 | 110 | return isMove; 111 | } 112 | return false; 113 | } 114 | }); 115 | ``` 116 | 117 | #### 4、注意 118 | 119 | 为了让`Window`与`Activity`脱离,这里我们采用`Service`来做,通过`Service`来添加和移除`View`;在权限申请成功之后我们需要通知Service(其实是Activity,可能会有保存数据等操作)作相应改变(提供一个接口给Service),然后在Service中使用广播来通知Activity;最后一个需要注意的地方就是我们需要判断应用程序是否在前台还是后台来添加或移除Window,这里通过使用ActivityLifecycleCallbacks来监听Activity在前台的数量来判断应用程序是在前台还是后台 120 | 121 | ``` 122 | class ApplicationLifecycle : Application.ActivityLifecycleCallbacks { 123 | 124 | private var started: Int = 0 125 | 126 | override fun onActivityPaused(activity: Activity?) { 127 | } 128 | 129 | override fun onActivityResumed(activity: Activity?) { 130 | } 131 | 132 | override fun onActivityStarted(activity: Activity?) { 133 | started++ 134 | if (started == 1) { 135 | Log.e("TAG", "应用在前台了!!!") 136 | } 137 | } 138 | 139 | override fun onActivityDestroyed(activity: Activity?) { 140 | } 141 | 142 | override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { 143 | } 144 | 145 | override fun onActivityStopped(activity: Activity?) { 146 | started-- 147 | if (started == 0) { 148 | Log.e("TAG", "应用在后台了!!!") 149 | } 150 | } 151 | 152 | override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { 153 | } 154 | } 155 | 156 | ``` 157 | 158 | 本文代码已传至[Github](https://github.com/24Kshign/SuspensionWindow),有需要的朋友可以下载下来看看。 159 | 160 | 161 | ### 参考 162 | 163 | - [Android 8.0 悬浮窗变动与用法](https://blog.csdn.net/mai763727999/article/details/78983375/) 164 | 165 | - [ 166 | Android 悬浮窗权限各机型各系统适配大全](https://blog.csdn.net/self_study/article/details/52859790) -------------------------------------------------------------------------------- /Android源码相关/setContentView源码.md: -------------------------------------------------------------------------------- 1 | ## setContentView源码 2 | 3 | ### 1、为什么要分析? 4 | (1)、为每个页面添加一个标题栏(不想每次都在`XML`文件中include),能不能在布局文件的上一层进行统一处理呢?我们的`XML`是加载到哪里去了呢? 5 | 6 | (2)、为每个页面添加一个Loading页和Error页,考虑到页面有titlebar的存在(有可能是(1)中统一的titlebar,也有可能是自己include的titlebar) 7 | 8 | ### 2、源码解析 9 | 10 | ``` 11 | Activity中: 12 | 13 | //加载资源 14 | public void setContentView(@LayoutRes int layoutResID) { 15 | getWindow().setContentView(layoutResID); 16 | initWindowDecorActionBar(); 17 | } 18 | 19 | 加载View 20 | public void setContentView(View view) { 21 | getWindow().setContentView(view); 22 | initWindowDecorActionBar(); 23 | } 24 | 25 | Window中: 26 | 27 | public abstract void setContentView(View view); 28 | 29 | ``` 30 | >不管继承什么`XXXActivity`,最终setContentView都会调用`Activity`中的方法,然后再调用`Window`中的方法,`Window`中setContentView是一个抽象方法,那我们肯定要去找到他的实现类: 31 | 32 | ``` 33 | PhoneWindow中: 34 | 35 | @Override 36 | public void setContentView(int layoutResID) { 37 | 38 | //mContentParent是Window内容放置的视图,也就是我们的根布局 39 | if (mContentParent == null) { 40 | installDecor(); //创建一个DecorView 41 | } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 42 | mContentParent.removeAllViews(); 43 | } 44 | 45 | ... 46 | } 47 | 48 | private void installDecor() { 49 | 50 | if (mDecor == null) { 51 | //判断DecorView为空时去手动创建一个 52 | mDecor = generateDecor(-1); 53 | mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 54 | mDecor.setIsRootNamespace(true); 55 | if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { 56 | mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); 57 | } 58 | } else { 59 | //不为空时将DecorView与Window绑定 60 | mDecor.setWindow(this); 61 | } 62 | if (mContentParent == null) { 63 | //当根布局为空时,系统也默认为其创建一个根布局 64 | mContentParent = generateLayout(mDecor); 65 | 66 | ... 67 | } 68 | } 69 | 70 | protected ViewGroup generateLayout(DecorView decor) { 71 | 72 | ... 73 | 74 | int layoutResource; 75 | int features = getLocalFeatures(); 76 | //根据设置的style属性来获取不同的layoutResource 77 | if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { 78 | layoutResource = R.layout.screen_swipe_dismiss; 79 | setCloseOnSwipeEnabled(true); 80 | } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { 81 | if (mIsFloating) { 82 | TypedValue res = new TypedValue(); 83 | getContext().getTheme().resolveAttribute( 84 | R.attr.dialogTitleIconsDecorLayout, res, true); 85 | layoutResource = res.resourceId; 86 | } else { 87 | layoutResource = R.layout.screen_title_icons; 88 | } 89 | // XXX Remove this once action bar supports these features. 90 | removeFeature(FEATURE_ACTION_BAR); 91 | // System.out.println("Title Icons!"); 92 | } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 93 | && (features & (1 << FEATURE_ACTION_BAR)) == 0) { 94 | // Special case for a window with only a progress bar (and title). 95 | // XXX Need to have a no-title version of embedded windows. 96 | layoutResource = R.layout.screen_progress; 97 | // System.out.println("Progress!"); 98 | } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { 99 | // Special case for a window with a custom title. 100 | // If the window is floating, we need a dialog layout 101 | if (mIsFloating) { 102 | TypedValue res = new TypedValue(); 103 | getContext().getTheme().resolveAttribute( 104 | R.attr.dialogCustomTitleDecorLayout, res, true); 105 | layoutResource = res.resourceId; 106 | } else { 107 | layoutResource = R.layout.screen_custom_title; 108 | } 109 | // XXX Remove this once action bar supports these features. 110 | removeFeature(FEATURE_ACTION_BAR); 111 | } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { 112 | // If no other features and not embedded, only need a title. 113 | // If the window is floating, we need a dialog layout 114 | if (mIsFloating) { 115 | TypedValue res = new TypedValue(); 116 | getContext().getTheme().resolveAttribute( 117 | R.attr.dialogTitleDecorLayout, res, true); 118 | layoutResource = res.resourceId; 119 | } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { 120 | layoutResource = a.getResourceId( 121 | R.styleable.Window_windowActionBarFullscreenDecorLayout, 122 | R.layout.screen_action_bar); 123 | } else { 124 | layoutResource = R.layout.screen_title; 125 | } 126 | // System.out.println("Title!"); 127 | } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { 128 | layoutResource = R.layout.screen_simple_overlay_action_mode; 129 | } else { 130 | // Embedded, so no decoration is needed. 131 | layoutResource = R.layout.screen_simple; 132 | // System.out.println("Simple!"); 133 | } 134 | 135 | mDecor.startChanging(); 136 | mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); 137 | 138 | ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 139 | 140 | ... 141 | 142 | //最后返回得到一个根布局(也就是id/content) 143 | return contentParent; 144 | } 145 | 146 | XML中: 147 | 148 | //下面列举一个最简单的布局 149 | 154 | 160 | 167 | 168 | ``` 169 | >把每一个系统给的`XML`文件都看了一遍,发现他都是这种类似的,LinearLayout包一个FrameLayout,这个FrameLayout就是我们经常看到的id/content(心里一阵喜),这样的话那我们就可以在这个LinearLayout里面做文章了。接着往下看,拿到这个根布局之后,然后会将我们setContentView过来的View添加到根布局上,这么一来,我们就有以下的视图树了: 170 | 171 | ![](http://ooaap25kv.bkt.clouddn.com/18-6-15/38756758.jpg) 172 | 173 | -------------------------------------------------------------------------------- /Web前端相关/JavaScript基础(一).md: -------------------------------------------------------------------------------- 1 | ## JavaScript基础(一) 2 | 3 | ### 操作符 4 | 5 | 在JavaScript中,有很多种操作符,算术操作符、赋值操作符、比较操作符以及逻辑操作符 6 | 7 | #### 1.1、算术操作符: 8 | 9 | `+`,`-`,`*`,`/`,除了加号(`+`)之外,其他都是按照四则运算大方式来进行,而加号(`+`)在字符串中可以作为连接符来使用,这个和Java是一样的。 10 | 11 | ![摘自慕课网](http://img.mukewang.com/52b9227d0001acc703000174.jpg) 12 | 13 | #### 1.2、赋值操作符: 14 | 15 | `=`操作符不是等于,而是赋值操作 16 | 17 | #### 1.3、比较操作符: 18 | 19 | | 操作符 | 描述 | 20 | | :-: | :-: | 21 | | < | 小于 | 22 | | > | 大于 | 23 | | <= | 小于等于 | 24 | | >= | 大于等于 | 25 | | == | 等于(比较值是否相同) | 26 | | === | 等于(比较值和值的类型是否相同) | 27 | | != | 不等于(同==) | 28 | | !== | 不等于(同===) | 29 | 30 | JavaScript中的比较操作符的用法和Java是一样的。但是需要注意的是表格中的`==`与`===`的不同用法。 31 | 32 | #### 1.4、操作符优先级: 33 | 34 | 算术操作符 > 比较操作符 > 逻辑操作符 > 赋值操作符;如果同级的运算是按从左到右次序进行,多层括号由里向外。 35 | 36 | 37 | ### 数组 38 | 39 | 在JavaScript中,定义一个数组是没有类型的,也就是说可以往数组中添加任何类型的数据。 40 | 41 | #### 1、定义 42 | 43 | ``` 44 | var myarray=new Array(); //没有指定数组的长度 45 | 46 | var myarray= new Array(8); //创建一个长度为8的数组 47 | ``` 48 | **注意:** 49 | 50 | - 创建的新数组是空数组,没有值,如输出,则显示undefined。 51 | - 虽然创建数组时,指定了长度,但实际上数组都是变长的,也就是说即使指定了长度为8,仍然可以将元素存储在规定长度以外。 52 | 53 | #### 2、数组长度属性 54 | 55 | 使用`array.length`属性来获取数组的长度,因为数组的索引总是由0开始,所以一个数组的上下限分别是:0和length-1;同时,在JavaScript中数组的length属性是可变的。 56 | 57 | #### 3、二维数组 58 | 59 | 在JavaScript中,二维数组用myarray[x][y]来表示,声明二维数组有两种方式: 60 | 61 | **方式一:** 62 | 63 | ``` 64 | var myarr=new Array(); //先声明一维 65 | for(var i=0;i<2;i++){ //一维长度为2 66 | myarr[i]=new Array(); //再声明二维 67 | for(var j=0;j<3;j++){ //二维长度为3 68 | myarr[i][j]=i+j; // 赋值,每个数组元素的值为i+j 69 | } 70 | } 71 | ``` 72 | 73 | **方式二:** 74 | 75 | ``` 76 | var Myarr = [[0 , 1 , 2 ],[1 , 2 , 3]] 77 | ``` 78 | 79 | ### 函数 80 | 81 | 在JavaScript中,函数的使用需要注意以下几点事项: 82 | 83 | #### 1、函数调用 84 | 85 | **方式一:在JS代码中直接调用** 86 | 87 | ``` 88 | 96 | ``` 97 | 98 | **方式二:在HTML标签中调用** 99 | 100 | ``` 101 | 108 | 109 | 110 | 111 | 112 | ``` 113 | 114 | #### 2、函数参数 115 | 116 | **2.1、无参:** 117 | 118 | ``` 119 | 126 | ``` 127 | 128 | **2.2、有参** 129 | 130 | ``` 131 | 138 | ``` 139 | **注意:有参函数中,参数类型是没有类型的(好随意的语言啊>_<)** 140 | 141 | #### 3、函数返回值 142 | 143 | 这里需要注意的是,在JavaScript中,函数的定义是没有返回值类型这一说的,不像Java里面,任何一个函数都需要指明返回值类型。 144 | 145 | ``` 146 | 153 | ``` 154 | 155 | 上面的函数中,你为它增加一条return语句,它就是一个有返回值的函数了,如果去掉return语句,那么它就是一个无返回值的函数(不得不又说一句,JS真随意!!!) 156 | 157 | 158 | ### 事件 159 | 160 | JavaScript 创建动态页面。事件是可以被 JavaScript 侦测到的行为。 网页中的每个元素都可以产生某些可以触发 JavaScript 函数或程序的事件。 161 | 162 | | 事件 | 说明 | 163 | | :-: | :-: | 164 | | onclick | 鼠标单击事件 | 165 | | onmouseover | 鼠标经过事件 | 166 | | onmouseout | 鼠标移开事件 | 167 | | onchange | 文本框内容改变事件 | 168 | | onselect | 文本框内容被选中事件 | 169 | | onfocus | 光标聚集 | 170 | | onblur | 光标离开 | 171 | | onload | 网页导入 | 172 | | onunload | 关闭网页 | 173 | 174 | 175 | ### 对象 176 | 177 | 在JavaScript中也是有对象这一说的,和Java中的对象差不多。JavaScript中的所有事物都是对象,如:字符串、数值、数组、函数等,每个对象带有属性和方法。 178 | 179 | **对象的属性:**反映该对象某些特定的性质的,如:字符串的长度、图像的长宽等; 180 | 181 | **对象的方法:**能够在对象上执行的动作。例如,表单的“提交”(Submit),时间的“获取”(getYear)等; 182 | 183 | #### 1、Date对象 184 | 185 | 在JavaScript中,Date对象被用来存储/获取日期,该对象有以下方法/属性: 186 | 187 | | 方法名称 | 功能描述 | 188 | | :-: | :-: | 189 | | get/setDate() | 返回/设置日期 | 190 | | get/setFullYear() | 返回/设置年份,用四位数表示 | 191 | | get/setYear | 返回/设置年份 | 192 | | get/setMonth | 返回/设置月份
    0:一月 ... 11:十二月,所以要加1 | 193 | | get/setHours | 返回/设置小时 24小时制 | 194 | | get/setMinutes() | 返回/设置分钟 | 195 | | get/setSeconds() | 返回/设置秒 | 196 | | get/setTimes() | 返回/设置时间(毫秒单位) | 197 | | getDay() | 返回星期
    0:周一 ... 6:周日 | 198 | 199 | #### 2、String对象 200 | 201 | 任何一门语言都离不开对String的操作,JavaScript也不例外,该对象有以下方法/属性: 202 | 203 | | 方法名称 | 功能描述 | 204 | | :-: | :-: | 205 | | String.charAt(1) | 获取字符串中第1个位置的字符 | 206 | | String.indexOf('x') | 或取字符x在字符串中第1次出现的位置,若没找到,返回-1 | 207 | | String.charAt(1) | 获取字符串中第1个位置的字符| 208 | | String.split(separator,limit)| 第一个参数是字符串分割的参照字符,第二个参数表示最多切割多少次,可以不设置,该方法返回的是一个数组| 209 | | String.substring(start,end)| 第一个参数表示开始位置,第二个参数表示结束位置,可省略,返回的是从start到end-1位置的子串| 210 | | String.substr(start,length)| 第一个参数表示开始位置,第二个参数表示裁剪几个字符,可省略,返回的是从start到start+length位置的子串| 211 | 212 | #### 3、Math对象 213 | 214 | 在JavaScript,Math对象提供对数据的数学计算。Math对象中有属性,也有方法,以下是Math对象中的属性: 215 | 216 | | 属性名称 | 功能描述 | 217 | | :-: | :-: | 218 | | E | 返回算数常量e,即自然对数的底数(约为2.718) | 219 | | LN2 | 返回2的自然对数(约等于0.693) | 220 | | LN10 | 返回10的自然对数(约等于2.302) | 221 | | LOG2E | 返回以2为底E的对数(约等于1.442) | 222 | | LOG10E | 返回以10为底E的对数(约等于0.434) | 223 | | PI | 返回圆周率(约等于3.14159) | 224 | | SQRT1_2 | 返回2的平方根的倒数(约等于0.707) | 225 | | SQRT2 | 返回2的平方根(约等于1.414) | 226 | 227 | 在JavaScript中,Math对象有以下方法: 228 | 229 | | 属性名称 | 功能描述 | 230 | | :-: | :-: | 231 | | abs(x) | 返回x的绝对值 | 232 | | acos(x) | 返回x的反余弦值 | 233 | | asin(x) | 返回x的反正弦值 | 234 | | atan(x) | 返回x的反正切值 | 235 | | atan2(y,x) | 返回x轴到点(x,y)的角度(以弧度为单位) | 236 | | ceil(x) | 对x进行上舍入(向上取整) | 237 | | floor(x) | 对x进行下舍入(向下取整) | 238 | | sin(x) | 返回x的正弦值 | 239 | | cos(x) | 返回x的余弦值 | 240 | | tan(x) | 返回x的正切值 | 241 | | exp(x) | 返回e的x次幂 | 242 | | log(x) | 返回x的自然对数(底为e) | 243 | | max(x,y) | 返回x和y中的最大值 | 244 | | min(x,y) | 返回x和y中的最小值 | 245 | | pow(x,y) | 返回x的y次幂 | 246 | | random() | 返回0-1之间的随机数 | 247 | | round(x) | 返回x的四舍五入值 | 248 | | sqrt(x) | 返回x的平方根 | 249 | 250 | 251 | #### 4、数组Array对象 252 | 253 | 数组在上面已经介绍过了,就不再做过多的介绍了,下面来看看JavaScript中数组的一些方法以及属性: 254 | 255 | | 属性/方法名称 | 功能描述 | 256 | | :-: | :-: | 257 | | Array.length | 返回数组长度 | 258 | | Array.contact(arr1,...arrn) | 连接两个或多个数组并返回一个最终的数组 | 259 | | Array.join(seprator) | 把数组的所有元素放入一个字符串,元素通过指定的字符seprator进行连接,返回一个字符串 | 260 | | Array.pop() | 删除并返回数组的最后一项 | 261 | | Array.push(x1,...xn) | 向数组的末尾添加一个或更多元素,并返回新数组的长度 | 262 | | Array.reverse() | 颠倒数组中元素的顺序并返回一个数组 | 263 | | Array.shift() | 删除并返回数组中的第一个元素 | 264 | | Array.slice(start,end) | 从某个已有的数组返回选定元素,第一个参数表示从start处开始到end处结束,如果start为-1,那么从倒数第一个元素开始,以此类推,返回一个子串 | 265 | | Array.sort(function) | 对元素进行排序操作,并返回一个数组,参数是可选的,但是必须是一个函数(升序,降序) | 266 | | Array.splice(index,howmaney,item1...itemn) | 删除元素,并向数组添加新的元素,第一个参数表示在数组中删除/添加的位置,第二个参数表示需要删除的元素的数量,后面是添加的参数 | 267 | | Array.toString() | 把数组转化为字符串并返回 | 268 | 269 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /Android杂记/ConstraintLayout最强布局解析.md: -------------------------------------------------------------------------------- 1 | ## ConstraintLayout最强布局解析 2 | 3 | `ConstraintLayout`布局出来已经很久了,刚出来那会儿就想尝试一下的,结果半天都没适应,前两天看到一篇`ConstraintLayout`实战的文章,看完之后发现这布局贼鸡儿好用啊,日常开发中的大多数布局使用它都可以完成,而且也不需要嵌套。 4 | 5 | ### 介绍 6 | 7 | `ConstraintLayout`又称约束布局,是谷歌在2016年开发者大会上推出的,之后在`Android Studio`上成为了默认布局,该布局能减少布局的层级嵌套,我们都知道,`View`嵌套的越多,在加载的过程中解析起来就越费时间,该布局几乎能做到`LinearLayout`和`RelativeLayout`嵌套完成的任何布局,下面跟着一波小`demo`来深入了解谷歌推荐的`ConstraintLayout`。 8 | 9 | ### 用法简介 10 | 11 | #### 1、xxx_toyyyOf属性 12 | 13 | `xxx`是当前的控件,`yyy`是指定控件,这个指定控件也可以是容器本身(parent) 14 | 15 | `ConstraintLayout`中有以下多种这样的属性: 16 | 17 | - **layout_constraintLeft_toLeftOf** 18 | 19 | - **layout_constraintLeft_toRightOf** 20 | 21 | - **layout_constraintRight_toLeftOf** 22 | 23 | - **layout_constraintRight_toRightOf** 24 | 25 | - **layout_constraintTop_toTopOf** 26 | 27 | - **layout_constraintTop_toBottomOf** 28 | 29 | - **layout_constraintBottom_toTopOf** 30 | 31 | - **layout_constraintBottom_toBottomOf** 32 | 33 | - **layout_constraintBaseline_toBaselineOf** 34 | 35 | - **layout_constraintStart_toEndOf** 36 | 37 | - **layout_constraintStart_toStartOf** 38 | 39 | - **layout_constraintEnd_toStartOf** 40 | 41 | - **layout_constraintEnd_toEndOf** 42 | 43 | ![](http://ooaap25kv.bkt.clouddn.com/18-9-3/98495697.jpg) 44 | 45 | 参照上图给出的解释,以上属性都可以这样用,有点类似`RelativeLayout`中的`toLeftOf`,`toRightOf`,上面的属性中还有一个关于`Baseline`的,我们通过另外一张图来了解一下: 46 | 47 | ![](http://ooaap25kv.bkt.clouddn.com/18-9-4/78875139.jpg) 48 | 49 | 50 | `Baseline`是控件中文字的基准线,这里可以理解为参照某个控件中的文字底部对齐,来看看样式: 51 | 52 | ![](http://ooaap25kv.bkt.clouddn.com/18-9-3/75559787.jpg) 53 | 54 | 如果不加基准线对齐的话,那么`ButtonA`的位置就在容器的左上角。 55 | 56 | #### 2、设置margin边距 57 | 58 | 边距,和传统的布局是一样的用法,但是这里要注意的是,必须要设置自己的相对位置(先要指定自己在容器中的位置,可以是相对容器的,也可以是相对某个控件的),如果不设置的话,那么设置`margin`是无效的,大家可以试试,在一个`ConstraintLayout`布局中放一个按钮,除了边距之外什么都不设置,这样是没有效果的,因为你没有在布局中给它设置相对位置。 59 | 60 | #### 3、隐藏空间设置边距 61 | 62 | ConstraintLayout中有以下多种这样的属性: 63 | 64 | - **layout_goneMarginStart** 65 | 66 | - **layout_goneMarginEnd** 67 | 68 | - **layout_goneMarginLeft** 69 | 70 | - **layout_goneMarginTop** 71 | 72 | - **layout_goneMarginRight** 73 | 74 | - **layout_goneMarginBottom** 75 | 76 | 平常我们在开发过程中会遇到这样一个问题,当一个控件被设置成`gone`之后,与之关联的控件的位置常常也会发生改变,来看看样式: 77 | 78 | ![](http://ooaap25kv.bkt.clouddn.com/18-9-3/35375631.jpg) 79 | 80 | 平常我们写标题栏的时候应该都遇到过右边放两个按钮的情况,而且是可以控制显示隐藏的,当最右边的按钮隐藏之后,左边的按钮也要距离右边有一个边距,这种情况下我们就可以使用上面这些属性来配置布局。 81 | 82 | #### 4、居中和偏移 83 | 84 | - 水平居中 85 | 86 | ``` 87 | app:layout_constraintLeft_toLeftOf="parent" 88 | app:layout_constraintRight_toRightOf="parent" 89 | ``` 90 | 91 | ![](http://ooaap25kv.bkt.clouddn.com/18-9-4/83163284.jpg) 92 | 93 | 这个很好理解,设置与容器的左边和右边分别对齐,这样的话就能让控件水平居中了,同理垂直居中和中心对齐也是这样。可能有人会吐槽了,写这么多还不如我用`LinearLayout`和`RelativeLayout`写一句代码来的快呢?老铁还真是个急性子,接着往下看。 94 | 95 | - 垂直居中 96 | 97 | ``` 98 | app:layout_constraintTop_toTopOf="parent" 99 | app:layout_constraintBottom_toBottomOf="parent" 100 | ``` 101 | 102 | - 中心 103 | 104 | ``` 105 | app:layout_constraintLeft_toLeftOf="parent" 106 | app:layout_constraintRight_toRightOf="parent" 107 | app:layout_constraintTop_toTopOf="parent" 108 | app:layout_constraintBottom_toBottomOf="parent" 109 | ``` 110 | 111 | 下面来实现一个这样的功能,让一个控件在距离左边和距离右边的比例是1:3,来看看正经写法: 112 | 113 | ![](http://ooaap25kv.bkt.clouddn.com/18-9-4/23123049.jpg) 114 | 115 | 116 | ``` 117 | 118 | 119 | 123 | 124 |