我的Glass GDK应用程序中有一些用户首选项,大多数是简单的复选框。 我找不到玻璃特定的偏好范例,所以我使用了PreferenceFragment,它在XE12上运行良好。
仅供参考:当我实现它时,它最初看起来很糟糕,但我通过AndroidManifest中的以下样式为我的SettingsActivity改进了它:
<style name="Theme.Preferences" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen" />
我没有选择不更新到XE16(除了关闭网络连接)。 更新后,我调整了我的应用程序的API用法,用于少数XE16更改。 一切都很有效。
我注意到的第一件事是我在沉浸式的MainActivity中向下滑动将不再返回Live Card。 我通过让我的MainActivity的onGesture处理程序为Gesture.SWIPE_DOWN返回false来修复此问题。
我注意到的第二件事就是这个问题的目的:包含PreferenceFragment的我的SettingsActivity不再允许我使用向左和向右滑动来上下移动首选项列表。 我的代码在这篇文章的最后。 我添加了一个GestureDetector来帮助调试此问题。 我可以看到SWIPE_LEFT和SWIPE_RIGHT被记录,但无论我返回什么,或者即使我删除了手势代码,首选项列表选择也永远不会从第一项移动。 第一项是CheckBoxPreference,当我点击时, 切换。
我见过其他一些使用Android偏好设置(PreferenceActivity或PreferenceFragment)的其他Glass应用程序,现在它们似乎都被打破了。
如何在Glass上正确实现Preferences,或者如何让PreferenceFragment正常工作?
public class SettingsActivity //
extends Activity //
implements GestureDetector.BaseListener
{
private static final String TAG = WtcLog.TAG(SettingsActivity.class);
public static final int RESULT_SIGN_OUT = RESULT_FIRST_USER + 1;
private static final String TAG_PREFERENCES = "preferences";
private GestureDetector mGestureDetector;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mGestureDetector = new GestureDetector(this);
mGestureDetector.setBaseListener(this);
getFragmentManager() //
.beginTransaction() //
.replace(android.R.id.content, new FragmentSettings(), TAG_PREFERENCES) //
.commit();
}
public static class FragmentSettings extends PreferenceFragment
{
private ApplicationGlass mApplication;
private WavePreferences mPreferences;
private PreferenceScreen mScreenTop;
private PreferenceCategory mCategoryDebug;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Activity activity = getActivity();
mApplication = (ApplicationGlass) activity.getApplication();
mPreferences = mApplication.getPreferences();
addPreferencesFromResource(R.xml.preferences);
mScreenTop = (PreferenceScreen) findPreference("screen_top");
//
// Remember Password
//
CheckBoxPreference prefRememberPassword = (CheckBoxPreference) findPreference("pref_remember_password");
prefRememberPassword.setChecked(mPreferences.getRememberPassword());
prefRememberPassword.setOnPreferenceChangeListener(new OnPreferenceChangeListener()
{
@Override
public boolean onPreferenceChange(Preference preference, Object newValue)
{
boolean rememberPassword = (Boolean) newValue;
String password = null;
if (rememberPassword)
{
password = mApplication.getSessionManager().getLastStartConnectInfo().getPassword();
}
mPreferences.setRememberPassword(rememberPassword);
mPreferences.setPassword(password);
return true;
}
});
...
}
}
@Override
public boolean onGenericMotionEvent(MotionEvent event)
{
if (mGestureDetector != null)
{
return mGestureDetector.onMotionEvent(event);
}
return false;
}
@Override
public boolean onGesture(Gesture gesture)
{
WtcLog.debug(TAG, "onGesture(" + gesture + ")");
switch (gesture)
{
case LONG_PRESS:
WtcLog.debug(TAG, "onGesture: LONG_PRESS");
break;
case TAP:
WtcLog.debug(TAG, "onGesture: TAP");
break;
case TWO_TAP:
WtcLog.debug(TAG, "onGesture: TWO_TAP");
break;
case SWIPE_RIGHT:
WtcLog.debug(TAG, "onGesture: SWIPE_RIGHT");
break;
case SWIPE_LEFT:
WtcLog.debug(TAG, "onGesture: SWIPE_LEFT");
break;
case SWIPE_DOWN:
WtcLog.debug(TAG, "onGesture: SWIPE_DOWN");
break;
case SWIPE_UP:
WtcLog.debug(TAG, "onGesture: SWIPE_UP");
break;
case THREE_LONG_PRESS:
WtcLog.debug(TAG, "onGesture: THREE_LONG_PRESS");
break;
case THREE_TAP:
WtcLog.debug(TAG, "onGesture: THREE_TAP");
break;
case TWO_LONG_PRESS:
WtcLog.debug(TAG, "onGesture: TWO_LONG_PRESS");
break;
case TWO_SWIPE_DOWN:
WtcLog.debug(TAG, "onGesture: TWO_SWIPE_DOWN");
break;
case TWO_SWIPE_LEFT:
WtcLog.debug(TAG, "onGesture: TWO_SWIPE_LEFT");
break;
case TWO_SWIPE_RIGHT:
WtcLog.debug(TAG, "onGesture: TWO_SWIPE_RIGHT");
break;
case TWO_SWIPE_UP:
WtcLog.debug(TAG, "onGesture: TWO_SWIPE_UP");
break;
default:
WtcLog.error(TAG, "onGesture: unknown gesture \"" + gesture + "\"");
break;
}
return false;
}
}
答案 0 :(得分:3)
我认为这与listViews不再在XE16中正确滚动有关。我在这里回答了如何回滚:https://stackoverflow.com/a/23146305/1114876。
基本上,在手势的switch / case语句中,调用
myListView.setSelection(myListView.getSelectedItemPosition()+1);
在列表中前进,myListView.setSelection(myListView.getSelectedItemPosition()-1);
向后退。
编辑:以下是PreferenceActivity
如何继承ListActivity
课程的一些信息:https://stackoverflow.com/a/8432096/1114876您可以通过调用{{1}来获取PreferenceActivity的列表视图}。然后附上手势识别器。
答案 1 :(得分:1)
不幸的是,Google的官方回答似乎是ListViews
(或使用ListViews
的任何UI对象)不应该在Glass上使用。来自https://code.google.com/p/google-glass-api/issues/detail?id=484#c6:
由于与其他Android设备相比,Glass上的用户交互模型大不相同,因此有许多库存小部件在Glass上无法使用。 ListView就是其中之一。您应该迁移代码以在GDK中使用CardScrollView,而不是试图解决这些问题以使用可为用户提供较差用户体验的小部件。
ListView
在LiveCard页面上被列为支持,因此这有点矛盾:
https://developers.google.com/glass/develop/gdk/live-cards
我知道我们不希望向Glass中的用户显示长列表,但IMO有用例(我的是实时传输信息 - 请参阅this comment,还有“ok,glass。 ..“列表”当你可能有一个或两个以上的项目,而不是你可以放在屏幕上,并且垂直滚动是有意义的,而不是强制级联到另一张卡。
我已要求澄清Google Glass here上垂直列表的最佳做法。
答案 2 :(得分:1)
键盘(dpad)事件仍由ListView
和XE16上的PreferenceFragment
正确处理,因此我通过将玻璃触摸板运动事件转换为键盘运动事件来提出解决方法。
我在下面添加了一个自动处理转换的变通方法PreferenceFragment实例的实现。完整的实施和使用可以在http://github.com/ne0fhyk/AR-Glass/blob/master/src/com/ne0fhyklabs/freeflight/fragments/GlassPreferenceFragment.java找到。
在下面的实现中,玻璃GestureDetector
通过将匹配的DPAD
键事件分派给当前Window.Callback
实例来处理收到的手势。
该类的结构使您可以将其放在项目中,并将PreferenceFragment实现更改为从中扩展,以使其再次运行。
/**
* This class provides the necessary workarounds to make the default preference fragment screen
* work again on glass post XE16.
*/
public class GlassPreferenceFragment extends PreferenceFragment {
@Override
public void onStart(){
super.onStart();
final Activity parentActivity = getActivity();
if(parentActivity != null){
updateWindowCallback(parentActivity.getWindow());
}
}
@Override
public void onStop(){
super.onStop();
final Activity parentActivity = getActivity();
if(parentActivity != null){
restoreWindowCallback(parentActivity.getWindow());
}
}
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference){
if(preference instanceof PreferenceScreen){
//Update the preference dialog window callback. The new callback is able to detect
// and handle the glass touchpad gestures.
final Dialog prefDialog = ((PreferenceScreen) preference).getDialog();
if(prefDialog != null) {
updateWindowCallback(prefDialog.getWindow());
}
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
/**
* Replace the current window callback with one supporting glass gesture events.
* @param window
*/
private void updateWindowCallback(Window window){
if(window == null) {
return;
}
final Window.Callback originalCb = window.getCallback();
if(!(originalCb instanceof GlassCallback)) {
final GlassCallback glassCb = new GlassCallback(window.getContext(), originalCb);
window.setCallback(glassCb);
}
}
/**
* Restore the original window callback for this window, if it was updated with a glass
* window callback.
* @param window
*/
private void restoreWindowCallback(Window window){
if(window == null){
return;
}
final Window.Callback currentCb = window.getCallback();
if(currentCb instanceof GlassCallback){
final Window.Callback originalCb = ((GlassCallback)currentCb).getOriginalCallback();
window.setCallback(originalCb);
}
}
/**
* Window.Callback implementation able to detect, and handle glass touchpad gestures.
*/
private static class GlassCallback implements Window.Callback {
/**
* Used to detect and handle glass touchpad events.
*/
private final GestureDetector mGlassDetector;
/**
* This handles the motion events not supported by the glass window callback.
*/
private final Window.Callback mOriginalCb;
public GlassCallback(Context context, Window.Callback original){
mOriginalCb = original;
mGlassDetector = new GestureDetector(context);
mGlassDetector.setBaseListener(new GestureDetector.BaseListener() {
@Override
public boolean onGesture(Gesture gesture) {
switch(gesture){
case TAP:
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_DPAD_CENTER));
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_DPAD_CENTER));
return true;
case SWIPE_LEFT:
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_DPAD_UP));
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_DPAD_UP));
return true;
case SWIPE_RIGHT:
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN));
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_DPAD_DOWN));
return true;
}
return false;
}
});
}
/**
* @return the Window.Callback instance this one replaced.
*/
public Window.Callback getOriginalCallback(){
return mOriginalCb;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return mOriginalCb.dispatchKeyEvent(event);
}
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
return mOriginalCb.dispatchKeyShortcutEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return mOriginalCb.dispatchTouchEvent(event);
}
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
return mOriginalCb.dispatchTrackballEvent(event);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
return mGlassDetector.onMotionEvent(event) || mOriginalCb.dispatchGenericMotionEvent
(event);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
return mOriginalCb.dispatchPopulateAccessibilityEvent(event);
}
@Nullable
@Override
public View onCreatePanelView(int featureId) {
return mOriginalCb.onCreatePanelView(featureId);
}
@Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
return mOriginalCb.onCreatePanelMenu(featureId, menu);
}
@Override
public boolean onPreparePanel(int featureId, View view, Menu menu) {
return mOriginalCb.onPreparePanel(featureId, view, menu);
}
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
return mOriginalCb.onMenuOpened(featureId, menu);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
return mOriginalCb.onMenuItemSelected(featureId, item);
}
@Override
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
mOriginalCb.onWindowAttributesChanged(attrs);
}
@Override
public void onContentChanged() {
mOriginalCb.onContentChanged();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
mOriginalCb.onWindowFocusChanged(hasFocus);
}
@Override
public void onAttachedToWindow() {
mOriginalCb.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
mOriginalCb.onDetachedFromWindow();
}
@Override
public void onPanelClosed(int featureId, Menu menu) {
mOriginalCb.onPanelClosed(featureId, menu);
}
@Override
public boolean onSearchRequested() {
return mOriginalCb.onSearchRequested();
}
@Nullable
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
return mOriginalCb.onWindowStartingActionMode(callback);
}
@Override
public void onActionModeStarted(ActionMode mode) {
mOriginalCb.onActionModeStarted(mode);
}
@Override
public void onActionModeFinished(ActionMode mode) {
mOriginalCb.onActionModeFinished(mode);
}
}
}