圆形片段磨损手表在模拟器/手表中变黑

时间:2014-09-12 12:20:50

标签: android android-layout android-fragments wear-os

我在Android Wear圆形手表上看到黑屏(在正方形上工作)。 两种布局在Android Studio布局设计中都显示为OK。

要测试的项目:

https://bitbucket.org/powder366/motoblack

我使用以下代码:

public class MyFragment extends Fragment {
...
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View contentView = inflater.inflate(R.layout.my_fragment, container, false);
...

布局my_fragment:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/my_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:keepScreenOn="true"
    tools:context=".WearFragment">

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text1"
        android:text="Hi1"
        android:layout_gravity="center_horizontal"
        style="@style/UnitsStyle"/>

    <TextView
        android:id="@+id/text2"
        android:text="Hi2"
        android:layout_gravity="center_horizontal"
        style="@style/BaseStyle"/>

    <TextView
        android:id="@+id/text3"
        android:text="Hi3"
        android:layout_gravity="center_horizontal"
        style="@style/UnitsStyle"/>

</LinearLayout>

</LinearLayout>

来自:

public class MenuAdapter extends FragmentGridPagerAdapter
...
    public MenuAdapter(Context context, FragmentManager fm, Handler handler) {
    super(fm);
    mContext = context;
    mHandler = handler;
    myFragment = new MyFragment();
}
...
@Override
public Fragment getFragment(int rowNum, int colNum) {
    Log.d(TAG, String.format("getFragment(%d, %d)", rowNum, colNum));

    if(colNum == 0) {
        return myFragment;
    }

    if(colNum == 1) {
        final SecondFragment switchAction = SecondFragment.newInstance(mHandler);
        return switchAction;
    }

    return null;
}
...

来自:

public class WearActivity extends Activity {
...

private GridViewPager gridViewPager;
private MenuAdapter menuAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "Create(Wear)");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.wear_activity);

    final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
    stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
        @Override
        public void onLayoutInflated(WatchViewStub stub) {
            menuAdapter = new MenuAdapter(WearActivity.this, getFragmentManager(), mHandler);
            gridViewPager = (GridViewPager) findViewById(R.id.pager);
            gridViewPager.setAdapter(menuAdapter);
        }
    });
}
...

有没有人看过这个或者能给我一个如何解决它的提示?

更新:

wear_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.WatchViewStub
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/watch_view_stub"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:rectLayout="@layout/rect_activity"
  app:roundLayout="@layout/round_activity"
  tools:context=".WearActivity"
  tools:deviceIds="wear">
</android.support.wearable.view.WatchViewStub>

rect_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_gravity="center"
  tools:deviceIds="wear_square">

<com.xyz.my.ScrollableGridViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"/>

</FrameLayout>

round_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_gravity="center"
  tools:deviceIds="wear_round">

<com.xyz.my.ScrollableGridViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"/>

</FrameLayout>

额外信息:

public class ScrollableGridViewPager extends GridViewPager {
...

enter image description here enter image description here

3 个答案:

答案 0 :(得分:11)

问题并非如此微不足道,所以我希望我已经非常清楚地描述了它。

原因#1:

此问题的真正原因是圆形设备上onLayoutInflated(WatchViewStub)方法被调用两次

rect_activity.xml夸大时首次调用它:

WearActivity$1.onLayoutInflated(WatchViewStub) line: 26 
WatchViewStub.inflate() line: 133   
WatchViewStub.onMeasure(int, int) line: 141 
WatchViewStub(View).measure(int, int) line: 16648   

但在此之后,第二次调用此方法是在调度round_activity.xml时使用新增的onApplyWindowInsets(WindowInsets)完成的:

WearActivity$1.onLayoutInflated(WatchViewStub) line: 26 
WatchViewStub.inflate() line: 133   
WatchViewStub.onApplyWindowInsets(WindowInsets) line: 112   
WatchViewStub(View).dispatchApplyWindowInsets(WindowInsets) line: 6051  
WatchViewStub(ViewGroup).dispatchApplyWindowInsets(WindowInsets) line: 5435 
SwipeDismissLayout(ViewGroup).dispatchApplyWindowInsets(WindowInsets) line: 5439    
PhoneWindow$DecorView(ViewGroup).dispatchApplyWindowInsets(WindowInsets) line: 5439 
ViewRootImpl.dispatchApplyInsets(View) line: 1170   

我不知道这个问题是仅在模拟器上还是在真实设备上发生,但我认为这应该被报告为wearable-support-lib中的错误(准确地说在WatchViewStub组件中)。这种行为在任何地方都没有记录。

原因#2(#1的视觉后果):

上面的问题听起来应该什么都不做......新的布局被夸大了(所以旧的布局被删除了),新的MenuAdapter将被创建(旧的布局将不再被使用,所以谁关心),这个新创建的MenuAdapter应该被设置为新GridViewPager中的适配器。一切都应该“重置”所以它应该仍然可以正常工作(除了由于布局膨胀两次而不是一次膨胀导致性能问题)。

不完全是......因为这是GridViewPager的棘手部分。

我不知道GridViewPagerFragmentGridPagerAdapter的确切源代码,但它似乎与ViewPagerFragmentPagerAdapter代码相似。 FragmentPagerAdapter将新创建的片段添加到FragmentManager,其中tagid的{​​{1}}和给定子片段的位置组成:

ViewPager

将片段添加到122 private static String More ...makeFragmentName(int viewId, int index) { 123 return "android:switcher:" + viewId + ":" + index; 124 } (当前FragmentManager)后,将来可以通过此Activity名称引用该片段。 真正的问题是由tag方法被调用两次这一事实引起的。第一个片段在第一个onLayoutInflated(WatchViewStub)中创建,并添加到ID为MenuAdapter的容器中(来自R.id.pager)。然后rect_activity.xml被夸大,并调用round_activity.xml的第二次调用 - 在新的onLayoutInflated(WatchViewStub)中创建新的片段,并将其添加到具有相同ID的新增MenuAdapter GridViewPager }。因此,第一个片段和第二个片段希望在R.id.pager中具有完全相同的tag

