关于一条定制长按Power键弹出Dialog的需求

  如题,需要定制长按Power键弹出的Dialog,UI上的大致效果是:全屏,中间下拉按钮“Swipe Down To Power Off”下拉关机,底部左右两侧“Reboot”,“Cancel”按钮,分别是重启,取消操作。并要求弹出Dialog的同时,背景渐变模糊,操作控件有相应动画效果,执行相应操作有同步动画,退出界面背景渐变至透明消失。设计效果酱紫:

具体控件动画要求就不再详述。主要两件事:1、关机流程,更准确的说应该是对长按Power键的处理;2、定制Dialog。

  1、长按Power键,PWM将捕获这一事件

    /frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java(基于MTK-M版本)

    在“interceptKeyBeforeQueueing”方法中,主要看片段:

 1 case KeyEvent.KEYCODE_POWER: {
 2                 result &= ~ACTION_PASS_TO_USER;
 3                 isWakeKey = false; // wake-up will be handled separately
 4                 if (down) {
 5                     interceptPowerKeyDown(event, interactive);
 6                 } else {
 7                     interceptPowerKeyUp(event, interactive, canceled);
 8                 }
 9                 break;
10 }

  再看“interceptPowerKeyDown”方法,包含了对多种情形下对长按电源键时间的处理,例如静默来电响铃、屏幕截图以及关闭电源等。 系统将根据电源键被按住的时间长短以及相关按键的使用情况来决定如何恰当地处理当前的用户操作。看下面片段:

// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
        || mScreenshotChordVolumeUpKeyTriggered;
if (!mPowerKeyHandled) {
    if (interactive) {
        // When interactive, we‘re already awake.
        // Wait for a long press or for the button to be released to decide what to do.
        if (hasLongPressOnPowerBehavior()) {
            Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
            msg.setAsynchronous(true);
            mHandler.sendMessageDelayed(msg,
                    ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
        }
    } else {
        wakeUpFromPowerKey(event.getDownTime());
        if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) {
            Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
            msg.setAsynchronous(true);
            mHandler.sendMessageDelayed(msg,
                   ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
            mBeganFromNonInteractive = true;
        } else {
            final int maxCount = getMaxMultiPressPowerCount();
            if (maxCount <= 1) {
                mPowerKeyHandled = true;
            } else {
                mBeganFromNonInteractive = true;
            }
        }
    }
}

跟踪“MSG_POWER_LONG_PRESS”到“powerLongPress”方法:

private void powerLongPress() {
        final int behavior = getResolvedLongPressOnPowerBehavior();
        switch (behavior) {
        case LONG_PRESS_POWER_NOTHING:
            break;
        case LONG_PRESS_POWER_GLOBAL_ACTIONS:
            mPowerKeyHandled = true;
            if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
                performAuditoryFeedbackForAccessibilityIfNeed();
            }
            showGlobalActionsInternal();
            break;
        case LONG_PRESS_POWER_SHUT_OFF:
        case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
            mPowerKeyHandled = true;
            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
            mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
            break;
        }
}

看case “LONG_PRESS_POWER_GLOBAL_ACTIONS”中的“showGlobalActionsInternal”方法:

void showGlobalActionsInternal() {
        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
        if (mGlobalActions == null) {
            mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
        }
        final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
        mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
        if (keyguardShowing) {
            // since it took two seconds of long press to bring this up,
            // poke the wake lock so they have some time to see the dialog.
            mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
        }
}

终于找到你“showDialog”

/frameworks/base/services/core/java/com/android/server/policy/GlobalActions.java

  /**
     * Show the global actions dialog (creating if necessary)
     * @param keyguardShowing True if keyguard is showing
     */
    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = isDeviceProvisioned;
        if (mDialog != null) {
            mDialog.dismiss();
            mDialog = null;
            // Show delayed, so that the dismiss of the previous dialog completes
            mHandler.sendEmptyMessage(MESSAGE_SHOW);
        } else {
            handleShow();
        }
    }

在方法“handleShow”中“createDialog”并show

private void handleShow() {
        awakenIfNecessary();
        mDialog = createDialog();
        prepareDialog();

        // If we only have 1 item and it‘s a simple press action, just do this action.
        if (mAdapter.getCount() == 1
                && mAdapter.getItem(0) instanceof SinglePressAction
                && !(mAdapter.getItem(0) instanceof LongPressAction)) {
            ((SinglePressAction) mAdapter.getItem(0)).onPress();
        } else {
            WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
            attrs.setTitle("GlobalActions");
            mDialog.getWindow().setAttributes(attrs);
            mDialog.show();
            mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
        }
}

