这个功能以前在6.0上做过,现在移植到8.1;顺便写个博客记录下,有不好的地方,还请各位大神不吝赐教
res/layout/qs_panel.xml
<com.android.systemui.qs.QSContainerImpl
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
//删除 begin
android:background="@drawable/qs_background_primary"
android:elevation="4dp"
//删除 end
android:clipToPadding="false"
android:clipChildren="false">
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:layout_marginTop="28dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp" />
"@layout/quick_status_bar_expanded_header" />
"@layout/qs_footer_impl" />
"@+id/qs_detail" layout="@layout/qs_detail" />
"@+id/qs_customize" layout="@layout/qs_customize_panel"
android:visibility="gone" />
com.android.systemui.qs.QSContainerImpl>
我这里顺便把投影也干掉了。
我这里还添加了一个半透明的view,因为只有一个高斯模糊会很难看
res/layout/status_bar_expanded.xml :
<com.android.systemui.statusbar.phone.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/notification_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent" >
<ImageView
android:id="@+id/blur_view"
android:alpha="0"
android:layout_width="match_parent"
android:scaleType="fitXY"
android:layout_height="match_parent"/>
<View
android:id="@+id/alpha_view"
android:alpha="0"
android:background="#666666"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include
layout="@layout/keyguard_status_view"
android:visibility="gone" />
......
先增加几个工具类
截图:
public class ScreenShotUtil {
public static Bitmap takeScreenShot(Context context) {
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
display.getRealMetrics(displayMetrics);
final float[] dims = {displayMetrics.widthPixels, displayMetrics.heightPixels};
Bitmap bitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
if (bitmap != null) {
return bitmap;
}
bitmap = SurfaceControl.screenshot((int) dims[1], (int) dims[0]);
Matrix matrix = new Matrix();
matrix.setRotate(-90);
if (bitmap == null) {
return null;
}
Bitmap newBM = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
bitmap.recycle();
return newBM;
}
}
图片处理:
public class BitmapUtils {
public static void recycleImageView(View view) {
if (view == null) return;
if (view instanceof ImageView) {
Drawable drawable = ((ImageView) view).getDrawable();
if (drawable instanceof BitmapDrawable) {
Bitmap bmp = ((BitmapDrawable) drawable).getBitmap();
if (bmp != null && !bmp.isRecycled()) {
((ImageView) view).setImageBitmap(null);
bmp.recycle();
bmp = null;
}
}
}
}
}
高斯模糊处理:
public class BlurUtil {
private static final float BITMAP_SCALE = 0.4f;
private static final int BLUR_RADIUS = 7;
public static final int BLUR_RADIUS_MAX = 25;
public static Bitmap blur(Context context, Bitmap bitmap) {
return blur(context, bitmap, BITMAP_SCALE, BLUR_RADIUS);
}
public static Bitmap blur(Context context, Bitmap bitmap, float bitmap_scale) {
return blur(context, bitmap, bitmap_scale, BLUR_RADIUS);
}
public static Bitmap blur(Context context, Bitmap bitmap, int blur_radius) {
return blur(context, bitmap, BITMAP_SCALE, blur_radius);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static Bitmap blur(Context context, Bitmap bitmap, float bitmap_scale, int blur_radius) {
Bitmap inputBitmap = Bitmap.createScaledBitmap(bitmap, Math.round(bitmap.getWidth() * bitmap_scale),
Math.round(bitmap.getHeight() * bitmap_scale), false);
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
RenderScript rs = RenderScript.create(context);
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
theIntrinsic.setRadius(blur_radius);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);
rs.destroy();
bitmap.recycle();
return outputBitmap;
}
}
再就是展示了
src/com/android/systemui/statusbar/phone/NotificationPanelView.java -> onTouchEvent():
先定义变量:
private static final int BLUR_START = 200;
private static final int BLUR_END = 700;
private View mAlphaView;
private ImageView mBlurView;
public boolean onTouchEvent(MotionEvent event) {
if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
return false;
}
initDownStates(event);
if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mIsExpansionFromHeadsUp = true;
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
}
boolean handled = false;
if ((!mIsExpanding || mHintAnimationRunning)
&& !mQsExpanded
&& mStatusBar.getBarState() != StatusBarState.SHADE
&& !mDozing) {
handled |= mAffordanceHelper.onTouchEvent(event);
}
if (mOnlyAffordanceInThisMotion) {
return true;
}
handled |= mHeadsUpTouchHelper.onTouchEvent(event);
if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
return true;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
updateVerticalPanelPosition(event.getX());
handled = true;
}
//添加 begin
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!mPanelExpanded) {
setBlurBackground();
}
break;
case MotionEvent.ACTION_MOVE:
if (y <= BLUR_END) {
float alpha = (y - BLUR_START) / (BLUR_END - BLUR_START);
if (alpha < 0) {
alpha = 0;
}
mAlphaView.setAlpha(alpha/2);
mBlurView.setAlpha(alpha);
}
break;
case MotionEvent.ACTION_UP:
float a = mBlurView.getAlpha();
startAlphaAnimation(a, 1.0f);
break;
}
// 添加 end
handled |= super.onTouchEvent(event);
return mDozing ? handled : true;
}
再添加几个方法:
private void startAlphaAnimation(float start, float end) {
ValueAnimator va = ValueAnimator.ofFloat(start, end);
va.setDuration((long) (Math.abs(end - start) * 500));
va.start();
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float alpha = (float) animation.getAnimatedValue();
mAlphaView.setAlpha(alpha/2);
mBlurView.setAlpha(alpha);
}
});
}
private void setBlurBackground() {
Bitmap bitmap = ScreenShotUtil.takeScreenShot(getContext());
if (bitmap == null) {
LogUtil.d(TAG, "setBlurBackground bitmap == null");
return;
}
Bitmap blurBitmap = BlurUtil.blur(getContext(), BlurUtil.blur(getContext(), bitmap, BlurUtil.BLUR_RADIUS_MAX), BlurUtil.BLUR_RADIUS_MAX);
BitmapUtils.recycleImageView(mBlurView);
mBlurView.setImageBitmap(blurBitmap);
}
到这里就可以了!
但是仍然有一个问题,就是在Systemui启动后的锁屏是没有问题,但是解锁后下拉通知栏,再锁屏,这个时候会发现,锁屏界面也出现了高斯模糊。这个是因为锁屏和下拉通知栏共用了布局。我们再添加一个判断:
src/com/android/systemui/statusbar/phone/NotificationPanelView.java -> setBarState():
public void setBarState(int statusBarState, boolean keyguardFadingAway,
boolean goingToFullShade) {
......
resetVerticalPanelPosition();
updateQsState();
/* 添加判断 begin */
updateBlurVisibility(keyguardShowing);
/* 添加判断 end */
}
private void updateBlurVisibility(boolean keyguardShowing){
if (keyguardShowing) {
mBlurView.setVisibility(View.GONE);
mAlphaView.setVisibility(View.GONE);
BitmapUtils.recycleImageView(mBlurView);
}else {
mBlurView.setVisibility(View.VISIBLE);
mAlphaView.setVisibility(View.VISIBLE);
}
}
在测试过程中,又发现一个问题,就是当headsup出现的时候,高斯模糊的view还是会出现
再次修改:
src/com/android/systemui/statusbar/phone/NotificationPanelView.java :
private boolean mIsFullClose;
@Override
protected void onHeightUpdated(float expandedHeight) {
if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
positionClockAndNotifications();
}
if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
&& !mQsExpansionFromOverscroll) {
float t;
if (mKeyguardShowing) {
// On Keyguard, interpolate the QS expansion linearly to the panel expansion
t = expandedHeight / (getMaxPanelHeight());
} else {
// In Shade, interpolate linearly such that QS is closed whenever panel height is
// minimum QS expansion + minStackHeight
float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
+ mNotificationStackScroller.getLayoutMinHeight();
float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
t = (expandedHeight - panelHeightQsCollapsed)
/ (panelHeightQsExpanded - panelHeightQsCollapsed);
}
setQsExpansion(mQsMinExpansionHeight
+ t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
}
updateExpandedHeight(expandedHeight);
/* 修改 begin */
updateIsFullClose(expandedHeight);
/* 修改 end */
updateHeader();
updateUnlockIcon();
updateNotificationTranslucency();
updatePanelExpanded();
mNotificationStackScroller.setShadeExpanded(!isFullyCollapsed());
if (DEBUG) {
invalidate();
}
}
增加一个方法
/* 增加 begin */
private void updateIsFullClose(float height){
boolean close = height == 0;
if (mIsFullClose != close) {
mIsFullClose = close;
updateBlurVisibility(mIsFullClose || mKeyguardShowing);
}
}
/* 增加 end */
现在这样应该就没什么问题了