編輯:關於Android編程
提示:在閱讀本文章之前,請確保您對Touch事件的分發機制有一定的了解
在Android的學習過程中經常會聽到或者見到“回調”這個詞,那麼什麼是回調呢?所謂的回調函數就是:在A類中定義了一個方法,這個方法中用到了一個接口和該接口中的抽象方法,但是抽象方法沒有具體的實現,需要B類去實現,B類實現該方法後,它本身不會去調用該方法,而是傳遞給A類,供A類去調用,這種機制就稱為回調。
下面我們拿具體的Button的點擊事件進行模擬分析:
首先,在View類中我們能找到setOnClickListener(OnClickListener l)方法:
復制代碼 代碼如下:
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
可以看到,在該方法中將OnClickListener賦值給了mOnClickListener,那麼我們繼續向下找,會看到在performClick()方法中執行了我們實現的onClick()方法。
復制代碼 代碼如下:
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
由此我們可以清楚的看不到,在父類中我們要用到onClick()方法,但是父類卻沒有去實現該方法,而是定義了一個方法setOnClickListener(OnClickListener l),如果子類想要自己能夠響應點擊事件,則它就必須重寫父類的該方法,實現OnClickListener接口和它的onClick()方法。在子類實現該接口和方法後,將其通過參數傳遞給父類,在父類中執行onClick()方法。
那麼,為什麼會在父類中執行到該方法呢,這就要說到Android中的另一個重要的機制——觸摸事件的傳遞機制。
我們知道,只要我們的手指觸摸到手機屏幕,就一定會執行dispatchTouchEvent(MotionEvent event)方法,接下來我們就看一下dispatchTouchEvent方法中都有哪些內容:
復制代碼 代碼如下:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
這裡我們不細講Touch事件的分發機制,因為網上有哥們已經講的很清楚了。請參看篇首提供的鏈接。
我們看一下第17行,由於我們沒有實現OnTouchListener接口,而onTouch()方法的默認返回值為false,所以第一個if語句中的代碼不會被執行到,進入第二個if語句中,執行了onTouchEvent()方法。那麼我們再來看一下該方法:
復制代碼 代碼如下:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP
&& (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true);
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap,
ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
代碼太長,我們只看重點,在ACTION_UP這個case當中,我們找到了關鍵的代碼(第109行):performClick()。
至此,我們已經基本搞清楚了回調機制的整個過程。
Android實現環形進度條的效果圖如下:自定義控件:AttendanceProgressBar代碼如下:public class AttendanceProgressB
相信大家在很多時候都會用到ListView這個控件,因為確實是用的很多很多,但是有木有遇到過當數據很多很多的時候,往下滑ListView時有時候會卡頓,這就需要我們來優化
ObjectAnimator,通過設置改變對象的屬性來實現動畫效果,常用的方法有這麼幾種,ofFloat()、ofInt()、ofObject()、ofArgb()、of
字符串操作JNI把Java字符串當成引用類型來處理,JNI提供了java字符串與C字符串之間相互轉換的必要函數。因為java字符串對象是不可變的,因此JNI不提供任何修改