天天看點

Android使用者首次打開APP的使用教學蒙闆效果實作

 轉載請注明出處:http://blog.csdn.net/zhaokaiqiang1992

    現在很多安全類的軟體,比如360手機助手,百度手機助手等等,都有一個懸浮窗,可以飄浮在桌面上,友善使用者使用一些常用的操作。今天這篇文章,就是介紹如何實作桌面懸浮窗效果的。

    首先,看一下效果圖。

Android使用者首次打開APP的使用教學蒙闆效果實作

    懸浮窗一共分為兩個部分,一個是平常顯示的小視窗,另外一個是點選小視窗顯示出來的二級懸浮視窗。

    首先,先看一下這個項目的目錄結構。

Android使用者首次打開APP的使用教學蒙闆效果實作

    最關鍵的就是紅框内的四個類。

    首先,floatwindowservice是一個背景的服務類,主要負責在背景不斷的重新整理桌面上的小懸浮視窗,否則會導緻更換界面之後,懸浮視窗也會随之消失,是以需要不斷的重新整理。下面是實作代碼。

[java] view

plaincopy

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

package com.qust.floatwindow;  

import java.util.timer;  

import java.util.timertask;  

import android.app.service;  

import android.content.context;  

import android.content.intent;  

import android.os.handler;  

import android.os.ibinder;  

/** 

 * 懸浮窗背景服務 

 *  

 * @author zhaokaiqiang 

 */  

public class floatwindowservice extends service {  

    public static final string layout_res_id = "layoutresid";  

    public static final string root_layout_id = "rootlayoutid";  

    // 用于線上程中建立/移除/更新懸浮窗  

    private handler handler = new handler();  

    private context context;  

    private timer timer;  

    // 小視窗布局資源id  

    private int layoutresid;  

    // 布局根布局id  

    private int rootlayoutid;  

    @override  