“createDialog”的代码就不再贴了,就是create了一个GlobalActionsDialog,至于Android原生的这个dialog构造,感兴趣的可以看看。这里完成需求就只需要替换掉这个dialog为自定义Dialog就ok了。

2、全屏Dialog,主要通过Style定义

<style name="power_dialog_style" >
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowFrame">@null</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
</style>

几个重要的属性:

<item name="android:windowIsFloating">true</item><!--是否浮现在activity之上-->
<item name="android:windowFullscreen">true</item>
<item name="android:windowIsTranslucent">false</item><!--半透明-->
<item name="android:windowNoTitle">true</item><!--无标题-->
<item name="android:windowBackground">@android:color/transparent</item><!--背景透明-->
<item name="android:backgroundDimEnabled">true</item><!--灰度-->
<item name="android:backgroundDimAmount">0.5</item>
<item name="android:alpha">0.3</item> 

背景高斯模糊,找到个简单的

public class BlurBuilder {
    private static final float BITMAP_SCALE = 0.4f;
    private static final float BLUR_RADIUS = 7.5f;

    public static Bitmap blur(View v) {
        return blur(v.getContext(), getScreenshot(v));
    }

    public static Bitmap blur(Context ctx, Bitmap image) {
        int width = Math.round(image.getWidth() * BITMAP_SCALE);
        int height = Math.round(image.getHeight() * BITMAP_SCALE);

        Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
        Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);

        RenderScript rs = RenderScript.create(ctx);
        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);

        return outputBitmap;
    }

    private static Bitmap getScreenshot(View v) {
        Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(b);
        v.draw(c);
        return b;
    }
}

To apply this to a fragment, add the following to onCreateView:

final Activity activity = getActivity();
final View content = activity.findViewById(android.R.id.content).getRootView();
if (content.getWidth() > 0) {
    Bitmap image = BlurBuilder.blur(content);
    window.setBackgroundDrawable(new BitmapDrawable(activity.getResources(), image));
} else {
    content.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            Bitmap image = BlurBuilder.blur(content);
            window.setBackgroundDrawable(new BitmapDrawable(activity.getResources(), image));
        }
    });
}

经验证,这个模糊效果太简陋,可以更改控制模糊效果的两个参数“BITMAP_SCALE”,“BLUR_RADIUS”,貌似后者代价太大,这里改的是scale,实测改为0.1f还ok的。

至于渐变模糊,也找了很多方法,这里参考网上一种思路,在dialog布局中内嵌一个MATCH_PARENT的ImageView用于放置模糊图片,由于dialog本身透明,只要对模糊图片进行透明度alpha的0~1动画处理即可实现“渐变”,同理退出时alpha由1~0“渐变”消失。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent">

    <ImageView
        android:id="@+id/power_iv_blur"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@null"/>

    <!-- 主布局 -->

</FrameLayout>

获取RootView模糊处理后,将其设置为该ImageView的background,并对其Alpha进行动画处理,即可实现“渐变”效果。但在移植到系统中后发现背景始终无法模糊渐变,

原来就没有获得Dialog下背景,或者说获得的是透明的Dialog的背景,因为这个GloabalActionDialog是由PhoneWindowManager直接弹出的,提供的Context不同于一般Activity的Context,通过context.getWindow().getDecorView().findViewById(android.R.id.content).getRootView()获取,如果context为当前Dialog,获取的是透明背景,而PhoneWindowManager提供的mContext不能强转Activity,否则直接crash。这样获取不到背景,只能另辟蹊径了,想到了截屏。

/frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java

  /**
     * Takes a screenshot of the current display and shows an animation.
     */
    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
        // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
        // only in the natural orientation of the device :!)
        mDisplay.getRealMetrics(mDisplayMetrics);
        float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
        float degrees = getDegreesForRotation(mDisplay.getRotation());
        boolean requiresRotation = (degrees > 0);
        if (requiresRotation) {
            // Get the dimensions of the device in its native orientation
            mDisplayMatrix.reset();
            mDisplayMatrix.preRotate(-degrees);
            mDisplayMatrix.mapPoints(dims);
            dims[0] = Math.abs(dims[0]);
            dims[1] = Math.abs(dims[1]);
        }

        // Take the screenshot
        mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
        if (mScreenBitmap == null) {
            notifyScreenshotError(mContext, mNotificationManager);
            finisher.run();
            return;
        }
        // 省略部分代码
  }

