您的位置:首页 > 移动开发 > Android开发

Android 悬浮窗实现

2016-08-10 20:31 288 查看
在工作中遇到开发悬浮窗的需求,在此整理一下,记录心得O(∩_∩)O哈哈~


大概的思路如下:

我们将悬浮窗绑定在一个service中,某个activity界面上的开关来控制悬浮窗的显示/隐藏/移除

这里 我们简化步骤 仅用activity来控制悬浮窗

1.首先是悬浮窗的布局(这里我们做一个时钟的悬浮窗)

floating_window.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@android:color/holo_purple"
android:orientation="vertical" >
<DigitalClock
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="12sp"
android:background="@android:color/background_light"
android:alpha="0.8"/>
<AnalogClock
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_light" />
</LinearLayout>


2.然后是activity的布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.demo_xuanfuchuang.MainActivity" >

<!-- <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" /> -->

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center" >
<!--这里我们使用两个button来控制悬浮窗显示/隐藏-->
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:background="@android:color/holo_blue_light"
android:text="@string/action_start" />

<Button
android:id="@+id/end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:background="@android:color/holo_red_light"
android:text="@string/action_end" />
</LinearLayout>

</LinearLayout>


3.(干货在此)重点在这里

3.1 悬浮窗初始化

WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);//获取

View view = LayoutInflater.from(this).inflate(R.layout.floating_window,null);
LayoutParams lp = new WindowManager.LayoutParams();
......
wm.addView(view,lp);


关于WindowManager,详细解释的链接

http://gundumw100.iteye.com/blog/830235

关于WindowManager.LayoutParams,详细解释的链接

http://blog.sina.com.cn/s/blog_4b3c1f950100qd9s.html

在实际操作中,仅仅这样虽然添加上了悬浮窗,但是悬浮窗有可能将整个桌面覆盖,这里就需要我们用到合理的WindowManager.LayoutParams属性


lp.type = WindowManager.LayoutParams.TYPE_TOAST;


首先是type属性,一般来说悬浮窗可以使用TYPE_SYSTEM_ALERT,但是该属性需要在AndroidManifest.xml中声明权限 如果我们在手机的权限管理中关闭该应用的悬浮窗权限,那么在不加处理的情况下,会报错,即使我们事先考虑过该问题,关闭权限时也会无法显示悬浮窗.

因此我们可以考虑使用WindowManager.LayoutParams.TYPE_TOAST,不需要申请权限就可以直接显示

float mHorizontalMargin = 0f;
float mVerticalMargin =0.3f;
lp.gravity = Gravity.LEFT|Gravity.TOP;
lp.horizontalMargin = mHorizontalMargin;
lp.verticalMargin = mVerticalMargin;


这里的代码来定位初始位置,首先是lp.gravity定位初始位置,这里是定位在左上角,而lp.horizontalMargin和lp.verticalMargin分别是相对于初始位置的偏移位置, 三者一起,就定位了一个精确的初始位置

lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;


这里必须要配置这两个属性,否则的话,就会全屏覆盖 - -! 你懂的

lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|......;


这里的属性可以自行百度

3.2悬浮窗的自由移动

view.setOnTouchListener(…)方法实现

private int X = 0, Y = 0;
private boolean mMoved=false;
@Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub
final int x = (int) event.getRawX();
final int y = (int) event.getRawY();
float dx, dy;
boolean ret=false;

switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
X = x;
Y = y;
mMoved = false;
break;

