随着Android手机的屏幕越来越大,Android浮动窗口的应用也越来越多。像经常会用到的,音乐播放器的桌面歌词以及一些手机卫士软件,像腾讯手机管家的小火箭清理内存,都应用到了浮动窗口的原理,今天拿来桌面歌词做一个简单的记录,举一反三即可实现类似的应用。效果图如下:
一、浮动窗口的实现
1.首先我们要申请权限,以便我们可以实现浮动窗口的拖拽
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />2.接下来,我们在service中的OnCreate方法显示浮动窗口,在OnDestory方法中关闭浮动窗口,这样我们的浮动窗口就可以与Service保持相同的生命周期。
3.显示浮动窗口:首先通过getApplicationContext().getSystemService(WINDOW_SERVICE)方法我们可以获得窗口管理类,接下来我们需要设置窗口的params,设置其类型为系统级,否则无法显示;设置焦点,否则无法获得触摸事件;最后通过wm.addView(tv, params)方法将我们的View添加到窗口中。
4.窗口的拖拽:在这里我们重新写一个TextView来放置我们的歌词,并在onTouchEvent中响应触摸事件,获得触摸点的移动来计算触摸点的移动,并重新设置窗口的位置,在这里需要注意,MotionEvent中的RawX,RawY是相对屏幕左上角的坐标(包括状态栏高度),而X,Y是相对于容器本身的坐标,即TextView左上角的坐标。这样利用RawX-X既可以得到TextView左上角点的屏幕x坐标,RawY-Y-状态栏高即可获得TextView左上角点的屏幕y坐标。之后我们调用wm.updateViewLayout(this, params)进行更新。
二、渲染歌词的实现
通过一个Shader shader = new LinearGradient(0, 0, len, 0, new int[] {Color.YELLOW, Color.RED }, new float[] { one, two },TileMode.CLAMP)可以进行歌词的渲染,其中前四个参数表示从哪里渲染到哪里,第5个参数为渲染的两种不同颜色, 第6个参数表示渲染的相对长度,范围从0到1, 第7个参数表示模式。我们可以通过一个异步线程不断更新one和two的值,并调用postInvalidate方法更新界面。
代码实现:
MainActivity类:
package com.example.windowtest; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.example.windowtest.service.TestService; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((Button) findViewById(R.id.button1)) .setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub Intent serviceIntent = new Intent(MainActivity.this, TestService.class); startService(serviceIntent); } }); ((Button) findViewById(R.id.button2)) .setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub Intent serviceIntent = new Intent(MainActivity.this, TestService.class); stopService(serviceIntent); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }WindowText类:实现TextView显示歌词
package com.example.windowtest.widget; import java.lang.reflect.Field; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Shader; import android.graphics.Shader.TileMode; import android.util.Log; import android.view.MotionEvent; import android.view.WindowManager; import android.widget.TextView; public class WindowText extends TextView { private static final String TAG = WindowText.class.getSimpleName(); public static WindowManager.LayoutParams params = new WindowManager.LayoutParams(); private float startX; private float startY; private float one = 0.0f; private float two = 0.01f; private WindowManager wm; private String text; private int statusBarHeight; public WindowText(Context context) { super(context); // handler.post(update); wm = (WindowManager) getContext().getApplicationContext() .getSystemService(Context.WINDOW_SERVICE); updateTextThread.start(); statusBarHeight = getStatusBarHeight(); } @Override public boolean onTouchEvent(MotionEvent event) { // 触摸点相对于屏幕左上角坐标 float x = event.getRawX(); float y = event.getRawY() - statusBarHeight; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); startY = event.getY(); break; case MotionEvent.ACTION_MOVE: Log.w(TAG, "x::" + startX + ",y::" + startY); Log.w(TAG, "rawx::" + x + ",rawy::" + y); case MotionEvent.ACTION_UP: updatePosition(x - startX, y - startY); break; } return true; } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); float len = getTextSize() * text.length(); /* * 渲染歌词,前四个参数表示从哪里渲染到哪里,第5个参数为渲染的两种不同颜色, 第6个参数表示渲染的相对位置,范围从0到1 第7个参数表示模式 */ Shader shader = new LinearGradient(0, 0, len, 0, new int[] { Color.YELLOW, Color.RED }, new float[] { one, two }, TileMode.CLAMP); Paint p = new Paint(); p.setShader(shader); p.setTextSize(getTextSize()); canvas.drawText(text, 0, getTextSize(), p); } // 通过一个异步线程来控制歌词渲染的速度 private Thread updateTextThread = new Thread() { @Override public void run() { // TODO Auto-generated method stub super.run(); while (true) { one += 0.001f; two += 0.001f; if (two > 1.0) { one = 0.0f; two = 0.01f; } postInvalidate(); try { Thread.sleep(10); } catch (Exception e) { // TODO: handle exception } } } }; // 更新浮动窗口位置参数 private void updatePosition(float x, float y) { // View的当前位置 params.x = (int) x; params.y = (int) y; wm.updateViewLayout(this, params); } // 获得状态栏高度 private int getStatusBarHeight() { Class<?> c = null; Object obj = null; Field field = null; int x = 0; try { c = Class.forName("com.android.internal.R$dimen"); obj = c.newInstance(); field = c.getField("status_bar_height"); x = Integer.parseInt(field.get(obj).toString()); return getResources().getDimensionPixelSize(x); } catch (Exception e1) { e1.printStackTrace(); return 75; } } public String getText() { return text; } public void setText(String text) { this.text = text; } }Service类:
package com.example.windowtest.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.view.Gravity; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import com.example.windowtest.widget.WindowText; public class TestService extends Service { private WindowManager wm; private WindowText tv; public TestService() { // TODO Auto-generated constructor stub } @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); wm = (WindowManager) getApplicationContext().getSystemService( WINDOW_SERVICE); showWindow(); } // 显示浮动窗口 private void showWindow() { WindowManager.LayoutParams params = WindowText.params; params.type = LayoutParams.TYPE_SYSTEM_ALERT | LayoutParams.TYPE_SYSTEM_OVERLAY;// 设置窗口类型为系统级 params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;// 设置窗口焦点 params.width = WindowManager.LayoutParams.FILL_PARENT; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.alpha = 80; params.gravity = Gravity.LEFT | Gravity.TOP; // 以屏幕左上角为原点,设置x、y初始值,将悬浮窗口设置在屏幕中间的位置 params.x = 0; params.y = wm.getDefaultDisplay().getHeight() / 2; tv = new WindowText(TestService.this); tv.setTextSize(20); tv.setText("难以忘记初次见你,一双迷人的眼睛"); wm.addView(tv, params); } // service退出时关闭浮动窗口 @Override public void onDestroy() { // TODO Auto-generated method stub WindowManager wm = (WindowManager) getApplicationContext() .getSystemService(WINDOW_SERVICE); if (tv != null && tv.isShown()) { wm.removeView(tv); } super.onDestroy(); } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } }
原文地址:http://blog.csdn.net/smbroe/article/details/43570891