您的位置:

让你的Android设备更有趣——自定义震动

一、随着手势定制震动

震动是很多应用中常见的交互方式,比如在游戏中提示用户任务完成、在闹钟应用中提醒用户该起床了等等。不同的震动模式可以传递不同的信息,但是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 List vibrationPattern = 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);
}

以上是三种不同的音乐震动模式实现例子,开发者可以根据自己的需求来进行选择和改进。

让你的Android设备更有趣——自定义震动

2023-05-14
Android Root:让你的设备更加自由

随着移动设备市场的不断发展,许多人希望能够更好地掌控自己的设备。而Android Root正是一个不错的选择。它可以帮助用户解锁设备上的限制,让他们可以自由地访问设备的文件系统、升级系统、删除预装应用

2023-12-08
让你的Android设备拥有更流畅的性能体验

2023-05-14
让你的Android App更加专业:实现状态栏颜色自定义

2023-05-14
Chrome Android APK:让你的移动端浏览体验更

2023-05-14
增强用户沉浸感的Android界面设计

2023-05-14
以为Android应用程序设计良好的用户体验

一、清晰的界面 新手第一次使用Android程序,往往不知道怎么使用,如果看到的界面充满了复杂的功能和密密麻麻的内容,那么用户很容易因为混乱而选择放弃使用。所以,为了让新手和老手都能够很好的使用,一个

2023-12-08
Android自定义注解指南

2023-05-17
Android ABI:让你的应用更快更小

2023-05-17
印象笔记记录java学习(Java成长笔记)

2022-11-12
Android屏幕适配:让你的应用兼容不同分辨率的设备

随着移动设备的不断发展,每年都会有新的设备上市,这些设备的分辨率和尺寸都不相同。如果你的应用只适配了特定的分辨率,那么在其他分辨率上就会有兼容性问题。为了确保你的应用可以适应各种屏幕大小和分辨率,本文

2023-12-08
让你的Android应用更具吸引力:5种引人注目的UI设计技

2023-05-14
Android OTA升级:让你的设备保持最新、最安全

2023-05-14
让你的应用更加便捷:Android文件选择器

在Android应用的开发中,文件选择器是一个非常常见的功能,让用户可以选择文件进行上传、下载、分享等操作。本文将介绍如何在Android应用中使用文件选择器,以便让你的应用更加便捷。 一、文件选择器

2023-12-08
让你的Android应用更吸引人的颜色设计

颜色是视觉设计中一个非常重要的元素。在Android应用中,合理的颜色设计可以提高用户体验,进而提高用户满意度和留存率。本文将从多个方面介绍如何让你的Android应用的颜色更吸引人。 一、色彩搭配原

2023-12-08
Android自定义属性实现响应式设计

2023-05-14
深度解析adb命令,让你的Android开发更高效

2023-05-17
掌握Android SeekBar的各种技巧

2023-05-19
让你的Android应用更加流畅的转场动画实现

2023-05-14
可视化界面设计工具:让Android Studio开发更高效

一、简介: Android Studio是一款专门为Android平台开发应用程序的综合性开发环境。但是它对界面设计却并不是很友好,需要开发人员自行手动编写XML布局文件,这会导致开发效率低下。那么,

2023-12-08