Android仿微信文章懸浮窗效果

序言

前些日子跟朋友聊天狞贱,朋友Z果粉,前些天更新了微信蜀涨,說微信出了個好方便的功能啊瞎嬉,我問是啥功能啊蝎毡,看看我大Android有沒有,他說現(xiàn)在閱讀公眾號文章如果有人給你發(fā)微信你可以把這篇文章當(dāng)作懸浮窗懸浮起來佑颇,方便你聊完天不用找繼續(xù)閱讀顶掉,聽完是不是覺得這叫啥啊,我大Android微信版不是早就有這個功能了嗎挑胸,我看文章的時候看到過有這個懸浮按鈕痒筒,但是我一直沒有使用過,試了一下還是挺方便的茬贵,就想著自己實現(xiàn)一下這個功能簿透,下面看圖,大家都習(xí)慣了無圖言X

image

原理

看完動圖我們來分析一下解藻,如何在每個頁面上都存在一個View呢老充,有些人可能會說,寫在base里面螟左,這樣每次啟動一個新的Activity都要往頁面上addView一次啡浊,性能不好,再說了胶背,我們作為一個優(yōu)秀的程序員能干這種重復(fù)的事嗎巷嚣,這種方案果斷打回去;既然這樣的話那我們肯定要在全局加了钳吟,那么全局是哪呢廷粒?相信了解過Activity源碼的朋友肯定知道,全局可以在Window層加啊红且,這樣既能一次性搞定坝茎,又不影響性能,說干就干暇番。

實現(xiàn)

1嗤放、權(quán)限

首先我們要考慮的一個問題就是權(quán)限問題,因為要適配Android 7.0 8.0壁酬,添加懸浮窗是需要申請權(quán)限的次酌,這里參考了
Android 懸浮窗權(quán)限各機型各系統(tǒng)適配大全
這篇文章,適配的比較全厨喂,可以直接拿來用和措。這里需要注意的是,為了適配Android 8.0蜕煌,Window的類型需要配置一下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    //Android 8.0
    mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
    //其他版本
    mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}

2派阱、添加ViewGroup到Window

判斷好權(quán)限之后,直接添加就可以了

@SuppressLint("CheckResult")
private void showWindow(Context context) {
    mWindowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
    mView = LayoutInflater.from(context).inflate(R.layout.article_window, null);

    ImageView ivImage = mView.findViewById(R.id.aw_iv_image);
    String imageUrl = SPUtil.getStringDefault(ARTICLE_IMAGE_URL, "");
    RequestOptions requestOptions = RequestOptions.circleCropTransform();
    requestOptions.placeholder(R.mipmap.ic_launcher_round).error(R.mipmap.ic_launcher_round);
    Glide.with(context).load(imageUrl).apply(requestOptions).into(ivImage);

    initListener(context);

    mLayoutParams = new WindowManager.LayoutParams();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    } else {
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
    }
    mLayoutParams.format = PixelFormat.RGBA_8888;   //窗口透明
    mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;  //窗口位置
    mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    mLayoutParams.width = 200;
    mLayoutParams.height = 200;
    mLayoutParams.x = mWindowManager.getDefaultDisplay().getWidth() - 200;
    mLayoutParams.y = 0;
    mWindowManager.addView(mView, mLayoutParams);
}

3斜纪、View的拖拽實現(xiàn)

借助WindowManager.LayoutParams來實現(xiàn)贫母,mLayoutParams.xmLayoutParams.y分別表示mView左上角的橫縱坐標(biāo)文兑,所以我們只需要改動這兩個值就行了,當(dāng)ACTION_UP時腺劣,計算當(dāng)前mView的中心點相對窗口的位置绿贞,然后將mView動態(tài)滑動到窗口左邊或者右邊:

