SystemUI下拉通知欄的原始碼分析
阿新 • • 發佈:2019-01-04
super_status_bar.xml是systemUI的一個總的佈局檔案。
下面是super_status_bar.xml的原始碼:
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:focusable="true"
android:descendantFocusability="afterDescendants"
android:fitsSystemWindows="true"
android:background="@android:color/transparent"
>
<include layout="@layout/status_bar"
android:layout_width="match_parent"
android:layout_height="@*android:dimen/status_bar_height"
/>
<com.android.systemui.statusbar.phone.PanelHolder
android:id="@+id/panel_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ViewStub android:id="@+id/quick_settings_stub"
android:layout="@layout/quick_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</com.android.systemui.statusbar.phone.PanelHolder>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
從上面的原始碼可以知道,systemUI佈局大概分為三個部分
第一:status_bar,這部分是狀態列。
第二:status_bar_expanded,這部分是下拉通知欄。
第三:quick_settings_stub,這部分是下拉快捷設定欄。
其中,本文中是重點分析下拉通知欄。
*************************************************分割線**************************************
下面是下拉通知欄的佈局檔案status_bar_expended.xml:
<com.android.systemui.statusbar.phone.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:id="@+id/notification_panel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/notification_panel_bg"
android:paddingTop="@dimen/notification_panel_padding_top"
android:layout_marginStart="@dimen/notification_panel_margin_left"
>
<View
android:id="@+id/handle"
android:layout_width="match_parent"
android:layout_height="@dimen/close_handle_height"
android:background="@drawable/status_bar_close"
android:visibility="invisible"
/>
<include
layout="@layout/carrier_label"
android:layout_height="@dimen/carrier_label_height"
android:layout_width="match_parent"
android:layout_marginBottom="@dimen/close_handle_height"
android:layout_gravity="bottom"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/close_handle_underlap"
android:orientation="vertical"
android:animateLayoutChanges="false"
>
<include layout="@layout/status_bar_expanded_header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_panel_header_height"
/>
<TextView
android:id="@+id/emergency_calls_only"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Network.EmergencyOnly"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="4dp"
android:gravity="center"
android:visibility="gone"
/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ViewStub android:id="@+id/flip_settings_stub"
android:layout="@layout/flip_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ScrollView
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadingEdge="none"
android:overScrollMode="ifContentScrolls"
>
<com.android.systemui.statusbar.policy.NotificationRowLayout
android:id="@+id/latestItems"
android:layout_width="match_parent"
android:layout_height="wrap_content"
systemui:rowHeight="@dimen/notification_row_min_height"
/>
</ScrollView>
</FrameLayout>
</LinearLayout>
</com.android.systemui.statusbar.phone.NotificationPanelView>
其中,程式碼段: <include layout="@layout/status_bar_expanded_header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_panel_header_height"
/>
該程式碼段是下拉通知欄的頂部的佈局,包括時間,日期和一個清除通知訊息的按鈕。
程式碼段:<com.android.systemui.statusbar.policy.NotificationRowLayout
android:id="@+id/latestItems"
android:layout_width="match_parent"
android:layout_height="wrap_content"
systemui:rowHeight="@dimen/notification_row_min_height"
/>
該程式碼段是用於存放下拉通知欄裡面的各種通知訊息。
下面主要分析com.android.systemui.statusbar.phone.NotificationPanelView下拉通知欄的實現類的原始碼:
public class NotificationPanelView extends PanelView {};由類定義的程式碼可以知道該類是繼承PanelView類,PanelView類則是繼承於FrameLayout。
下拉選單欄的主要實現都是在NotificationPanelView的父類PanelView中實現。
看以下的程式碼段:
protected void onFinishInflate() {
super.onFinishInflate();
mHandleView = findViewById(R.id.handle);
loadDimens();
if (DEBUG) logf("handle view: " + mHandleView);
if (mHandleView != null) {
mHandleView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("Garment30", "onTouch");
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
final float y = event.getY(pointerIndex);
final float rawDelta = event.getRawY() - event.getY();
final float rawY = y + rawDelta;
if (DEBUG) logf("handle.onTouch: a=%s p=[%d,%d] y=%.1f rawY=%.1f off=%.1f",
MotionEvent.actionToString(event.getAction()),
mTrackingPointer, pointerIndex,
y, rawY, mTouchOffset);
PanelView.this.getLocationOnScreen(mAbsPos);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
Log.i("Garment30", "ACTION_DOWN");
mTracking = true;
mHandleView.setPressed(true);
postInvalidate(); // catch the press state change
mInitialTouchY = y;
mVelocityTracker = FlingTracker.obtain();
trackMovement(event);
mTimeAnimator.cancel(); // end any outstanding animations
mBar.onTrackingStarted(PanelView.this);
mTouchOffset = (rawY - mAbsPos[1]) - mExpandedHeight;
if (mExpandedHeight == 0) {
mJustPeeked = true;
runPeekAnimation();
}
break;
case MotionEvent.ACTION_POINTER_UP:
Log.i("Garment30", "ACTION_POINTER_UP");
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
final float newY = event.getY(newIndex);
final float newRawY = newY + rawDelta;
mTrackingPointer = event.getPointerId(newIndex);
mTouchOffset = (newRawY - mAbsPos[1]) - mExpandedHeight;
mInitialTouchY = newY;
}
break;
case MotionEvent.ACTION_MOVE:
Log.i("Garment30", "ACTION_MOVE");
Log.i("Garment28", "PanelView---ACTION_MOVE");
final float h = rawY - mAbsPos[1] - mTouchOffset;
if (h > mPeekHeight) {
Log.i("Garment28", "PanelView---h > mPeekHeight");
if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
mPeekAnimator.cancel();
}
mJustPeeked = false;
}
if (!mJustPeeked) {
Log.i("Garment28", "PanelView---!mJustPeeked--h:"+h);
//下面兩段程式碼需要同時使用
PanelView.this.setExpandedHeightInternal(h);//遮蔽了這段程式碼,下拉選單無法隨手的移動一起下拉,鬆手後選單移動到預設的長度
mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);//遮蔽了這段程式碼,下拉通知欄顯示為透明,鬆手後才正常
}
trackMovement(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
Log.i("Garment30", "ACTION_CANCEL");
mFinalTouchY = y;
mTracking = false;
mTrackingPointer = -1;
mHandleView.setPressed(false);
postInvalidate(); // catch the press state change
mBar.onTrackingStopped(PanelView.this);
trackMovement(event);
float vel = 0, yVel = 0, xVel = 0;
boolean negative = false;
if (mVelocityTracker != null) {
// the velocitytracker might be null if we got a bad input stream
mVelocityTracker.computeCurrentVelocity(1000);
yVel = mVelocityTracker.getYVelocity();
negative = yVel < 0;
xVel = mVelocityTracker.getXVelocity();
if (xVel < 0) {
xVel = -xVel;
}
if (xVel > mFlingGestureMaxXVelocityPx) {
xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
}
vel = (float)Math.hypot(yVel, xVel);
if (vel > mFlingGestureMaxOutputVelocityPx) {
vel = mFlingGestureMaxOutputVelocityPx;
}
mVelocityTracker.recycle();
mVelocityTracker = null;
}
// if you've barely moved your finger, we treat the velocity as 0
// preventing spurious flings due to touch screen jitter
final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
if (deltaY < mFlingGestureMinDistPx
|| vel < mFlingExpandMinVelocityPx
) {
vel = 0;
}
if (negative) {
vel = -vel;
}
if (DEBUG) logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
deltaY,
xVel, yVel,
vel);
fling(vel, true); //鬆手後下拉通知欄回彈的動畫
break;
}
return true;
}});
}
}
其中,mHandleView = findViewById(R.id.handle);程式碼用於初始化下拉通知欄最下面的那一條直線,可以知道該view的觸控事件用於控制下拉通知欄的拉動時的高度的變化。
在MotionEvent.ACTION_MOVE事件中PanelView.this.setExpandedHeightInternal(h);這段程式碼實現了拉動過程在中下拉通知欄的大小的改變,傳入的h變數就是下拉通知欄的高度。看該方法的實現:
public void setExpandedHeightInternal(float h) {
if (Float.isNaN(h)) {
// If a NaN gets in here, it will freeze the Animators.
if (DEBUG_NAN) {
Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
new Throwable());
}
h = 0;
}
float fh = getFullHeight();
if (fh == 0) {
// Hmm, full height hasn't been computed yet
}
if (h < 0) h = 0;
if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
mExpandedHeight = h; //更新最新的通知欄的長度,遮蔽了這段程式碼,通知欄無法拉出
if (DEBUG) logf("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
Log.i("Garment30", "PanelView---setExpandedHeightInternal--h:"+h);
requestLayout(); //觸發OnMeasure()和onLayout()進行View的重繪, 遮蔽這段程式碼,下拉通知欄無法顯示,初始時h=0,預設狀態時,h=729,最大為1919
// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
// lp.height = (int) mExpandedHeight;
// setLayoutParams(lp);
mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
}
在setExpandedHeightInternal()函式中,mExpandedHeight = h;這段程式碼用於更新需要重畫的最新的下拉選單欄的高度。
requestLayout();該函式觸發了OnMeasure()和onLayout()函式,對該View進行重新測量並重新佈局。
當手鬆開的時候,通知欄會有一個回彈的動作,看到fling(vel, true);這段程式碼:
public void fling(float vel, boolean always) {
if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
mVel = vel;
if (always||mVel != 0) {
animationTick(0); // begin the animation 開始執行回彈的動畫
}
}
其中animationTick(0);是回彈動畫的實現方法:
private void animationTick(long dtms) {
if (!mTimeAnimator.isStarted()) {
// XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
mTimeAnimator = new TimeAnimator();
mTimeAnimator.setTimeListener(mAnimationCallback);
if (mPeekAnimator != null) mPeekAnimator.cancel();
mTimeAnimator.start();
mRubberbanding = mRubberbandingEnabled // is it enabled at all?
&& mExpandedHeight > getFullHeight() // are we past the end?
&& mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
if (mRubberbanding) {
mClosing = true;
} else if (mVel == 0) {
// if the panel is less than halfway open, close it
mClosing = (mFinalTouchY / getFullHeight()) < 0.5f;
} else {
mClosing = mExpandedHeight > 0 && mVel < 0;
}
} else if (dtms > 0) {
final float dt = dtms * 0.001f; // ms -> s
if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
final float fh = getFullHeight();
boolean braking = false;
if (BRAKES) {
if (mClosing) {
braking = mExpandedHeight <= mCollapseBrakingDistancePx;
mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
} else {
braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
}
} else {
mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
}
mVel += mAccel * dt;
if (braking) {
if (mClosing && mVel > -mBrakingSpeedPx) {
mVel = -mBrakingSpeedPx;
} else if (!mClosing && mVel < mBrakingSpeedPx) {
mVel = mBrakingSpeedPx;
}
} else {
if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
mVel = -mFlingCollapseMinVelocityPx;
} else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
mVel = mFlingGestureMaxOutputVelocityPx;
}
}
float h = mExpandedHeight + mVel * dt;
if (mRubberbanding && h < fh) {
h = fh;
}
if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Log.i("Garment30", "PanelView---animationTick--h:"+h); //end at 729
setExpandedHeightInternal(h);
mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
if (mVel == 0
|| (mClosing && mExpandedHeight == 0)
|| ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
post(mStopAnimator); //回彈動畫停止
}
} else {
Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
+ mExpandedHeight + " v=" + mVel + ")");
}
}
可以看到,setExpandedHeightInternal(h); mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);該方法中有呼叫這兩個方法對下拉選單欄的長度進行更新的操作。mTimeAnimator = new TimeAnimator();mTimeAnimator.setTimeListener(mAnimationCallback);也初始化了一個TimeAnimator物件,並設定了監聽器。
private final TimeListener mAnimationCallback = new TimeListener() {
@Override
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
animationTick(deltaTime);
}
};
該監聽器中的回撥函式呼叫了animationTick(deltaTime)下拉通知欄的回彈動畫的方法,所以我們鬆開手後通知欄會有一個回彈的動作。
當滿足一定條件時,回彈動畫停止。
if (mVel == 0|| (mClosing && mExpandedHeight == 0)|| ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
post(mStopAnimator); //回彈動畫停止
}
------------------------------------分割線----------------------------------------------------
注意:下拉快捷設定欄的實現和下拉通知欄的實現是差不多,只是裝載的內容不同。所以也可以通過類似的方法來分析下拉快速設定欄。
下面是super_status_bar.xml的原始碼:
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:focusable="true"
android:descendantFocusability="afterDescendants"
android:fitsSystemWindows="true"
android:background="@android:color/transparent"
>
<include layout="@layout/status_bar"
android:layout_width="match_parent"
android:layout_height="@*android:dimen/status_bar_height"
/>
<com.android.systemui.statusbar.phone.PanelHolder
android:id="@+id/panel_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ViewStub android:id="@+id/quick_settings_stub"
android:layout="@layout/quick_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</com.android.systemui.statusbar.phone.PanelHolder>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
從上面的原始碼可以知道,systemUI佈局大概分為三個部分
第一:status_bar,這部分是狀態列。
第二:status_bar_expanded,這部分是下拉通知欄。
第三:quick_settings_stub,這部分是下拉快捷設定欄。
其中,本文中是重點分析下拉通知欄。
*************************************************分割線**************************************
下面是下拉通知欄的佈局檔案status_bar_expended.xml:
<com.android.systemui.statusbar.phone.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:id="@+id/notification_panel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/notification_panel_bg"
android:paddingTop="@dimen/notification_panel_padding_top"
android:layout_marginStart="@dimen/notification_panel_margin_left"
>
<View
android:id="@+id/handle"
android:layout_width="match_parent"
android:layout_height="@dimen/close_handle_height"
android:background="@drawable/status_bar_close"
android:visibility="invisible"
/>
<include
layout="@layout/carrier_label"
android:layout_height="@dimen/carrier_label_height"
android:layout_width="match_parent"
android:layout_marginBottom="@dimen/close_handle_height"
android:layout_gravity="bottom"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/close_handle_underlap"
android:orientation="vertical"
android:animateLayoutChanges="false"
>
<include layout="@layout/status_bar_expanded_header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_panel_header_height"
/>
<TextView
android:id="@+id/emergency_calls_only"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Network.EmergencyOnly"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="4dp"
android:gravity="center"
android:visibility="gone"
/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ViewStub android:id="@+id/flip_settings_stub"
android:layout="@layout/flip_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ScrollView
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadingEdge="none"
android:overScrollMode="ifContentScrolls"
>
<com.android.systemui.statusbar.policy.NotificationRowLayout
android:id="@+id/latestItems"
android:layout_width="match_parent"
android:layout_height="wrap_content"
systemui:rowHeight="@dimen/notification_row_min_height"
/>
</ScrollView>
</FrameLayout>
</LinearLayout>
</com.android.systemui.statusbar.phone.NotificationPanelView>
其中,程式碼段: <include layout="@layout/status_bar_expanded_header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_panel_header_height"
/>
該程式碼段是下拉通知欄的頂部的佈局,包括時間,日期和一個清除通知訊息的按鈕。
程式碼段:<com.android.systemui.statusbar.policy.NotificationRowLayout
android:id="@+id/latestItems"
android:layout_width="match_parent"
android:layout_height="wrap_content"
systemui:rowHeight="@dimen/notification_row_min_height"
/>
該程式碼段是用於存放下拉通知欄裡面的各種通知訊息。
下面主要分析com.android.systemui.statusbar.phone.NotificationPanelView下拉通知欄的實現類的原始碼:
public class NotificationPanelView extends PanelView {};由類定義的程式碼可以知道該類是繼承PanelView類,PanelView類則是繼承於FrameLayout。
下拉選單欄的主要實現都是在NotificationPanelView的父類PanelView中實現。
看以下的程式碼段:
protected void onFinishInflate() {
super.onFinishInflate();
mHandleView = findViewById(R.id.handle);
loadDimens();
if (DEBUG) logf("handle view: " + mHandleView);
if (mHandleView != null) {
mHandleView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("Garment30", "onTouch");
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
final float y = event.getY(pointerIndex);
final float rawDelta = event.getRawY() - event.getY();
final float rawY = y + rawDelta;
if (DEBUG) logf("handle.onTouch: a=%s p=[%d,%d] y=%.1f rawY=%.1f off=%.1f",
MotionEvent.actionToString(event.getAction()),
mTrackingPointer, pointerIndex,
y, rawY, mTouchOffset);
PanelView.this.getLocationOnScreen(mAbsPos);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
Log.i("Garment30", "ACTION_DOWN");
mTracking = true;
mHandleView.setPressed(true);
postInvalidate(); // catch the press state change
mInitialTouchY = y;
mVelocityTracker = FlingTracker.obtain();
trackMovement(event);
mTimeAnimator.cancel(); // end any outstanding animations
mBar.onTrackingStarted(PanelView.this);
mTouchOffset = (rawY - mAbsPos[1]) - mExpandedHeight;
if (mExpandedHeight == 0) {
mJustPeeked = true;
runPeekAnimation();
}
break;
case MotionEvent.ACTION_POINTER_UP:
Log.i("Garment30", "ACTION_POINTER_UP");
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
final float newY = event.getY(newIndex);
final float newRawY = newY + rawDelta;
mTrackingPointer = event.getPointerId(newIndex);
mTouchOffset = (newRawY - mAbsPos[1]) - mExpandedHeight;
mInitialTouchY = newY;
}
break;
case MotionEvent.ACTION_MOVE:
Log.i("Garment30", "ACTION_MOVE");
Log.i("Garment28", "PanelView---ACTION_MOVE");
final float h = rawY - mAbsPos[1] - mTouchOffset;
if (h > mPeekHeight) {
Log.i("Garment28", "PanelView---h > mPeekHeight");
if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
mPeekAnimator.cancel();
}
mJustPeeked = false;
}
if (!mJustPeeked) {
Log.i("Garment28", "PanelView---!mJustPeeked--h:"+h);
//下面兩段程式碼需要同時使用
PanelView.this.setExpandedHeightInternal(h);//遮蔽了這段程式碼,下拉選單無法隨手的移動一起下拉,鬆手後選單移動到預設的長度
mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);//遮蔽了這段程式碼,下拉通知欄顯示為透明,鬆手後才正常
}
trackMovement(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
Log.i("Garment30", "ACTION_CANCEL");
mFinalTouchY = y;
mTracking = false;
mTrackingPointer = -1;
mHandleView.setPressed(false);
postInvalidate(); // catch the press state change
mBar.onTrackingStopped(PanelView.this);
trackMovement(event);
float vel = 0, yVel = 0, xVel = 0;
boolean negative = false;
if (mVelocityTracker != null) {
// the velocitytracker might be null if we got a bad input stream
mVelocityTracker.computeCurrentVelocity(1000);
yVel = mVelocityTracker.getYVelocity();
negative = yVel < 0;
xVel = mVelocityTracker.getXVelocity();
if (xVel < 0) {
xVel = -xVel;
}
if (xVel > mFlingGestureMaxXVelocityPx) {
xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
}
vel = (float)Math.hypot(yVel, xVel);
if (vel > mFlingGestureMaxOutputVelocityPx) {
vel = mFlingGestureMaxOutputVelocityPx;
}
mVelocityTracker.recycle();
mVelocityTracker = null;
}
// if you've barely moved your finger, we treat the velocity as 0
// preventing spurious flings due to touch screen jitter
final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
if (deltaY < mFlingGestureMinDistPx
|| vel < mFlingExpandMinVelocityPx
) {
vel = 0;
}
if (negative) {
vel = -vel;
}
if (DEBUG) logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
deltaY,
xVel, yVel,
vel);
fling(vel, true); //鬆手後下拉通知欄回彈的動畫
break;
}
return true;
}});
}
}
其中,mHandleView = findViewById(R.id.handle);程式碼用於初始化下拉通知欄最下面的那一條直線,可以知道該view的觸控事件用於控制下拉通知欄的拉動時的高度的變化。
在MotionEvent.ACTION_MOVE事件中PanelView.this.setExpandedHeightInternal(h);這段程式碼實現了拉動過程在中下拉通知欄的大小的改變,傳入的h變數就是下拉通知欄的高度。看該方法的實現:
public void setExpandedHeightInternal(float h) {
if (Float.isNaN(h)) {
// If a NaN gets in here, it will freeze the Animators.
if (DEBUG_NAN) {
Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
new Throwable());
}
h = 0;
}
float fh = getFullHeight();
if (fh == 0) {
// Hmm, full height hasn't been computed yet
}
if (h < 0) h = 0;
if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
mExpandedHeight = h; //更新最新的通知欄的長度,遮蔽了這段程式碼,通知欄無法拉出
if (DEBUG) logf("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
Log.i("Garment30", "PanelView---setExpandedHeightInternal--h:"+h);
requestLayout(); //觸發OnMeasure()和onLayout()進行View的重繪, 遮蔽這段程式碼,下拉通知欄無法顯示,初始時h=0,預設狀態時,h=729,最大為1919
// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
// lp.height = (int) mExpandedHeight;
// setLayoutParams(lp);
mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
}
在setExpandedHeightInternal()函式中,mExpandedHeight = h;這段程式碼用於更新需要重畫的最新的下拉選單欄的高度。
requestLayout();該函式觸發了OnMeasure()和onLayout()函式,對該View進行重新測量並重新佈局。
當手鬆開的時候,通知欄會有一個回彈的動作,看到fling(vel, true);這段程式碼:
public void fling(float vel, boolean always) {
if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
mVel = vel;
if (always||mVel != 0) {
animationTick(0); // begin the animation 開始執行回彈的動畫
}
}
其中animationTick(0);是回彈動畫的實現方法:
private void animationTick(long dtms) {
if (!mTimeAnimator.isStarted()) {
// XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
mTimeAnimator = new TimeAnimator();
mTimeAnimator.setTimeListener(mAnimationCallback);
if (mPeekAnimator != null) mPeekAnimator.cancel();
mTimeAnimator.start();
mRubberbanding = mRubberbandingEnabled // is it enabled at all?
&& mExpandedHeight > getFullHeight() // are we past the end?
&& mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
if (mRubberbanding) {
mClosing = true;
} else if (mVel == 0) {
// if the panel is less than halfway open, close it
mClosing = (mFinalTouchY / getFullHeight()) < 0.5f;
} else {
mClosing = mExpandedHeight > 0 && mVel < 0;
}
} else if (dtms > 0) {
final float dt = dtms * 0.001f; // ms -> s
if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
final float fh = getFullHeight();
boolean braking = false;
if (BRAKES) {
if (mClosing) {
braking = mExpandedHeight <= mCollapseBrakingDistancePx;
mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
} else {
braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
}
} else {
mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
}
mVel += mAccel * dt;
if (braking) {
if (mClosing && mVel > -mBrakingSpeedPx) {
mVel = -mBrakingSpeedPx;
} else if (!mClosing && mVel < mBrakingSpeedPx) {
mVel = mBrakingSpeedPx;
}
} else {
if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
mVel = -mFlingCollapseMinVelocityPx;
} else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
mVel = mFlingGestureMaxOutputVelocityPx;
}
}
float h = mExpandedHeight + mVel * dt;
if (mRubberbanding && h < fh) {
h = fh;
}
if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Log.i("Garment30", "PanelView---animationTick--h:"+h); //end at 729
setExpandedHeightInternal(h);
mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
if (mVel == 0
|| (mClosing && mExpandedHeight == 0)
|| ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
post(mStopAnimator); //回彈動畫停止
}
} else {
Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
+ mExpandedHeight + " v=" + mVel + ")");
}
}
可以看到,setExpandedHeightInternal(h); mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);該方法中有呼叫這兩個方法對下拉選單欄的長度進行更新的操作。mTimeAnimator = new TimeAnimator();mTimeAnimator.setTimeListener(mAnimationCallback);也初始化了一個TimeAnimator物件,並設定了監聽器。
private final TimeListener mAnimationCallback = new TimeListener() {
@Override
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
animationTick(deltaTime);
}
};
該監聽器中的回撥函式呼叫了animationTick(deltaTime)下拉通知欄的回彈動畫的方法,所以我們鬆開手後通知欄會有一個回彈的動作。
當滿足一定條件時,回彈動畫停止。
if (mVel == 0|| (mClosing && mExpandedHeight == 0)|| ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
post(mStopAnimator); //回彈動畫停止
}
------------------------------------分割線----------------------------------------------------
注意:下拉快捷設定欄的實現和下拉通知欄的實現是差不多,只是裝載的內容不同。所以也可以通過類似的方法來分析下拉快速設定欄。