四大组件
Activity
典型的生命周期
onStart:Activity已经可见了,但是还没有出现在前台,还无法和用户交互
onResume:onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时 Activity才显示到前台.
onPause:表示Activity正在停止,正常情况下,紧接着onStop就会被调用,不能执行太耗时的操作
onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。
onDestory:
当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause -> onStop。这里有一种特殊情况,如果新Activity采用了透明主题,那么当前Activity不会回调onStop”
onStart和onStop是从Activity是否可见这个角度来回调的,而onResume和onPause是从Activity是否位于前台这个角度来回调的,除了这种区别,在实际使用中没有其他明显区别。
在新Activity启动之前,桟顶的Activity需要先onPause后,新Activity才能启动
异常情况下的生命周期
不做特殊处理,那么当系统配置发生改变后,Activity就会被销毁并重新创建
image.png
onPause、onStop、onDestroy均会被调用,会调用onSaveInstanceState来保存当前Activity的状态。Activity被重新创建后,onRestoreInstanceState会被调用。把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。
可通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建 。onRestoreInstanceState总是在onStart之后调用
Activity的Flags
“FLAG_ACTIVITY_NEW_TASK
这个标记位的作用是为Activity指定“singleTask”启动模式,其效果和在XML中指定该启动模式相同。
FLAG_ACTIVITY_SINGLE_TOP
这个标记位的作用是为Activity指定“singleTop”启动模式,其效果和在XML中指定该启动模式相同。
FLAG_ACTIVITY_CLEAR_TOP”
同一个任务栈中所有位于它上面的Activity都要出栈
在这种情况下,被启动Activity的实例如果已经存在,那么系统就会调用它的onNewIntent。如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。通过1.2.1节中的分析可以知道,singleTask启动模式默认就具有此标记位的效果。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
等同于在XML中指定Activity的属性android:excludeFromRecents="true"。不会出现在系统的回退列表中
IntentFilter的匹配规则
IntentFilter中的过滤信息有action、category、data
IPC
Binder:一中跨进程的通讯方式。
Android的消息机制
Handler
Handler的主要作用是将一个任务切换到某个指定的线程中去执行
Handler的工作原理
基本使用
class Mhandler extends Handler {
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
// 根据不同线程发送过来的消息,执行不同的UI操作
// 根据 Message对象的what属性 标识不同的消息
switch (msg.what) {
case 1:
mTextView.setText("执行了线程1的UI操作");
break;
case 2:
mTextView.setText("执行了线程2的UI操作");
break;
}
}
}
//
// 步骤2:在主线程中创建Handler实例
mHandler = new Mhandler();
// 采用继承Thread类实现多线程演示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 步骤3:创建所需的消息对象
Message msg = Message.obtain();
msg.what = 1; // 消息标识
msg.obj = "A"; // 消息内存存放
// 步骤4:在工作线程中 通过Handler发送消息到消息队列中
mHandler.sendMessage(msg);
}
}.start();
// 步骤5:开启工作线程(同时启动了Handler)
1.Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,没有Looper报错;
2.创建完毕后,Looper,MessageQueue;
3.Handler的post方法将一个Runnable投递到Handler内部的Looper中处理/send方法同理;
3.1send方法调用时,MessageQuenue的enqueueMessage方法将消息放入消息队列,Looper发现有新消息时进行处理。
3.2消息中的Runnable或者Handler的handleMessage方法就会被调用
注意Looper是运行在创建Handler所在的线程中的,这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了
ThreadLocal的工作原理
是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal
Android中的drawable
Bitmap处理
变换
圆角
private Bitmap adjustBitmapSize(Bitmap bitmap){
Bitmap newBitmap = null;
float scale = 1;
int maxWidth = getResources().getDisplayMetrics().widthPixels * 9/10;
Matrix matrix = new Matrix();
scale = (float)maxWidth / bitmap.getWidth();
matrix.postScale(scale,scale);
newBitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
return newBitmap;
}
private Bitmap getRoundedCornerBitmap(Bitmap bitmap) {
Bitmap newBitmap = null;
newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
final int color = 0xffffffff;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final float roundPx = 15;
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return newBitmap;
}
private Bitmap getRoundedCornerBitmap2(Bitmap bitmap){
Bitmap newBitmap = null;
newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Canvas canvas = new Canvas(newBitmap);
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final Paint paint = new Paint();
final float roundPx = 15;
paint.setAntiAlias(true);
paint.setColor(0xffffffff);
paint.setShader(shader);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
return newBitmap;
}
View
一些需要注意的小细节
-
View类默认的onMeasure()方法只支持EXACTLY模式,不重写onMeasure()方法的话,就只能使用EXACTLY模
-
ViewGroup的大小为wrap_content时,ViewGroup要对子View进行遍历,以便获得所有子View的大小,决定自己的大小
- 遍历所有子View,从而调用子View的Measure方法来获得每一个子View的测量结果
- Layout过程使用遍历来调用子View的Layout方法,并指定其具体显示的位置,从而来决定其布局位置
-
ViewGroup自身通常无需绘制。会使用dispatchDraw()方法来绘制其子View,通过遍历所有子View,并调用子View的绘制方法来完成绘制工作。
自定义View
几个常见的方法
onFinishInflate():从XML加载组件后回调。
onSizeChanged():组件大小改变时回调。
onMeasure():回调该方法来进行测量。
onLayout():回调该方法来确定显示的位置。
onTouchEvent():监听到触摸事件时回调。
事件拦截机制
2600-2600/com.imooc.viewlayout D/xys﹕ ViewGroupA dispatchTouchEvent
2600-2600/com.imooc.viewlayout D/xys﹕ ViewGroupA onInterceptTouchEvent
2600-2600/com.imooc.viewlayout D/xys﹕ ViewGroupB dispatchTouchEvent
2600-2600/com.imooc.viewlayout D/xys﹕ ViewGroupB onInterceptTouchEvent
2600-2600/com.imooc.viewlayout D/xys﹕ View dispatchTouchEvent
2600-2600/com.imooc.viewlayout D/xys﹕ View onTouchEvent
2600-2600/com.imooc.viewlayout D/xys﹕ ViewGroupB onTouchEvent
2600-2600/com.imooc.viewlayout D/xys﹕ ViewGroupA onTouchEvent
Activity与Activity调用栈
多线和
handler
image.pngimage.png
handler引发的内存泄露
-
原因
主线程的Looper对象的生命周期 = 该应用程序的生命周期
在Java中,非静态内部类 & 匿名内部类都默认持有 外部类的引用 -
解决方案
静态内部类+弱引用
当 外部类(此处以Activity为例) 结束生命周期时(此时系统会调用onDestroy()),清除 Handler消息队列里的所有消息(调用removeCallbacksAndMessages(null))
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
}
自定义View
image.pngCanvas.drawXXX() 和 Paint 基础
Paint类的常用方法
- Paint.setStyle(Style style) 设置绘制模式
- Paint.setColor(int color) 设置颜色
- Paint.setStrokeWidth(float width) 设置线条宽度
- Paint.setTextSize(float textSize) 设置文字大小
- Paint.setAntiAlias(boolean aa) 设置抗锯齿开关
Canvas的常用方法
-
Canvas.drawColor(@ColorInt int color) 颜色填充
-
drawCircle(float centerX, float centerY, float radius, Paint paint) 画圆
-
drawRect(float left, float top, float right, float bottom, Paint paint) 画矩形
-
drawPoint(float x, float y, Paint paint) 画点
-
drawPoints(float[] pts, int offset, int count, Paint paint) / drawPoints(float[] pts, Paint paint) 画点(批量)
-
drawOval(float left, float top, float right, float bottom, Paint paint) 画椭圆
-
drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 画线
-
drawLines(float[] pts, int offset, int count, Paint paint) / drawLines(float[] pts, Paint paint) 画线(批量)
-
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 画圆角矩形
-
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形
Paint.setStrokeCap(cap) 可以设置点的形状,但这个方法并不是专门用来设置点的形状的,而是一个设置线条端点形状的方法。端点有圆头 (ROUND)、平头 (BUTT) 和方头 (SQUARE) 三种,具体会在下一节里面讲。
Path方法
Path 方法第一类:直接描述路径。
addXxx() ——添加子图形
- addCircle(float x, float y, float radius, Direction dir) 添加圆
//最后一个参数为方向,顺时针 (CW clockwise) 和逆时针 (CCW counter-clockwise)
path.addCircle(300, 300, 200, Path.Direction.CW);
canvas.drawPath(path, paint);
//画圆的复杂写法
xxxTo() ——画线(直线或曲线)
这一组和第一组 addXxx() 方法的区别在于,第一组是添加的完整封闭图形(除了 addPath() ),而这一组添加的只是一条线。
- lineTo(float x, float y) / rLineTo(float x, float y) 画直线
paint.setStyle(Style.STROKE);
path.lineTo(100, 100); // 由当前位置 (0, 0) 向 (100, 100) 画一条直线
path.rLineTo(100, 0); // 由当前位置 (100, 100) 向正右方 100 像素的位置画一条直线
当前位置:所谓当前位置,即最后一次调用画 Path 的方法的终点位置。初始值为原点 (0, 0)。
- moveTo(float x, float y) / rMoveTo(float x, float y) 移动到目标位置
Path 方法第二类:辅助的设置或计算
- drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 画 Bitmap
- drawText(String text, float x, float y, Paint paint) 绘制文字
Paint
颜色的设置方式
- 直接设置
paint.setColor(Color.parseColor("#009688"));
paint.setARGB(100, 255, 0, 0);
canvas.drawRect(0, 0, 200, 200, paint);
- setShader(Shader shader) 设置 Shader
LinearGradient RadialGradient SweepGradient BitmapShader ComposeShader 这么几个:
LinearGradient
Shader shader = new LinearGradient(100, 100, 500, 500, Color.parseColor("#E91E63"),
Color.parseColor("#2196F3"), Shader.TileMode.CLAMP);
RadialGradient 辐射渐变
RadialGradient(float centerX, float centerY, float radius,
int centerColor, int edgeColor, TileMode tileMode)
Shader shader = new RadialGradient(300, 300, 200, Color.parseColor("#E91E63"),
Color.parseColor("#2196F3"), Shader.TileMode.CLAMP);
paint.setShader(shader);
SweepGradient 扫描渐变
Shader shader = new SweepGradient(300, 300, Color.parseColor("#E91E63"),
Color.parseColor("#2196F3"));
paint.setShader(shader);
image.png
BitmapShader
//圆形头像
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.batman);
Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(shader);
canvas.drawCircle(300, 300, 200, paint);
ComposeShader 混合着色器
文字的绘制
Canvas 对绘制的辅助 clipXXX() 和 Matrix
范围裁剪
- clipRect()
//参数内为裁剪范围
canvas.save();
canvas.clipRect(left, top, right, bottom);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();
- clipPath()
路径裁剪
几何变换
Canvas 来做常见的二维变换
- Canvas.translate(float dx, float dy) 平移
- Canvas.rotate(float degrees, float px, float py) 旋转
- Canvas.scale(float sx, float sy, float px, float py) 放缩
- skew(float sx, float sy) 错切 ???
使用 Matrix 来做变换
Matrix matrix = new Matrix();
...
matrix.reset();
matrix.postTranslate();
matrix.postRotate();
canvas.save();
canvas.concat(matrix);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();
Canvas.setMatrix(matrix):用 Matrix 直接替换 Canvas 当前的变换矩阵,即抛弃 Canvas 当前的变换,改用 Matrix 的变换(注:根据下面评论里以及我在微信公众号中收到的反馈,不同的系统中 setMatrix(matrix) 的行为可能不一致,所以还是尽量用 concat(matrix) 吧);
Canvas.concat(matrix):用 Canvas 当前的变换矩阵和 Matrix 相乘,即基于 Canvas 当前的变换,叠加上 Matrix 中的变换。
使用 Matrix 来做自定义变换
(使图片发生扭曲)
Matrix.setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount) 用点对点映射的方式设置变换
绘制顺序
- super.onDraw() 前 or 后?
- dispatchDraw():绘制子 View 的方法
每个 View 和 ViewGroup 都会先调用 onDraw() 方法来绘制主体,再调用 dispatchDraw() 方法来绘制子 View。
此时,只能重写dispatchDraw
1.背景
2.主体(onDraw())
3.子 View(dispatchDraw())
4.滑动边缘渐变和滑动条
5.前景
onDrawForeground()必须在API 23以上
draw() 总调度方法
image.png属性动画
ViewPropertyAnimator
view.animate.translationX(500).setDuration(500);
image.png
ObjectAnimator
如果是自定义控件,
需要添加 setter / getter 方法;
用 ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象;
用 start() 方法执行动画。
- 设置动画时长
// imageView1: 500 毫秒
imageView1.animate()
.translationX(500)
.setDuration(500);
// imageView2: 2 秒
ObjectAnimator animator = ObjectAnimator.ofFloat(
imageView2, "translationX", 500);
animator.setDuration(2000);
animator.start();
- setInterpolator(Interpolator interpolator) 设置 Interpolator
1.AccelerateDecelerateInterpolator
1.1 FastOutSlowInInterpolator
//先加速后减速
2.LinearInterpolator
3.AccelerateInterpolator
3.1 FastOutLinearInInterpolator
5.DecelerateInterpolator
5.1 LinearOutSlowInInterpolator
//持续减速到0
6.AnticipateInterpolator
//先回拉一下再进行正常动画轨迹。效果看起来有点像投掷物体或跳跃等动作前的蓄力。
7.OvershootInterpolator
//超过目标值一些,然后再弹回来
8.AnticipateOvershootInterpolator
//开始前回拉,最后超过一些然后回弹
9.BounceInterpolator
//在目标值处弹跳
10.PathInterpolator
//自定义
属性动画高级用法
布局过程的工作内容
测量阶段
onMeasure() 做的事,View 和 ViewGroup 不一样:
- view 在onMeasure()中会计算出自己的尺寸然后保存
- ViewGroup会调用所有子 View 的 measure() 让它们进行自我测量,并根据子 View 计算出的期望尺寸来计算出它们的实际尺寸和位置(注意,期望尺寸与实际尺寸经常不一样)
2.重写 onMeasure() 来全新定义自定义 View 的尺寸
- layout_打头的属性针对父布局
- 具体过程
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = getMeasuredWidth() /2;
int measureHeight = getMeasuredHeight() / 2;
measuredWidth = resolveSize(measuredWidth,widthMeasureSpec);
measureHeight = resolveSize(measureHeight,heightMeasureSpec);
setMeasuredDimension(measuredWidth,measureHeight);
}
-
resolveSize方法
布局阶段
//只有ViewGroup才需要 [图片上传失败...(image-403f01-1541941419980)]
网友评论