//設(shè)置觸摸滑動事件
mView.setOnTouchListener(new View.OnTouchListener() {
    int startX, startY;  //起始點
    boolean isMove;  //是否在移動
    long startTime;
    int finalMoveX;  //最后通過動畫將mView的X軸坐標(biāo)移動到finalMoveX

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = (int) event.getX();
                startY = (int) event.getY();
                startTime = System.currentTimeMillis();
                isMove = false;
                return false;
            case MotionEvent.ACTION_MOVE:
                mLayoutParams.x = (int) (event.getRawX() - startX);
                mLayoutParams.y = (int) (event.getRawY() - startY);
                updateViewLayout();   //更新mView 的位置
                return true;
            case MotionEvent.ACTION_UP:
                long curTime = System.currentTimeMillis();
                isMove = curTime - startTime > 100;
                
                //判斷mView是在Window中的位置,以中間為界
                if (mLayoutParams.x + mView.getMeasuredWidth() / 2 >= mWindowManager.getDefaultDisplay().getWidth() / 2) {
                    finalMoveX = mWindowManager.getDefaultDisplay().getWidth() - mView.getMeasuredWidth();
                } else {
                    finalMoveX = 0;
                }
                
                //使用動畫移動mView
                ValueAnimator animator = ValueAnimator.ofInt(mLayoutParams.x, finalMoveX).setDuration(Math.abs(mLayoutParams.x - finalMoveX));
                animator.addUpdateListener((ValueAnimator animation) -> {
                    mLayoutParams.x = (int) animation.getAnimatedValue();
                    updateViewLayout();
                });
                animator.start();

                return isMove;
        }
        return false;
    }
});

4橘原、注意

為了讓WindowActivity脫離籍铁,這里我們采用Service來做,通過Service來添加和移除View趾断;在權(quán)限申請成功之后我們需要通知Service(其實是Activity拒名,可能會有保存數(shù)據(jù)等操作)作相應(yīng)改變(提供一個接口給Service),然后在Service中使用廣播來通知Activity芋酌;最后一個需要注意的地方就是我們需要判斷應(yīng)用程序是否在前臺還是后臺來添加或移除Window增显,這里通過使用ActivityLifecycleCallbacks來監(jiān)聽Activity在前臺的數(shù)量來判斷應(yīng)用程序是在前臺還是后臺

class ApplicationLifecycle : Application.ActivityLifecycleCallbacks {

    private var started: Int = 0

    override fun onActivityPaused(activity: Activity?) {
    }

    override fun onActivityResumed(activity: Activity?) {
    }

    override fun onActivityStarted(activity: Activity?) {
        started++
        if (started == 1) {
            Log.e("TAG", "應(yīng)用在前臺了!F甑邸同云!")
        }
    }

    override fun onActivityDestroyed(activity: Activity?) {
    }

    override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {
    }

    override fun onActivityStopped(activity: Activity?) {
        started--
        if (started == 0) {
            Log.e("TAG", "應(yīng)用在后臺了!6赂埂炸站!")
        }
    }

    override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
    }
}

本文代碼已傳至Github,有需要的朋友可以下載下來看看秸滴。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末武契,一起剝皮案震驚了整個濱河市募判,隨后出現(xiàn)的幾起案子荡含,更是在濱河造成了極大的恐慌,老刑警劉巖届垫,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件释液,死亡現(xiàn)場離奇詭異,居然都是意外死亡装处,警方通過查閱死者的電腦和手機误债,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妄迁,“玉大人寝蹈,你說我怎么就攤上這事〉翘裕” “怎么了箫老?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長黔州。 經(jīng)常有香客問我耍鬓,道長阔籽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任牲蜀,我火速辦了婚禮笆制,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涣达。我一直安慰自己在辆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布度苔。 她就那樣靜靜地躺著开缎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪林螃。 梳的紋絲不亂的頭發(fā)上奕删,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音疗认,去河邊找鬼完残。 笑死,一個胖子當(dāng)著我的面吹牛横漏,可吹牛的內(nèi)容都是我干的谨设。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼缎浇,長吁一口氣:“原來是場噩夢啊……” “哼扎拣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起素跺,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤二蓝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后指厌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刊愚,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年踩验,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸥诽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡箕憾,死狀恐怖牡借,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情袭异,我是刑警寧澤钠龙,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響俊鱼,放射性物質(zhì)發(fā)生泄漏刻像。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一并闲、第九天 我趴在偏房一處隱蔽的房頂上張望细睡。 院中可真熱鬧,春花似錦帝火、人聲如沸溜徙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蠢壹。三九已至,卻和暖如春九巡,著一層夾襖步出監(jiān)牢的瞬間图贸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工冕广, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疏日,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓撒汉,卻偏偏與公主長得像沟优,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子睬辐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355