sceneform 简单使用记录(AndroidStudio)

sceneform简介:

        Sceneform makes it straightforward to render realistic 3D scenes in AR and non-AR apps, without having to learn OpenGL. It includes: A high-level scene graph API(包括动画、人脸检测等等)、A realistic physically based render provided by filament(渲染器)、导入3d文件的插件(好像用不了)。总体来说,sceneform就是让arcore的使用变的简单。

放置一个模型:

配置文件的修改,依赖的添加等,这里略过。首先创建布局文件:

<fragment android:name="com.google.ar.sceneform.ux.ArFragment"
    android:id="@+id/ux_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

        这里用到了sceneform的ArFragment,它会自动创建并管理一个ARCoreSession(可用getArSceneView()获取)、ArSceneView(可用getSession()获取),当然这发生在在检查设备是否支持ARCore并且获取了相机权限之后。 检查代码:

        下一步Create renderables,Renderable( is a 3D model that can be placed anywhere in the scene and consists of Meshes, Materials and Textures.)可以理解为,将你的3d文件代入到代码中。举个例子,假如要把本地的3d文件添加到相机的预览画面中,首先在(module级)build.gradle文件中引入:

apply plugin: 'com.google.ar.sceneform.plugin'

sceneform.asset('sampledata/models/andy.obj', // 'Source Asset Path' specified during import.
                'default',                    // 'Material Path' specified during import.
                'sampledata/models/andy.sfa', // '.sfa Output Path' specified during import.
                'src/main/res/raw/andy')      // '.sfb Output Path' specified during import.

然后在代码中就可以创建:

private ModelRenderable andyRenderable;

@Override
protected void onCreate(Bundle savedInstanceState) {
    …

    ModelRenderable.builder()
        .setSource(this, R.raw.andy)
        .build()
        .thenAccept(renderable -> andyRenderable = renderable)
        .exceptionally(
            throwable -> {
            Log.e(TAG, "Unable to load Renderable.", throwable);
            return null;
        });
}

这里的ModelRenderable继承自Renderable,扩展了动画相关的属性,如果你的模型没有动画,可以直接用Renderable,

         下一步就要创建Scene,可以理解为,Scene挂载在ArSceneView上,而Scene像是一个树状的数据结构,可以有很多个Node,Node则是包含了所有Sceneform需要的信息(位置、方向、模型等),下面的代码展示的是创建一个AnchorNode(相当与根节点)并将它挂在ArSceneView上,最后设置它的Renderable为官方示例中的andy模型,这里的Node可以有多个孩子,但是只有一个父亲。这样运行后,待检测处plane后点击可以将andy模型放置在平面上。

 arFragment.setOnTapArPlaneListener(
        (HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
          if (andyRenderable == null) {
            return;
          }

          // Create the Anchor.
          Anchor anchor = hitResult.createAnchor();
          AnchorNode anchorNode = new AnchorNode(anchor);
          anchorNode.setParent(arFragment.getArSceneView().getScene());

          // Create the node andy and add it to the anchor.
          Node andy=new Node();
          andy.setParent(anchorNode);
          andy.setRenderable(andyRenderable);
           
        });

移动一个模型

        上面的代码运行结果它是不可以移动、缩放、旋转模型的,如果要达到上述的效果,需要研究下TransformableNode,经下图可发现,它继承BaseTransformableNode,继承关系如下下图:

         NodeParent类如下图所示:可以根据方法名大致理解其意义:至于接口TransfromProvider规定了一个方法名,getWorldModelMatrix()应该是要获取模型的位置数据

         Node类主要是实现了getWorldModelMatrix(),主要是规定节点的位置、方向、和模型等信息,重要的是添加了点击、触碰等方法:

         BaseTransformavleNode添加了TransformationSystem和BaseTransformationController,看一下TransformationSystem,就可以理解它干了什么,可以看到添加了一系列的监听事件,而对于