在标准FragmentManager中,如果FragmentPagerAdapter中已存在具有给定tag的片段,则它再次附加到其前一个父级。您可以在此处查看源代码:

FragmentManager

所以可能情况类似,第一个片段被重用,它被添加到第一个50 @Override 51 public Object More ...instantiateItem(View container, int position) { 52 if (mCurTransaction == null) { 53 mCurTransaction = mFragmentManager.beginTransaction(); 54 } 55 56 // Do we already have this fragment? 57 String name = makeFragmentName(container.getId(), position); 58 Fragment fragment = mFragmentManager.findFragmentByTag(name); 59 if (fragment != null) { 60 if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment); 61 mCurTransaction.attach(fragment); 62 } else { 63 fragment = getItem(position); 64 if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); 65 mCurTransaction.add(container.getId(), fragment, 66 makeFragmentName(container.getId(), position)); 67 } 68 if (fragment != mCurrentPrimaryItem) { 69 fragment.setMenuVisibility(false); 70 } 71 72 return fragment; 73 } ,ID为GridViewPager - 但是这个父节点不再存在,所以片段无法附加到任何地方(并显示)。这是一个实现的事情,只有一个R.id.pager - 就像在ViewPager中具有给定ID的父级一样。

对于另一个实验,我为Activity设置了不同的ID。 GridViewPagers的{​​{1}} ID为rect_activity.xml,而R.id.pager1中的另一个拥有`R.id.pager2'。

round_activity.xml

在第一次传递中,仅找到final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub); stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() { @Override public void onLayoutInflated(WatchViewStub stub) { menuAdapter = new MenuAdapter(WearActivity.this, getFragmentManager()); GridViewPager gridViewPager1 = (GridViewPager) findViewById(R.id.pager1); if(gridViewPager1!=null) { // rect_activity gridViewPager1.setAdapter(menuAdapter); } GridViewPager gridViewPager2 = (GridViewPager) findViewById(R.id.pager2); if(gridViewPager2!=null) { // round_activity gridViewPager2.setAdapter(menuAdapter); } } }); 并且仅设置其适配器。类似于第二次传递 - 仅设置了gridViewPager1的适配器。 由于gridViewPager2具有不同的ID,因此片段GridViewPagers中没有冲突,因此最终结果符合预期。
但这只是为了证明上述理论:)

enter image description here

解决方案:

tags用于为WatchViewStubrect屏幕提供不同的布局。我可以看到你对两种形状使用完全相同的布局。你可以摆脱round,只需这样做:

WatchViewStub

这将使您的示例在不使用public class WearActivity extends Activity { private GridViewPager gridViewPager; private MenuAdapter menuAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.rect_activity); menuAdapter = new MenuAdapter(WearActivity.this, getFragmentManager()); gridViewPager = (GridViewPager) findViewById(R.id.pager); gridViewPager.setAdapter(menuAdapter); } } 的情况下完美运行。

答案 1 :(得分:0)

我使用普通的ViewPager遇到了同样的问题。 MaciejCiemięga的实验性修复使用了不同布局的单独id为我工作。我向Google发布了一个错误报告,其中包含此问题和答案的链接。我会对答案发表评论,但我还没有足够的声誉留下评论。

答案 2 :(得分:0)

好的,所以Android生命周期已经完全耗尽了......截至目前onCreate并没有真正创造任何东西......它必须等到 WatchViewStub.onMeasure(...) 将被调用(在onResume完成后调用很长时间)然后它会膨胀正确的视图(或者首先是矩形视图,然后是圆形视图,这就是双重调用的原因)......这会导致很多问题。

我非常喜欢一致性(不确定为什么)所以我已经尝试确保只对矩形和圆形设备处理onViewInflated一次,无论它被调用多少次,这是我的解决方案:

class MyActivity
        extends Activity {


    private class WearableUI_Helper
            implements OnLayoutInflatedListener {


        public Runnable processView = new Runnable() {
            @Override
            public void run() {
                processView(stub);
                postRender();
            }
        };

        private WatchViewStub stub;

        private Handler h = new Handler(Looper.getMainLooper());

        public void postRender() {
            if (stub != null)
                render();
        }

        @Override
        public void onLayoutInflated(WatchViewStub stub) {
            Log.w("Step", "onLayoutInflated");
            this.stub = stub;
            h.removeCallbacks(processView);
            h.postAtFrontOfQueue(processView);
        }
    }

    private void render() {
        Log.w("Step", "Render");
        // do here all the rendering you need... set images and text and all
        // and never call this method directly because it is only initialized
        // long after the onResume is called.
    }

    WearableUI_Helper uiHelper = new WearableUI_Helper();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.w("Step", "onCreate");
        setContentView(R.layout.layout__app_notification_action);
        WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
        stub.setOnLayoutInflatedListener(uiHelper);
    }

    private void processView(WatchViewStub stub) {
        Log.w("Step", "processView");
        // here you should find all you views and assign them to your activity fields.
    }

    @Override
    protected void onResume() {
        super.onResume();
        uiHelper.postRender(); // if you call render() here it will crash!!
    }
}

我用圆形和矩形仿真器测试了这个,并得到了预期的结果。 我没有在Fragments中尝试过这个,但是我没有看到它应该在那里失败的原因。

我希望我能为你节省一些挫折......