‘壹’ android:窗口、自定义view、bitmap
1、ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对棚弯象和 DecorView 建立关联
2、 自定义View-绘制流程概述
4、 Android Handler
6、 Android Bitmap
2、MeasureSpec:
3、一般来说,使用多进程会造成以下几个方面的问题:
5、Window 概念与分类:
Window 是一个抽象类,它的具体实现是 PhoneWindow。WindowManager 是外界访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中所有的视图都是通过 Window 来呈现,因此 Window 实际是 View 的直接管理者。
6、window的三大操作:addView、upView、removeView
7、 Bitmap 中有两个内部枚举类:
保存图片资源:
图片压缩:
基本使用:
8、Context 本身是一个抽象类,是对一系列系统服务接口的封装,包括:内部伏丛资源、包、类加载、I/O操作、权限、主线程、IPC 和组件启动等操作的管理。ContextImpl, Activity, Service, Application 这些都是 Context 的直接或间接子类
9、SharedPreferences 采用key-value(键值对)形式, 主要用于轻量级的数据存储, 尤其适合保存应用的配置参数, 但不建议使用 SharedPreferences 来存储大规模的数据, 可能会降低性能
10、SharedPreferences源码有用synchronize进行加锁同步
11、Handler 有两个主要用途:
(1)安排 Message 和 runnables 在将来的某个时刻执行;
(2)将要在不同链厅闷于自己的线程上执行的操作排入队列。(在多个线程并发更新UI的同时保证线程安全。)
只有主线程能对UI进行操作,所以在对UI进行跟改之前,ViewRootImpl 对UI操作做了验证,这个验证工作是由 ViewRootImpl的 checkThread 方法完成:
12、ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,其他线程则无法获取。Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。当不同线程访问同一个ThreadLocal 的 get方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLcoal 的索引去查找对应的value值:
13、Android 提供了几种途径来从其他线程访问 UI 线程:
Android单线程模式必须遵守的规则:
14、HandlerThread 集成了 Thread,却和普通的 Thread 有显着的不同。普通的 Thread 主要用于在 run 方法中执行一个耗时任务,而 HandlerThread 在内部创建了消息队列,外界需要通过 Handler 的消息方式通知 HanderThread 执行一个具体的任务。
15、IntentService 可用于执行后台耗时的任务,当任务执行后会自动停止,由于其是 Service 的原因,它的优先级比单纯的线程要高,所以 IntentService 适合执行一些高优先级的后台任务。在实现上,IntentService 封装了 HandlerThread 和 Handler。IntentService 第一次启动时,会在 onCreatea 方法中创建一个 HandlerThread,然后使用的 Looper 来构造一个 Handler 对象 mServiceHandler,这样通过 mServiceHandler 发送的消息最终都会在 HandlerThread 中执行。每次启动 IntentService,它的 onStartCommand 方法就会调用一次,onStartCommand 中处理每个后台任务的 Intent,onStartCommand 调用了 onStart 方法。可以看出,IntentService 仅仅是通过 mServiceHandler 发送了一个消息,这个消息会在 HandlerThread 中被处理。mServiceHandler 收到消息后,会将 Intent 对象传递给 onHandlerIntent 方法中处理,执行结束后,通过 stopSelf(int startId) 来尝试停止服务。(stopSelf() 会立即停止服务,而 stopSelf(int startId) 则会等待所有的消息都处理完毕后才终止服务)。
16、RecyclerView 优化
‘贰’ Android自定义View——从零开始实现书籍翻页效果(一)
前言 :本篇是系列博客的第三篇,这次我们要研究 书籍翻页效果 。不知道大家平时有没用过iReader、掌阅这些小说软件,里面的翻页效果感觉十分的酷炫。有心想研究研究如何实现,于是网上找了找,发现这方面的教学资料非常少,所幸能找到 何明桂大大 的 Android 实现书籍翻页效果----原理篇 这样的入门博客(感谢大大 Orz),我们就以这篇博客为切入点从零实现我们自己的翻页效果。由于这次坑比较深,预计会写好几期,感兴趣的小伙伴可以点下关注以便及时收到更新提醒,谢谢大家的支持 ~
本篇只着重于思路和实现步骤,里面用到的一些知识原理不会非常细地拿来讲,如果有不清楚的api或方法可以在网上搜下相应的资料,肯定有大神讲得非常清楚的,我这就不献丑了。本着认真负责的精神我会把相关知识的博文链接也贴出来(其实就是懒不想写那么多哈哈),大家可以自行传送。为了照顾第一次阅读系列博客的小伙伴,本篇会出现一些在之前 系列博客 就讲过的内容,看过的童鞋自行跳过该段即可
国际惯例,先上效果图,本次主要实现了 基本的上下翻页效果 与 右侧最大翻页距离的限制
在看这篇博客之前,希望大家能先了解一下书籍翻页的实现原理,博客链接我已经贴出来了。通过原理讲解我们知道,整个书籍翻页效果界面分成了三个区域, A 为当前页区域, B 为下一页区域, C 为当前页背面,如图所示
书籍翻页效果的实现就是要以我们 触摸屏幕位置的坐标 为基础绘制出这三个区域,形成模拟翻页的特效。要绘制这三个区域,我们需要通过一组 特定的点 来完成,这些点的坐标需要通过两个已知的点( 触摸点 、 相对边缘角 )计算得到,下图我将各个特定点的位置和计算公式贴出来,大家对照着原理一起理解(渣画工望体谅 ╮(╯▽╰)╭ ),其中 b 点是由 ae 和 cj 的交点, k 点是由 ah 和 cj 的交点
简单总结一下, a 是触摸点, f 是触摸点相对的边缘角, eh 我们设置为 af 的垂直平分线,则 g 是 af 的中点, ab 、 ak 、 dj 是 直线 ; 曲线cdb 是起点为 c ,控制点为 e ,终点为 b 的 二阶贝塞尔曲线 ; 曲线kij 是起点为 k ,控制点为 h ,终点为 j 的 二阶贝塞尔曲线 ,区域 A 、 B 、 C 就由这些点和线划分开来。我们将这些点称为标识点,下一步就是模拟设定 a 和 f 点的位置,将这组标识点绘制到屏幕上来验证我们的计算公式是否正确,创建 BookPageView
实体类 MyPoint 用来存放我们的标识点坐标
界面布局:
在Activity中进行注册
效果如图
前文我们提到 ab 、 ak 、 dj 是 直线 ; 曲线cdb 是起点为 c ,控制点为 e ,终点为 b 的 二阶贝塞尔曲线 ; 曲线kij 是起点为 k ,控制点为 h ,终点为 j 的 二阶贝塞尔曲线 。通过观察分析得知, 区域A 是由View 左上角 , 左下角 , 曲线cdb , 直线 ab 、 ak , 曲线kij , 右上角 连接而成的区域,修改 BookPageView ,利用 path 绘制处 区域A
效果如图
区域C 理论上应该是由点 a , b , d , i , k 连接而成的闭合区域,但由于 d 和 i 是曲线上的点,我们没办法直接从 d 出发通过 path 绘制路径连接 b 点( i , k 同理),也就不能只用 path 的情况下直接绘制出 区域C ,我们需要用 PorterDuffXfermode 方面的知识“曲线救国”。我们试着先将点 a , b , d , i , k 连接起来,观察闭合区域与 区域A 之间的联系。修改 BookPageView
效果如图
我们将两条曲线也画出来对比观察
观察分析后可以得出结论, 区域C 是 由直线ab,bd,dj,ik,ak连接而成的区域 减去 与区域A交集部分 后剩余的区域。于是我们设置 区域C 画笔 Xfermode 模式为 DST_ATOP
效果如图
最后是 区域B ,因为 区域B 处于最底层,我们直接将 区域B 画笔 Xfermode 模式设为 DST_ATOP ,在 区域A、C 之后绘制即可,修改 BookPageView
效果如图
翻页可以从右下方翻自然也可以从右上方翻,我们将 f 点设在右上角,由于View上下两部分是呈 镜像 的,所以各标识点的位置也应该是镜像对应的,因为 区域B和C 的绘制与 f 点没有关系,所以我们只需要修改 区域A 的绘制逻辑,新增 getPathAFromTopRight() 方法
效果如图
之前由于测试效果没有对View的大小进行重新测量,在实现触摸翻页之前先把这个结了。重写View的 onMeasure() 方法
我们的需求是,在上半部分翻页时 f 点在右上角,在下半部分翻页时 f 则在右下角,当手指离开屏幕时回到 初始状态 ,根据需求,修改 BookPageView
在Activity中监听View的 onTouch 状态
注意,要设置 android:clickable 为 true ,否则无法监听到 ACTION_MOVE 和 ACTION_UP 状态
效果如图
到这里我们已经实现了基本的翻页效果,但要还原真实的书籍翻页效果,我们还需要设置一些限制条件来完善我们的项目
对于一般的书本来说,最左侧应该是钉起来的,也就是说如果我们从右侧翻页,翻动的距离是 有限制的 ,最下方翻页形成的曲线起点( c 点)的x坐标不能小于0(上方同理),按照这个限定条件,修改我们的 BookPageView
效果如图
至此本篇教程就告一段落了,当然还有许多功能需要继续完善,例如横向翻页、翻页动画、阴影效果等等,这些都会在后面的教程中一一解决。如果大家看了感觉还不错麻烦点个赞,你们的支持是我最大的动力~
‘叁’ Android 自定义TextView实现文字两端对齐
Android组件有很多自带的属性,如 android:layout_width、android:layout_height等,使用 attrs.xml 文件,可以自己定义属性,这里我在 attrs.xml 文件中添加如下属性,用作判断是否需要两端对齐
自定义view计算文字长度实现两端对齐效果
然后直接在使用布局文件中使用即可
‘肆’ Android开发 自定义View
Android自定义View实现很简单:
1、继承View,重写构造函数、onDraw,(onMeasure)等函数。
2、如果自定义的View需要有自定义的属性,需要在values下建立attrs.xml。在其中定义你的属性。
3、在使用到自定义View的xml布局文件中需要加入xmlns:前缀="http://schemas.android.com/apk/res/你的自定义View所在的包路径".
4、在使用自定义属性的时候,使用前缀:属性名,如my:textColor="#FFFFFFF"。
实例:
自定义TextView类:
复制代码
package com.zst.service.component;
import com.example.hello_wangle.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class MyTextView extends TextView {
//不能在布局文件中使用
public MyTextView(Context context) {
super(context);
}
//布局文件中用到此构造函数
‘伍’ Carson带你学Android:手把手教你写一个完整的自定义View
自定义View一共分为两大类,具体如下图:
对于自定义View的类型介绍及使用场景如下图:
在使用自定义View时有很多注意点(坑),希望大家要非常留意:
View的内部本身提供了post系列的方法,完全可以替代Handler的作用,使用起来更加方便、直接。
主要针对View中含有线程或动画的情况: 当View退出或不可见时,记得及时停止该View包含的线程和动画,否则会造成内存泄露问题 。
当View带有滑动嵌套情况时,必须要处理好滑动冲突,否则会严重影响View的显示效果。
接下来,我将用自定义View中最常用的 继承View 来说明自定义View的具体应用和需要注意的点
在下面的例子中,我将讲解:
下面我将逐个步骤进行说明:
步骤1:创建自定义View类(继承View类)
特别注意:
步骤2:在布局文件中添加自定义View类的组件及显示
至此,一个基本的自定义View已经实现了,运行效果如下图。
接下来继续看自定义View关于属性自定义的问题:
先来看wrap_content & match_parent属性的区别
如果不手动设置支持 wrap_content 属性,那么 wrap_content 属性是不会生效(显示效果同 match_parent )
padding 属性:用于设置控件内容相对控件边缘的边距;
如果不手动设置支持padding属性,那么padding属性在自定义View中是不会生效的。
绘制时考虑传入的padding属性值(四个方向)。
除了常见的以android:开头的系统属性(如下所示),很多场景下自定义View还需要系统所没有的属性,即自定义属性。
实现自定义属性的步骤如下:
下面我将对每个步骤进行具体介绍
对于自定义属性类型 & 格式如下:
至此,一个较为规范的自定义View已经完成了。
Carson_Ho的github: 自定义View的具体应用
不定期分享关于 安卓开发 的干货,追求 短、平、快 ,但 却不缺深度 。
‘陆’ Android 自定义View之Layout过程
系列文章:
在上篇文章: Android 自定义View之Measure过程 ,我们分析了Measure过程,本次将会掀开承上启下的Layout过程神秘面纱,
通过本篇文章,你将了解到:
在上篇文章的比喻里,我们说过:
该ViewGroup 重写了onMeasure(xx)和onLayout(xx)方法:
同时,当layout 执行结束,清除PFLAG_FORCE_LAYOUT标记,该标记会影响Measure过程是否需要执行onMeasure。
该View 重写了onMeasure(xx)和onLayout(xx)方法:
MyViewGroup里添加了MyView、Button两个控件,最终运行的效果如下:
可以看出,MyViewGroup 里子布局的是横向摆放的。我们重点关注Layout过程。实际上,MyViewGroup里我们只重写了onLayout(xx)方法,MyView也是重写了onLayout(xx)方法。
接下来,分析View Layout过程。
与Measure过程类似,连接ViewGroup onLayout(xx)和View onLayout(xx)之间的桥梁是View layout(xx)。
可以看出,最终都调用了setFrame(xx)方法。
对于Measure过程在onMeasure(xx)里记录了尺寸的值,而对于Layout过程则在layout(xx)里记录了坐标值,具体来说是在setFrame(xx)里,该方法两个重点地方:
View.onLayout(xx)是空实现
从layout(xx)和onLayout(xx)声明可知,这两个方法都是可以被重写的,接下来看看ViewGroup是否重写了它们。
ViewGroup.layout(xx)虽然重写了layout(xx),但是仅仅做了简单判断,最后还是调用了View.layout(xx)。
这重写后将onLayout变为抽象方法,也就是说继承自ViewGroup的类必须重写onLayout(xx)方法。
我们以FrameLayout为例,分析其onLayout(xx)做了什么。
FrameLayout.onLayout(xx)为子布局Layout的时候,起始坐标都是以FrameLayout为基准,并没有记录上一个子布局占了哪块位置,因此子布局的摆放位置可能会重叠,这也是FrameLayout布局特性的由来。而我们之前的Demo在水平方向上记录了上一个子布局的摆放位置,下一个摆放时只能在它之后,因此就形成了水平摆放的功能。
由此类推,我们常说的某个子布局在父布局里的哪个位置,决定这个位置的即是ViewGroup.onLayout(xx)。
上边我们分析了View.layout(xx)、View.onLayout(xx)、ViewGroup.layout(xx)、ViewGroup.onLayout(xx),这四者什么关系呢?
View.layout(xx)
View.onLayout(xx)
ViewGroup.layout(xx)
ViewGroup.onLayout(xx)
View/ViewGroup 子类需要重写哪些方法:
用图表示:
通过上述的描述,我们发现Measure过程和Layout过程里定义的方法比较类似:
它俩的套路比较类似:measure(xx)、layout(xx)一般不需要我们重写,measure(xx)里调用onMeasure(xx),layout(xx)为调用者设置坐标值。
若是ViewGroup:onMeasure(xx)里遍历子布局,并测量每个子布局,最后将结果汇总,设置自己测量的尺寸;onLayout(xx)里遍历子布局,并设置每个子布局的坐标。
若是View:onMeasure(xx)则测量自身,并存储测量尺寸;onLayout(xx)不需要做什么。
Measure过程虽然比Layout过程复杂,但仔细分析后就会发现其本质就是为了设置两个成员变量:
而Layout过程虽然比较简单,其本质是为了设置坐标值
将Measure设置的变量和Layout设置的变量联系起来:
此外,Measure过程通过设置PFLAG_LAYOUT_REQUIRED 标记来告诉需要进行onLayout,而Layout过程通过清除 PFLAG_FORCE_LAYOUT来告诉Measure过程不需要执行onMeasure了。
这就是Layout的承上作用
我们知道View的绘制需要依靠Canvas绘制,而Canvas是有作用区域限制的。例如我们使用:
Cavas绘制的起点是哪呢?
对于硬件绘制加速来说:正是通过Layout过程中设置的RenderNode坐标。
而对于软件绘制来说:
关于硬件绘制加速/软件绘制 后续文章会分析。
这就是Layout的启下作用
以上即是Measure、Layout、Draw三者的内在联系。
当然Layout的"承上"还需要考虑margin、gravity等参数的影响。具体用法参见最开始的Demo。
getMeasuredWidth()/getMeasuredHeight 与 getWidth/getHeight区别
我们以获取width为例,分别来看看其方法:
getMeasuredWidth():获取测量的宽,属于"临时值"
getWidth():获取View真实的宽
在Layout过程之前,getWidth() 默认为0
何时可以获取真实的宽、高
下篇将分析Draw()过程,我们将分析"一切都是draw出来的"道理
本篇基于 Android 10.0