Android仿微信文章懸浮窗效果的實現(xiàn)代碼
序言
前些日子跟朋友聊天,朋友Z果粉,前些天更新了微信,說微信出了個好方便的功能啊,我問是啥功能啊,看看我大Android有沒有,他說現(xiàn)在閱讀公眾號文章如果有人給你發(fā)微信你可以把這篇文章當作懸浮窗懸浮起來,方便你聊完天不用找繼續(xù)閱讀,聽完是不是覺得這叫啥啊,我大Android微信版不是早就有這個功能了嗎,我看文章的時候看到過有這個懸浮按鈕,但是我一直沒有使用過,試了一下還是挺方便的,就想著自己實現(xiàn)一下這個功能,下面看圖,大家都習慣了無圖言X
原理
看完動圖我們來分析一下,如何在每個頁面上都存在一個View呢,有些人可能會說,寫在base里面,這樣每次啟動一個新的Activity都要往頁面上addView一次,性能不好,再說了,我們作為一個優(yōu)秀的程序員能干這種重復的事嗎,這種方案果斷打回去;既然這樣的話那我們肯定要在全局加了,那么全局是哪呢?相信了解過Activity源碼的朋友肯定知道,全局可以在Window層加啊,這樣既能一次性搞定,又不影響性能,說干就干。
實現(xiàn)
1、權(quán)限
首先我們要考慮的一個問題就是權(quán)限問題,因為要適配Android 7.0 8.0,添加懸浮窗是需要申請權(quán)限的,適配的比較全,可以直接拿來用。這里需要注意的是,為了適配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.x和mLayoutParams.y分別表示mView左上角的橫縱坐標,所以我們只需要改動這兩個值就行了,當ACTION_UP時,計算當前mView的中心點相對窗口的位置,然后將mView動態(tài)滑動到窗口左邊或者右邊:
//設置觸摸滑動事件 mView.setOnTouchListener(new View.OnTouchListener() { int startX, startY; //起始點 boolean isMove; //是否在移動 long startTime; int finalMoveX; //最后通過動畫將mView的X軸坐標移動到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、注意
為了讓Window與Activity脫離,這里我們采用Service來做,通過Service來添加和移除View;在權(quán)限申請成功之后我們需要通知Service(其實是Activity,可能會有保存數(shù)據(jù)等操作)作相應改變(提供一個接口給Service),然后在Service中使用廣播來通知Activity;最后一個需要注意的地方就是我們需要判斷應用程序是否在前臺還是后臺來添加或移除Window,這里通過使用ActivityLifecycleCallbacks來監(jiān)聽Activity在前臺的數(shù)量來判斷應用程序是在前臺還是后臺
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", "應用在前臺了?。?!") } } override fun onActivityDestroyed(activity: Activity?) { } override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { } override fun onActivityStopped(activity: Activity?) { started-- if (started == 0) { Log.e("TAG", "應用在后臺了?。?!") } } override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { } }
本文代碼已傳至Github,有需要的朋友可以下載下來看看。
總結(jié)
以上所述是小編給大家介紹的Android仿微信文章懸浮窗效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關文章
android開發(fā)教程之系統(tǒng)資源的使用方法 android資源文件
這篇文章主要介紹了android中的系統(tǒng)資源的使用方法,包括顏色資源 、字符串資源、尺寸資源、XML資源文件,需要的朋友可以參考下2014-02-02android 使用okhttp可能引發(fā)OOM的一個點
這篇文章主要介紹了android 使用okhttp可能引發(fā)OOM的一個點,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-10-10Android自定義view實現(xiàn)圓形與半圓形菜單
這篇文章主要介紹了Android自定義view實現(xiàn)圓形與半圓形菜單的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11Android使用IntentService進行apk更新示例代碼
這篇文章主要介紹了Android使用IntentService進行apk更新示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01