BaseTransformationController,可以看到它是对手势操作的控制器:

         最终到了TransformableNode,可以看到定义了移动、缩放、旋转三个控制器,分别追一下源码可以看到,也是继承自 BaseTransformationController<DragGesture>,到这里不在介绍具体的原理,有兴趣的可以自己去追源码。

        前人栽树,后人乘凉。了解了原理后,就可以直接用了,将上一部分的代码改一下,然后重新运行一下,就会实现移动、缩放、旋转模型的效果。

 动画相关:

        如果你的模型带有动画,该如何控制动画呢。先来了解ModelAnimator,继承自Animator,很多方法名都是顾名思义。

 

         添加一个按钮,点击后播放动画,ModelAnimator有一个构造方法,只需传入ModelRenderable就可指明播放的是哪一个模型的动画。

 ...   
 private ModelAnimator animator;
 ...
 private FloatingActionButton animationButton;
 ...
    
 protected void onCreate(Bundle savedInstanceState) {
    ...

    animationButton = findViewById(R.id.animate);
    animationButton.setEnabled(false);
    animationButton.setOnClickListener(this::onPlayAnimation);
 }  
   
 private void onPlayAnimation(View unusedView) {
    if (animator == null || !animator.isRunning()) {
      AnimationData data = modelRenderable.getAnimationData(nextAnimation);
   
      //创建动画(将动画绑定到ModelRenderable上)
      animator = new ModelAnimator(data, modelRenderable);
      animator.start();
    }
  }

               暂停,继续播放动画也是类似:

private void onStopAnimation(View view) {
    if(animator!=null&&!animator.isPaused()){
      stopButton.setImageResource(R.drawable.stop_animation);
      animator.pause();
    }else if (animator!=null&&animator.isPaused()){
      stopButton.setImageResource(R.drawable.active_animation);
      animator.resume();
    }
  }

         有一个方法,在每一帧都会被执行,在这里可以控制一些按钮的可用性或样式等:

private void onFrameUpdate(FrameTime unusedframeTime) {
  
    if (anchorNode == null) {
      if (animationButton.isEnabled()) {
        animationButton.setBackgroundTintList(ColorStateList.valueOf(android.graphics.Color.GRAY));
        animationButton.setEnabled(false);

      stopButton.setBackgroundTintList(ColorStateList.valueOf(android.graphics.Color.GRAY));
        stopButton.setEnabled(false);
      }
    } else {
      if (!animationButton.isEnabled()) {
        animationButton.setBackgroundTintList(
            ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorPrimary)));
        animationButton.setEnabled(true);
                
       stopButton.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorPrimary)));
        stopButton.setEnabled(true);
      }
    }
  }

从自己的服务器加载模型:

        这部分是花时间最久的,但是最终的解决方法却还是用官方提供的方法,在创建Modelrenderable时,将自己服务器的下载地址填进就ok,文件类型(glb、gltf、sfb)都可以

ModelRenderable.builder().setSource(
            this,
            Uri.parse("自己的下载地址"))
            .build()
            .thenAccept(
                    modelRenderable -> {
                      MainActivity activity = owner.get();
                      if (activity != null) {
                        activity.modelRenderable = modelRenderable;
                      }
                    })
            .exceptionally(
                    throwable -> {
                      Toast toast =
                              Toast.makeText(this, "Unable to load Tiger renderable", Toast.LENGTH_LONG);
                      toast.setGravity(Gravity.CENTER, 0, 0);
                      toast.show();
                      return null;
                    });

视频录制:

        这部分不多介绍,花了一天的时间自己写了代码,却发现官方示例中的代码写的太好了,还是用了人家的,官方封装了视频录制功能,关键在于Set up Surface for the MediaRecorder。

public class VideoRecorder {
  private static final String TAG = "VideoRecorder";
  private static final int DEFAULT_BITRATE = 10000000;
  private static final int DEFAULT_FRAMERATE = 30;

  // recordingVideoFlag is true when the media recorder is capturing video.
  private boolean recordingVideoFlag;

  private MediaRecorder mediaRecorder;

  private Size videoSize;

  private SceneView sceneView;
  private int videoCodec;
  private File videoDirectory;
  private String videoBaseName;
  private File videoPath;
  private int bitRate = DEFAULT_BITRATE;
  private int frameRate = DEFAULT_FRAMERATE;
  private Surface encoderSurface;

  public VideoRecorder() {
    recordingVideoFlag = false;
  }

  public File getVideoPath() {
    return videoPath;
  }

  public void setBitRate(int bitRate) {
    this.bitRate = bitRate;
  }

  public void setFrameRate(int frameRate) {
    this.frameRate = frameRate;
  }

  public void setSceneView(SceneView sceneView) {
    this.sceneView = sceneView;
  }

