第一章
1.为什么不能在onPause方法中进行耗时操作:
当有两个活动,由第一个活动启动第二个活动时,会先执行第一个活动的onpause方法,然后才会创建第二个活动,执行oncreate,onstart,onresume方法,随后才会执行第一个活动的onstop方法,所以不能在活动的onpause方法中进行耗时操作,否则会严重影响第二个活动创建出来的速度
2.异常情况下的生命周期分析:
1.因为资源相关的系统配置导致activity重新被杀死并重新创建:
系统会调用onSaveInstanceState来保存当前 Activity 的状态。然后重新创建这个活动,并把onSaveInstanceState方法中的Bundle对象作为参数传递给oncreate方法onRestoreInstanceState方法,但一般建议在onRestoreInstanceState方法中接收参数。
(onSaveInstanceState方法的调用时机是在 onStop 之前,它和 onPause 没有既定的时序关系,它既可能在 onPause 之前调用,也可能在其之后)
(onRestoreInstanceState方法的调用时机在onstart方法之后)
2. 系统内存不足导致优先级低的活动被杀死
数据恢复方式同上一样,需要的话可以将一些后台任务放进service中从而保证其有一定的优先级避免其被杀死
如果避免重新创建活动:
有些情况下当活动被杀死以上两种情况杀死后我们不希望重新创建活动,可以在系统配置中配置android:configChanges="......",......代表需要不创建活动的情况,需要多种情况中间用|分割开
......如下:
常用的只有 locale、orientation和keyboardHidden这三个选项
当设置后发生设置的情况时,便不会在调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,而是调用onConfigurationChanged方法
Activity的启动模式:
1.standard:标准模式,这也是系统的默认模式。每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在。
一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个 Activity ,那么这个Activity 就运行在启动它的那个 Activity 所在的栈中
当我们用 ApplicationContext 去启动 standard 模式的 Activity 的时候会报错,原因是非 Activity 类型的 Context (如 ApplicationContext )并没有所谓的任务栈。
2.singleTop:栈顶复用模式。如果新的活动已经处于任务栈栈顶,则不会创建新的实例,并且会回调onNewIntent方法,通过此方法我么可以取出我们需要的信息
3.singleTask:栈内复用模式。在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例,和 singleTop 一样,系统也会回调其 onNewIntent 。
如果 D 所需的任务栈为 S1 ,并且当前任务栈 S1 的情况为 ADBC ,根据栈内复用的原则,此时 D 不会重新创建,系统会把 D 切换到栈顶并调用其 onNewIntent 方法,同时由于 singleTask 默认具有 clearTop 的效果,会导致栈内所有在 D 上面的 Activity 全部出栈,于是最终 S1 中的情况为 AD 。这一点比较特殊。
4.singleInstance :单实例模式。这是一种加强的 singleTask 模式,它除了具有 singleTask 模式的所有特性外,还加强了一点,那就是具有此种模式的 Activity 只能单独地位于一个任务栈中,换句话说,比如 Activity A 是 singleInstance 模式,当 A 启动后,系统会为它创建一个新的任务栈,然后 A 独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的 Activity ,除非这个独特的任务栈被系统销毁了。
IntentFilter的匹配规则:
IntentFilter中的过滤信息有action、category、date,一个过滤列表中的 action 、 category 和 data 可以有多个,一个Intent必须同时匹配action、category、date才算完全匹配,只有完全匹配才能成功启动目标 Activity。
action 的匹配规则:
一个过滤规则中可以有多个 action ,那么只要 Intent 中的 action 能够和过滤规则中的任何一个 action 相同即可匹配成功。
category的匹配规则:
如果Intent中含有category,那么必须要与过滤规则中的某一个category匹配成功,但如果没有category,也可以匹配成功,因为在过滤规则中必然含有“android.intent.category.DEFAULT” 这个 category。
date的匹配规则:
data 的语法如下所示。
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
data 由两部分组成, mimeType 和 URI 。 mimeType 指媒体类型,比如 image/jpeg 、 audio/mpeg4-generic 和 video/* 等,可以表示图片、文本、视频等不同的媒体格式,而 URI 中包含的数据就比较多了,下面是URI 的结构:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
Scheme : URI 的模式,比如 http 、 file 、 content 等,如果 URI 中没有指定 scheme ,那么整个 URI 的其他参数无效,这也意味着 URI 是无效的。
Host : URI 的主机名,比如 www.baidu.com ,如果 host 未指定,那么整个 URI 中的其他参数无效,这也意味着 URI 是无效的。
Port : URI 中的端口号,比如 80 ,仅当 URI 中指定了 scheme 和 host 参数的时候 port 参数才是有意义的。
Path 、 pathPattern 和 pathPrefix :这三个参数表述路径信息,其中 path 表示完整的路径信息; pathPattern 也表示完整的路径信息,但是它里面可以包含通配符 “*” , “*” 表示 0 个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么 “*” 要写成 “\\*” , “\” 要写成 “\\\\” ; pathPrefix 表示路径的前缀信息。
如果要为 Intent 指定完整的 data ,必须要调用 setDataAndType 方法,不能先调用 setData 再调用 setType ,因为这两个方法彼此会清除对方的值。
例:intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg")
当我们通过隐式方式启动一个 Activity 的时候,可以做一下判断,看是否有 Activity 能够匹配我们的隐式 Intent。判断方法有两种:采用 PackageManager 的 resolveActivity 方法或者 Intent 的 resolveActivity 方法,如果它们找不到匹配的 Activity 就会返回 null 。另外, PackageManager 还提供了 queryIntentActivities 方法,这个方法和 resolveActivity 方法不同的是:它不是返回最佳匹配的 Activity 信息而是返回所有成功匹配的 Activity 信息。
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,intflags);
public abstract ResolveInfo resolveActivity(Intent intent,int flags);
上述两个方法的第一个参数比较好理解,第二个参数需要注意,我们要使用 MATCH_DEFAULT_ONLY 这个标记位,这个标记位的含义是仅仅匹配那些在 intent-filter 中声明了 <category android:name="android.intent.category.DEFAULT"/> 这个 category 的 Activity 。使用这个标记位的意义在于,只要上述两个方法不返回 null ,那么 startActivity 一定可以成功。如果不用这个标记位,就可以把 intent-filter 中 category 不含 DEFAULT 的那些 Activity 给匹配出来,从而导致 startActivity 可能失败。
在 action 和 category 中,有一类 action 和 category 比较重要,它们是:
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
这二者共同作用是用来标明这是一个入口 Activity 并且会出现在系统的应用列表中,少了任何一个都没有实际意义,也无法出现在系统的应用列表中,也就是二者缺一不可。
第三章
view的位置参数:
view的位置主要由四个参数决定,left、right、top、bottom。这四个参数关系如下:
由此图可以得知view的width = right - left,height = bottom - top。
这四个参数分别通过getxxx()的方式获得。
从Android3.0开始,View增加了几个新的参数,分别为x、y、translationX和translationY。x、y代表View左上角的坐标,translationX与translationY代表了View相对于左上角的偏移量,并且默认值都为0。
这几个参数之间的关系为:
x = left + translationX y = top + translationY
需要注意的是,View在平移的过程中,left与top是原始左上角的位置信息,是不会发生变化的,发生变化的是x、y、translationX和translationY。
MotionEvent和TouchSlop:
motionEvent:
在手指触碰手机屏幕后产生的一系列事件主要有以下三个:
ACTION_DOWN:手指刚接触到屏幕
ACTION_MOVE:手指在屏幕上滑行
ACTION_UP:手指松开屏幕的一瞬间
通过 MotionEvent 对象我们可以得到点击事件发生的 x 和 y 坐标。为此,系统提供了两组方法: getX/getY 和getRawX/getRawY 。它们的区别其实很简单, getX/getY 返回的是相对于当前 View 左上角的 x 和 y 坐标,而 getRawX/getRawY 返回的是相对于手机屏幕左上角的 x 和y 标。
涉及MotionEvent
使用的代码一般如下:
重写onTouchEvent方法:
int action = MotionEventCompat.getActionMasked(event);
switch(action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
作者:程序员历小冰
链接:https://www.jianshu.com/p/0c863bbde8eb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Pointer:
用于当有多个触摸点时的情况:
为了可以表示多个触摸点的动作,MotionEvent
中引入了Pointer
的概念,一个pointer就代表一个触摸点,每个pointer都有自己的事件类型,也有自己的横轴坐标值。
一个MotionEvent
对象中可能会存储多个pointer的相关信息,每个pointer都会有一个自己的id和index。pointer的id在整个事件流中是不会发生变化的,但是index会发生变化。
除了pointer的概念,MotionEvent
还引入了两个事件类型:
ACTION_POINTER_DOWN
:代表用户又使用一个手指触摸到屏幕上,也就是说,在已经有一个触摸点的情况下,有新出现了一个触摸点。ACTION_POINTER_UP
:代表用户的一个手指离开了触摸屏,但是还有其他手指还在触摸屏上。也就是说,在多个触摸点存在的情况下,其中一个触摸点消失了。它与ACTION_UP
的区别就是,它是在多个触摸点中的一个触摸点消失时(此时,还有触摸点存在,也就是说用户还有手指触摸屏幕)产生,而ACTION_UP
可以说是最后一个触摸点消失时产生。
private final static int INVALID_ID = -1;
private int mActivePointerId = INVALID_ID;
private int mSecondaryPointerId = INVALID_ID;
private float mPrimaryLastX = -1;
private float mPrimaryLastY = -1;
private float mSecondaryLastX = -1;
private float mSecondaryLastY = -1;
public boolean onTouchEvent(MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case MotionEvent.ACTION_DOWN:
int index = event.getActionIndex();
mActivePointerId = event.getPointerId(index);
mPrimaryLastX = MotionEventCompat.getX(event,index);
mPrimaryLastY = MotionEventCompat.getY(event,index);
break;
case MotionEvent.ACTION_POINTER_DOWN:
index = event.getActionIndex();
mSecondaryPointerId = event.getPointerId(index);
mSecondaryLastX = event.getX(index);
mSecondaryLastY = event.getY(index);
break;
case MotionEvent.ACTION_MOVE:
index = event.findPointerIndex(mActivePointerId);
int secondaryIndex = MotionEventCompat.findPointerIndex(event,mSecondaryPointerId);
final float x = MotionEventCompat.getX(event,index);
final float y = MotionEventCompat.getY(event,index);
final float secondX = MotionEventCompat.getX(event,secondaryIndex);
final float secondY = MotionEventCompat.getY(event,secondaryIndex);
break;
case MotionEvent.ACTION_POINTER_UP:
xxxxxx(涉及pointer id的转换,之后的文章会讲解)
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_ID;
mPrimaryLastX =-1;
mPrimaryLastY = -1;
break;
}
return true;
}
作者:程序员历小冰
链接:https://www.jianshu.com/p/0c863bbde8eb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
getAction 和 getActionMasked:
getAction
获得的int值是由pointer的index值和事件类型值组合而成的,而getActionWithMasked
则只返回事件的类型值。
一般来说,getAction() & ACTION_POINTER_INDEX_MASK
就获得了pointer的id,等同于getActionIndex
函数;getAction()& ACTION_MASK
就获得了pointer的事件类型,等同于getActionMasked
函数。
TouchSlop:
通常通过ViewConfiguration.get(getContext()).getScaledEdgeSlop()方法获取此常量。
其实如果细心的话,可以在源码中找到这个常量的定义,文件frameworks/base/core/res/res/values/config.xml 中, “config_viewConfigurationTouchSlop” 对应的就是这个常量的定义。
VelocityTracker 、 GestureDetector 和 Scroller:
velocityTracker:
用于速度追踪,使用时首先在View的onTouchEvent方法中追踪当前单击事件的速度:
VelocityTracker velocityTracker = VelocityTracker.obtain(); velocityTracker.addMovement(event);
当我们想要知道当前的滑动速度时,采用如下方法:
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
需注意的是,在调用下面两个方法获取速度前先执行computeCurrentVelocity方法,里面的参数代表一个单位时间或者单位间隔,单位是ms,下面的两个方法获得速度的单位是:像素/每1000ms, 当我们不使用它时,需要调用velocityTracker.clear(); velocityTracker.recycle();方法回收。
GestureDetector:
手势检测,用于辅助检测用户的单击、双击、滑动、长按等行为,使用方法如下:
1. 首先,需要创建一个 GestureDetector 对象并实现 OnGestureListener 接口,根据需要我们还可以实现 OnDoubleTapListener 从而能够监听双击行为:
GestureDetector mGestureDetector = new GestureDetector(this,new OnGestureListener(){
....................................
});
// 解决长按屏幕后无法拖动的现象
mGestureDetector.setIsLongpressEnabled(false);
2. 接着,接管目标 View 的 onTouchEvent 方法,在待监听 View 的 onTouchEvent 方法中添加如下实现:
boolean consume = mGestureDetector.onTouchEvent(event);
return consume;
3.实现OnGestureListener与OnDoubleTapListener中的方法
在日常开发中,比较常用的有: onSingleTapUp (单击)、 onFling (快速滑动)、onScroll(拖动)、 onLongPress (长按)和 onDoubleTap (双击)。
这里有一个建议供读者参考:如果只是监听滑动相关的,建议自己在 onTouchEvent 中实现,如果要监听双击这种行为的话,那么就使用 GestureDetector。
Scroller:固定代码,暂时我看不懂,等以后懂了在回来补充
Scroller scroller = new Scroller(mContext);
// 缓慢滚动到指定位置
private void smoothScrollTo(int destX,int destY) {
int scrollX = getScrollX();
int delta = destX -scrollX;
// 1000ms 内滑向 destX ,效果就是慢慢滑动
mScroller.startScroll(scrollX,0,delta,0,1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
View 的滑动:
有四种方式实现:
1.使用view本身提供的srollBy与scrollTo方法实现
这种方法移动的是view中的内容,而不是view本身。
源码:
public void scrollTo(int x,int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX,mScrollY,oldX,oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x,int y) {
scrollTo(mScrollX + x,mScrollY + y);
}
其中mScrollX,mScrollY代表的含义为:
这两个方法的区别在于,scrollTo(a,b)是将内容转移距离view边框的位置a,b的距离,是绝对滑动,而scrollBy是将内容移动到距离上次位置的距离a,b的位置,是相对位移。
使用方法直接调用即可
2.使用动画给View添加平移效果来实现滑动
1.在res目录下新建一个animation类型的文件夹,在其中新建一个xml文件,添加设置例如
<set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true"> <translate android:duration="1000" android:fromXDelta="0" android:toXDelta="300"/> </set>
其中android:fillAfter="true"目的为如果不设置该属性,则实行动画后view会在此回到原 点。android:duration="1000"代表动画执行时间, android:fromXDelta="0"代表动画执 行前x轴方向的起始位置,android:toXDelta="300"代表动画执行完毕的x轴的位置,但 这种方法只是将这个view位置进行了移动,但其位置参数其实还是在原地,这个时候点 击view原来的位置能触发点击事件,点击屏幕中view所在的位置无法触发点击事件。
2.使用属性动画移动(比较吊一点,因为能改变位置参数): ObjectAnimator.ofFloat(mCustomView,"translationX",0,300).setDuration(1000).start(); 代表将这个view在1000毫秒的时间内从x方向0的位置移动到x轴方向为300的位置,且这 种方法可以改变该view的位置参数
3.通过改变View的LayoutParams使得View重新布局从而实现滑动
先通过getLayoutParams方法获取到对应view的layoutParams对象,将该对象的 leftmargin等参数进行改变从而实现滑动
例: MarginLayoutParams params = (MarginLayoutParams)mButton1.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
mButton1.requestLayout();
// 或者 mButton1.setLayoutParams(params);
4.通过layout方法实现滑动
public boolean onTouchEvent(MotionEvent event) { //获取手指触摸点的横坐标和纵坐标 int x = (int) event.getX();//不能是getRawX() int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = x; lastY = y; break; case MotionEvent.ACTION_MOVE: //计算移动的距离 int offsetX = x - lastX; int offsetY = y - lastY; //调用layout 方法来重新放置它的位置 layout(getLeft()+offsetX, getTop()+offsetY,getRight()+offsetX , getBottom()+offsetY); break; } return true;
弹性滑动:
1.使用scroller:模板:
Scroller scroller = new Scroller(mContext);
// 缓慢滚动到指定位置
private void smoothScrollTo(int destX,int destY) {
int scrollX = getScrollX();
int deltaX = destX -scrollX;
// 1000ms 内滑向 destX ,效果就是慢慢滑动
mScroller.startScroll(scrollX,0,deltaX,0,1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
上述代码流程如下:在其他activity中调用smoothScrollTo方法,执行startScroll方法,该方法其实本身并没有什么特别的操作,只是将一些值进行了保存,然后执行invalidate方法,该方法会重新绘制view,调用onDrew方法,调用该方法时会执行重写过的computeScroll方法,在该方法中会调用computeScrollOffset方法进行判断,如果为true代表滑动还未结束,反之代表已结束,然后调用scrollTo方法进行短暂的位移,在执行postInvalidate方法重新对view进行绘制,从而进行一个循环直到滑动结束循环结束。
2.使用动画:
final int startX = 0;
final int deltaX = 100;
ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
float fraction = animator.getAnimatedFraction();
mButton1.scrollTo(startX + (int) (deltaX * fraction),0);
}
});
animator.start();
在上述代码中,我们的动画本质上没有作用于任何对象上,它只是在 1000ms 内完成了整个动画过程。利用这个特性,我们就可以在动画的每一帧到来时获取动画完成的比例,然后再根据这个比例计算出当前 View 所要滑动的距离。
3.使用延时策略:
private static final int MESSAGE_SCROLL_TO = 1;
private static final int FRAME_COUNT = 30;
private static final int DELAYED_TIME = 33;
private int mCount = 0;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_SCROLL_TO: {
mCount++;
if (mCount <= FRAME_COUNT) {
float fraction = mCount / (float) FRAME_COUNT;
int scrollX = (int) (fraction * 100);
mButton1.scrollTo(scrollX,0);
mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,
DELAYED_TIME);
}
break;
}
default:
break;
}
};
};