Search code examples
androidarcoresceneform

How to refresh/repaint a TextView rendered in Android Sceneform?


My intention is to show dynamic sensor readings using a TextView ("info card") in an Android Sceneform app's augmented image.

I use SceneForm's AugmentedImage sample as a basis. I copied the info card concept from the Solarsystem sample and finally (after some frustrating days of effort) managed to come up with a way to get the TextView out from the lambda-function so that I can use setText in the main activity (quite some time since my previous java 1.2 experiences). I'm now able to setText() successfully to the TextView (debugged with getText()) with timestamp values but will try to change it to fetch the value over HTTP or MQTT later on.

The issue is that I'm not able to get the TextView/(infocard) repainted on the screen once the view has been initiated with the first value that shows up correctly. I have tried to invalidate the TextView instance and some other ways to get the node repainted without success.

In AugmentedImageActivity:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
    fitToScanView = findViewById(R.id.image_view_fit_to_scan);

    LinearLayout mainLayout = (LinearLayout) findViewById(R.layout.activity_main);
    LayoutInflater inflater = getLayoutInflater();
    View infoCardLayout = inflater.inflate(R.layout.info_card_view, mainLayout, false);
    textView = (TextView) infoCardLayout.findViewById(R.id.textView);

    arFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdateFrame);
}

In AugmentedImageActivity:

private void onUpdateFrame(FrameTime frameTime) {
    Frame frame = arFragment.getArSceneView().getArFrame();
    Log.d("TW_onUpdateFrame","onUpdateFrame");
    String t = String.valueOf(System.currentTimeMillis());
    t = t.substring(t.length()-6);
    textView.setText(t);

In AugmentedImageNode.java in setImage:

  ...
  localPosition.set(0.0f, 0.0f, -0.8f * image.getExtentZ());
  infoCard = new Node();
  infoCard.setParent(this);
  infoCard.setEnabled(true);
  infoCard.setLocalPosition(localPosition);
  infoCard.setLocalRotation(new Quaternion(new Vector3(1.0f,0.0f,0.0f),-90));

  ViewRenderable.builder()
          .setView(context, R.layout.info_card_view)
          .build()
          .thenAccept(
                  (renderable) -> {
                      infoCard.setRenderable(renderable);
                      TextView textView = (TextView) renderable.getView();
                      textView.setText("Value from setImage");
                  })
          .exceptionally(
                  (throwable) -> {
                      throw new AssertionError("Could not load info card view.", throwable);
                  });

How and where do I get the info card's TextView repainted? Also taking into account that the values need to be fetched asynchronously over HTTP in the future? This ViewRenderable.builder() with thenAccept's lambda functions makes me cry :)


Solution

  • You're creating the view twice by inflating it in your constructor and then having the ViewRenderable builder inflate it again. The one you saved is not the one that's actually being rendered.

    You only build the ViewRenderable once, so the callback for thenAccept is only called once. You'll need to save the view you get in there to some variable in AugmentedImageNode and then expose that with a getter function so you can retrieve it in AugmentedImageActivity. You would then call setText in onUpdateFrame as before.

    Also keep in mind that renderable.getView() gives you the layout view, you still need to call findViewById to get the textView.