简体   繁体   中英

How to loop a dynamic vibration pattern in android?

OK, so I have a much larger application that I am not actually going to post here. In it, there is a vibration event that is looped through, though technically it is not a while loop [the event ends at the users request] and the values that make up the long[] pattern = {0,dot, gap, dash, gap, dot, gap, dot}; change at every iteration so setting vibrator.vibrate(pattern, 0); is not an option.

I set up another application to test some things about Vibrator. I found that running a static pattern gave a desired output, but putting the onVibrate method in a while loop basically destroyed any ability to recognized the signal. I didn't find any way for the system to know if the phone was currently vibrating only if it could vibrate. So I decided to nest onVibrate behind a boolean that the method would ultimate control and that control would be a callback that would 'pause' for more than the length the pattern took up. It ended up looking like this:

public class MainActivity extends Activity {
Boolean im_vibrating;
CallBack mCallBack;
Vibrator vibrator;

interface MyCallBack {
    void offSet(Integer span);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mCallBack = new CallBack();
    vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
    im_vibrating = false;

    while (true) {
        if (!im_vibrating) {
            im_vibrating = true;
            onVibrate();
        }
    }
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

public void onVibrate () {
    int dot = 200;
    int dash = 500;
    int gap = 200;
    long[] pattern = {
            0,
            dot, gap, dash, gap, dot, gap, dot
    };

    vibrator.vibrate(pattern, -1);
    int span = dot + gap + dash + gap + dot + gap + dot + dot;
    mCallBack.offSet(span);
}

class CallBack implements MyCallBack {

    @Override
    public void offSet(Integer span) {
        final android.os.Handler handler = new android.os.Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                im_vibrating = false;
            }
        }, span);
    }
}

}

However, despite having no errors, this doesn't work, something ends up blocking the rendering of the layout so the display is just blank, it vibrates once and then never again. Despite the fact that mCallBack auto completed offSet the SDK highlights offset stating that it is never used.

Every operation done in the main thread, the one that renders views and in which the activities are called, must be as simple as it could. If this is not the case, the UI will be blocked.

Given that, the way to achieve something like you want is using a Handler . This class allows you to dispatch messages to a given thread, in this case, the main one, even with delayed times.

One possible implementation for your solution:

long[] pattern = new long[]{
            0,
            dot, gap, dash, gap, dot, gap, dot
};
final Handler handler = new Handler();
handler.postDelayed(new Runnable(){
    @Override
    public void run(){
        vibrator.vibrate();
        if(!endVibration){
            handler.postDelayed(this, timeToRun);
        }
    }
}, timeToRun);

This will generate a loop that can be cancelled by setting the endVibration variable to true and that is not blocking the UI.

You cannot use the UI thread to do long looping actions - move them into a separate thread.

Your scheme to use a Handler as a sort of threaded callback will fail because Handler instances use the thread they were created on to do their work. Since your handler is created by the UI thread and you've got the UI thread hanging in a while (true) loop in your onCreate() method, nothing will happen - the UI will never update (which is why the display is blank) and the handler will never run.

Just use an AsyncTask or aa separate Thread to do the vibration and never block the UI thread.

OK taking into consideration what both parties above me said I ended up with this, which was exactly what I was looking for:

MainActivity.java

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Vibrator;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;


public class MainActivity extends Activity {
    Vibrator vibrator;
    VibratePattern task;
    Button stop_button;
    Button start_button;
    MyTaskParams params;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        stop_button = (Button) findViewById(R.id.button_stop);
        stop_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                task.cancel(true);
                task = null;
            }
        });
        start_button = (Button) findViewById(R.id.button_start);
        start_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (task == null) {
                    startTask();
                }
            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private static class MyTaskParams {
        int dot, dash, gap;

        MyTaskParams (int dot, int dash, int gap) {
            this.dot = dot;
            this.dash = dash;
            this.gap = gap;
        }
    }

    private void startTask() {
        params = new MyTaskParams(200,500,200);
        task = new VibratePattern();
        task.execute(params);
    }

    public Integer onVibrate (Integer dot, Integer dash, Integer gap) {
        long[] pattern = {
                0,
                dot, gap, dash, gap, dot, gap, dot
        };

        vibrator.vibrate(pattern, -1);
        int span = dot + gap + dash + gap + dot + gap + dot + gap;
        return span;
    }

    private class VibratePattern extends AsyncTask<MyTaskParams, Void, Integer> {

        @Override
        protected Integer doInBackground(MyTaskParams... params) {
            int span;
            span = onVibrate(params[0].dot,params[0].dash,params[0].gap);
            return span;
        }

        @Override
        protected void onPostExecute(Integer span) {
            final android.os.Handler handler = new android.os.Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (!isCancelled()) {
                        startTask();
                    }
                }
            }, span);
        }
    }
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/button_start"
        android:id="@+id/button_start"
        android:layout_marginTop="90dp"
        android:layout_alignParentTop="true"
        android:layout_toLeftOf="@+id/button_stop"
        android:layout_toStartOf="@+id/button_stop" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/button_stop"
        android:id="@+id/button_stop"
        android:layout_alignTop="@+id/button_start"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_marginRight="80dp"
        android:layout_marginEnd="80dp" />

</RelativeLayout>

string.xml

<resources>
    <string name="app_name">VibrationTest</string>

    <string name="action_settings">Settings</string>
    <string name="button_stop">Stop</string>
    <string name="button_start">Start</string>
</resources>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="" >

    <uses-permission android:name="android.permission.VIBRATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            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>

</manifest>

startTask could then load up new values every loop cycle dynamically changing the pulse as needed. Ty for your help.

it was nice for me

 private void startTask() {
    G.vibrator.vibrate(new long[]{0, 500, 200, 200, 500, 200, 200}, -1);
    G.HANDLER.postDelayed(new Runnable() {
      @Override
      public void run() {
        startTask();
      }
    }, 3000);
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM