Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 1113|回复: 0

[实例教程]【Android进阶】天天动听 悬浮歌词(迷你歌词)效果

[复制链接]

该用户从未签到

发表于 2011-10-22 13:21:31 | 显示全部楼层 |阅读模式
天天动听, 这款Android手机上的音乐播放器,相信不少朋友都曾用过。 不知大家是否注意到,天天动听有一个迷你歌词的特效。
什么效果呢? 就是不管你切到什么画面, 歌词永远显示,并且可以拖动。 类型QQ音乐,在电脑上播放时显示的歌词效果。
下面先来看一下效果。

这个歌词是在所有界面之上的。
下面我们将这个效果解剖一下, 我认为主要有三个难点:
1.  歌词悬浮在所有页面之上
2.  歌词可以拖动位置
3.  歌词的播放效果 (颜色覆盖)
对于第一点,首先想到的就是 WindowManager , 这个类可能不少人都用过, 一般用于获取屏幕宽度、高度,那么这次就要利用这个类来让我们的歌词永远置顶。
通过查看API,我们看到,在WindowManager.LayoutParams类中,有好几个属性可以设置View置顶。




下面我们来测试一下, 通过下面几句代码,就可以让一个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也是通过 getSystemService 来获取,但必须先 getApplicationContext, 否则就无效了。

      直接WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE);  这样是无效的 !!       还有一点就是,别忘了在Manifest.xml中添加权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
现在我们这样做,我们已经可以让歌词永远置顶了。 但是不要得意,现在这样,结果是我们TextView在最顶层了, 然后你就会发现,页面上什么操作都不能做了, 在TextView下面的任何东西,你都点不了。

      为了解决这个,我们必须加上flags参数,让当前的View失去焦点,从而让后面的页面获得焦点。代码如下:

params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
加上这一句就可以了。

           好了,下面要处理的,就是让歌词可以移动。应该如何做呢?    我们知道,想要让一个View对象在页面上可以移动,只要实现其onTouchEvent事件即可。   
     下面开始实现第二步: 歌词移动!
       首先我们自定义一个TextView类:MyTextView, 该类继承自TextView, 并实现其中的onTouchEvent方法,来看一下代码:

        @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(){

                 // View的当前位置

                 params.x = (int)( x - startX);

                 params.y = (int) (y - startY);

                 wm.updateViewLayout(this, params);

         }
其中getRawX、getRawY用于获取触摸点离屏幕左上角的距离。 而getX、getY用于获取触摸点离textView左上角的距离.两者相减,就是View左上角的坐标了。另外需要注意的是,在显示View这个View的时候,需要正确指定View的x,y坐标,否则拖动时会错位。 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;

            //以屏幕左上角为原点,设置x、y初始值

                params.x = 0;

                params.y = 0;

               

                tv = new MyTextView(TopFrame.this);

                wm.addView(tv, params);

其中下面三句是关键:                 params.gravity=Gravity.LEFT|Gravity.TOP;

            //以屏幕左上角为原点,设置x、y初始值

                params.x = 0;

                params.y = 0;
现在这样的话,就可以实现View的移动了。
下面实现第三步: 歌词的播放效果。那么本例仅仅做一个循环, 实际音乐播放器要复杂些,需要根据歌剧的长度及时间间隔,来计算歌词的覆盖速度, 再根据这个速度来覆盖歌词,呈现给用户。要实现歌词播放的效果,需要用到画笔Paint, 还要用到Shader, 还有一个就是UI刷新的问题。一起来看下代码:         @Override

        protected void onDraw(Canvas canvas) {

                // TODO Auto-generated method stub

                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);

                //此处x,y坐标也要注意,尤其是y坐标,要与字体大小协调

                canvas.drawText(text, 0, 20, p);

                        

        }
再加上handler, 让他每隔3毫秒画一次, 就有了这个歌词播放的效果。         private Runnable update = new Runnable() {

        public void run() {

                MyTextView.this.update();

                handler.postDelayed(update, 3);

        }

    };

        

        private void update(){

                postInvalidate();

        }
好了,下面发一下效果图:  


最后附上整个工程的代码:(一个就两个类,一个布局文件)AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/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.android.com/apk/res/android"

    androidrientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    >

    <Button

            android:id="@+id/bt"

            android:text=" 点我试试"

            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 {

        /** Called when the activity is first created. */

        @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;

            //以屏幕左上角为原点,设置x、y初始值

                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));

                // 下面这句话在此并不是控制歌词大小,仅仅是为了控制背景大小,如果不设置的话,Paint字体大时会被遮挡

                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) {

                // TODO Auto-generated method stub

                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);

                //此处x,y坐标也要注意,尤其是y坐标,要与字体大小协调

                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(){

                 // View的当前位置

                 params.x = (int)( x - startX);

                 params.y = (int) (y - startY);

                 wm.updateViewLayout(this, params);

         }

}
好了,就讲这么多

TopFrame.zip (49.67 KB, 下载次数: 13)
回复

使用道具 举报

该用户从未签到

发表于 2011-10-22 13:21:38 | 显示全部楼层

Re:[实例教程]【Android进阶】天天动

谢谢楼主分享
回复 支持 反对

使用道具 举报

该用户从未签到

发表于 2011-10-22 13:21:46 | 显示全部楼层

Re:[实例教程]【Android进阶】天天动

good,good...
回复 支持 反对

使用道具 举报

该用户从未签到

发表于 2011-10-22 13:21:55 | 显示全部楼层

Re:[实例教程]【Android进阶】天天动

下一个,要回复一个
回复 支持 反对

使用道具 举报

该用户从未签到

发表于 2011-10-22 13:22:02 | 显示全部楼层

Re:[实例教程]【Android进阶】天天动

效果挺好的,木哈哈
回复 支持 反对

使用道具 举报

该用户从未签到

发表于 2011-10-22 13:22:11 | 显示全部楼层

Re:[实例教程]【Android进阶】天天动

下来学习一下......
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

GMT+8, 2024-5-19 07:11 , Processed in 0.432585 second(s), 45 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表