通过“SurfaceControl.screenshot”截取背景,终于实现了渐变模糊。剩下的就是根据需求来的View属性动画了,这个教程都很多的。还有下拉关机这个滑动操作,这里参考的是滑动解锁实现的,具体看参考资料。滑动处理部分:

  private void handleTouch() {
        mSwipeDownLLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean handled = false;
                final int action = event.getActionMasked();
                final float rawY = event.getRawY();
                if (null == mVelocityTracker) {
                    mVelocityTracker = VelocityTracker.obtain();
                }
                mVelocityTracker.addMovement(event);
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                    case MotionEvent.ACTION_POINTER_DOWN:
                        handleDown(rawY);
                        handled = true;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        handleMove(rawY);
                        handled = true;
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_POINTER_UP:
                        handleUp();
                        handled = true;
                        break;
                    case MotionEvent.ACTION_CANCEL:
                        reStartVBreathAnimation();
                        handled = true;
                        break;
                    default:
                        handled = false;
                        break;
                }
                return handled;
            }
        });

        mBtnCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startCancelAnimation();
            }
        });

        mBtnReboot.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startRebootAnimation();
            }
        });
    }

    private void handleDown(float rawY) {
        mEventDownY = rawY;
        stopVBreathAnimation();
    }
  private void handleMove(float rawY) {
        mSwipeDownHeight = rawY - mEventDownY + mSwipeStartY;
        mBottomDownHeight = rawY - mEventDownY + mBottomStartY;
        if (mSwipeDownHeight <= mSwipeStartY) {
            mSwipeDownHeight = mSwipeStartY;
            mBottomDownHeight = mBottomStartY;
        }

        mSwipeDownLLayout.setY(mSwipeDownHeight);
        mBottomRLayout.setY(mBottomDownHeight);
    }

    private void handleUp() {
        //1. if user swipe down some distance, shut down
        if (mSwipeDownHeight > MIN_DISTANCE_Y_TO_SWIPE_OFF) {
            swipeDownToShut();
        } else if (velocityTrigShut()) {
        //2. if user swipe very fast, shut down
        } else {
            //otherwise reset the controls
            resetControls();
        }
    }

  /**
     * another way to shut down, if user swipe very fast
     */
    private boolean velocityTrigShut() {
        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(1000);

        int velocityY = (int) velocityTracker.getYVelocity();

        if(velocityY > MIN_VELOCITY_Y_TO_SWIPE_OFF){
            swipeDownToShut();
            return true;
        }

        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
        return false;
    }

主要功能就基本完成了,开始从Demo移到系统中。这其中有两点:1、framework-res新增资源;2、内置需求要求字体:Aovel Sans。

1、一般新增文件都是private的,即SDK无关的,都要在类似“/frameworks/base/core/res/res/values/symbols.xml”的symbols文件中声明:

<resources>
    <java-symbol type="drawable" name="power_off" />
    <java-symbol type="drawable" name="power_reboot" />
    <java-symbol type="drawable" name="power_cancel" />
    <java-symbol type="layout" name="dialog_layout_power" />
    <java-symbol type="id" name="power_layout" />
    <java-symbol type="id" name="power_iv_blur" />
    <java-symbol type="id" name="power_ll_swipe_down" />
    <java-symbol type="id" name="power_tv_swipe_down_label" />
    <java-symbol type="id" name="power_btn_swipe_down" />
    <java-symbol type="id" name="power_rl_bottom" />
    <java-symbol type="id" name="power_btn_reboot" />
    <java-symbol type="id" name="power_tv_reboot_label" />
    <java-symbol type="id" name="power_btn_cancel" />
    <java-symbol type="id" name="power_tv_cancel_label" />
    <java-symbol type="style" name="power_dialog_style" />
    <java-symbol type="string" name="power_swipe_down_label" />
    <java-symbol type="string" name="power_cancel_label" />
    <java-symbol type="string" name="power_reboot_label" />
</resources>

然后单模块编译,先编译“/frameworks/base/core/res/”,再编译“/frameworks/base/”,然后再renew一编,否则可能会出现R文件错误。

2、内置字体于“/frameworks/base/data/fonts”,可以参照其他系统字体内置方式,最后会生成在“/system/fonts/”下,可以通过

Typeface typeface = Typeface.createFromFile("/system/fonts/AovelSans.ttf");
mTvSwipeDownLabel.setTypeface(typeface);

方式设置,也可以直接通过“android:fontFamily”方式设置:

    <TextView
            android:id="@+id/power_tv_swipe_down_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:fontFamily="aovelsans"
            android:text="@string/power_swipe_down_label"
            android:textSize="24sp"/>

整个需求就完成了。还有待改善的是对背景色的判断,当前庆幸在模糊浅色背景时,Dialog中控件及字体会看不清楚。可以在浅色背景时自动加深背景色避免。