    public int onstartcommand(intent intent, int flags, int startid) {  

        context = this;  

        layoutresid = intent.getintextra(layout_res_id, 0);  

        rootlayoutid = intent.getintextra(root_layout_id, 0);  

        if (layoutresid == 0 || rootlayoutid == 0) {  

            throw new illegalargumentexception(  

                    "layoutresid or rootlayoutid is illegal");  

        }  

        if (timer == null) {  

            timer = new timer();  

            // 每500毫秒就執行一次重新整理任務  

            timer.scheduleatfixedrate(new refreshtask(), 0, 500);  

        return super.onstartcommand(intent, flags, startid);  

    }  

    public void ondestroy() {  

        super.ondestroy();  

        // service被終止的同時也停止定時器繼續運作  

        timer.cancel();  

        timer = null;  

    private class refreshtask extends timertask {  

        @override  

        public void run() {  

            // 目前界面沒有懸浮窗顯示,則建立懸浮  

            if (!floatwindowmanager.getinstance(context).iswindowshowing()) {  

                handler.post(new runnable() {  

                    @override  

                    public void run() {  

                        floatwindowmanager.getinstance(context)  

                                .createsmallwindow(context, layoutresid,  

                                        rootlayoutid);  

                    }  

                });  

            }  

    public ibinder onbind(intent intent) {  

        return null;  

}  

    除了背景服務之外,我們還需要兩個自定義的布局,分别是floatwindowsmallview和floatwindowbigview,這兩個自定義的布局,主要負責懸浮窗的前台顯示,我們分别看一下代碼實作。

    首先是floatwindowsmallview類的實作。

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

import java.lang.reflect.field;  

import android.annotation.suppresslint;  

import android.graphics.pixelformat;  

import android.view.gravity;  

import android.view.layoutinflater;  

import android.view.motionevent;  

import android.view.view;  

import android.view.windowmanager;  

import android.widget.linearlayout;  

import android.widget.textview;  

import com.qust.demo.screenutils;  

import com.qust.floatingwindow.r;  

 * 小懸浮窗,用于初始顯示 

public class floatwindowsmallview extends linearlayout {  

    // 小懸浮窗的寬  

    public int viewwidth;  

    // 小懸浮窗的高  

    public int viewheight;  

    // 系統狀态欄的高度  

    private static int statusbarheight;  

    // 用于更新小懸浮窗的位置  

    private windowmanager windowmanager;  

    // 小懸浮窗的布局參數  

    public windowmanager.layoutparams smallwindowparams;  

    // 記錄目前手指位置在螢幕上的橫坐标  

    private float xinscreen;  

    // 記錄目前手指位置在螢幕上的縱坐标  

    private float yinscreen;  

    // 記錄手指按下時在螢幕上的橫坐标,用來判斷單擊事件  

    private float xdowninscreen;  

    // 記錄手指按下時在螢幕上的縱坐标,用來判斷單擊事件  

    private float ydowninscreen;  

    // 記錄手指按下時在小懸浮窗的view上的橫坐标  

    private float xinview;  

    // 記錄手指按下時在小懸浮窗的view上的縱坐标  

    private float yinview;  

    // 單擊接口  

    private onclicklistener listener;  

    /** 

     * 構造函數 

     *  

     * @param context 

     *            上下文對象 

     * @param layoutresid 

     *            布局資源id 

     * @param rootlayoutid 

     *            根布局id 

     */  

    public floatwindowsmallview(context context, int layoutresid,  

            int rootlayoutid) {  

        super(context);  

        windowmanager = (windowmanager) context  

                .getsystemservice(context.window_service);  

        layoutinflater.from(context).inflate(layoutresid, this);  

        view view = findviewbyid(rootlayoutid);  

        viewwidth = view.getlayoutparams().width;  

        viewheight = view.getlayoutparams().height;  

        statusbarheight = getstatusbarheight();  

        textview percentview = (textview) findviewbyid(r.id.percent);  

        percentview.settext("懸浮窗");  

        smallwindowparams = new windowmanager.layoutparams();  

        // 設定顯示類型為phone  

        smallwindowparams.type = windowmanager.layoutparams.type_phone;  

        // 顯示圖檔格式  

        smallwindowparams.format = pixelformat.rgba_8888;  

        // 設定互動模式  

        smallwindowparams.flags = windowmanager.layoutparams.flag_not_touch_modal  

                | windowmanager.layoutparams.flag_not_focusable;  

        // 設定對齊方式為左上  

        smallwindowparams.gravity = gravity.left | gravity.top;  

        smallwindowparams.width = viewwidth;  

        smallwindowparams.height = viewheight;  

        smallwindowparams.x = screenutils.getscreenwidth(context);  

        smallwindowparams.y = screenutils.getscreenheight(context) / 2;  

    @suppresslint("clickableviewaccessibility")  

    public boolean ontouchevent(motionevent event) {  

        switch (event.getaction()) {  

        // 手指按下時記錄必要的資料,縱坐标的值都減去狀态欄的高度  

        case motionevent.action_down:  

            // 擷取相對與小懸浮窗的坐标  

            xinview = event.getx();  

            yinview = event.gety();  

            // 按下時的坐标位置,隻記錄一次  

            xdowninscreen = event.getrawx();  

            ydowninscreen = event.getrawy() - statusbarheight;  

            break;  

        case motionevent.action_move:  

            // 時時的更新目前手指在螢幕上的位置  

            xinscreen = event.getrawx();  

            yinscreen = event.getrawy() - statusbarheight;  

            // 手指移動的時候更新小懸浮窗的位置  

            updateviewposition();  

        case motionevent.action_up:  

            // 如果手指離開螢幕時,按下坐标與目前坐标相等,則視為觸發了單擊事件  

            if (xdowninscreen == event.getrawx()  

                    && ydowninscreen == (event.getrawy() - getstatusbarheight())) {  

                if (listener != null) {  

                    listener.click();  

                }  

        return true;  

     * 設定單擊事件的回調接口 

    public void setonclicklistener(onclicklistener listener) {  

        this.listener = listener;  

     * 更新小懸浮窗在螢幕中的位置 

    private void updateviewposition() {  

        smallwindowparams.x = (int) (xinscreen - xinview);  

        smallwindowparams.y = (int) (yinscreen - yinview);  

        windowmanager.updateviewlayout(this, smallwindowparams);  

     * 擷取狀态欄的高度 

     * @return 

    private int getstatusbarheight() {  

        try {  

            class<?> c = class.forname("com.android.internal.r$dimen");  

            object o = c.newinstance();  

            field field = c.getfield("status_bar_height");  

            int x = (integer) field.get(o);  

            return getresources().getdimensionpixelsize(x);  

        } catch (exception e) {  

            e.printstacktrace();  

        return 0;  

     * 單擊接口 

     * @author zhaokaiqiang 

    public interface onclicklistener {  

        public void click();  

    在這個類裡面,主要的工作是實作懸浮視窗在桌面前端的實作,還有就是位置的移動和單擊事件的判斷以及處理。這裡使用的是主要是windowmanager類的一些方法和屬性,下一篇會詳細說明,這篇隻說實作。

    除了小懸浮窗之外,點選之後彈出的二級懸浮窗也是類似的方式添加到桌面上,下面是二級懸浮窗的代碼。

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

public class floatwindowbigview extends linearlayout {  

    // 記錄大懸浮窗的寬  

    // 記錄大懸浮窗的高  

    public windowmanager.layoutparams bigwindowparams;  

    public floatwindowbigview(context context) {  

        this.context = context;  

        layoutinflater.from(context).inflate(r.layout.float_window_big, this);  

        view view = findviewbyid(r.id.big_window_layout);  

        bigwindowparams = new windowmanager.layoutparams();  

        // 設定顯示的位置,預設的是螢幕中心  

        bigwindowparams.x = screenutils.getscreenwidth(context) / 2 - viewwidth  

                / 2;  

        bigwindowparams.y = screenutils.getscreenheight(context) / 2  

                - viewheight / 2;  

        bigwindowparams.type = windowmanager.layoutparams.type_phone;  

        bigwindowparams.format = pixelformat.rgba_8888;  

        bigwindowparams.flags = windowmanager.layoutparams.flag_not_touch_modal  

        bigwindowparams.gravity = gravity.left | gravity.top;  

        bigwindowparams.width = viewwidth;  

        bigwindowparams.height = viewheight;  

        initview();  

    private void initview() {  

        textview tv_back = (textview) findviewbyid(r.id.tv_back);  

        tv_back.setonclicklistener(new onclicklistener() {  

            @override  

            public void onclick(view v) {  

                floatwindowmanager.getinstance(context).removebigwindow();  

        });  

    這些基本的類建立起來之後,剩下的就是最重要的類floatwindowmanager的實作。這個類實作的就是對懸浮窗的操作。

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

 * 懸浮窗管理器 

public class floatwindowmanager {  

    // 小懸浮窗對象  

    private floatwindowsmallview smallwindow;  

    // 大懸浮窗對象  

    private floatwindowbigview bigwindow;  

    // 用于控制在螢幕上添加或移除懸浮窗  

    private windowmanager mwindowmanager;  

    // floatwindowmanager的單例  

    private static floatwindowmanager floatwindowmanager;  

    // 上下文對象  

    private floatwindowmanager(context context) {  

    public static floatwindowmanager getinstance(context context) {  

        if (floatwindowmanager == null) {  

            floatwindowmanager = new floatwindowmanager(context);  

        return floatwindowmanager;  

     * 建立小懸浮窗 

     *            必須為應用程式的context. 

    public void createsmallwindow(context context, int layoutresid,  

        windowmanager windowmanager = getwindowmanager();  

        if (smallwindow == null) {  

            smallwindow = new floatwindowsmallview(context, layoutresid,  

                    rootlayoutid);  

            windowmanager.addview(smallwindow, smallwindow.smallwindowparams);  

     * 将小懸浮窗從螢幕上移除 

    public void removesmallwindow() {  

        if (smallwindow != null) {  

            windowmanager windowmanager = getwindowmanager();  

            windowmanager.removeview(smallwindow);  

            smallwindow = null;  

    public void setonclicklistener(floatwindowsmallview.onclicklistener listener) {  

            smallwindow.setonclicklistener(listener);  

     * 建立大懸浮窗 

    public void createbigwindow(context context) {  

        if (bigwindow == null) {  

            bigwindow = new floatwindowbigview(context);  

            windowmanager.addview(bigwindow, bigwindow.bigwindowparams);  

     * 将大懸浮窗從螢幕上移除 

    public void removebigwindow() {  

        if (bigwindow != null) {  

            windowmanager.removeview(bigwindow);  

            bigwindow = null;  

    public void removeall() {  

        context.stopservice(new intent(context, floatwindowservice.class));  

        removesmallwindow();  

        removebigwindow();  

     * 是否有懸浮窗顯示(包括小懸浮窗和大懸浮) 

     * @return 有懸浮窗顯示在桌面上傳回true,沒有的話傳回false 

    public boolean iswindowshowing() {  

        return smallwindow != null || bigwindow != null;  

     * 如果windowmanager還未建立,則建立新的windowmanager傳回。否則傳回目前已建立的windowmanager 

    private windowmanager getwindowmanager() {  

        if (mwindowmanager == null) {  

            mwindowmanager = (windowmanager) context  

                    .getsystemservice(context.window_service);  

        return mwindowmanager;  

    還有個擷取螢幕寬高的幫助類。

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

package com.qust.demo;  

 * 螢幕幫助類 

public class screenutils {  

     * 擷取螢幕寬度 

    @suppresswarnings("deprecation")  

    public static int getscreenwidth(context context) {  

        return ((windowmanager) context  

                .getsystemservice(context.window_service)).getdefaultdisplay()  

                .getwidth();  

    public static int getscreenheight(context context) {  

                .getheight();  

    完成這些,我們就可以直接用了。

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

import android.app.activity;  

import android.os.bundle;  

import android.view.keyevent;  

import com.qust.floatwindow.floatwindowmanager;  

import com.qust.floatwindow.floatwindowservice;  

import com.qust.floatwindow.floatwindowsmallview.onclicklistener;  

 * 示例 

 * @classname: com.qust.demo.mainactivity 

 * @description: 

 * @date 2014-10-23 下午11:30:13 

public class mainactivity extends activity {  

    private floatwindowmanager floatwindowmanager;  

    protected void oncreate(bundle savedinstancestate) {  

        super.oncreate(savedinstancestate);  

        setcontentview(r.layout.activity_main);  

        floatwindowmanager = floatwindowmanager.getinstance(context);  

     * 顯示小視窗 

     * @param view 

    public void show(view view) {  

        // 需要傳遞小懸浮窗布局,以及根布局的id,啟動背景服務  

        intent intent = new intent(context, floatwindowservice.class);  

        intent.putextra(floatwindowservice.layout_res_id,  

                r.layout.float_window_small);  

        intent.putextra(floatwindowservice.root_layout_id,  

                r.id.small_window_layout);  

        startservice(intent);  

     * 顯示二級懸浮窗 

    public void showbig(view view) {  

        // 設定小懸浮窗的單擊事件  

        floatwindowmanager.setonclicklistener(new onclicklistener() {  

            public void click() {  

                floatwindowmanager.createbigwindow(context);  

     * 移除所有的懸浮窗 

    public void remove(view view) {  

        floatwindowmanager.removeall();  

    public boolean onkeydown(int keycode, keyevent event) {  

        // 傳回鍵移除二級懸浮窗  

        if (keycode == keyevent.keycode_back  

                && event.getaction() == keyevent.action_down) {  

            floatwindowmanager.removebigwindow();  

            return true;  

        return super.onkeydown(keycode, event);  

    項目下載下傳位址:https://github.com/zhaokaiqiang/floatwindow

在上面文章中,我們介紹了如何實作桌面懸浮視窗,在這個效果的實作過程中,最重要的一個類就是windowmanager,今天這篇文章,将對windowmanager的使用進行介紹,并且實作一個使用windowmanager來實作使用者打開app,顯示首次使用教學蒙闆的效果。

    windowmanager類實作了viewmanager接口,viewmanager接口允許我們在activity上添加或者是移除view,是以windowmanager也允許我們在activity上進行view的添加和移除操作。

    我們可以通過下面的方法擷取一個windowmanager對象

    context.getsystemservice(context.window_service)

    在activity之中,我們可以直接通過getwindowmanager()擷取到一個windowmanager對象。

    每一個windowmanager執行個體都被綁定到一個獨有的display對象上面,如果我們想擷取不同display的windowmanager對象,我們可以通過createdisplaycontext(display)擷取到這個display的context對象,然後使用上面的方法,也可以擷取到一個windowmanager對象。

   我們在使用windowmanager類的時候,通常使用下面的幾個方法:

    windowmanager.addview(view,windowmanager.layoutparam);

    windowmanager.removeview();

    windowmanager.getdefaultdisplay();

    windowmanager.addview()方法用來向目前的視窗上添加view對象,需要接受兩個參數,view是要添加到視窗的view對象,而windowmanager.layoutparam則是添加的視窗的參數,在上一篇添加懸浮窗的操作的時候,需要對layoutparam設定很多參數,下面我們看一下常用的設定

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

// 設定layoutparams參數  

        layoutparams params = new windowmanager.layoutparams();  

        //設定顯示的類型,type_phone指的是來電話的時候會被覆寫,其他時候會在最前端,顯示位置在statebar下面,其他更多的值請查閱文檔  

        params.type = windowmanager.layoutparams.type_phone;  

        //設定顯示格式  

        params.format = pixelformat.rgba_8888;  

        //設定對齊方式  

        params.gravity = gravity.left | gravity.top;  

        //設定寬高  

        params.width = screenutils.getscreenwidth(this);  

        params.height = screenutils.getscreenheight(this);  

        //設定顯示的位置  

        params.x;  

        params.y;  

    設定好layoutparam之後,我們就可以通過windowmanager.addview(view,windowmanager.layoutparam)将view添加到視窗之上,不過,我們需要申明權限

    <uses-permissionandroid:name="android.permission.system_alert_window"/>

     添加完成之後,我們就可以在視窗上看到我們添加的view對象了。如果我們想将添加的view移除,我們隻需要調用windowmanager.removeview()即可,參數就是我們前面使用的view對象,使用很簡單。除了這個方法,還有個windowmanager.removeviewimmediate(),也可以将view移除,但是文檔中說,這個方法并不是給一般程式調用的,是以需要小心使用,我們開發的都屬于一般程式,建議不要使用這個方法。

    除了這兩個方法之外,我們最常用的另外一個方法就是windowmanager.getdefaultdisplay(),通過這個方法,我們可以擷取到目前界面的display的一個對象,然後我們就可以擷取到目前螢幕的一些參數,比如說寬高。

    下面是我常用的一個工具類。

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

package com.qust.teachmask;  

    知道上面這些之後,我們就可以實作教學模闆效果了,首先看效果圖。

Android使用者首次打開APP的使用教學蒙闆效果實作

    下面是代碼實作

Android使用者首次打開APP的使用教學蒙闆效果實作
Android使用者首次打開APP的使用教學蒙闆效果實作

import android.view.view.onclicklistener;  

import android.view.windowmanager.layoutparams;  

import android.widget.imageview;  

import android.widget.imageview.scaletype;  

    private imageview img;  

        windowmanager = getwindowmanager();  

        // 動态初始化圖層  

        img = new imageview(this);  

        img.setlayoutparams(new layoutparams(  

                android.view.viewgroup.layoutparams.match_parent,  

                android.view.viewgroup.layoutparams.match_parent));  

        img.setscaletype(scaletype.fit_xy);  

        img.setimageresource(r.drawable.guide);  

        // 設定layoutparams參數  

        // 設定顯示的類型,type_phone指的是來電話的時候會被覆寫,其他時候會在最前端,顯示位置在statebar下面,其他更多的值請查閱文檔  

        // 設定顯示格式  

        // 設定對齊方式  

        // 設定寬高  

        // 添加到目前的視窗上  

        windowmanager.addview(img, params);  

        // 點選圖層之後,将圖層移除  

        img.setonclicklistener(new onclicklistener() {  

            public void onclick(view arg0) {  

                windowmanager.removeview(img);  

   github項目位址:https://github.com/zhaokaiqiang/teachmask

繼續閱讀