Android项目实战---指尖翻译系列0x02

上次我们实现了对粘贴板的内容获取与复制行为的监听,这次我会带着大家完成悬浮窗功能.

创建悬浮窗的布局

首先我们先来点简单的,就是创建我们悬浮窗的布局文件.项目中的悬浮窗比较简单,是一个位于屏幕上方简洁的卡片,其布局文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00000000">


<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#f2f4f6"
app:cardCornerRadius="2dp"
app:contentPaddingLeft="10dp"
app:contentPaddingRight="10dp"
app:contentPaddingBottom="10dp"
app:contentPaddingTop="5dp">


<RelativeLayout
android:id="@+id/pv_rl_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">


<TextView
android:id="@+id/pv_tv_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:ellipsize="end"
android:maxLines="1"
android:text="复制的内容"
android:textColor="@android:color/black"
android:textStyle="bold" />


<TextView
android:id="@+id/pv_tv_translate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pv_tv_query"
android:ellipsize="end"
android:lineSpacingExtra="2dp"
android:text="翻译的结果"
android:maxLines="4"
android:textColor="#aa000000" />


<TextView
android:text="词典的详细解析"
android:id="@+id/pv_tv_explains"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pv_tv_translate"
android:ellipsize="end"
android:lineSpacingExtra="2dp"
android:maxLines="4"
android:textColor="#aa000000" />

</RelativeLayout>
</android.support.v7.widget.CardView>

</LinearLayout>

布局的效果如下:
layout

悬浮窗的实现

其实悬浮窗的实现只需要通过系统的WindowManager向当前的界面添加一个View即可,所添加的View便是我们的悬浮窗.所有我们先来创建我们的View,新建一个名为PopView的类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class PopView implements View.OnClickListener{
private WindowManager mWindowManager;
private Context mContext;
// 取消悬浮窗的监听器
private OnViewDismissListener mOnViewDismissListener;
// 悬浮窗的View
private LinearLayout mPopView;
// 悬浮窗内的各个控件
@BindView(R.id.pv_tv_query)
TextView tv_query;
@BindView(R.id.pv_tv_translate)
TextView tv_translation;
@BindView(R.id.pv_tv_explains)
TextView tv_explain;
// 作为悬浮窗是否显示的标志
private boolean isDismiss = true;


public PopView(Context application) {
mContext = application;
// 获取系统的WindowManager
mWindowManager = (WindowManager) application.getSystemService(Context.WINDOW_SERVICE);

}


/**
* 计算获取悬浮窗的布局参数
* @return the popView layout parameter
*/

private WindowManager.LayoutParams getPopViewParams() {
int w = WindowManager.LayoutParams.MATCH_PARENT;
int h = WindowManager.LayoutParams.WRAP_CONTENT;

int flags = 0;
int type;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
type = WindowManager.LayoutParams.TYPE_PHONE;
}
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(w, h, type, flags, PixelFormat.TRANSLUCENT);
layoutParams.gravity = Gravity.TOP;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

layoutParams.gravity = Gravity.CENTER | Gravity.TOP;
layoutParams.x = 0;
layoutParams.y = 0;
return layoutParams;
}


/**
* remove the popView
*/

private void removePoppedView() {
if (isDismiss)return;
if (mWindowManager != null && mPopView != null) {
mWindowManager.removeView(mPopView);
}
isDismiss = true;
}

/**
* remove the popView and handle the dismiss mission
*/

private void removePoppedViewAndClear() {
removePoppedView();
if (mOnViewDismissListener != null) {
mOnViewDismissListener.onViewDismiss();
}
}

@Override
public void onClick(View view) {
removePoppedViewAndClear();
}

public void setOnViewDismissListener(OnViewDismissListener listener) {
mOnViewDismissListener = listener;
}

/**
* dismiss listener interface
*/

public interface OnViewDismissListener {
void onViewDismiss();
}
}

从上面的代码可以看出,创建悬浮窗需要以下的几个步骤:

  1. 获取系统的WindowManager对象
  2. 为悬浮窗设置相应的布局参数
  3. 展现悬浮窗及处理相应的事件

其中第2步就是我们代码里的getPopViewParams()方法,首先我们为悬浮窗设置了其宽高

1
2
int w = WindowManager.LayoutParams.MATCH_PARENT;
int h = WindowManager.LayoutParams.WRAP_CONTENT;

随后根据系统的版本来设置悬浮窗的类型,关于这部分能内容,有兴趣的可以看一下Android 悬浮窗的小结这篇博客,里面讲的很详细.

1
2
3
4
5
6
int type;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
type = WindowManager.LayoutParams.TYPE_PHONE;
}

最后设置好其他的属性并把布局参数返回

1
2
3
4
5
6
7
8
9
int flags = 0;
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(w, h, type, flags, PixelFormat.TRANSLUCENT);
layoutParams.gravity = Gravity.TOP;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.gravity = Gravity.CENTER | Gravity.TOP;
layoutParams.x = 0;
layoutParams.y = 0;
return layoutParams;

第3步是展示我们的悬浮窗,由于项目采用了MVP的架构,而PopView则是其中的一个View模块,下面我先给出有关悬浮窗的MVP代码
PopContract.java

1
2
3
4
5
6
7
8
9
10
11
12
/**
* the popup contract class
*/

public class PopContract {

interface View extends BaseView<Presenter>{
void showPopView(TransInfo info);
}
interface Presenter extends BasePresenter{
void translate(String query);
}
}

我们让PopView实现PopContract.View接口,然后实现showPopView()方法

1
2
3
4
5
6
7
8
9
10
11
@Override
public void showPopView(TransInfo info) {
removePoppedView();
if (mPopView == null) {
mPopView = (LinearLayout) View.inflate(mContext, R.layout.pop_view, null);
ButterKnife.bind(this, mPopView);
mPopView.setOnClickListener(this);
}
mWindowManager.addView(mPopView, getPopViewParams());
isDismiss = false;
}

其中的TranInfo对象是我们获取网络翻译处理后的对象,我们会在后面讲到的.到这里我们的悬浮窗功能基本上实现了,但需要注意的是,由于我们用到了第三方的开源库,所以需要在依赖配置中配置我们的依赖
app/build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
android {
compileSdkVersion 23
buildToolsVersion "24.0.0 rc3"

defaultConfig {
applicationId "com.source.kevin.fingertrans"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile 'com.jakewharton:butterknife:8.0.1'
apt 'com.jakewharton:butterknife-compiler:8.0.1'
compile 'com.android.support:cardview-v7:23.4.0'
}

项目根目录下的build.gradle文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
jcenter()
}
}
repositories{
mavenLocal()
mavenCentral()
}
task clean(type: Delete) {
delete rootProject.buildDir
}

最后我们可以在MainActivity.java中添加我们的测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class MainActivity extends AppCompatActivity implements ClipboardManager.OnPrimaryClipChangedListener {

Clipboard clipboard;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
clipboard = Clipboard.getInstance();
clipboard.setPrimaryClipChangedListener(this);
}

@Override
public void onPrimaryClipChanged() {

String text = clipboard.getText();
Log.e("Text", "粘贴板的内容是:" + text);
PopView popView = new PopView(FingerApp.get());
popView.showPopView(null);
}
}

记住我们还需要在AndroidManifest.xml文件添加悬浮窗权限

1
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

运行我们的代码,当我们进行复制的时候就会在屏幕上方弹出我们的悬浮窗.可能部分的机型需要手动添加悬浮窗的权限.
测试图

到这里我们悬浮窗的功能就基本上完成了,下次我会带大家完成数据部分的功能.