Android开发--桌面浮动歌词窗口

       随着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;
	}

}




       

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。