知识点:
1、multithreading
2、surface view
3、drawing with paint and canvas
4 simple collision detection
5. soundpool and audiomanager
6. Fragment lifecycle
Technologies Overview
1. Attaching Custom Views to Layouts
2. res/raw folders
3. Fragment and Activity
1、 资源中带有变量的strings.xml
<string name="results_format">Shots fired: %1$d\nTotal time: %2$.1f</string>
其中,%表示后面是变量,如果只有一个变量,则直接用%d或%.1f即可,多个变量,需要在中间加1$和2$来区分。
2、设置音量按钮控制游戏音量
setVolumeControlStream(AudioManager.STREAM_MUSIC);
3、SparseIntArray : SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的class,目的是提高效率,其核心是折半查找函数(binarySearch)
http://developer.android.com/reference/android/util/SparseIntArray.html
与hashMap的对比:
Memory usage for a SparseIntArray
will be roughly a factor of 10 less that for an equivalent HashMap
. This is due to a combination of things:
(However, if you create the Integer
objects the right way, the "boxing" overhead can be mitigated by the effects of the Integer
classes instance cache.)
By contrast, the sparse version requires 2 * capacity
4 byte words.
the hash table array holds roughly 1 reference per entry (depending on how full the table is ...),
the keys and values have to be "boxed" as Integer
objects in a HashMap
, and
each entry in a HashMap
requires a "node" object that is fairly heavy weight - 4 fields in the standard implementation.
Lookup (i.e. get
) is O(logN)
compared with O(1)
for a HashMap
.
Random insertion is O(N)
compared with O(1)
for a HashMap
. (This is because an insertion has to move on average half of the existing entries so that the new entry can be added at the correct position in the arrays.)
Sequential insertion (i.e. in ascending in key order) is O(1)
.
4. SoundPool :引自 http://www.devdiv.com/Android-SoundPool%E7%B1%BB%E7%9A%84%E4%BD%BF%E7%94%A8-thread-130200-1-1.html
SoundPool类是Android用于管理和播放应用程序的音频资源的类。一个SoundPool对象可以看作是一个可以从APK中导入资源或者从文件系统中载入文件的样本集合。它利用MediaPlayer服务为音频解码为一个原始16位PCM流。这个特性使得应用程序可以进行流压缩,而无须忍受在播放音频时解压所带来的CPU负载和时延。
此外对于低延迟播放,SoundPool还可以管理多个音频流。当SoundPool对象构造时,maxStreams参数的设置表示的是在单一的SoundPool中,同一时间所能播放流的最大数量。利用SoundPool可以跟踪活跃的流的数量。如果其数量超过流的最大数目,SoundPool会基于优先级自动停止先前播放的流。限制流的最大数目,有助于减轻CPU的负荷,减少音频混合对视觉和UI性能的影响。
声音可以通过设置一个非零的循环价值循环。如果值为-1将导致声音永远循环。在这种情况下,应用程序必须明确地调用stop()函数,以停止声音。其他非零值将导致声音按指定数量的时间重复。
在SoundPool中,播放速率也可以改变。1.0的播放率可以使声音按照其原始频率(如果必要的话,将重新采样硬件输出频率)。而2.0的播放速率,可以使声音按照其原始频率的两倍播放。如果为0.5的播放率,则播放速率是原始频率的一半。播放速率的取值范围是0.5至2.0。
优先级的运行从低到高排列的。当用户调用play()函数时,如果活跃的流数目大于规定的maxStreams参数,流分配器将会结束优先级最低的流。如果有多条流都处于最低优先级,优先级系统将会选择关闭最老的流。
一旦声音被成功加载和播放,应用程序可以调用SoundPool.play()来触发的声音。播放过程中的流可又被暂停或继续播放。应用程序还可以通过调整播放率改变音高。
注意,由于资源的限制,流可以被停止,streamID是一个对特定流实例的引用。如果流被停止并且允许更高优先级的流播放,流就不再有效了。然而,应用程序允许调用没有错误的streamID方法。因为如果应用程序不需要关心流的生命周期,这可能有助于简化程序逻辑。
适用场合
SoundPool在载入声音文件过程中,使用了单独的线程,不会对视觉和UI性能产生影响。但是由于SoundPool对载入声音文件大小有所限制,这就导致了如果SoundPool没有载入完成,而不能安全调用play方法。好在Android SDK提供了一个SoundPool.OnLoadCompleteListener类来帮助我们了解声音文件是否载入完成,用户只须重载 onLoadComplete(SoundPool soundPool, int sampleId, int status) 方法即可实现。
与MediaPlayer相比,MediaPlayer存在着资源占用量较高、延迟时间较长、不支持多个音频同时播放等缺点,但SoundPool本身由于内存资源申请有严格限制,所以在开发过程中,笔者建议尽量用SoundPool来播放一些较短的声音片段或者音效。
DEMO
1)SoundPool对象初始化
我们可以调用SoundPool的构造函数public SoundPool (int maxStreams, int streamType, int srcQuality)来初始化SoundPool对象。
其中参数说明如下:
maxStreams:指定支持多少个声音,SoundPool对象中允许同时存在的最大流的数量。
streamType:制定声音类型,流类型可以分为STREAM_VOICE_CALL, STREAM_SYSTEM, STREAM_RING,STREAM_MUSIC 和 STREAM_ALARM四种类型。在AudioManager中定义。
srcQuality:指定声音品质(采样率变换质量)。目前没有用到,可以设为0。
2)载入声音资源
调用load方法载入声音资源,SoundPool提供了4个Load方法,比较常用的是以下两个:
Public int load(Context context, int resId, int priority):从resId所对应的资源加载声音。
Public int load(String path, int priority):从path对应的文件中加载声音。
上面两个方法中都有一个priority参数,该参数目前还没有任何作用,Android建议将该参数设置为1,保持和未来的兼容性。
同时,这两个方法加载声音之后,都会返回该声音的ID,以后程序就可以通过该声音的ID来播放指定声音。
3)播放控制
要播放SoundPool中的声音,须调用int play (int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)方法。
其中参数说明如下:
soundID:Load()函数返回的声音ID号。
leftVolume:左声道音量设置。
rightVolume:右声道音量设置。
priority:指定播放声音的优先级,数值越高,优先级越大。
loop:指定是否循环。-1表示无限循环,0表示不循环,其他值表示要重复播放的次数。
rate:指定播放速率。1.0的播放率可以使声音按照其原始频率。而2.0的播放速率,可以使声音按照其原始频率的两倍播放。如果为0.5的播放率,则播放速率是原始频率的一半。播放速率的取值范围是0.5至2.0。
4)释放资源
播放结束,我们可以调用release()方法释放所有SoundPool对象占据的内存和资源。
5)实例分析
import java.util.HashMap; import android.app.Activity; import android.media.AudioManager; import android.media.SoundPool; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity implements OnClickListener { Button bombButton; Button shotButton; Button arrowButton; Button allButton; SoundPool mSoundPool = null; HashMap<Integer, Integer> soundMap = new HashMap<Integer, Integer>(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bombButton = (Button) findViewById(R.id.bomb); shotButton = (Button) findViewById(R.id.shot); arrowButton = (Button) findViewById(R.id.arrow); allButton = (Button) findViewById(R.id.all); //设置最多可容纳10个音频流,音频的品质为5 mSoundPool = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5); //load方法加载指定音频文件,并返回所加载的音频ID。此处使用HashMap来管理这些音频流 soundMap.put(1 , mSoundPool.load(this, R.raw.bomb , 1)); soundMap.put(2 , mSoundPool.load(this, R.raw.shot , 1)); soundMap.put(3 , mSoundPool.load(this, R.raw.arrow , 1)); bombButton.setOnClickListener(this); shotButton.setOnClickListener(this); arrowButton.setOnClickListener(this); allButton.setOnClickListener(this); } //重写OnClickListener监听器接口的方法 @Override public void onClick(View v) { // TODO Auto-generated method stub //判断哪个按钮被单击 switch (v.getId()) { case R.id.bomb: mSoundPool.play(soundMap.get(1), 1, 1, 0, 0, 1); break; case R.id.shot: mSoundPool.play(soundMap.get(2), 1, 1, 0, 0, 1); break; case R.id.arrow: mSoundPool.play(soundMap.get(3), 1, 1, 0, 0, 1); break; case R.id.all: mSoundPool.play(soundMap.get(1), 1, 1, 0, 0, 1); mSoundPool.play(soundMap.get(2), 1, 1, 0, 0, 1); mSoundPool.play(soundMap.get(3), 1, 1, 0, 0, 1); default: break; } } }
通常,为了更好地管理SoundPool所加载的每个声音的ID,程序一般会使用一个HashMap<Integer,Integer>对象来管理声音。按照上面的步骤初始化,载入声音,并且把按钮绑定监听器,点击按钮播放相应声音。
5. 判断触屏的角度
// aligns the cannon in response to a user touch public double alignCannon(MotionEvent event) { // get the location of the touch in this view Point touchPoint = new Point((int) event.getX(), (int) event.getY()); // compute the touch's distance from center of the screen // on the y-axis double centerMinusY = (screenHeight / 2 - touchPoint.y); double angle = 0; // initialize angle to 0 // calculate the angle the barrel makes with the horizontal if (centerMinusY != 0) // prevent division by 0 angle = Math.atan((double) touchPoint.x / centerMinusY); // if the touch is on the lower half of the screen if (touchPoint.y > screenHeight / 2) angle += Math.PI; // adjust the angle // calculate the endpoint of the cannon barrel barrelEnd.x = (int) (cannonLength * Math.sin(angle)); barrelEnd.y = (int) (-cannonLength * Math.cos(angle) + screenHeight / 2); return angle; // return the computed angle } // end method alignCannon