国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android仿微信文章懸浮窗效果的實(shí)現(xiàn)代碼

2019-10-21 21:38:03
字體:
供稿:網(wǎng)友

序言

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

Android,仿微信,懸浮窗,代碼

原理

看完動(dòng)圖我們來分析一下,如何在每個(gè)頁面上都存在一個(gè)View呢,有些人可能會(huì)說,寫在base里面,這樣每次啟動(dòng)一個(gè)新的Activity都要往頁面上addView一次,性能不好,再說了,我們作為一個(gè)優(yōu)秀的程序員能干這種重復(fù)的事嗎,這種方案果斷打回去;既然這樣的話那我們肯定要在全局加了,那么全局是哪呢?相信了解過Activity源碼的朋友肯定知道,全局可以在Window層加啊,這樣既能一次性搞定,又不影響性能,說干就干。

實(shí)現(xiàn)

1、權(quán)限

首先我們要考慮的一個(gè)問題就是權(quán)限問題,因?yàn)橐m配Android 7.0 8.0,添加懸浮窗是需要申請(qǐng)權(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的拖拽實(shí)現(xiàn)

借助WindowManager.LayoutParams來實(shí)現(xiàn),mLayoutParams.x和mLayoutParams.y分別表示mView左上角的橫縱坐標(biāo),所以我們只需要改動(dòng)這兩個(gè)值就行了,當(dāng)ACTION_UP時(shí),計(jì)算當(dāng)前mView的中心點(diǎn)相對(duì)窗口的位置,然后將mView動(dòng)態(tài)滑動(dòng)到窗口左邊或者右邊:

//設(shè)置觸摸滑動(dòng)事件mView.setOnTouchListener(new View.OnTouchListener() { int startX, startY; //起始點(diǎn) boolean isMove; //是否在移動(dòng) long startTime; int finalMoveX; //最后通過動(dòng)畫將mView的X軸坐標(biāo)移動(dòng)到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;    }    //使用動(dòng)畫移動(dòng)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、注意

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

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)用在前臺(tái)了!!!")  } } 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)用在后臺(tái)了!!!")  } } override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { }}

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

總結(jié)

以上所述是小編給大家介紹的Android仿微信文章懸浮窗效果,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)VEVB武林網(wǎng)網(wǎng)站的支持!


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到Android開發(fā)頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 苏尼特左旗| 金寨县| 文昌市| 历史| 界首市| 渑池县| 伊春市| 赫章县| 阿克| 苗栗县| 凤山县| 余干县| 莎车县| 大城县| 洪泽县| 永济市| 左云县| 革吉县| 会泽县| 桃园市| 余江县| 岫岩| 陆良县| 淄博市| 郁南县| 固安县| 建湖县| 沾化县| 芷江| 荔波县| 珲春市| 保定市| 沁源县| 黄浦区| 清丰县| 辛集市| 阳东县| 会昌县| 浦北县| 奎屯市| 探索|