case MotionEvent.ACTION_MOVE:
if(mMoved || Math.abs(x-X) > 10 || Math.abs(y-Y) > 10){
dx = (x-X)*1.0f/wm.getDefaultDisplay().getWidth();
dy = (y-Y)*1.0f/wm.getDefaultDisplay().getHeight();
X = x;
Y = y;
lp.horizontalMargin = lp.horizontalMargin + dx;
lp.verticalMargin = lp.verticalMargin + dy;

wm.updateViewLayout(view, lp);
mMoved = true;
}
break;
case MotionEvent.ACTION_UP:
if(mMoved){
dx = (x-X)*1.0f/wm.getDefaultDisplay().getWidth();
dy = (y-Y)*1.0f/wm.getDefaultDisplay().getHeight();
X = x;
Y = y;
lp.horizontalMargin = lp.horizontalMargin + dx;
lp.verticalMargin = lp.verticalMargin + dy;
if(lp.horizontalMargin < 0f) lp.horizontalMargin = 0f;
else if(lp.horizontalMargin > 1.0f) lp.horizontalMargin = 1.0f;
if(lp.verticalMargin < 0f) lp.verticalMargin = 0f;
else if(lp.verticalMargin > 1.0f) lp.verticalMargin = 1.0f;
wm.updateViewLayout(view, lp);
ret = true;
}
break;

}

return ret;
}


此处代码就是简单的移动位置的计算,不做解释了,另外

WindowManager.LayoutParams.horizontalMargin和

WindowManager.LayoutParams.verticalMargin属性取值

0.0f到1.0f代表整个屏幕长宽

下面完整Activity代码

package com.example.demo_xuanfuchuang;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.LayoutAnimationController;
import android.widget.Button;

public class MainActivity extends Activity implements View.OnClickListener, View.OnTouchListener{

private View view;
private WindowManager wm;
private LayoutParams lp;
private float mHorizontalMargin = 0f;
private float mVerticalMargin =0.3f;

private Button bt_start;
private Button bt_end;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_start = (Button)findViewById(R.id.start);
bt_end = (Button)findViewById(R.id.end);
bt_start.setOnClickListener(this);
bt_end.setOnClickListener(this);

wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);

view = LayoutInflater.from(this).inflate(R.layout.floating_window,null);
lp = new WindowManager.LayoutParams();
lp.type = WindowManager.LayoutParams.TYPE_TOAST;
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
lp.gravity = Gravity.LEFT|Gravity.TOP;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.horizontalMargin = mHorizontalMargin;
lp.verticalMargin = mVerticalMargin;
wm.addView(view,lp);
view.setVisibility(View.GONE);
view.setOnTouchListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(bt_start.equals(v)){
view.setVisibility(View.VISIBLE);
}else if(bt_end.equals(v)){
view.setVisibility(View.GONE);
}
}

private int X = 0, Y = 0;
private boolean mMoved=false;
@Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub
final int x = (int) event.getRawX();
final int y = (int) event.getRawY();
float dx, dy;
boolean ret=false;

switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
X = x;
Y = y;
mMoved = false;
break;

case MotionEvent.ACTION_MOVE:
if(mMoved || Math.abs(x-X) > 10 || Math.abs(y-Y) > 10){
dx = (x-X)*1.0f/wm.getDefaultDisplay().getWidth();
dy = (y-Y)*1.0f/wm.getDefaultDisplay().getHeight();
X = x;
Y = y;
lp.horizontalMargin = lp.horizontalMargin + dx;
lp.verticalMargin = lp.verticalMargin + dy;
android.util.Log.i("ztest","lp.horizontalMargin = " + lp.horizontalMargin + "; lp.verticalMargin = " + lp.verticalMargin);
wm.updateViewLayout(view, lp);
mMoved = true;
}
break;

case MotionEvent.ACTION_UP:
if(mMoved){
dx = (x-X)*1.0f/wm.getDefaultDisplay().getWidth();
dy = (y-Y)*1.0f/wm.getDefaultDisplay().getHeight();
X = x;
Y = y;
lp.horizontalMargin = lp.horizontalMargin + dx;
lp.verticalMargin = lp.verticalMargin + dy;
if(lp.horizontalMargin < 0f) lp.horizontalMargin = 0f;
else if(lp.horizontalMargin > 1.0f) lp.horizontalMargin = 1.0f;
if(lp.verticalMargin < 0f) lp.verticalMargin = 0f;
else if(lp.verticalMargin > 1.0f) lp.verticalMargin = 1.0f;
wm.updateViewLayout(view, lp);
ret = true;
}
break;

}

return ret;
}

}


到这里,一个简单的悬浮窗Demo就实现了,具体效果在我的小米手机上如下:







内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 悬浮窗 原创