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

Android 圆形磨损手表碎片在模拟器/手表中变黑,android,android-layout,android-fragments,wear-os,Android,Android Layout,Android Fragments,Wear Os,我在我的安卓Wear圆形手表上有一个黑屏(在正方形上,它可以工作)。 这两种布局在Android Studio布局设计中都显示为OK 要测试的项目: Im使用以下代码: public class MyFragment extends Fragment { ... @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

我在我的安卓Wear圆形手表上有一个黑屏(在正方形上,它可以工作)。 这两种布局在Android Studio布局设计中都显示为OK

要测试的项目:

Im使用以下代码:

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);
...
布局我的_片段:

<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>
122    private static String More ...makeFragmentName(int viewId, int index) {
123        return "android:switcher:" + viewId + ":" + index;
124    }
这被称为:

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>
<?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>
<?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>

这个问题并不是那么简单,所以我希望我已经非常清楚地描述了它

原因#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   
但紧接着,当调度应用程序WindowInsets(WindowInsets)时,对该方法的第二次调用将使用新膨胀的
round_activity.xml
完成:

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   
我不知道这个问题是只发生在emulator上还是在真实设备上,但我认为这应该被报告为可穿戴支持库中的一个bug(确切地说是在
WatchViewStub
组件中)。这种行为在任何地方都没有记录

原因2(1的视觉后果): 上面的问题听起来好像什么都不做。。。新布局已膨胀(因此旧布局已删除),将创建新的
MenuAdapter
(旧布局将不再使用,谁在乎呢),新创建的
MenuAdapter
应设置为新
GridViewPager
中的适配器。所有内容都应该“重置”,这样它仍然可以正常工作(除了由于布局膨胀两次而不是一次导致的性能问题)

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

我不知道
GridViewPager
FragmentGridPagerAdapter
的确切源代码,但它似乎类似于
ViewPager
FragmentPagerAdapter
code
FragmentPagerAdapter
使用特殊的
标记将新创建的片段添加到
FragmentManager
,该标记由
ViewPager
id
和给定子片段的位置组成:

122    private static String More ...makeFragmentName(int viewId, int index) {
123        return "android:switcher:" + viewId + ":" + index;
124    }
将片段添加到
片段管理器
(在当前的
活动
)后,该
标记
名称可以引用该片段。 真正的问题是由于
onLayoutInflated(WatchViewStub)
方法被调用了两次。第一个片段在first
MenuAdapter
中创建,并添加到id为
R.id.pager
的容器中(来自
rect_activity.xml
)。然后扩展
round\u activity.xml
,并调用对
onlayoutflated(WatchViewStub)
的第二次调用-在new
MenuAdapter
中创建新片段,并将其添加到新扩展的
GridViewPager
中,其id为:
R.id.pager
。因此,第一个片段和第二个片段希望在
FragmentManager
中具有完全相同的
标记

在标准
FragmentPagerAdapter
中,如果带有给定
标记的片段已存在于
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为R.id.pager的
GridViewPager
——但此父级已不存在,因此片段无法附加到任何位置(并显示)。这是一个实现问题,即在一个
活动中只能有一个
ViewPager
-类似于父级的、具有给定id的
活动

为了进行更多的实验,我为两个
GridViewPagers
设置了不同的ID。一个具有
rect_activity.xml
的具有id
R.id.pager1
,另一个位于
round_activity.xml
的具有'R.id.pager2'

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
的适配器。 由于
GridViewPagers
具有不同的ID,因此片段
标记中没有冲突,因此最终结果与预期一致。
但这只是为了证明上述理论:)

解决方案:
WatchViewStub
用于为
rect
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);
    }
}

这将使您的示例在不使用
WatchViewStub

的情况下完美运行。我使用普通的ViewPager时也遇到了同样的问题。Maciej Ciemięga的实验性修复为不同的布局使用了单独的id,这对我很有用。我在谷歌上发布了一个bug报告,并链接到这个问题和答案。我本来会对答案发表评论的,但我还没有足够的声誉留下评论。

好的,所以Android生命周期已经完全消失了。。。到目前为止,onCreate并没有真正创建任何东西。。。它必须等待调用WatchViewStub.onMeasure(…)(在onResume完成后很长一段时间内调用),然后才会膨胀正确的视图(或者首先是矩形视图,然后是圆形视图,这就是双重调用的原因)。。。这会引起很多问题

我非常喜欢一致性(不确定为什么),因此我尝试确保OnViewExploaded对于矩形和圆形设备只处理一次,不管调用了多少次,以下是我的解决方案:

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!!
    }
}
我已经用圆形和矩形模拟器对此进行了测试,并得到了预期的结果。 我没有零碎地试过,我也不认为有什么原因会失败

我希望我为您省去了一些挫折…

您确定<代码
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!!
    }
}