一、随着手势定制震动
震动是很多应用中常见的交互方式,比如在游戏中提示用户任务完成、在闹钟应用中提醒用户该起床了等等。不同的震动模式可以传递不同的信息,但是Android系统默认提供的震动模式有限。那么怎么自定义一个比较独特的震动模式呢?这里我们提供一个通过手势实现震动模式定制的例子。
首先我们需要实现一个抽象基类ExtendedGestureListener,它能够处理用户的手势并返回一个数字数组,该数组可以用于定义震动模式。我们可以继承SimpleOnGestureListener并重写onDown()、onFling()和onScroll()三个方法来实现定制。
public abstract class ExtendedGestureListener extends SimpleOnGestureListener { protected static final String TAG = "ExtendedGestureListener"; private static final int SWIPE_MIN_DISTANCE = 120; private static final int SWIPE_THRESHOLD_VELOCITY = 200; private static final int SCROLL_THRESHOLD_DISTANCE = 30; private float downX; private float downY; @Override public boolean onDown(MotionEvent e) { downX = e.getX(); downY = e.getY(); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { float deltaX = e2.getX() - e1.getX(); float deltaY = e2.getY() - e1.getY(); float absDeltaX = Math.abs(deltaX); float absDeltaY = Math.abs(deltaY); if (absDeltaX > absDeltaY) { if (absDeltaX > SCROLL_THRESHOLD_DISTANCE) { if (deltaX > 0) { // Right swipe return onSwipeRight(); } else { // Left swipe return onSwipeLeft(); } } } else { if (absDeltaY > SCROLL_THRESHOLD_DISTANCE) { if (deltaY > 0) { // Down swipe return onSwipeDown(); } else { // Up swipe return onSwipeUp(); } } } return false; } protected abstract boolean onSwipeRight(); protected abstract boolean onSwipeLeft(); protected abstract boolean onSwipeUp(); protected abstract boolean onSwipeDown(); }
然后我们实现一个类CustomVibrator,它能够接收一个数字数组,并转化为具体的震动模式。下面是它的代码片段。
public class CustomVibrator { private Vibrator vibrator; public CustomVibrator(Context context) { vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); } /** * @param vibration pattern represented by an array of integers. Each element * specifies the duration of a single vibration in milliseconds. * The array must contain an even number of elements, with the * first element specifying the duration to wait before starting * the vibration and every subsequent pair of elements specifying * the duration of a vibration and the duration to wait before * vibrating again. */ public void vibrate(int[] pattern) { if (vibrator.hasVibrator()) { vibrator.vibrate(pattern, -1); } else { Log.w(TAG, "No vibrator found on device"); } } }
最后我们来看一下如何使用上面所述的两个类。在MainActivity中我们需要实例化CustomVibrator,同时传递一个ExtendedGestureListener实例并实现相应的方法来处理用户手势输入。下面是MainActivity的代码片段。
public class MainActivity extends AppCompatActivity { private CustomVibrator vibrator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); vibrator = new CustomVibrator(this); final ExtendedGestureListener gestureListener = new ExtendedGestureListener() { private ListvibrationPattern = new ArrayList<>(); @Override protected boolean onSwipeRight() { vibrationPattern.add(200); vibrationPattern.add(200); vibrator.vibrate(toIntArray(vibrationPattern)); return true; } @Override protected boolean onSwipeLeft() { vibrationPattern.add(300); vibrationPattern.add(200); vibrator.vibrate(toIntArray(vibrationPattern)); return true; } @Override protected boolean onSwipeUp() { vibrationPattern.add(400); vibrationPattern.add(200); vibrator.vibrate(toIntArray(vibrationPattern)); return true; } @Override protected boolean onSwipeDown() { vibrationPattern.clear(); vibrator.vibrate(new int[]{0}); return true; } private int[] toIntArray(List list) { int[] array = new int[list.size()]; for (int i = 0; i < list.size(); i++) { array[i] = list.get(i); } return array; } }; final GestureDetector gestureDetector = new GestureDetector(this, gestureListener); View gestureView = findViewById(R.id.gesture_view); gestureView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return gestureDetector.onTouchEvent(event); } }); } }
二、根据音乐节奏震动
当你在听音乐时,你是否有过这样的体验:如果能够把手机的震动和音乐的节奏同步,那该多好啊!这里我们提供一个例子来实现这一点。
首先,我们需要读取音乐的节奏信息。这里我们可以使用Android系统提供的MediaMetadataRetriever类来获取一个媒体文件的元数据信息,包括它的时长、MIME类型以及其中的所有ID3标签和章节信息。
下面是如何使用MediaMetadataRetriever类读取音乐标签信息的示例代码。
MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource(path); String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); String mimeType = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE); byte[] art = retriever.getEmbeddedPicture(); int bitrate = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
然后,我们需要根据读取到的音乐信息,计算出每一个时间片段应该持续多长时间。这里我们可以使用Android系统提供的Visualizer类。它可以用FFT计算出实时音频频谱,并提供了有用的回调函数,如onWaveFormDataCapture()和onFftDataCapture()。
public class MusicVisualizerView extends View implements Visualizer.OnDataCaptureListener { private static final int MAX_AMPLITUDE = 32768; private Visualizer visualizer; private Paint paint; private byte[] waveformBytes; public MusicVisualizerView(Context context, AttributeSet attrs) { super(context, attrs); paint = new Paint(); } public void setVisualizer(Visualizer visualizer) { this.visualizer = visualizer; this.visualizer.setDataCaptureListener(this, Visualizer.getMaxCaptureRate() / 2, true, false); } @Override public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) { waveformBytes = waveform; invalidate(); } @Override public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) { } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (waveformBytes == null) { return; } int width = getWidth(); int height = getHeight(); paint.setStrokeWidth(1.5f); paint.setColor(Color.BLUE); for (int i = 0; i < waveformBytes.length - 1; i++) { float left = width * i / (waveformBytes.length - 1); float top = (0.5f + ((byte) (waveformBytes[i] + 128)) / (2 * MAX_AMPLITUDE)) * height; float right = width * (i + 1) / (waveformBytes.length - 1); float bottom = (0.5f + ((byte) (waveformBytes[i + 1] + 128)) / (2 * MAX_AMPLITUDE)) * height; canvas.drawColor(Color.WHITE); canvas.drawLine(left, top, right, bottom, paint); } } }
最后我们将震动和的音乐节奏同步起来。为了更好地控制时间粒度,我们使用Handler来定时执行震动任务,同时音乐的节奏信息也是按照一定的时间片段来处理。代码实现如下:
final CustomVibrator vibrator = new CustomVibrator(this); final Visualizer visualizer = new Visualizer(0); visualizer.setEnabled(true); final MusicVisualizerView musicVisualizerView = findViewById(R.id.music_visualizer_view); musicVisualizerView.setVisualizer(visualizer); final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { final int size = Visualizer.getCaptureSizeRange()[1]; if (waveformBytes == null) { return; } int bytesPerSecond = (waveformBytes.length * 1000 / Integer.parseInt(duration)); int millisecondsPerSample = size * 1000 / Integer.parseInt(duration); int samplesPerSecond = size / millisecondsPerSample; int frequencyBucketSize = (int) Math.floor((double) samplesPerSecond / 20); int maxFrequencyVolume = 0; for (int i = 0; i < frequencyBucketSize; i++) { int start = i * millisecondsPerSample; int end = start + millisecondsPerSample; int volume = 0; for (int j = start; j < end && j < waveformBytes.length; j++) { volume += waveformBytes[j]; } volume = Math.abs(volume) / (255 * millisecondsPerSample); maxFrequencyVolume = Math.max(maxFrequencyVolume, volume); } if (maxFrequencyVolume >= 20) { int durationInMillis = (int) Math.ceil((double) bytesPerSecond / frequencyBucketSize) * 10; Log.d(TAG, "vibrate durationInMillis: " + durationInMillis); int[] pattern = new int[2]; pattern[0] = 0; pattern[1] = durationInMillis; vibrator.vibrate(pattern); } handler.postDelayed(this, 10); } }, 10);
三、Android系统提供的震动模式
除了自定义震动模式,Android系统还提供了一些预定义的震动模式,比如调用某个API时产生的模式、短信、电话挂断等事件产生的模式等等。下面是一个例子,展示如何调用这些API。
private void playSystemVibrationPattern() { // Vibrate for 500 milliseconds vibrator.vibrate(500); // Play a vibration pattern defined in an XML resource vibrator.vibrate(getResources().getIntArray(R.array.fade_in_out), -1); // Play a vibration pattern designed for notifications vibrator.vibrate(NotificationManager.IMPORTANCE_HIGH); // Play a vibration pattern similar to the system's low battery alert vibrator.vibrate(NotificationManager.IMPORTANCE_LOW); }
以上是三种不同的音乐震动模式实现例子,开发者可以根据自己的需求来进行选择和改进。