参考资料: 深入解析Android关机

     Android长按Power键弹出关机Dialog框GlobalActions解析

      Android全屏对话框(附带延时关闭效果)

      dialog style 的 activity 的全屏设置

      弹出Dialog后背景模糊

      简单高斯模糊

      滑动模糊渐变效果

     Android 属性动画(Property Animation) 完全解析

      Android属性动画完全解析(上)

      Android属性动画深入分析

      Android高仿IOS7滑动解锁 

      Android滑动解锁实现

Android字体工作原理与应用

android字体工作原理

时间: 2024-10-06 00:43:16

关于一条定制长按Power键弹出Dialog的需求的相关文章

长按power键弹出关机菜单,点击关机会弹出提示框,点击重启没有提示框确认直接进入重启状态,添加确认提示框

--- a/idh.code/frameworks/base/core/res/res/values-es/strings.xml +++ b/idh.code/frameworks/base/core/res/res/values-es/strings.xml @@ -1710,5 +1710,5 @@ <string name="station_state_connected">"Stación %1$s esta conectada"</st

Android学习----------长按列表项弹出菜单,给菜单项添加事件,获取上下文

这里是先显示一个listview,长按listview的一个列表项,弹出一个菜单来,菜单有两个条目, "更新该条"和"删除该条",并且这两条都有监听事件,整体就像微信好友对话操作一样. 其中菜单项有一个获取上下文的操作:AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); 这句也是用于列表项的. 具体代码如下: protected void onCreate(Bundle

WPF 之 左键弹出操作菜单,并禁用右键菜单

在目前的WPF版本中,很多的控件都有一个ContextMenu的属性,可以设置组件的右键菜单,这点确实是很方便,但是有些时候我们可能会需要当单击鼠标左键才弹出这个ContextMenu,而不是单击鼠标右键(即:当单击鼠标右键的时候不弹出该ContextMenu),这样我们就能很方便的实现类似Flex中设置弹出式菜单的效果. 1.在前台加入一个带菜单的Button,如下: <Button Name="btnMenu" Width="50" Height=&quo

android 长按 ListView 无法弹出 ContextMeun

可能的原因: onItemLongClick 消费了长按事件 mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { mApSelect = position; Log.d(TAG, &quo

Anroid关于fragment控件设置长按事件无法弹出Popupwindows控件问题解决记录

一.问题描述 ????记录一下最近在安卓的gragment控件中设置长按事件遇见的一个坑!!! ????在正常的activity中整个活动中设置长按事件我通常实例化根部局,例如LinearLayout longSetting = (LinearLayout) view.findViewById(R.id.testlong);,然后对变量longSetting调用setOnLongClickListener函数实现注册长按事件,但是在一个fragment控件中如此设置长按无法弹出popupwind

android取消点击音量键弹出音量调节界面

比如在viewpager中点击音量键调节上一页下一页,return true就可以取消音量界面的显示 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (voiceRead.equals("open")) { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { vp.setCurrentItem(vp.getCurrentItem()+1); return tr

总误按win+Enter键弹出讲述人

解决办法: 进入系统目录:C:\Windows\System32 查找“Narrator” 鼠标右键“Narrator” 然后点属性 安全项,点“高级” 看见上面蓝色的“更改"吗?点它会出现图中的界面,然后点”高级“ 进入又一个新界面,点“立即查找”在下面列表中找到“Users”双击它!  然后点确定 权限条目中,每条都编辑它的权限! 上面的类型 全部选择“拒绝”! 当权限条目中,类型像全部都是“拒绝”点确定,提示也点“确定”!大功告成! 来自:https://jingyan.baidu.com

长按tools Icon 弹出Tips音效

快速点击,还没弹出tips,bubble音效已播放 在 Widget_ToolsTips 的 OnAwake 函数加一个延时 transform:DOScale(1, 0.1):OnComlete(fucntion() SoundManager.inst:PlayCue("general_bubble") end) transform:DOScale(1, 0.1):OnComlete(fucntion() end) scale不变,延迟0.1秒之后执行OnComlete内函数(播放音

Android 长按电源键关机整个流程小学习

最近研究了一下android关机跟重新启动功能,看了一些长按电源键到弹出关机对话框,到真正关机的一系列处理过程. 首先还是来看看这个长按电源键都干了些什么吧?一般来说,电源键都是接到PMU上的,PMU来判断是长按还短按,当有按键消息产生的时候,系统会有中断,然后去读PMU的状态就可以知道是什么了.笔者以全志平台的AXP209小议一下,先贴上关键代码: static int axp_battery_event(struct notifier_block *nb, unsigned long eve