原创文字,转载请标明出处:
利用Button实现简单地电子钢琴,可以简单地响应按钮的click事件来发出相应的声音。但是这样不能达到手指在屏幕滑动,而连续发声的效果,就像手指在真实钢琴按键上滑过一样。本文就是为了解决这个问题。思路:通过父控件响应touchevent,在响应函数中判断位置是否在按钮所在位置,或是从一个按钮移动到另一个按钮内,从而进行相应的操作。
形状文件:res/drawable
button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" > <corners android:bottomLeftRadius="10dp" android:bottomRightRadius="10dp" > </corners> <stroke android:width="2dp" android:color="#605C59" /> <gradient android:angle="270" android:endColor="#FFFFFF" android:startColor="#F5F5F5" /> </shape>
button_pressed.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" > <solid android:color="#A4A4A4" /> <corners android:bottomLeftRadius="10dp" android:bottomRightRadius="10dp" > </corners> <stroke android:width="2dp" android:color="#605C59" /> </shape>
布局文件:res/layout
activitymain.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/parent" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" > <LinearLayout android:id="@+id/title" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:id="@+id/text" android:text="电子钢琴 by ZH" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:id="@+id/Keys" android:layout_height="0dp" android:layout_weight="5" android:orientation="horizontal" android:padding="10dp" > <Button android:id="@+id/button1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/button" android:text="1" /> <Button android:id="@+id/button2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/button" android:text="2" /> <Button android:id="@+id/button3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/button" android:text="3" /> <Button android:id="@+id/button4" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/button" android:text="4" /> <Button android:id="@+id/button5" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/button" android:text="5" /> <Button android:id="@+id/button6" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/button" android:text="6" /> <Button android:id="@+id/button7" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/button" android:text="7" /> </LinearLayout> </LinearLayout>
源文件:
MyMusicUtils.java
/** * 音乐播放帮助类 */ package com.example.android_simple_piano; import java.util.HashMap; import android.content.Context; import android.media.AudioManager; import android.media.SoundPool; public class MyMusicUtils { // 资源文件 int Music[] = { R.raw.do1, R.raw.re2, R.raw.mi3, R.raw.fa4, R.raw.sol5, R.raw.la6, R.raw.si7, }; SoundPool soundPool; HashMap<Integer, Integer> soundPoolMap; /** * * @param context * 用于soundpool.load * @param no * 播放声音的编号 */ public MyMusicUtils(Context context) { soundPool = new SoundPool(2, AudioManager.STREAM_MUSIC, 100); soundPoolMap = new HashMap<Integer, Integer>(); for (int i = 0; i < Music.length; i++) { soundPoolMap.put(i, soundPool.load(context, Music[i], 1)); } } public int soundPlay(int no) { return soundPool.play(soundPoolMap.get(no), 100, 100, 1, 0, 1.0f); } public int soundOver() { return soundPool.play(soundPoolMap.get(1), 100, 100, 1, 0, 1.0f); } @Override protected void finalize() throws Throwable { // TODO Auto-generated method stub soundPool.release(); super.finalize(); } }
MainActivity.java
package com.example.android_simple_piano; import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.Button; public class MainActivity extends Activity { private Button button[];//按钮数组 private MyMusicUtils utils;//工具类 private View parent;//父视图 private int buttonId[];//按钮id private boolean havePlayed[];//是否已经播放了声音,当手指在同一个按钮内滑动,且已经发声,就为true private int currentKey;//手指当前所在按钮 private int lastKey;//上一个按钮 private View keys;//按钮们所在的视图 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //新建工具类 utils = new MyMusicUtils(getApplicationContext()); //按钮资源Id buttonId = new int[7]; buttonId[0] = R.id.button1; buttonId[1] = R.id.button2; buttonId[2] = R.id.button3; buttonId[3] = R.id.button4; buttonId[4] = R.id.button5; buttonId[5] = R.id.button6; buttonId[6] = R.id.button7; button = new Button[7]; havePlayed = new boolean[7]; //获取按钮对象 for (int i = 0; i < button.length; i++) { button[i] = (Button) findViewById(buttonId[i]); button[i].setClickable(false); havePlayed[i] = false; } currentKey = 0; lastKey = 0; parent = (View) findViewById(R.id.parent); parent.setClickable(true); parent.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int temp; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: temp = isInAnyScale(event.getX(), event.getY(), button); if (temp != -1) {// 在某个按键范围内 currentKey = temp; button[currentKey] .setBackgroundResource(R.drawable.button_pressed); // 播放音阶 utils.soundPlay(currentKey); Log.i("--", "sound" + currentKey); havePlayed[currentKey] = true; } break; case MotionEvent.ACTION_MOVE: temp = currentKey; for (int i = temp + 1; i >= temp - 1; i--) { //当在两端的按钮时,会有一边越界 if (i < 0 || i >= button.length) { continue; } if (isInScale(event.getX(), event.getY(), button[i])) {// 在某个按键内 if (i == currentKey) { // 在当前按键内且未发音 if (!havePlayed[i]) { utils.soundPlay(currentKey); Log.i("--", "sounD" + i); } break; } else {// 在相邻按键内 lastKey = currentKey; // 设置当前按键 currentKey = i; button[currentKey] .setBackgroundResource(R.drawable.button_pressed); // 发音 utils.soundPlay(currentKey); Log.i("--", "sound" + currentKey); havePlayed[currentKey] = true; // 设置上一个按键 button[lastKey] .setBackgroundResource(R.drawable.button); havePlayed[lastKey] = false; break; } } } break; case MotionEvent.ACTION_UP: lastKey = currentKey; button[currentKey].setBackgroundResource(R.drawable.button); havePlayed[currentKey] = false; break; } return true; } }); keys = (View) findViewById(R.id.Keys); } /** * 判断某个点是否在某个按钮的范围内 * * @param x 横坐标 * @param y 纵坐标 * @param button 按钮对象 * @return 在:true;不在:false */ private boolean isInScale(float x, float y, Button button) { //keys.getTop()是获取按钮所在父视图相对其父视图的右上角纵坐标 if (x > button.getLeft() && x < button.getRight() && y > button.getTop() + keys.getTop() && y < button.getBottom() + keys.getTop()) { return true; } else { return false; } } /** * 判断某个点是否在一个按钮集合中的某个按钮内 * * @param x 横坐标 * @param y 纵坐标 * @param button 按钮数组 * @return */ private int isInAnyScale(float x, float y, Button[] button) { //keys.getTop()是获取按钮所在父视图相对其父视图的右上角纵坐标 for (int i = 0; i < button.length; i++) { if (x > button[i].getLeft() && x < button[i].getRight() && y > button[i].getTop() + keys.getTop() && y < button[i].getBottom() + keys.getTop()) { return i; } } return -1; } }
效果图:
声音文件:http://pan.baidu.com/s/1hq0xXC4
参考文章:
事件分发:http://blog.csdn.net/guolin_blog/article/details/9097463
坐标问题:http://www.cnblogs.com/zhengbeibei/archive/2013/05/07/3065999.html