#### 3.5.3 滑动冲突的解决方式
在3.5.1节中描述了三种典型的滑动冲突场景,在本节将会一一分析各种场景并给出具体的解决方法。首先我们要分析第一种滑动冲突场景,这也是最简单、最典型的一种滑动冲突,因为它的滑动规则比较简单,不管多复杂的滑动冲突,它们之间的区别仅仅是滑动规则不同而已。抛开滑动规则不说,我们需要找到一种不依赖具体的滑动规则的通用的解决方法,在这里,我们就根据场景1的情况来得出通用的解决方案,然后场景2和场景3我们只需要修改有关滑动规则的逻辑即可。
上面说过,针对场景1中的滑动,我们可以根据滑动的距离差来进行判断,这个距离差就是所谓的滑动规则。如果用ViewPager去实现场景1中的效果,我们不需要手动处理滑动冲突,因为ViewPager已经帮我们做了,但是这里为了更好地演示滑动冲突的解决思想,没有采用ViewPager。其实在滑动过程中得到滑动的角度这个是相当简单的,但是到底要怎么做才能将点击事件交给合适的View去处理呢?这时就要用到3.4节所讲述的事件分发机制了。针对滑动冲突,这里给出两种解决滑动冲突的方式:外部拦截法和内部拦截法。
**1.外部拦截法**
所谓外部拦截法是指点击事情都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,这种方法的伪代码如下所示。
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (父容器需要当前点击事件) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
上述代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件这个条件即可,其他均不需做修改并且也不能修改。这里对上述代码再描述一下,在onInterceptTouchEvent方法中,首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP事件都会直接交由父容器处理,这个时候事件没法再传递给子元素了;其次是ACTION_MOVE事件,这个事件可以根据需要来决定是否拦截,如果父容器需要拦截就返回true,否则返回false;最后是ACTION_UP事件,这里必须要返回false,因为ACTION_UP事件本身没有太多意义。
考虑一种情况,假设事件交由子元素处理,如果父容器在ACTION_UP时返回了true,就会导致子元素无法接收到ACTION_UP事件,这个时候子元素中的onClick事件就无法触发,但是父容器比较特殊,一旦它开始拦截任何一个事件,那么后续的事件都会交给它来处理,而ACTION_UP作为最后一个事件也必定可以传递给父容器,即便父容器的onInterceptTouchEvent方法在ACTION_UP时返回了false。
**2.内部拦截法**
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。它的伪代码如下,我们需要重写子元素的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要此类点击事件)) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
上述代码是内部拦截法的典型代码,当面对不同的滑动策略时只需要修改里面的条件即可,其他不需要做改动而且也不能有改动。除了子元素需要做处理以外,父元素也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisal-lowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
为什么父容器不能拦截ACTION_DOWN事件呢?那是因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去,这样内部拦截就无法起作用了。父元素所做的修改如下所示。
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
下面通过一个实例来分别介绍这两种方法。我们来实现一个类似于ViewPager中嵌套ListView的效果,为了制造滑动冲突,我们写一个类似于ViewPager的控件即可,名字就叫HorizontalScrollViewEx,这个控件的具体实现思想会在第4章进行详细介绍,这里只讲述滑动冲突的部分。
为了实现ViewPager的效果,我们定义了一个类似于水平的LinearLayout的东西,只不过它可以水平滑动,初始化时我们在它的内部添加若干个ListView,这样一来,由于它内部的Listview可以竖直滑动。而它本身又可以水平滑动,因此一个典型的滑动冲突场景就出现了,并且这种冲突属于类型1的冲突。根据滑动策略,我们可以选择水平和竖直的滑动距离差来解决滑动冲突。
首先来看一下Activity中的初始化代码,如下所示。
public class DemoActivity_1 extends Activity {
private static final String TAG = "SecondActivity";
private HorizontalScrollViewEx mListContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.demo_1);
Log.d(TAG, "onCreate");
initView();
}
private void initView() {
LayoutInflater inflater = getLayoutInflater();
mListContainer = (HorizontalScrollViewEx) findViewById(R.id.
container);
final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
final int screenHeight = MyUtils.getScreenMetrics(this).height-
Pixels;
for (int i = 0; i < 3; i++) {
ViewGroup layout = (ViewGroup) inflater.inflate(
R.layout.content_layout, mListContainer, false);
layout.getLayoutParams().width = screenWidth;
TextView textView = (TextView) layout.findViewById(R.id.title);
textView.setText("page " + (i + 1));
layout.setBackgroundColor(Color.rgb(255/(i+1),255 / (i + 1), 0));
createList(layout);
mListContainer.addView(layout);
}
}
private void createList(ViewGroup layout) {
ListView listView = (ListView) layout.findViewById(R.id.list);
ArrayList<String> datas = new ArrayList<String>();
for (int i = 0; i < 50; i++) {
datas.add("name " + i);
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.content_list_item, R.id.name, datas);
listView.setAdapter(adapter);
}
}
上述初始化代码很简单,就是创建了3个ListView并且把ListView加入到我们自定义的HorizontalScrollViewEx中,这里HorizontalScrollViewEx是父容器,而ListView则是子元素,这里就不再多介绍了。
首先采用外部拦截法来解决这个问题,按照前面的分析,我们只需要修改父容器需要拦截事件的条件即可。对于本例来说,父容器的拦截条件就是滑动过程中水平距离差比竖直距离差大,在这种情况下,父容器就拦截当前点击事件,根据这一条件进行相应修改,修改后的HorizontalScrollViewEx的onInterceptTouchEvent方法如下所示。
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
if (! mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
Log.d(TAG, "intercepted=" + intercepted);
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
从上面的代码来看,它和外部拦截法的伪代码的差别很小,只是把父容器的拦截条件换成了具体的逻辑。在滑动过程中,当水平方向的距离大时就判断为水平滑动,为了能够水平滑动所以让父容器拦截事件;而竖直距离大时父容器就不拦截事件,于是事件就传递给了ListView,所以ListView也能上下滑动,如此滑动冲突就解决了。至于mScroller. abortAnimation()这一句话主要是为了优化滑动体验而加入的。
考虑一种情况,如果此时用户正在水平滑动,但是在水平滑动停止之前如果用户再迅速进行竖直滑动,就会导致界面在水平方向无法滑动到终点从而处于一种中间状态。为了避免这种不好的体验,当水平方向正在滑动时,下一个序列的点击事件仍然交给父容器处理,这样水平方向就不会停留在中间状态了。
下面是HorizontalScrollViewEx的具体实现,只展示了和滑动冲突相关的代码:
public class HorizontalScrollViewEx extends ViewGroup {
private static final String TAG = "HorizontalScrollViewEx";
private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
…
private void init() {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
if (! mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
Log.d(TAG, "intercepted=" + intercepted);
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
if (! mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
scrollBy(-deltaX, 0);
break;
}
case MotionEvent.ACTION_UP: {
int scrollX = getScrollX();
int scrollToChildIndex = scrollX / mChildWidth;
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50) {
mChildIndex = xVelocity>0? mChildIndex -1 : mChildIndex + 1;
} else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize -
1));
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
…
}
如果采用内部拦截法也是可以的,按照前面对内部拦截法的分析,我们只需要修改ListView的dispatchTouchEvent方法中的父容器的拦截逻辑,同时让父容器拦截ACTION_MOVE和ACTION_UP事件即可。为了重写ListView的dispatchTouchEvent方法,我们必须自定义一个ListView,称为ListViewEx,然后对内部拦截法的模板代码进行修改,根据需要,ListViewEx的实现如下所示。
public class ListViewEx extends ListView {
private static final String TAG = "ListViewEx";
private HorizontalScrollViewEx2 mHorizontalScrollViewEx2;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
…
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent
(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
mHorizontalScrollViewEx2.requestDisallowInterceptTouch-
Event(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
}
除了上面对ListView所做的修改,我们还需要修改HorizontalScrollViewEx的onInte-rceptTouchEvent方法,修改后的类暂且叫HorizontalScrollViewEx2,其onInterceptTouchEvent方法如下所示。
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
mLastX = x;
mLastY = y;
if (! mScroller.isFinished()) {
mScroller.abortAnimation();
return true;
}
return false;
} else {
return true;
}
}
上面的代码就是内部拦截法的示例,其中mScroller.abortAnimation()这一句不是必须的,在当前这种情形下主要是为了优化滑动体验。从实现上来看,内部拦截法的操作要稍微复杂一些,因此推荐采用外部拦截法来解决常见的滑动冲突。
前面说过,只要我们根据场景1的情况来得出通用的解决方案,那么对于场景2和场景3来说我们只需要修改相关滑动规则的逻辑即可,下面我们就来演示如何利用场景1得出的通用的解决方案来解决更复杂的滑动冲突。这里只详细分析场景2中的滑动冲突,对于场景3中的叠加型滑动冲突,由于它可以拆解为单一的滑动冲突,所以其滑动冲突的解决思想和场景1、场景2中的单一滑动冲突的解决思想一致,只需要分别解决每层之间的滑动冲突即可,再加上本书的篇幅有限,这里就不对场景3进行详细分析了。
对于场景2来说,它的解决方法和场景1一样,只是滑动规则不同而已,在前面我们已经得出了通用的解决方案,因此这里我们只需要替换父容器的拦截规则即可。注意,这里不再演示如何通过内部拦截法来解决场景2中的滑动冲突,因为内部拦截法没有外部拦截法简单易用,所以推荐采用外部拦截法来解决常见的滑动冲突。
下面通过一个实际的例子来分析场景2,首先我们提供一个可以上下滑动的父容器,这里就叫StickyLayout,它看起来就像是可以上下滑动的竖直的LinearLayout,然后在它的内部分别放一个Header和一个ListView,这样内外两层都能上下滑动,于是就形成了场景2中的滑动冲突了。当然这个StickyLayout是有滑动规则的:当Header显示时或者ListView滑动到顶部时,由StickyLayout拦截事件;当Header隐藏时,这要分情况,如果ListView已经滑动到顶部并且当前手势是向下滑动的话,这个时候还是StickyLayout拦截事件,其他情况则由ListView拦截事件。这种滑动规则看起来有点复杂,为了解决它们之间的滑动冲突,我们还是需要重写父容器StickyLayout的onInterceptTouchEvent方法,至于ListView则不用做任何修改,我们来看一下StickyLayout的具体实现,滑动冲突相关的主要代码如下所示。
public class StickyLayout extends LinearLayout {
private int mTouchSlop;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
…
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int intercepted = 0;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mLastXIntercept = x;
mLastYIntercept = y;
mLastX = x;
mLastY = y;
intercepted = 0;
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (mDisallowInterceptTouchEventOnHeader && y <= getHeader-
Height()) {
intercepted = 0;
} else if (Math.abs(deltaY) <= Math.abs(deltaX)) {
intercepted = 0;
}else if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) {
intercepted = 1;
} else if (mGiveUpTouchEventListener ! = null) {
if (mGiveUpTouchEventListener.giveUpTouchEvent(event) &&
deltaY >= mTouchSlop) {
intercepted = 1;
}
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = 0;
mLastXIntercept = mLastYIntercept = 0;
break;
}
default:
break;
}
if (DEBUG) {
Log.d(TAG, "intercepted=" + intercepted);
}
return intercepted ! = 0 && mIsSticky;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (! mIsSticky) {
return true;
}
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (DEBUG) {
Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" +
deltaY + " mlastY=" + mLastY);
}
mHeaderHeight += deltaY;
setHeaderHeight(mHeaderHeight);
break;
}
case MotionEvent.ACTION_UP: {
// 这里做了一下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当
前所处的位置
int destHeight = 0;
if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) {
destHeight = 0;
mStatus = STATUS_COLLAPSED;
} else {
destHeight = mOriginalHeaderHeight;
mStatus = STATUS_EXPANDED;
}
// 慢慢滑向终点
this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
…
}
从上面的代码来看,这个StickyLayout的实现有点复杂,在第4章会详细介绍这个自定义View的实现思想,这里先有大概的印象即可。下面我们主要看它的onIntercept-TouchEvent方法中对ACTION_MOVE的处理,如下所示。
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (mDisallowInterceptTouchEventOnHeader && y <= getHeaderHeight()) {
intercepted = 0;
} else if (Math.abs(deltaY) <= Math.abs(deltaX)) {
intercepted = 0;
} else if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) {
intercepted = 1;
} else if (mGiveUpTouchEventListener ! = null) {
if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >=
mTouchSlop) {
intercepted = 1;
}
}
break;
}
我们来分析上面这段代码的逻辑,这里的父容器是StickyLayout,子元素是ListView。首先,当事件落在Header上面时父容器不会拦截事件;接着,如果竖直距离差小于水平距离差,那么父容器也不会拦截事件;然后,当Header是展开状态并且向上滑动时父容器拦截事件。另外一种情况,当ListView滑动到顶部了并且向下滑动时,父容器也会拦截事件,经过这些层层判断就可以达到我们想要的效果了。另外,giveUpTouchEvent是一个接口方法,由外部实现,在本例中主要是用来判断ListView是否滑动到顶部,它的具体实现如下:
public boolean giveUpTouchEvent(MotionEvent event) {
if (expandableListView.getFirstVisiblePosition() == 0) {
View view = expandableListView.getChildAt(0);
if (view ! = null && view.getTop() >= 0) {
return true;
}
}
return false;
}
上面这个例子比较复杂,需要读者多多体会其中的写法和思想。到这里滑动冲突的解决方法就介绍完毕了,至于场景3中的滑动冲突,利用本节所给出的通用的方法是可以轻松解决的,读者可以自己练习一下。在第4章会介绍View的底层工作原理,并且会介绍如何写出一个好的自定义View。同时,在本节中所提到的两个自定义View:Horizontal-ScrollViewEx和StickyLayout将会在第4章中进行详细的介绍,它们的完整源码请查看本书所提供的示例代码。
- 前言
- 第1章 Activity的生命周期和启动模式
- 1.1 Activity的生命周期全面分析
- 1.1.1 典型情况下的生命周期分析
- 1.1.2 异常情况下的生命周期分析
- 1.2 Activity的启动模式
- 1.2.1 Activity的LaunchMode
- 1.2.2 Activity的Flags
- 1.3 IntentFilter的匹配规则
- 第2章 IPC机制
- 2.1 Android IPC简介
- 2.2 Android中的多进程模式
- 2.2.1 开启多进程模式
- 2.2.2 多进程模式的运行机制
- 2.3 IPC基础概念介绍
- 2.3.1 Serializable接口
- 2.3.2 Parcelable接口
- 2.3.3 Binder
- 2.4 Android中的IPC方式
- 2.4.1 使用Bundle
- 2.4.2 使用文件共享
- 2.4.3 使用Messenger
- 2.4.4 使用AIDL
- 2.4.5 使用ContentProvider
- 2.4.6 使用Socket
- 2.5 Binder连接池
- 2.6 选用合适的IPC方式
- 第3章 View的事件体系
- 3.1 View基础知识
- 3.1.1 什么是View
- 3.1.2 View的位置参数
- 3.1.3 MotionEvent和TouchSlop
- 3.1.4 VelocityTracker、GestureDetector和Scroller
- 3.2 View的滑动
- 3.2.1 使用scrollTo/scrollBy
- 3.2.2 使用动画
- 3.2.3 改变布局参数
- 3.2.4 各种滑动方式的对比
- 3.3 弹性滑动
- 3.3.1 使用Scroller7
- 3.3.2 通过动画
- 3.3.3 使用延时策略
- 3.4 View的事件分发机制
- 3.4.1 点击事件的传递规则
- 3.4.2 事件分发的源码解析
- 3.5 View的滑动冲突
- 3.5.1 常见的滑动冲突场景
- 3.5.2 滑动冲突的处理规则
- 3.5.3 滑动冲突的解决方式
- 第4章 View的工作原理
- 4.1 初识ViewRoot和DecorView
- 4.2 理解MeasureSpec
- 4.2.1 MeasureSpec
- 4.2.2 MeasureSpec和LayoutParams的对应关系
- 4.3 View的工作流程
- 4.3.1 measure过程
- 4.3.2 layout过程
- 4.3.3 draw过程
- 4.4 自定义View
- 4.4.1 自定义View的分类
- 4.4.2 自定义View须知
- 4.4.3 自定义View示例
- 4.4.4 自定义View的思想
- 第5章 理解RemoteViews
- 5.1 RemoteViews的应用
- 5.1.1 RemoteViews在通知栏上的应用
- 5.1.2 RemoteViews在桌面小部件上的应用
- 5.1.3 PendingIntent概述
- 5.2 RemoteViews的内部机制
- 5.3 RemoteViews的意义
- 第6章 Android的Drawable
- 6.1 Drawable简介
- 6.2 Drawable的分类
- 6.2.1 BitmapDrawable2
- 6.2.2 ShapeDrawable
- 6.2.3 LayerDrawable
- 6.2.4 StateListDrawable
- 6.2.5 LevelListDrawable
- 6.2.6 TransitionDrawable
- 6.2.7 InsetDrawable
- 6.2.8 ScaleDrawable
- 6.2.9 ClipDrawable
- 6.3 自定义Drawable
- 第7章 Android动画深入分析
- 7.1 View动画
- 7.1.1 View动画的种类
- 7.1.2 自定义View动画
- 7.1.3 帧动画
- 7.2 View动画的特殊使用场景
- 7.2.1 LayoutAnimation
- 7.2.2 Activity的切换效果
- 7.3 属性动画
- 7.3.1 使用属性动画
- 7.3.2 理解插值器和估值器 /
- 7.3.3 属性动画的监听器
- 7.3.4 对任意属性做动画
- 7.3.5 属性动画的工作原理
- 7.4 使用动画的注意事项
- 第8章 理解Window和WindowManager
- 8.1 Window和WindowManager
- 8.2 Window的内部机制
- 8.2.1 Window的添加过程
- 8.2.2 Window的删除过程
- 8.2.3 Window的更新过程
- 8.3 Window的创建过程
- 8.3.1 Activity的Window创建过程
- 8.3.2 Dialog的Window创建过程
- 8.3.3 Toast的Window创建过程
- 第9章 四大组件的工作过程
- 9.1 四大组件的运行状态
- 9.2 Activity的工作过程
- 9.3 Service的工作过程
- 9.3.1 Service的启动过程
- 9.3.2 Service的绑定过程
- 9.4 BroadcastReceiver的工作过程
- 9.4.1 广播的注册过程
- 9.4.2 广播的发送和接收过程
- 9.5 ContentProvider的工作过程
- 第10章 Android的消息机制
- 10.1 Android的消息机制概述
- 10.2 Android的消息机制分析
- 10.2.1 ThreadLocal的工作原理
- 10.2.2 消息队列的工作原理
- 10.2.3 Looper的工作原理
- 10.2.4 Handler的工作原理
- 10.3 主线程的消息循环
- 第11章 Android的线程和线程池
- 11.1 主线程和子线程
- 11.2 Android中的线程形态
- 11.2.1 AsyncTask
- 11.2.2 AsyncTask的工作原理
- 11.2.3 HandlerThread
- 11.2.4 IntentService
- 11.3 Android中的线程池
- 11.3.1 ThreadPoolExecutor
- 11.3.2 线程池的分类
- 第12章 Bitmap的加载和Cache
- 12.1 Bitmap的高效加载
- 12.2 Android中的缓存策略
- 12.2.1 LruCache
- 12.2.2 DiskLruCache
- 12.2.3 ImageLoader的实现446
- 12.3 ImageLoader的使用
- 12.3.1 照片墙效果
- 12.3.2 优化列表的卡顿现象
- 第13章 综合技术
- 13.1 使用CrashHandler来获取应用的crash信息
- 13.2 使用multidex来解决方法数越界
- 13.3 Android的动态加载技术
- 13.4 反编译初步
- 13.4.1 使用dex2jar和jd-gui反编译apk
- 13.4.2 使用apktool对apk进行二次打包
- 第14章 JNI和NDK编程
- 14.1 JNI的开发流程
- 14.2 NDK的开发流程
- 14.3 JNI的数据类型和类型签名
- 14.4 JNI调用Java方法的流程
- 第15章 Android性能优化
- 15.1 Android的性能优化方法
- 15.1.1 布局优化
- 15.1.2 绘制优化
- 15.1.3 内存泄露优化
- 15.1.4 响应速度优化和ANR日志分析
- 15.1.5 ListView和Bitmap优化
- 15.1.6 线程优化
- 15.1.7 一些性能优化建议
- 15.2 内存泄露分析之MAT工具
- 15.3 提高程序的可维护性