Android自定義懸浮按鈕效果
本文實(shí)例為大家分享了Android自定義懸浮按鈕效果的具體代碼,供大家參考,具體內(nèi)容如下
以下:內(nèi)容沒(méi)有參考,寫的也是一個(gè)比較簡(jiǎn)單的例子,主要就是應(yīng)用切換前后臺(tái)時(shí)會(huì)顯示/隱藏懸浮窗。內(nèi)容僅用于自我記錄學(xué)習(xí)使用。
項(xiàng)目的開(kāi)發(fā)時(shí)應(yīng)用在登陸后顯示一個(gè)懸浮窗,同時(shí)顯示在線人數(shù)等一些其他信息,點(diǎn)擊懸浮按鈕可以顯示全局彈窗名單。開(kāi)發(fā)完成后,覺(jué)著需要記錄一下這種實(shí)現(xiàn)方式。所以寫一個(gè)簡(jiǎn)單的Demo。
Demo思路是通過(guò)啟動(dòng)Service來(lái)添加/移除 懸浮窗,因?yàn)槭且粋€(gè)全局懸浮窗,所以選擇依附于Service。
在MyAppliction中監(jiān)聽(tīng)?wèi)?yīng)用的前后臺(tái)切換來(lái)添加或者隱藏懸浮窗。
其自定義的懸浮窗View在項(xiàng)目中是通過(guò)Canvas手動(dòng)繪制的,這里圖個(gè)省事直接加載一個(gè)布局文件。主要是想理解這種添加全局懸浮窗的方式,所以Demo樣式和功能能簡(jiǎn)則簡(jiǎn)。
MyAppliction
package com.example.qxb_810.floatbuttondemo.application;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import com.example.qxb_810.floatbuttondemo.service.FloatingActionService;
import java.util.List;
import static android.content.ContentValues.TAG;
/**
* create 2018/12/1 13:31
* desc 自定義Application
*/
public class MyApplication extends Application {
private Intent mIntent;
private WindowManager.LayoutParams mFloatingLayoutParams = new WindowManager.LayoutParams();
public WindowManager.LayoutParams getmFloatingLayoutParams() {
return mFloatingLayoutParams;
}
@Override
public void onCreate() {
super.onCreate();
this.monitorActivityLifecycle();
}
/**
* 監(jiān)聽(tīng)程序Activity聲明周期
*/
private void monitorActivityLifecycle() {
this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
if (isRunningForeground()){
mIntent = new Intent(MyApplication.this, FloatingActionService.class);
startService(mIntent);
}
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
if (!isRunningForeground()){
stopService(mIntent);
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
/**
* 判斷應(yīng)用運(yùn)行在前后臺(tái)
*/
public boolean isRunningForeground() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcessInfos = activityManager.getRunningAppProcesses(); // 枚舉進(jìn)程
for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessInfos) {
if (appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
if (appProcessInfo.processName.equals(this.getApplicationInfo().processName)) {
Log.d(TAG, "EntryActivity isRunningForeGround");
return true;
}
}
}
Log.d(TAG, "EntryActivity isRunningBackGround");
return false;
}
}
FloatingActionService.java ---- 懸浮窗所依附的Service
package com.example.qxb_810.floatbuttondemo.service;
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Gravity;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.example.qxb_810.floatbuttondemo.application.MyApplication;
import com.example.qxb_810.floatbuttondemo.button.FloatingActionButton;
/**
* create 2018/12/1 13:34
* desc 懸浮按鈕Service
*/
public class FloatingActionService extends Service {
private WindowManager mWindowManager;
private FloatingActionButton mButton;
private int mScreenWidth;
private int mScreenHeight;
@Override
public void onCreate() {
super.onCreate();
this.initView();
this.initEvent();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
this.mWindowManager.removeViewImmediate(this.mButton);
}
/**
* 添加按鈕
*/
private void initView() {
// 通過(guò)WindowManager來(lái)添加懸浮窗
this.mWindowManager = (WindowManager) this.getApplicationContext().getSystemService(WINDOW_SERVICE);
Display display = this.mWindowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
this.mScreenWidth = metrics.widthPixels;
this.mScreenHeight = metrics.heightPixels;
WindowManager.LayoutParams params = ((MyApplication) this.getApplication()).getmFloatingLayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mFloatingLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mFloatingLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
}
params.format = PixelFormat.RGBA_8888;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.gravity = Gravity.LEFT | Gravity.TOP; // 左上為坐標(biāo)點(diǎn)
// 設(shè)置View大小
params.height = 50;
params.width = 50;
// 設(shè)置View坐標(biāo)位置
params.x = 0;
params.y = mScreenHeight / 2;
this.mButton = FloatingActionButton.getInstance(this);
this.mWindowManager.addView(mButton, params);
}
/**
* 初始化事件
*/
private void initEvent() {
this.mButton.setOnClickListener(v -> {
// 項(xiàng)目中是在這里進(jìn)行添加 名單彈窗 和移除名單彈窗的操作,但名單彈窗的初始化不在這里
Toast.makeText(this, "點(diǎn)擊了Button", Toast.LENGTH_SHORT).show();
});
}
}
FloatingActionButton – 彈窗按鈕
package com.example.qxb_810.floatbuttondemo.button;
import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.example.qxb_810.floatbuttondemo.R;
import com.example.qxb_810.floatbuttondemo.application.MyApplication;
import static android.content.Context.WINDOW_SERVICE;
/**
* create 2018/12/1 13:35
* desc 自定義懸浮按鈕 -- 這里隨便寫的一個(gè)布局文件
*/
public class FloatingActionButton extends FrameLayout {
private Context mContext;
private float mStartPointX;
private float mStartPointY;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mFloatingLayoutParams;
private boolean isIntercept = false;
private int mStatusHeight;
public static FloatingActionButton getInstance(Context context) {
FloatingActionButton button = new FloatingActionButton(context);
return button;
}
public FloatingActionButton(Context context) {
this(context, null);
}
public FloatingActionButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
public FloatingActionButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initView();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
/**
* 初始化View
*/
private void initView(){
// 隨便添加一個(gè)簡(jiǎn)單的View布局作為懸浮窗
View view = LayoutInflater.from(mContext).inflate(R.layout.layout_floating_button, null, false);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);
this.addView(view, params);
this.mWindowManager = (WindowManager) getContext().getApplicationContext().getSystemService(WINDOW_SERVICE);
this.mFloatingLayoutParams = ((MyApplication) getContext().getApplicationContext()).getmFloatingLayoutParams();
this.mStatusHeight = getStatusHeight(mContext);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//獲取相對(duì)屏幕的坐標(biāo),即以屏幕左上角為原點(diǎn)
float rawX = event.getRawX();
float rawY = event.getRawY() - mStatusHeight; //statusHeight是系統(tǒng)狀態(tài)欄的高度
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
this.mStartPointX = event.getX();
this.mStartPointY = event.getY();
isIntercept = false;
break;
case MotionEvent.ACTION_MOVE:
mFloatingLayoutParams.x = (int)(rawX - mStartPointX);
mFloatingLayoutParams.y = (int)(rawY - mStartPointY);
this.mWindowManager.updateViewLayout(this, mFloatingLayoutParams);
isIntercept = true;
break;
case MotionEvent.ACTION_UP:
mFloatingLayoutParams.x = 0; // 這里的策略是默認(rèn)松手吸附到左側(cè) 如果需要吸附到右側(cè)則改為mFloatingLayoutParams.x = mScreenWidth; mScreenWidth 是屏幕寬度,不想吸附則注釋這一句即可
this.mWindowManager.updateViewLayout(this, mFloatingLayoutParams);
break;
}
return isIntercept ? isIntercept : super.onTouchEvent(event);
}
/**
* 狀態(tài)欄的高度
*/
public static int getStatusHeight(Context context) {
int statusHeight = -1;
try {
Class clazz = Class.forName("com.android.internal.R$dimen"); //使用反射獲取實(shí)例
Object object = clazz.newInstance();
int height = Integer.parseInt(clazz.getField("status_bar_height")
.get(object).toString());
statusHeight = context.getResources().getDimensionPixelSize(height);
} catch (Exception e) {
e.printStackTrace();
}
return statusHeight;
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@color/colorPrimary">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="12312313131313"
android:textSize="20sp" />
</LinearLayout>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Flutter仿微信通訊錄實(shí)現(xiàn)自定義導(dǎo)航條的示例代碼
某些頁(yè)面比如我們?cè)谶x擇聯(lián)系人或者某個(gè)城市的時(shí)候需要快速定位到我們需要的選項(xiàng),一般都會(huì)需要像微信通訊錄右邊有一個(gè)導(dǎo)航條一樣的功能,本文將利用Flutter實(shí)現(xiàn)這一效果,需要的可以參考一下2022-04-04
android應(yīng)用開(kāi)發(fā)之spinner控件的簡(jiǎn)單使用
Android的控件有很多種,其中就有一個(gè)Spinner的控件,這個(gè)控件其實(shí)就是一個(gè)下拉顯示列表。本文通過(guò)腳本之家平臺(tái)給大家介紹android應(yīng)用開(kāi)發(fā)之spinner控件的簡(jiǎn)單使用,感興趣的朋友可以參考下2015-11-11
android商品詳情頁(yè)面設(shè)計(jì)詳解
這篇文章主要為大家詳細(xì)介紹了android商品詳情頁(yè)面設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Kotlin協(xié)程操作之創(chuàng)建啟動(dòng)掛起恢復(fù)詳解
本文的定位是協(xié)程的創(chuàng)建、啟動(dòng)、掛起、恢復(fù),也會(huì)示例一些簡(jiǎn)單的使用,這里不對(duì)suspend講解,,也不對(duì)協(xié)程的高級(jí)用法做闡述(熱數(shù)據(jù)通道Channel、冷數(shù)據(jù)流Flow...),本文主要講協(xié)程稍微深入的全面知識(shí)2022-08-08
基于Android實(shí)現(xiàn)點(diǎn)擊某個(gè)按鈕讓菜單選項(xiàng)從按鈕周圍指定位置彈出
這篇文章主要介紹了基于Android實(shí)現(xiàn)點(diǎn)擊某個(gè)按鈕讓菜單選項(xiàng)從按鈕周圍指定位置彈出的相關(guān)資料,需要的朋友可以參考下2015-12-12

