天天動(dòng)聽(tīng),, 這款A(yù)ndroid手機(jī)上的音樂(lè)播放器,,相信不少朋友都曾用過(guò),。 不知大家是否注意到,,天天動(dòng)聽(tīng)有一個(gè)迷你歌詞的特效,。
什么效果呢? 就是不管你切到什么畫面,, 歌詞永遠(yuǎn)顯示,,并且可以拖動(dòng)。 類型QQ音樂(lè),,在電腦上播放時(shí)顯示的歌詞效果,。
下面先來(lái)看一下效果。
這個(gè)歌詞是在所有界面之上的,。
下面我們將這個(gè)效果解剖一下,, 我認(rèn)為主要有三個(gè)難點(diǎn):
1. 歌詞懸浮在所有頁(yè)面之上
2. 歌詞可以拖動(dòng)位置
3. 歌詞的播放效果 (顏色覆蓋)
對(duì)于第一點(diǎn),,首先想到的就是 WindowManager , 這個(gè)類可能不少人都用過(guò),, 一般用于獲取屏幕寬度,、高度,那么這次就要利用這個(gè)類來(lái)讓我們的歌詞永遠(yuǎn)置頂,。
通過(guò)查看API,,我們看到,在WindowManager.LayoutParams類中,,有好幾個(gè)屬性可以設(shè)置View置頂,。
TYPE_SYSTEM_OVERLAY
Window type: system overlay windows, which need to be displayed on top of everything else.
TYPE_SYSTEM_ALERT
Window type: system window, such as low power alert.
TYPE_PHONE
These windows are normally placed above all applications, but behind the status bar.
下面我們來(lái)測(cè)試一下, 通過(guò)下面幾句代碼,就可以讓一個(gè)View凌駕在所有View之上,。
- WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
- WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
-
- params.width = WindowManager.LayoutParams.WRAP_CONTENT;
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
-
- TextView tv = new TextView(this);
- wm.addView(tv, params);
這邊需要注意的是,, WindowManager也是通過(guò) getSystemService 來(lái)獲取,但必須先 getApplicationContext,, 否則就無(wú)效了,。
直接WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE); 這樣是無(wú)效的 !,!
還有一點(diǎn)就是,,別忘了在Manifest.xml中添加權(quán)限:
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
現(xiàn)在我們這樣做,我們已經(jīng)可以讓歌詞永遠(yuǎn)置頂了,。 但是不要得意,,現(xiàn)在這樣,結(jié)果是我們TextView在最頂層了,, 然后你就會(huì)發(fā)現(xiàn),,頁(yè)面上什么操作都不能做了, 在TextView下面的任何東西,,你都點(diǎn)不了,。
為了解決這個(gè),我們必須加上flags參數(shù),,讓當(dāng)前的View失去焦點(diǎn),,從而讓后面的頁(yè)面獲得焦點(diǎn)。代碼如下:
- params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
加上這一句就可以了,。
好了,,下面要處理的,就是讓歌詞可以移動(dòng),。應(yīng)該如何做呢,?
我們知道,想要讓一個(gè)View對(duì)象在頁(yè)面上可以移動(dòng),只要實(shí)現(xiàn)其onTouchEvent事件即可,。
下面開(kāi)始實(shí)現(xiàn)第二步: 歌詞移動(dòng),!
首先我們自定義一個(gè)TextView類:MyTextView, 該類繼承自TextView,, 并實(shí)現(xiàn)其中的onTouchEvent方法,,來(lái)看一下代碼:
- @Override
- public boolean onTouchEvent(MotionEvent event) {
-
- x = event.getRawX();
- y = event.getRawY() - TOOL_BAR_HIGH;
- Log.d(TAG, "------X: "+ x +"------Y:" + y);
-
- switch(event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- startX = event.getX();
- startY = event.getY();
- break;
- case MotionEvent.ACTION_MOVE:
- updatePosition();
- break;
- case MotionEvent.ACTION_UP:
- updatePosition();
- startX = startY = 0;
- break;
- }
-
- return true;
- }
-
- private void updatePosition(){
-
- params.x = (int)( x - startX);
- params.y = (int) (y - startY);
- wm.updateViewLayout(this, params);
- }
其中g(shù)etRawX、getRawY用于獲取觸摸點(diǎn)離屏幕左上角的距離,。 而getX,、getY用于獲取觸摸點(diǎn)離textView左上角的距離.
兩者相減,就是View左上角的坐標(biāo)了,。
另外需要注意的是,,在顯示View這個(gè)View的時(shí)候,需要正確指定View的x,y坐標(biāo),,否則拖動(dòng)時(shí)會(huì)錯(cuò)位,。
- WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
- WindowManager.LayoutParams params = MyTextView.params;
-
- params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | WindowManager.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;
-
- params.x = 0;
- params.y = 0;
-
- tv = new MyTextView(TopFrame.this);
- wm.addView(tv, params);
其中下面三句是關(guān)鍵:
- params.gravity=Gravity.LEFT|Gravity.TOP;
-
- params.x = 0;
- params.y = 0;
現(xiàn)在這樣的話,,就可以實(shí)現(xiàn)View的移動(dòng)了。
下面實(shí)現(xiàn)第三步: 歌詞的播放效果,。
那么本例僅僅做一個(gè)循環(huán),, 實(shí)際音樂(lè)播放器要復(fù)雜些,需要根據(jù)歌劇的長(zhǎng)度及時(shí)間間隔,,來(lái)計(jì)算歌詞的覆蓋速度,, 再根據(jù)這個(gè)速度來(lái)覆蓋歌詞,呈現(xiàn)給用戶,。
要實(shí)現(xiàn)歌詞播放的效果,,需要用到畫筆Paint, 還要用到Shader,, 還有一個(gè)就是UI刷新的問(wèn)題,。
一起來(lái)看下代碼:
- @Override
- protected void onDraw(Canvas canvas) {
-
- super.onDraw(canvas);
- float1 += 0.001f;
- float2 += 0.001f;
-
- if(float2 > 1.0){
- float1 = 0.0f;
- float2 = 0.01f;
- }
- this.setText("");
- float len = this.getTextSize() * text.length();
- Shader shader = new LinearGradient(0, 0, len, 0,
- new int[] { Color.YELLOW, Color.RED }, new float[]{float1, float2},
- TileMode.CLAMP);
- Paint p = new Paint();
- p.setShader(shader);
-
- p.setTextSize(20f);
- p.setTypeface(Typeface.DEFAULT_BOLD);
-
- canvas.drawText(text, 0, 20, p);
-
- }
好了,下面發(fā)一下效果圖:
最后附上整個(gè)工程的代碼:(一個(gè)就兩個(gè)類,,一個(gè)布局文件)
AndroidManifest.xml
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas./apk/res/android"
- package="com.yfz"
- android:versionCode="1"
- android:versionName="1.0">
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".TopFrame"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
- <uses-sdk android:minSdkVersion="8"></uses-sdk>
- </manifest>
main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas./apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <Button
- android:id="@+id/bt"
- android:text=" 點(diǎn)我試試"
- android:layout_width = "wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- />
- </LinearLayout>
TopFrame.java
- package com.yfz;
- import com.yfz.view.MyTextView;
- import android.app.Activity;
- import android.graphics.Rect;
- import android.os.Bundle;
- import android.view.Gravity;
- import android.view.View;
- import android.view.WindowManager;
- import android.view.View.OnClickListener;
- import android.view.WindowManager.LayoutParams;
- import android.widget.Button;
- public class TopFrame extends Activity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- Button button = (Button) findViewById(R.id.bt);
-
- button.setOnClickListener(onclick);
- }
-
- private MyTextView tv = null;
-
- OnClickListener onclick = new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- if(tv != null && tv.isShown()){
- WindowManager wm = (WindowManager)getApplicationContext().getSystemService(TopFrame.this.WINDOW_SERVICE);
- wm.removeView(tv);
- }
- show();
-
- }
- };
-
-
- private void show(){
- Rect frame = new Rect();
- getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
- MyTextView.TOOL_BAR_HIGH = frame.top;
-
- WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
- WindowManager.LayoutParams params = MyTextView.params;
-
- params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | WindowManager.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;
-
- params.x = 0;
- params.y = 0;
-
- tv = new MyTextView(TopFrame.this);
- wm.addView(tv, params);
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- }
- }
MyTextView.java
- package com.yfz.view;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.LinearGradient;
- import android.graphics.Paint;
- import android.graphics.Rect;
- import android.graphics.Shader;
- import android.graphics.Typeface;
- import android.graphics.Shader.TileMode;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.WindowManager;
- import android.widget.TextView;
- import android.widget.Toast;
- public class MyTextView extends TextView {
- private final String TAG = MyTextView.class.getSimpleName();
-
- public static int TOOL_BAR_HIGH = 0;
- public static WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- private float startX;
- private float startY;
- private float x;
- private float y;
-
- private String text;
-
- WindowManager wm = (WindowManager)getContext().getApplicationContext().getSystemService(getContext().WINDOW_SERVICE);
-
- public MyTextView(Context context) {
- super(context);
- text = "世上只有媽媽好,有媽的孩子像塊寶";
- this.setBackgroundColor(Color.argb(90, 150, 150, 150));
-
- this.setTextSize(20f);
- handler = new Handler();
- handler.post(update);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
-
- x = event.getRawX();
- y = event.getRawY() - TOOL_BAR_HIGH;
- Log.d(TAG, "------X: "+ x +"------Y:" + y);
-
- switch(event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- startX = event.getX();
- startY = event.getY();
- break;
- case MotionEvent.ACTION_MOVE:
- updatePosition();
- break;
- case MotionEvent.ACTION_UP:
- updatePosition();
- startX = startY = 0;
- break;
- }
-
- return true;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
-
- super.onDraw(canvas);
- float1 += 0.001f;
- float2 += 0.001f;
-
- if(float2 > 1.0){
- float1 = 0.0f;
- float2 = 0.01f;
- }
- this.setText("");
- float len = this.getTextSize() * text.length();
- Shader shader = new LinearGradient(0, 0, len, 0,
- new int[] { Color.YELLOW, Color.RED }, new float[]{float1, float2},
- TileMode.CLAMP);
- Paint p = new Paint();
- p.setShader(shader);
-
- p.setTextSize(20f);
- p.setTypeface(Typeface.DEFAULT_BOLD);
-
- canvas.drawText(text, 0, 20, p);
-
- }
-
- private Runnable update = new Runnable() {
- public void run() {
- MyTextView.this.update();
- handler.postDelayed(update, 3);
- }
- };
-
- private void update(){
- postInvalidate();
- }
-
- private float float1 = 0.0f;
- private float float2 = 0.01f;
-
- private Handler handler;
-
- private void updatePosition(){
-
- params.x = (int)( x - startX);
- params.y = (int) (y - startY);
- wm.updateViewLayout(this, params);
- }
- }