  /**
   * Toggles the state of video recording.
   *
   * @return true if recording is now active.
   */
  public boolean onToggleRecord() {
    if (recordingVideoFlag) {
      stopRecordingVideo();
    } else {
      startRecordingVideo();
    }
    return recordingVideoFlag;
  }

  private void startRecordingVideo() {
    if (mediaRecorder == null) {
      mediaRecorder = new MediaRecorder();
    }

    try {
      buildFilename();
      setUpMediaRecorder();
    } catch (IOException e) {
      Log.e(TAG, "Exception setting up recorder", e);
      return;
    }

    // Set up Surface for the MediaRecorder
    encoderSurface = mediaRecorder.getSurface();

    sceneView.startMirroringToSurface(
        encoderSurface, 0, 0, videoSize.getWidth(), videoSize.getHeight());

    recordingVideoFlag = true;
  }

  private void buildFilename() {
    if (videoDirectory == null) {
      videoDirectory =
          new File(
              Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
                  + "/Sceneform");
    }
    if (videoBaseName == null || videoBaseName.isEmpty()) {
      videoBaseName = "Sample";
    }
    videoPath =
        new File(
            videoDirectory, videoBaseName + Long.toHexString(System.currentTimeMillis()) + ".mp4");
    File dir = videoPath.getParentFile();
    if (!dir.exists()) {
      dir.mkdirs();
    }
  }

  private void stopRecordingVideo() {
    // UI
    recordingVideoFlag = false;

    if (encoderSurface != null) {
      sceneView.stopMirroringToSurface(encoderSurface);
      encoderSurface = null;
    }
    // Stop recording
    mediaRecorder.stop();
    mediaRecorder.reset();
  }

  private void setUpMediaRecorder() throws IOException {

    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

    mediaRecorder.setOutputFile(videoPath.getAbsolutePath());
    mediaRecorder.setVideoEncodingBitRate(bitRate);
    mediaRecorder.setVideoFrameRate(frameRate);
    mediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
    mediaRecorder.setVideoEncoder(videoCodec);

    mediaRecorder.prepare();

    try {
      mediaRecorder.start();
    } catch (IllegalStateException e) {
      Log.e(TAG, "Exception starting capture: " + e.getMessage(), e);
    }
  }

  public void setVideoSize(int width, int height) {
    videoSize = new Size(width, height);
  }

  public void setVideoQuality(int quality, int orientation) {
    CamcorderProfile profile = CamcorderProfile.get(quality);
    if (profile == null) {
      profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
    }
    if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
      setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
    } else {
      setVideoSize(profile.videoFrameHeight, profile.videoFrameWidth);
    }
    setVideoCodec(profile.videoCodec);
    setBitRate(profile.videoBitRate);
    setFrameRate(profile.videoFrameRate);
  }

  public void setVideoCodec(int videoCodec) {
    this.videoCodec = videoCodec;
  }
}

     

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
为了在 Android Studio 计算器中实现记录功能,您可以考虑以下步骤: 1.创建一个用于显示历史记录的 TextView 组件。 2.在计算器的每个操作中,将操作数和操作符添加到历史记录中。您可以使用字符串拼接来创建历史记录字符串,例如:"1 + 2 =","3 - 4 ="等等。 3.将历史记录字符串设置为 TextView 组件的文本属性。 以下是一个简单的示例代码,帮助您实现记录功能: 在 XML 布局文件中添加一个 TextView: ``` <TextView android:id="@+id/historyTextView" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="历史记录:" android:textSize="20sp" android:textStyle="bold" /> ``` 在 Java 代码中,使用 StringBuilder 类来创建历史记录字符串,然后将其设置为 TextView 组件的文本属性: ``` public class MainActivity extends AppCompatActivity { private TextView historyTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); historyTextView = findViewById(R.id.historyTextView); } public void onButtonClick(View view) { // 获取操作数和操作符 String number = "1"; String operator = "+"; // 创建历史记录字符串 StringBuilder history = new StringBuilder(historyTextView.getText().toString()); history.append(number).append(" ").append(operator).append(" "); // 设置历史记录字符串为 TextView 的文本属性 historyTextView.setText(history.toString()); } } ``` 在此示例中,onButtonClick() 方法是一个示例操作,用于向历史记录添加操作数和操作符。您需要根据您的计算器逻辑编写自己的操作方法。 希望这可以帮助您实现在 Android Studio 计算器中使用记录功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值