#### 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 提高程序的可維護性