内置ScrollView,可以头部运动滚动

时间:2014-02-05 16:39:33

标签: google-glass google-gdk

说“ok glass”会显示一个命令列表,根据用户的头部动作自动滚动。

GDK中是否有内置的UI元素来实现这一点?或者我是否必须编写自己的使用传感器的代码?

3 个答案:

答案 0 :(得分:2)

答案 1 :(得分:1)

我在https://developers.google.com/glass/develop/gdk/dev-guides浏览了GDK的开发人员指南,在https://developers.google.com/glass/develop/gdk/reference/index浏览了参考资料,从2013年12月发布的XE 12开始,GDK中肯定没有这样的内置UI元素。

所以现在的答案是肯定的,你必须使用传感器来实现它。

答案 2 :(得分:1)

目前没有使用传感器滚动列表的原生GDK UI元素(事实上,根据this issue,似乎不鼓励使用ListView。)

但是,我能够在my app中合理地完成以下工作。我的列表固定为4个元素(这有助于确定滚动发生了多少),因此您可以相应地调整它(请参阅注释)。

import com.google.android.glass.media.Sounds;
import com.google.android.glass.touchpad.Gesture;
import com.google.android.glass.touchpad.GestureDetector;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.view.MotionEvent;
import android.widget.ListView;

/**
 * Implements sensor-based scrolling of a ListView
 */
public class SensorListController implements SensorEventListener, GestureDetector.BaseListener {

    static final String TAG = "SensorListController";

    Context mContext;

    ListView mList;

    SensorManager mSensorManager;

    private float[] mRotationMatrix = new float[16];

    private float[] mOrientation = new float[9];

    private float[] history = new float[2];

    private float mHeading;

    private float mPitch;

    boolean mActive = true;

    GestureDetector mGestureDetector;

    public SensorListController(Context context, ListView list) {
        this.mContext = context;
        this.mList = list;
        history[0] = 10;
        history[1] = 10;
        mGestureDetector = new GestureDetector(mContext);
        mGestureDetector.setBaseListener(this);
    }

    /**
     * Receive pass-through of event from View
     */
    public boolean onMotionEvent(MotionEvent event) {
        return mGestureDetector.onMotionEvent(event);
    }

    @Override
    public boolean onGesture(Gesture gesture) {
        switch (gesture) {
            case TWO_LONG_PRESS:
                // Toggle on and off accelerometer control of the list by long press
                playSuccessSound();
                toggleActive();
                return true;
            case TWO_TAP:
                // Go to top of the list
                playSuccessSound();
                scrollToTop();
                return true;
        }
        return false;
    }

    /**
     * Should be called from the onResume() of Activity
     */
    public void onResume() {
        mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
        mSensorManager.registerListener(this,
            mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR),
            SensorManager.SENSOR_DELAY_UI);
    }

    /**
     * Should be called from the onPause() of Activity
     */
    public void onPause() {
        mSensorManager.unregisterListener(this);
    }

    /**
     * Toggles whether the controller modifies the view
     */
    public void toggleActive() {
        mActive = !mActive;
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (mList == null || !mActive) {
            return;
        }

        if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
            SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);
            SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_X,
                SensorManager.AXIS_Z, mRotationMatrix);
            SensorManager.getOrientation(mRotationMatrix, mOrientation);

            mHeading = (float) Math.toDegrees(mOrientation[0]);
            mPitch = (float) Math.toDegrees(mOrientation[1]);

            float xDelta = history[0] - mHeading;  // Currently unused
            float yDelta = history[1] - mPitch;

            history[0] = mHeading;
            history[1] = mPitch;

            float Y_DELTA_THRESHOLD = 0.13f;

//            Log.d(TAG, "Y Delta = " + yDelta);

            int scrollHeight = mList.getHeight()
                / 19; // 4 items per page, scroll almost 1/5 an item

//            Log.d(TAG, "ScrollHeight = " + scrollHeight);

            if (yDelta > Y_DELTA_THRESHOLD) {
//                Log.d(TAG, "Detected change in pitch up...");
                mList.smoothScrollBy(-scrollHeight, 0);
            } else if (yDelta < -Y_DELTA_THRESHOLD) {
//                Log.d(TAG, "Detected change in pitch down...");
                mList.smoothScrollBy(scrollHeight, 0);
            }
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }

    private void scrollToTop() {
        mList.smoothScrollToPosition(0);
    }

    private void playSuccessSound() {
        // Play sound to acknowledge action
        AudioManager audio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        audio.playSoundEffect(Sounds.SUCCESS);
    }
}

我在ListActivity中使用了上述内容。我在onCreate()初始化它,这是初始化它的方法:

private void initListController() {
    mListView = getListView();
    mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
    mListView.setSelector(android.R.color.transparent);
    mListView.setClickable(true);

    mListController = new SensorListController(this, mListView);
}

这也会使选择指标从视图中删除,使其变得透明。

上述控制器还使用两个手指按下来暂停/恢复滚动,并用两个手指点击滚动到列表顶部(并用声音确认这两个动作)。请注意,要使这些手势生效,您需要覆盖活动中的onGenericMotionEvent()并传递事件,例如:

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
    // We need to pass events through to the list controller
    if (mListController != null) {
        return mListController.onMotionEvent(event);
    }
    return false;
}

可以看到此解决方案的完整源代码on Github,可以下载APK here