主题:添加样式
This commit is contained in:
parent
c7eac54c04
commit
b02fdf2ea7
|
|
@ -100,6 +100,14 @@ dependencies {
|
|||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.viewpager2:viewpager2:1.0.0")
|
||||
|
||||
// CameraX 相机库
|
||||
val cameraxVersion = "1.3.1"
|
||||
implementation("androidx.camera:camera-core:$cameraxVersion")
|
||||
implementation("androidx.camera:camera-camera2:$cameraxVersion")
|
||||
implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
|
||||
implementation("androidx.camera:camera-video:$cameraxVersion")
|
||||
implementation("androidx.camera:camera-view:$cameraxVersion")
|
||||
|
||||
implementation("com.github.bumptech.glide:glide:4.16.0")
|
||||
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
|
||||
|
||||
|
|
|
|||
|
|
@ -204,7 +204,8 @@
|
|||
<activity
|
||||
android:name="com.example.livestreaming.PublishCenterActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.livestreaming.WishTreeActivity"
|
||||
|
|
@ -357,6 +358,13 @@
|
|||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<!-- 相机拍摄Activity -->
|
||||
<activity
|
||||
android:name="com.example.livestreaming.CameraCaptureActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -110,6 +110,14 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
|
|||
setupUI();
|
||||
setupSurface();
|
||||
|
||||
// 接收传入的标题
|
||||
String passedTitle = getIntent().getStringExtra("title");
|
||||
if (!TextUtils.isEmpty(passedTitle)) {
|
||||
binding.etTitle.setText(passedTitle);
|
||||
// 如果有传入标题,隐藏标题输入卡片,让界面更简洁
|
||||
binding.cardLiveInfo.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// 先检查主播资格,通过后再检查权限
|
||||
checkStreamerStatus();
|
||||
}
|
||||
|
|
@ -580,9 +588,16 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
|
|||
String title = binding.etTitle.getText() != null ?
|
||||
binding.etTitle.getText().toString().trim() : "";
|
||||
|
||||
// 如果输入框为空,尝试使用传入的标题
|
||||
if (TextUtils.isEmpty(title)) {
|
||||
Toast.makeText(this, "请输入直播标题", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
String passedTitle = getIntent().getStringExtra("title");
|
||||
if (!TextUtils.isEmpty(passedTitle)) {
|
||||
title = passedTitle;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(title)) {
|
||||
title = "我的直播间"; // 默认标题
|
||||
}
|
||||
|
||||
if (!cameraInitialized) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,445 @@
|
|||
package com.example.livestreaming;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.camera.core.CameraSelector;
|
||||
import androidx.camera.core.ImageCapture;
|
||||
import androidx.camera.core.ImageCaptureException;
|
||||
import androidx.camera.core.Preview;
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||
import androidx.camera.video.MediaStoreOutputOptions;
|
||||
import androidx.camera.video.Quality;
|
||||
import androidx.camera.video.QualitySelector;
|
||||
import androidx.camera.video.Recorder;
|
||||
import androidx.camera.video.Recording;
|
||||
import androidx.camera.video.VideoCapture;
|
||||
import androidx.camera.video.VideoRecordEvent;
|
||||
import androidx.camera.view.PreviewView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class CameraCaptureActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "CameraCaptureActivity";
|
||||
|
||||
private PreviewView cameraPreview;
|
||||
private ImageView btnClose, ivGalleryThumbnail;
|
||||
private View btnFlipCamera;
|
||||
private LinearLayout btnGallery;
|
||||
private FrameLayout btnCapture;
|
||||
private View captureInner;
|
||||
private TextView btnModePhoto, btnModeVideo, tvRecordTime;
|
||||
|
||||
private ProcessCameraProvider cameraProvider;
|
||||
private ImageCapture imageCapture;
|
||||
private VideoCapture<Recorder> videoCapture;
|
||||
private Recording recording;
|
||||
|
||||
private boolean isPhotoMode = true;
|
||||
private boolean isRecording = false;
|
||||
private int lensFacing = CameraSelector.LENS_FACING_BACK;
|
||||
|
||||
private ExecutorService cameraExecutor;
|
||||
private Handler handler = new Handler(Looper.getMainLooper());
|
||||
private long recordStartTime;
|
||||
private Runnable recordTimeRunnable;
|
||||
|
||||
private ActivityResultLauncher<String[]> permissionLauncher;
|
||||
private ActivityResultLauncher<Intent> galleryLauncher;
|
||||
|
||||
public static void start(Context context) {
|
||||
context.startActivity(new Intent(context, CameraCaptureActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_camera_capture);
|
||||
|
||||
initViews();
|
||||
setupLaunchers();
|
||||
setupClickListeners();
|
||||
|
||||
cameraExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
if (checkPermissions()) {
|
||||
startCamera();
|
||||
} else {
|
||||
requestPermissions();
|
||||
}
|
||||
|
||||
loadLastGalleryImage();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
cameraPreview = findViewById(R.id.cameraPreview);
|
||||
btnClose = findViewById(R.id.btnClose);
|
||||
btnFlipCamera = findViewById(R.id.btnFlipCamera);
|
||||
btnGallery = findViewById(R.id.btnGallery);
|
||||
ivGalleryThumbnail = btnGallery.findViewById(R.id.ivGalleryThumbnail);
|
||||
btnCapture = findViewById(R.id.btnCapture);
|
||||
captureInner = findViewById(R.id.captureInner);
|
||||
btnModePhoto = findViewById(R.id.btnModePhoto);
|
||||
btnModeVideo = findViewById(R.id.btnModeVideo);
|
||||
tvRecordTime = findViewById(R.id.tvRecordTime);
|
||||
}
|
||||
|
||||
private void setupLaunchers() {
|
||||
permissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestMultiplePermissions(),
|
||||
result -> {
|
||||
boolean allGranted = true;
|
||||
for (Boolean granted : result.values()) {
|
||||
if (!granted) allGranted = false;
|
||||
}
|
||||
if (allGranted) {
|
||||
startCamera();
|
||||
} else {
|
||||
Toast.makeText(this, "需要相机和存储权限", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
galleryLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
||||
Uri uri = result.getData().getData();
|
||||
if (uri != null) {
|
||||
goToPublishWork(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void setupClickListeners() {
|
||||
btnClose.setOnClickListener(v -> finish());
|
||||
|
||||
btnFlipCamera.setOnClickListener(v -> {
|
||||
lensFacing = (lensFacing == CameraSelector.LENS_FACING_BACK)
|
||||
? CameraSelector.LENS_FACING_FRONT
|
||||
: CameraSelector.LENS_FACING_BACK;
|
||||
startCamera();
|
||||
});
|
||||
|
||||
btnGallery.setOnClickListener(v -> openGallery());
|
||||
|
||||
btnCapture.setOnClickListener(v -> {
|
||||
if (isPhotoMode) {
|
||||
takePhoto();
|
||||
} else {
|
||||
toggleVideoRecording();
|
||||
}
|
||||
});
|
||||
|
||||
btnModePhoto.setOnClickListener(v -> switchToPhotoMode());
|
||||
btnModeVideo.setOnClickListener(v -> switchToVideoMode());
|
||||
|
||||
// 底部Tab点击事件
|
||||
findViewById(R.id.tabDynamic).setOnClickListener(v -> {
|
||||
// 跳转到发布中心的动态页面
|
||||
Intent intent = new Intent(this, PublishCenterActivity.class);
|
||||
intent.putExtra("tab", 0); // 动态
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
|
||||
// 相机Tab - 当前页面,不需要处理
|
||||
|
||||
findViewById(R.id.tabLive).setOnClickListener(v -> {
|
||||
// 跳转到发布中心的开直播页面
|
||||
Intent intent = new Intent(this, PublishCenterActivity.class);
|
||||
intent.putExtra("tab", 2); // 开直播
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private boolean checkPermissions() {
|
||||
return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
permissionLauncher.launch(new String[]{
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO
|
||||
});
|
||||
}
|
||||
|
||||
private void startCamera() {
|
||||
ListenableFuture<ProcessCameraProvider> future = ProcessCameraProvider.getInstance(this);
|
||||
future.addListener(() -> {
|
||||
try {
|
||||
cameraProvider = future.get();
|
||||
bindCameraUseCases();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Camera init failed", e);
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this));
|
||||
}
|
||||
|
||||
private void bindCameraUseCases() {
|
||||
if (cameraProvider == null) return;
|
||||
|
||||
cameraProvider.unbindAll();
|
||||
|
||||
CameraSelector cameraSelector = new CameraSelector.Builder()
|
||||
.requireLensFacing(lensFacing)
|
||||
.build();
|
||||
|
||||
Preview preview = new Preview.Builder().build();
|
||||
preview.setSurfaceProvider(cameraPreview.getSurfaceProvider());
|
||||
|
||||
imageCapture = new ImageCapture.Builder()
|
||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
||||
.build();
|
||||
|
||||
Recorder recorder = new Recorder.Builder()
|
||||
.setQualitySelector(QualitySelector.from(Quality.HD))
|
||||
.build();
|
||||
videoCapture = VideoCapture.withOutput(recorder);
|
||||
|
||||
try {
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, videoCapture);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Use case binding failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void switchToPhotoMode() {
|
||||
if (isPhotoMode) return;
|
||||
isPhotoMode = true;
|
||||
|
||||
btnModePhoto.setBackgroundResource(R.drawable.bg_mode_selected);
|
||||
btnModePhoto.setTextColor(0xFF333333); // 选中时黑色
|
||||
btnModeVideo.setBackground(null);
|
||||
btnModeVideo.setTextColor(0xFFFFFFFF); // 未选中时白色
|
||||
|
||||
captureInner.setBackgroundResource(R.drawable.bg_capture_inner);
|
||||
captureInner.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
dpToPx(68), dpToPx(68), android.view.Gravity.CENTER));
|
||||
}
|
||||
|
||||
private void switchToVideoMode() {
|
||||
if (!isPhotoMode) return;
|
||||
isPhotoMode = false;
|
||||
|
||||
btnModeVideo.setBackgroundResource(R.drawable.bg_mode_selected);
|
||||
btnModeVideo.setTextColor(0xFF333333); // 选中时黑色
|
||||
btnModePhoto.setBackground(null);
|
||||
btnModePhoto.setTextColor(0xFFFFFFFF); // 未选中时白色
|
||||
|
||||
captureInner.setBackgroundResource(R.drawable.bg_capture_inner);
|
||||
}
|
||||
|
||||
private void takePhoto() {
|
||||
if (imageCapture == null) return;
|
||||
|
||||
String name = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
|
||||
.format(System.currentTimeMillis());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
|
||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
|
||||
|
||||
ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(
|
||||
getContentResolver(),
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
contentValues
|
||||
).build();
|
||||
|
||||
imageCapture.takePicture(outputOptions, cameraExecutor,
|
||||
new ImageCapture.OnImageSavedCallback() {
|
||||
@Override
|
||||
public void onImageSaved(@NonNull ImageCapture.OutputFileResults results) {
|
||||
Uri savedUri = results.getSavedUri();
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(CameraCaptureActivity.this, "照片已保存", Toast.LENGTH_SHORT).show();
|
||||
if (savedUri != null) {
|
||||
goToPublishWork(savedUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull ImageCaptureException e) {
|
||||
runOnUiThread(() ->
|
||||
Toast.makeText(CameraCaptureActivity.this, "拍照失败", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleVideoRecording() {
|
||||
if (videoCapture == null) return;
|
||||
|
||||
if (isRecording) {
|
||||
stopRecording();
|
||||
} else {
|
||||
startRecording();
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecording() {
|
||||
String name = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
|
||||
.format(System.currentTimeMillis());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
|
||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
|
||||
|
||||
MediaStoreOutputOptions outputOptions = new MediaStoreOutputOptions.Builder(
|
||||
getContentResolver(),
|
||||
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
).setContentValues(contentValues).build();
|
||||
|
||||
recording = videoCapture.getOutput()
|
||||
.prepareRecording(this, outputOptions)
|
||||
.withAudioEnabled()
|
||||
.start(cameraExecutor, event -> {
|
||||
if (event instanceof VideoRecordEvent.Start) {
|
||||
runOnUiThread(() -> onRecordingStarted());
|
||||
} else if (event instanceof VideoRecordEvent.Finalize) {
|
||||
VideoRecordEvent.Finalize finalizeEvent = (VideoRecordEvent.Finalize) event;
|
||||
Uri savedUri = finalizeEvent.getOutputResults().getOutputUri();
|
||||
runOnUiThread(() -> onRecordingStopped(savedUri));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void stopRecording() {
|
||||
if (recording != null) {
|
||||
recording.stop();
|
||||
recording = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onRecordingStarted() {
|
||||
isRecording = true;
|
||||
recordStartTime = System.currentTimeMillis();
|
||||
|
||||
captureInner.setBackgroundResource(R.drawable.bg_capture_inner_recording);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||
dpToPx(32), dpToPx(32), android.view.Gravity.CENTER);
|
||||
captureInner.setLayoutParams(params);
|
||||
|
||||
tvRecordTime.setVisibility(View.VISIBLE);
|
||||
startRecordTimer();
|
||||
}
|
||||
|
||||
private void onRecordingStopped(Uri savedUri) {
|
||||
isRecording = false;
|
||||
stopRecordTimer();
|
||||
|
||||
captureInner.setBackgroundResource(R.drawable.bg_capture_inner);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||
dpToPx(68), dpToPx(68), android.view.Gravity.CENTER);
|
||||
captureInner.setLayoutParams(params);
|
||||
|
||||
tvRecordTime.setVisibility(View.GONE);
|
||||
|
||||
Toast.makeText(this, "视频已保存", Toast.LENGTH_SHORT).show();
|
||||
if (savedUri != null) {
|
||||
goToPublishWork(savedUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecordTimer() {
|
||||
recordTimeRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long elapsed = System.currentTimeMillis() - recordStartTime;
|
||||
int seconds = (int) (elapsed / 1000);
|
||||
int minutes = seconds / 60;
|
||||
seconds = seconds % 60;
|
||||
tvRecordTime.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
handler.postDelayed(this, 1000);
|
||||
}
|
||||
};
|
||||
handler.post(recordTimeRunnable);
|
||||
}
|
||||
|
||||
private void stopRecordTimer() {
|
||||
if (recordTimeRunnable != null) {
|
||||
handler.removeCallbacks(recordTimeRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void openGallery() {
|
||||
Intent intent = new Intent(Intent.ACTION_PICK);
|
||||
intent.setType("image/* video/*");
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
||||
galleryLauncher.launch(intent);
|
||||
}
|
||||
|
||||
private void loadLastGalleryImage() {
|
||||
try {
|
||||
String[] projection = {MediaStore.Images.Media._ID};
|
||||
String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";
|
||||
|
||||
Cursor cursor = getContentResolver().query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, null, null, sortOrder);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
|
||||
Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
|
||||
Glide.with(this).load(uri).centerCrop().into(ivGalleryThumbnail);
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Load gallery thumbnail failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void goToPublishWork(Uri mediaUri) {
|
||||
Intent intent = new Intent(this, PublishWorkActivity.class);
|
||||
intent.putExtra("media_uri", mediaUri.toString());
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private int dpToPx(int dp) {
|
||||
return (int) (dp * getResources().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopRecordTimer();
|
||||
if (cameraExecutor != null) {
|
||||
cameraExecutor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,46 @@
|
|||
package com.example.livestreaming;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.camera.core.CameraSelector;
|
||||
import androidx.camera.core.ImageCapture;
|
||||
import androidx.camera.core.ImageCaptureException;
|
||||
import androidx.camera.core.Preview;
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||
import androidx.camera.video.MediaStoreOutputOptions;
|
||||
import androidx.camera.video.Quality;
|
||||
import androidx.camera.video.QualitySelector;
|
||||
import androidx.camera.video.Recorder;
|
||||
import androidx.camera.video.Recording;
|
||||
import androidx.camera.video.VideoCapture;
|
||||
import androidx.camera.video.VideoRecordEvent;
|
||||
import androidx.camera.view.PreviewView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.example.livestreaming.databinding.ActivityPublishCenterBinding;
|
||||
|
|
@ -26,13 +52,18 @@ import com.example.livestreaming.net.FileUploadResponse;
|
|||
import com.example.livestreaming.net.Room;
|
||||
import com.example.livestreaming.net.StreamUrls;
|
||||
import com.example.livestreaming.net.WorksRequest;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
|
|
@ -43,13 +74,34 @@ import retrofit2.Response;
|
|||
|
||||
public class PublishCenterActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "PublishCenterActivity";
|
||||
|
||||
private ActivityPublishCenterBinding binding;
|
||||
private int currentTab = 0;
|
||||
private int currentBottomTab = 2; // 0=动态, 1=相机, 2=开直播
|
||||
private boolean isMobileLiveMode = true; // true=手机, false=电脑
|
||||
private List<CategoryResponse> categoryList = new ArrayList<>();
|
||||
private int selectedCategoryId = -1;
|
||||
private Uri dynamicCoverUri = null;
|
||||
private ActivityResultLauncher<Intent> imagePickerLauncher;
|
||||
|
||||
// 相机相关
|
||||
private ProcessCameraProvider cameraProvider;
|
||||
private int lensFacing = CameraSelector.LENS_FACING_FRONT; // 默认前置摄像头
|
||||
private ActivityResultLauncher<String[]> permissionLauncher;
|
||||
|
||||
// 相机拍摄相关
|
||||
private ImageCapture imageCapture;
|
||||
private VideoCapture<Recorder> videoCapture;
|
||||
private Recording recording;
|
||||
private boolean isCapturePhotoMode = true;
|
||||
private boolean isRecording = false;
|
||||
private int captureLensFacing = CameraSelector.LENS_FACING_FRONT; // 默认前置摄像头
|
||||
private ExecutorService cameraExecutor;
|
||||
private Handler handler = new Handler(Looper.getMainLooper());
|
||||
private long recordStartTime;
|
||||
private Runnable recordTimeRunnable;
|
||||
private ActivityResultLauncher<Intent> galleryLauncher;
|
||||
|
||||
public static void start(Context context) {
|
||||
context.startActivity(new Intent(context, PublishCenterActivity.class));
|
||||
}
|
||||
|
|
@ -64,11 +116,102 @@ public class PublishCenterActivity extends AppCompatActivity {
|
|||
binding = ActivityPublishCenterBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
setupImagePicker();
|
||||
setupToolbar();
|
||||
setupTabs();
|
||||
setupPermissionLauncher();
|
||||
setupGalleryLauncher();
|
||||
setupClickListeners();
|
||||
loadCategories();
|
||||
showTab(0);
|
||||
|
||||
cameraExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
// 检查是否有传入的tab参数
|
||||
int tabFromIntent = getIntent().getIntExtra("tab", 2);
|
||||
showBottomTab(tabFromIntent);
|
||||
|
||||
// 检查相机权限并启动预览
|
||||
checkCameraPermissionAndStart();
|
||||
}
|
||||
|
||||
private void setupPermissionLauncher() {
|
||||
permissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestMultiplePermissions(),
|
||||
result -> {
|
||||
Boolean cameraGranted = result.get(Manifest.permission.CAMERA);
|
||||
if (cameraGranted != null && cameraGranted) {
|
||||
startCamera();
|
||||
if (currentBottomTab == 1) {
|
||||
startCaptureCamera();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "需要相机权限才能预览", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void setupGalleryLauncher() {
|
||||
galleryLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
||||
Uri uri = result.getData().getData();
|
||||
if (uri != null) {
|
||||
goToPublishWork(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void checkCameraPermissionAndStart() {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
startCamera();
|
||||
} else {
|
||||
permissionLauncher.launch(new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO});
|
||||
}
|
||||
}
|
||||
|
||||
private void startCamera() {
|
||||
ListenableFuture<ProcessCameraProvider> future = ProcessCameraProvider.getInstance(this);
|
||||
future.addListener(() -> {
|
||||
try {
|
||||
cameraProvider = future.get();
|
||||
bindCameraPreview();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Camera init failed", e);
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this));
|
||||
}
|
||||
|
||||
private void bindCameraPreview() {
|
||||
if (cameraProvider == null) return;
|
||||
|
||||
cameraProvider.unbindAll();
|
||||
|
||||
CameraSelector cameraSelector = new CameraSelector.Builder()
|
||||
.requireLensFacing(lensFacing)
|
||||
.build();
|
||||
|
||||
Preview preview = new Preview.Builder().build();
|
||||
preview.setSurfaceProvider(binding.cameraPreview.getSurfaceProvider());
|
||||
|
||||
// 设置预览缩放类型为 FILL_CENTER(等比例放大填满,裁剪多余部分)
|
||||
binding.cameraPreview.setScaleType(PreviewView.ScaleType.FILL_CENTER);
|
||||
// 设置实现模式为兼容模式,确保正确裁剪
|
||||
binding.cameraPreview.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
|
||||
|
||||
try {
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Camera binding failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void flipCamera() {
|
||||
lensFacing = (lensFacing == CameraSelector.LENS_FACING_FRONT)
|
||||
? CameraSelector.LENS_FACING_BACK
|
||||
: CameraSelector.LENS_FACING_FRONT;
|
||||
bindCameraPreview();
|
||||
}
|
||||
|
||||
private void setupImagePicker() {
|
||||
|
|
@ -86,44 +229,185 @@ public class PublishCenterActivity extends AppCompatActivity {
|
|||
);
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
binding.btnBack.setOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void setupTabs() {
|
||||
binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) { showTab(tab.getPosition()); }
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupClickListeners() {
|
||||
// 关闭按钮
|
||||
binding.btnBack.setOnClickListener(v -> finish());
|
||||
binding.btnCloseDynamic.setOnClickListener(v -> finish());
|
||||
|
||||
// 手机/电脑切换
|
||||
binding.btnModeMobile.setOnClickListener(v -> switchToMobileMode());
|
||||
binding.btnModePc.setOnClickListener(v -> switchToPcMode());
|
||||
binding.btnModeMobile2.setOnClickListener(v -> switchToMobileMode());
|
||||
binding.btnModePc2.setOnClickListener(v -> switchToPcMode());
|
||||
binding.btnBackPc.setOnClickListener(v -> finish());
|
||||
|
||||
// 翻转摄像头
|
||||
binding.btnFlipCamera.setOnClickListener(v -> flipCamera());
|
||||
|
||||
// 底部Tab
|
||||
binding.tabDynamic.setOnClickListener(v -> showBottomTab(0));
|
||||
binding.tabCamera.setOnClickListener(v -> showBottomTab(1));
|
||||
binding.tabLive.setOnClickListener(v -> showBottomTab(2));
|
||||
|
||||
// 相机拍摄相关
|
||||
binding.btnCloseCamera.setOnClickListener(v -> finish());
|
||||
binding.btnFlipCameraCapture.setOnClickListener(v -> flipCaptureCamera());
|
||||
binding.btnCaptureModePhoto.setOnClickListener(v -> switchToCapturePhotoMode());
|
||||
binding.btnCaptureModeVideo.setOnClickListener(v -> switchToCaptureVideoMode());
|
||||
binding.btnCapturePhoto.setOnClickListener(v -> {
|
||||
if (isCapturePhotoMode) {
|
||||
takePhoto();
|
||||
} else {
|
||||
toggleVideoRecording();
|
||||
}
|
||||
});
|
||||
binding.btnCaptureGallery.setOnClickListener(v -> openGallery());
|
||||
|
||||
// 手机直播
|
||||
binding.btnStartLive.setOnClickListener(v -> startMobileLive());
|
||||
binding.btnPublishDynamic.setOnClickListener(v -> publishDynamic());
|
||||
binding.dynamicCoverContainer.setOnClickListener(v -> pickDynamicCover());
|
||||
binding.btnRemoveDynamicCover.setOnClickListener(v -> removeDynamicCover());
|
||||
binding.btnPublishWork.setOnClickListener(v -> PublishWorkActivity.start(this));
|
||||
|
||||
// 电脑直播
|
||||
binding.btnCopyStreamUrl.setOnClickListener(v -> copyToClipboard("推流地址", binding.tvStreamUrl.getText().toString()));
|
||||
binding.btnCopyStreamKey.setOnClickListener(v -> copyToClipboard("推流码", binding.tvStreamKey.getText().toString()));
|
||||
binding.btnGetStreamInfo.setOnClickListener(v -> createPcLiveRoom());
|
||||
|
||||
// 发布动态
|
||||
binding.btnPublishDynamic.setOnClickListener(v -> publishDynamic());
|
||||
binding.dynamicCoverContainer.setOnClickListener(v -> pickDynamicCover());
|
||||
binding.btnRemoveDynamicCover.setOnClickListener(v -> removeDynamicCover());
|
||||
binding.btnAddImage.setOnClickListener(v -> {
|
||||
binding.dynamicCoverContainer.setVisibility(View.VISIBLE);
|
||||
pickDynamicCover();
|
||||
});
|
||||
}
|
||||
|
||||
private void showTab(int position) {
|
||||
currentTab = position;
|
||||
binding.contentMobileLive.setVisibility(View.GONE);
|
||||
binding.contentDynamic.setVisibility(View.GONE);
|
||||
binding.contentWork.setVisibility(View.GONE);
|
||||
binding.contentPcLive.setVisibility(View.GONE);
|
||||
switch (position) {
|
||||
case 0: binding.contentMobileLive.setVisibility(View.VISIBLE); break;
|
||||
case 1: binding.contentDynamic.setVisibility(View.VISIBLE); break;
|
||||
case 2: binding.contentWork.setVisibility(View.VISIBLE); break;
|
||||
case 3: binding.contentPcLive.setVisibility(View.VISIBLE); break;
|
||||
private void showBottomTab(int position) {
|
||||
currentBottomTab = position;
|
||||
|
||||
// 切换tab时隐藏键盘
|
||||
hideKeyboard();
|
||||
|
||||
// 根据tab切换底部导航栏样式
|
||||
if (position == 1 || (position == 2 && isMobileLiveMode)) {
|
||||
// 相机模式或手机直播模式:透明背景,白色字体
|
||||
binding.bottomNav.setBackgroundColor(0x00000000); // 透明
|
||||
binding.bottomNav.setElevation(0);
|
||||
binding.tabDynamicText.setTextColor(0xFFFFFFFF); // 白色
|
||||
binding.tabCameraText.setTextColor(0xFFFFFFFF);
|
||||
binding.tabLiveText.setTextColor(0xFFFFFFFF);
|
||||
} else {
|
||||
// 其他模式:白色背景,正常颜色
|
||||
binding.bottomNav.setBackgroundColor(0xFFFFFFFF); // 白色
|
||||
binding.bottomNav.setElevation(8 * getResources().getDisplayMetrics().density);
|
||||
binding.tabDynamicText.setTextColor(position == 0 ? 0xFFFF4757 : 0xFF999999);
|
||||
binding.tabCameraText.setTextColor(0xFF999999);
|
||||
binding.tabLiveText.setTextColor(position == 2 ? 0xFFFF4757 : 0xFF999999);
|
||||
}
|
||||
|
||||
// 更新选中状态的粗体
|
||||
binding.tabDynamicText.setTypeface(null, position == 0 ? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL);
|
||||
binding.tabCameraText.setTypeface(null, position == 1 ? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL);
|
||||
binding.tabLiveText.setTypeface(null, position == 2 ? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL);
|
||||
|
||||
// 显示对应内容
|
||||
binding.contentDynamic.setVisibility(position == 0 ? View.VISIBLE : View.GONE);
|
||||
binding.contentCamera.setVisibility(position == 1 ? View.VISIBLE : View.GONE);
|
||||
binding.contentLive.setVisibility(position == 2 ? View.VISIBLE : View.GONE);
|
||||
|
||||
// 根据tab启动对应的相机
|
||||
if (position == 0) {
|
||||
// 自动聚焦输入框
|
||||
binding.etDynamicContent.postDelayed(() -> {
|
||||
binding.etDynamicContent.requestFocus();
|
||||
android.view.inputmethod.InputMethodManager imm =
|
||||
(android.view.inputmethod.InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.showSoftInput(binding.etDynamicContent, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}, 200);
|
||||
} else if (position == 1) {
|
||||
// 启动作品拍摄相机预览
|
||||
startCaptureCamera();
|
||||
loadLastGalleryImage();
|
||||
} else if (position == 2 && isMobileLiveMode) {
|
||||
// 启动开直播相机预览
|
||||
startCamera();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
View view = getCurrentFocus();
|
||||
if (view != null) {
|
||||
android.view.inputmethod.InputMethodManager imm =
|
||||
(android.view.inputmethod.InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
view.clearFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private void switchToMobileMode() {
|
||||
if (isMobileLiveMode) return;
|
||||
isMobileLiveMode = true;
|
||||
|
||||
// 更新手机模式顶部栏的切换按钮
|
||||
binding.btnModeMobile.setBackgroundResource(R.drawable.bg_mode_selected);
|
||||
binding.btnModeMobile.setTextColor(0xFF333333);
|
||||
binding.btnModePc.setBackground(null);
|
||||
binding.btnModePc.setTextColor(0xFFFFFFFF);
|
||||
|
||||
// 更新电脑模式顶部栏的切换按钮
|
||||
binding.btnModeMobile2.setBackground(null);
|
||||
binding.btnModeMobile2.setTextColor(0xFF666666);
|
||||
binding.btnModePc2.setBackgroundResource(R.drawable.bg_mode_selected_dark);
|
||||
binding.btnModePc2.setTextColor(0xFFFFFFFF);
|
||||
|
||||
binding.contentMobileLive.setVisibility(View.VISIBLE);
|
||||
binding.contentPcLive.setVisibility(View.GONE);
|
||||
binding.pcTopBar.setVisibility(View.GONE);
|
||||
binding.bottomNav.setVisibility(View.VISIBLE);
|
||||
|
||||
// 手机直播模式:透明背景,白色字体(和相机页面一样)
|
||||
binding.bottomNav.setBackgroundColor(0x00000000);
|
||||
binding.bottomNav.setElevation(0);
|
||||
binding.tabDynamicText.setTextColor(0xFFFFFFFF);
|
||||
binding.tabCameraText.setTextColor(0xFFFFFFFF);
|
||||
binding.tabLiveText.setTextColor(0xFFFFFFFF);
|
||||
binding.tabLiveText.setTypeface(null, android.graphics.Typeface.BOLD);
|
||||
|
||||
// 启动相机预览
|
||||
startCamera();
|
||||
}
|
||||
|
||||
private void switchToPcMode() {
|
||||
if (!isMobileLiveMode) return;
|
||||
isMobileLiveMode = false;
|
||||
|
||||
// 更新手机模式顶部栏的切换按钮
|
||||
binding.btnModePc.setBackgroundResource(R.drawable.bg_mode_selected);
|
||||
binding.btnModePc.setTextColor(0xFF333333);
|
||||
binding.btnModeMobile.setBackground(null);
|
||||
binding.btnModeMobile.setTextColor(0xFFFFFFFF);
|
||||
|
||||
// 更新电脑模式顶部栏的切换按钮
|
||||
binding.btnModePc2.setBackgroundResource(R.drawable.bg_mode_selected_dark);
|
||||
binding.btnModePc2.setTextColor(0xFFFFFFFF);
|
||||
binding.btnModeMobile2.setBackground(null);
|
||||
binding.btnModeMobile2.setTextColor(0xFF666666);
|
||||
|
||||
binding.contentMobileLive.setVisibility(View.GONE);
|
||||
binding.contentPcLive.setVisibility(View.VISIBLE);
|
||||
binding.pcTopBar.setVisibility(View.VISIBLE);
|
||||
binding.bottomNav.setVisibility(View.VISIBLE);
|
||||
|
||||
// 电脑直播模式:白色背景,正常颜色
|
||||
binding.bottomNav.setBackgroundColor(0xFFFFFFFF);
|
||||
binding.bottomNav.setElevation(8 * getResources().getDisplayMetrics().density);
|
||||
binding.tabDynamicText.setTextColor(0xFF999999);
|
||||
binding.tabCameraText.setTextColor(0xFF999999);
|
||||
binding.tabLiveText.setTextColor(0xFFFF4757);
|
||||
binding.tabLiveText.setTypeface(null, android.graphics.Typeface.BOLD);
|
||||
}
|
||||
|
||||
private void loadCategories() {
|
||||
|
|
@ -203,11 +487,8 @@ public class PublishCenterActivity extends AppCompatActivity {
|
|||
binding.btnPublishDynamic.setText("发布中...");
|
||||
|
||||
if (dynamicCoverUri != null) {
|
||||
// 用户选择了封面,上传后发布
|
||||
uploadDynamicCoverAndPublish(content);
|
||||
} else {
|
||||
// 用户没选封面,使用特殊标记(纯文字动态)
|
||||
// 使用 "text_only" 作为标记,在展示时识别为纯文字动态
|
||||
doPublishDynamic(content, "text_only");
|
||||
}
|
||||
}
|
||||
|
|
@ -253,7 +534,6 @@ public class PublishCenterActivity extends AppCompatActivity {
|
|||
request.setDescription(content);
|
||||
request.setType("IMAGE");
|
||||
request.setImageUrls(new ArrayList<>());
|
||||
// 如果没有封面,设置特殊标识URL,表示这是纯文字动态
|
||||
if (coverUrl == null || coverUrl.isEmpty()) {
|
||||
request.setCoverUrl("TEXT_ONLY_DYNAMIC_PLACEHOLDER");
|
||||
} else {
|
||||
|
|
@ -283,7 +563,7 @@ public class PublishCenterActivity extends AppCompatActivity {
|
|||
|
||||
private void resetDynamicButton() {
|
||||
binding.btnPublishDynamic.setEnabled(true);
|
||||
binding.btnPublishDynamic.setText("发布动态");
|
||||
binding.btnPublishDynamic.setText("发布");
|
||||
}
|
||||
|
||||
private File uriToFile(Uri uri) {
|
||||
|
|
@ -317,10 +597,9 @@ public class PublishCenterActivity extends AppCompatActivity {
|
|||
binding.btnGetStreamInfo.setText("创建中...");
|
||||
binding.streamInfoLoading.setVisibility(View.VISIBLE);
|
||||
|
||||
// 获取当前用户昵称作为主播名称
|
||||
String streamerName = com.example.livestreaming.net.AuthStore.getNickname(this);
|
||||
if (TextUtils.isEmpty(streamerName)) {
|
||||
streamerName = title; // 如果没有昵称,使用标题
|
||||
streamerName = title;
|
||||
}
|
||||
|
||||
CreateRoomRequest request = new CreateRoomRequest(title, streamerName, "pc");
|
||||
|
|
@ -375,4 +654,282 @@ public class PublishCenterActivity extends AppCompatActivity {
|
|||
Toast.makeText(this, label + "已复制", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 相机拍摄相关方法 ==========
|
||||
|
||||
private void startCaptureCamera() {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
ListenableFuture<ProcessCameraProvider> future = ProcessCameraProvider.getInstance(this);
|
||||
future.addListener(() -> {
|
||||
try {
|
||||
cameraProvider = future.get();
|
||||
bindCaptureCameraUseCases();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Camera init failed", e);
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this));
|
||||
}
|
||||
|
||||
private void bindCaptureCameraUseCases() {
|
||||
if (cameraProvider == null) return;
|
||||
|
||||
cameraProvider.unbindAll();
|
||||
|
||||
CameraSelector cameraSelector = new CameraSelector.Builder()
|
||||
.requireLensFacing(captureLensFacing)
|
||||
.build();
|
||||
|
||||
Preview preview = new Preview.Builder().build();
|
||||
preview.setSurfaceProvider(binding.cameraCapturePreview.getSurfaceProvider());
|
||||
|
||||
binding.cameraCapturePreview.setScaleType(PreviewView.ScaleType.FILL_CENTER);
|
||||
binding.cameraCapturePreview.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
|
||||
|
||||
imageCapture = new ImageCapture.Builder()
|
||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
||||
.build();
|
||||
|
||||
Recorder recorder = new Recorder.Builder()
|
||||
.setQualitySelector(QualitySelector.from(Quality.HD))
|
||||
.build();
|
||||
videoCapture = VideoCapture.withOutput(recorder);
|
||||
|
||||
try {
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, videoCapture);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Use case binding failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void flipCaptureCamera() {
|
||||
captureLensFacing = (captureLensFacing == CameraSelector.LENS_FACING_BACK)
|
||||
? CameraSelector.LENS_FACING_FRONT
|
||||
: CameraSelector.LENS_FACING_BACK;
|
||||
bindCaptureCameraUseCases();
|
||||
}
|
||||
|
||||
private void switchToCapturePhotoMode() {
|
||||
if (isCapturePhotoMode) return;
|
||||
isCapturePhotoMode = true;
|
||||
|
||||
binding.btnCaptureModePhoto.setBackgroundResource(R.drawable.bg_mode_selected);
|
||||
binding.btnCaptureModePhoto.setTextColor(0xFF333333);
|
||||
binding.btnCaptureModeVideo.setBackground(null);
|
||||
binding.btnCaptureModeVideo.setTextColor(0xFFFFFFFF);
|
||||
|
||||
binding.captureInnerView.setBackgroundResource(R.drawable.bg_capture_inner);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||
dpToPx(68), dpToPx(68), android.view.Gravity.CENTER);
|
||||
binding.captureInnerView.setLayoutParams(params);
|
||||
}
|
||||
|
||||
private void switchToCaptureVideoMode() {
|
||||
if (!isCapturePhotoMode) return;
|
||||
isCapturePhotoMode = false;
|
||||
|
||||
binding.btnCaptureModeVideo.setBackgroundResource(R.drawable.bg_mode_selected);
|
||||
binding.btnCaptureModeVideo.setTextColor(0xFF333333);
|
||||
binding.btnCaptureModePhoto.setBackground(null);
|
||||
binding.btnCaptureModePhoto.setTextColor(0xFFFFFFFF);
|
||||
|
||||
binding.captureInnerView.setBackgroundResource(R.drawable.bg_capture_inner);
|
||||
}
|
||||
|
||||
private void takePhoto() {
|
||||
if (imageCapture == null) return;
|
||||
|
||||
String name = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
|
||||
.format(System.currentTimeMillis());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
|
||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
|
||||
|
||||
ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(
|
||||
getContentResolver(),
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
contentValues
|
||||
).build();
|
||||
|
||||
imageCapture.takePicture(outputOptions, cameraExecutor,
|
||||
new ImageCapture.OnImageSavedCallback() {
|
||||
@Override
|
||||
public void onImageSaved(@NonNull ImageCapture.OutputFileResults results) {
|
||||
Uri savedUri = results.getSavedUri();
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(PublishCenterActivity.this, "照片已保存", Toast.LENGTH_SHORT).show();
|
||||
if (savedUri != null) {
|
||||
goToPublishWork(savedUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull ImageCaptureException e) {
|
||||
runOnUiThread(() ->
|
||||
Toast.makeText(PublishCenterActivity.this, "拍照失败", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleVideoRecording() {
|
||||
if (videoCapture == null) return;
|
||||
|
||||
if (isRecording) {
|
||||
stopRecording();
|
||||
} else {
|
||||
startRecording();
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecording() {
|
||||
String name = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
|
||||
.format(System.currentTimeMillis());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
|
||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
|
||||
|
||||
MediaStoreOutputOptions outputOptions = new MediaStoreOutputOptions.Builder(
|
||||
getContentResolver(),
|
||||
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
).setContentValues(contentValues).build();
|
||||
|
||||
recording = videoCapture.getOutput()
|
||||
.prepareRecording(this, outputOptions)
|
||||
.withAudioEnabled()
|
||||
.start(cameraExecutor, event -> {
|
||||
if (event instanceof VideoRecordEvent.Start) {
|
||||
runOnUiThread(() -> onRecordingStarted());
|
||||
} else if (event instanceof VideoRecordEvent.Finalize) {
|
||||
VideoRecordEvent.Finalize finalizeEvent = (VideoRecordEvent.Finalize) event;
|
||||
Uri savedUri = finalizeEvent.getOutputResults().getOutputUri();
|
||||
runOnUiThread(() -> onRecordingStopped(savedUri));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void stopRecording() {
|
||||
if (recording != null) {
|
||||
recording.stop();
|
||||
recording = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onRecordingStarted() {
|
||||
isRecording = true;
|
||||
recordStartTime = System.currentTimeMillis();
|
||||
|
||||
binding.captureInnerView.setBackgroundResource(R.drawable.bg_capture_inner_recording);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||
dpToPx(32), dpToPx(32), android.view.Gravity.CENTER);
|
||||
binding.captureInnerView.setLayoutParams(params);
|
||||
|
||||
binding.tvCaptureRecordTime.setVisibility(View.VISIBLE);
|
||||
startRecordTimer();
|
||||
}
|
||||
|
||||
private void onRecordingStopped(Uri savedUri) {
|
||||
isRecording = false;
|
||||
stopRecordTimer();
|
||||
|
||||
binding.captureInnerView.setBackgroundResource(R.drawable.bg_capture_inner);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||
dpToPx(68), dpToPx(68), android.view.Gravity.CENTER);
|
||||
binding.captureInnerView.setLayoutParams(params);
|
||||
|
||||
binding.tvCaptureRecordTime.setVisibility(View.GONE);
|
||||
|
||||
Toast.makeText(this, "视频已保存", Toast.LENGTH_SHORT).show();
|
||||
if (savedUri != null) {
|
||||
goToPublishWork(savedUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecordTimer() {
|
||||
recordTimeRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long elapsed = System.currentTimeMillis() - recordStartTime;
|
||||
int seconds = (int) (elapsed / 1000);
|
||||
int minutes = seconds / 60;
|
||||
seconds = seconds % 60;
|
||||
binding.tvCaptureRecordTime.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
handler.postDelayed(this, 1000);
|
||||
}
|
||||
};
|
||||
handler.post(recordTimeRunnable);
|
||||
}
|
||||
|
||||
private void stopRecordTimer() {
|
||||
if (recordTimeRunnable != null) {
|
||||
handler.removeCallbacks(recordTimeRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void openGallery() {
|
||||
Intent intent = new Intent(Intent.ACTION_PICK);
|
||||
intent.setType("image/* video/*");
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
||||
galleryLauncher.launch(intent);
|
||||
}
|
||||
|
||||
private void loadLastGalleryImage() {
|
||||
try {
|
||||
String[] projection = {MediaStore.Images.Media._ID};
|
||||
String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";
|
||||
|
||||
Cursor cursor = getContentResolver().query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, null, null, sortOrder);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
|
||||
Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
|
||||
Glide.with(this).load(uri).centerCrop().into(binding.ivCaptureGalleryThumbnail);
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Load gallery thumbnail failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void goToPublishWork(Uri mediaUri) {
|
||||
Intent intent = new Intent(this, PublishWorkActivity.class);
|
||||
intent.putExtra("media_uri", mediaUri.toString());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private int dpToPx(int dp) {
|
||||
return (int) (dp * getResources().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// 恢复相机预览
|
||||
if (currentBottomTab == 2 && isMobileLiveMode) {
|
||||
// 开直播手机模式,重新绑定相机预览
|
||||
startCamera();
|
||||
} else if (currentBottomTab == 1) {
|
||||
// 作品拍摄模式,重新绑定相机
|
||||
startCaptureCamera();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopRecordTimer();
|
||||
if (cameraProvider != null) {
|
||||
cameraProvider.unbindAll();
|
||||
}
|
||||
if (cameraExecutor != null) {
|
||||
cameraExecutor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:startColor="#00000000"
|
||||
android:endColor="#99000000"
|
||||
android:angle="270" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#FFFFFF" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF4757" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<stroke
|
||||
android:width="4dp"
|
||||
android:color="#FFFFFF" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#F5F5F5" />
|
||||
<corners android:radius="8dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#E0E0E0" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#33FFFFFF" />
|
||||
<corners android:radius="8dp" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#FFFFFF" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#33FFFFFF" />
|
||||
<corners android:radius="24dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#44FFFFFF" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FFFFFF" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#333333" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#33FFFFFF" />
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#F0F0F0" />
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
||||
|
|
@ -1,6 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF4757" />
|
||||
<corners android:radius="8dp" />
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#E53E4F" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_enabled="false">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#FFCDD2" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#FF4757" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
|
|
|
|||
6
android-app/app/src/main/res/drawable/bg_record_time.xml
Normal file
6
android-app/app/src/main/res/drawable/bg_record_time.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#CC000000" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#F5F5F5" />
|
||||
<corners android:radius="8dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#E0E0E0" />
|
||||
</shape>
|
||||
13
android-app/app/src/main/res/drawable/ic_flip_camera_24.xml
Normal file
13
android-app/app/src/main/res/drawable/ic_flip_camera_24.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M9,12c0,1.66 1.34,3 3,3s3,-1.34 3,-3s-1.34,-3 -3,-3S9,10.34 9,12zM7,12c0,-2.76 2.24,-5 5,-5s5,2.24 5,5s-2.24,5 -5,5S7,14.76 7,12zM20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h4.05l1.83,-2h4.24l1.83,2H20V19z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,8.5L12,6L8,9.5L12,13L12,10.5C14.21,10.5 16,12.29 16,14.5C16,15.32 15.75,16.08 15.33,16.71L16.44,17.82C17.11,16.89 17.5,15.74 17.5,14.5C17.5,11.46 15.04,9 12,9L12,8.5Z"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z"/>
|
||||
</vector>
|
||||
276
android-app/app/src/main/res/layout/activity_camera_capture.xml
Normal file
276
android-app/app/src/main/res/layout/activity_camera_capture.xml
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000000">
|
||||
|
||||
<!-- 相机预览 -->
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/cameraPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- 顶部工具栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<ImageView
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/ic_close_24"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="关闭"
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 翻转摄像头 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/btnFlipCamera"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:src="@drawable/ic_flip_camera_24"
|
||||
android:contentDescription="翻转"
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="翻转"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="10sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 底部控制区域 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="56dp"
|
||||
android:background="@drawable/bg_camera_bottom_gradient">
|
||||
|
||||
<!-- 模式切换:照片/视频 -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/bg_mode_selector"
|
||||
android:padding="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnModePhoto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="照片"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold"
|
||||
android:background="@drawable/bg_mode_selected" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnModeVideo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="视频"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#FFFFFF" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 拍照控制区 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="48dp">
|
||||
|
||||
<!-- 左侧占位 -->
|
||||
<View
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 拍照按钮 - 更大 -->
|
||||
<FrameLayout
|
||||
android:id="@+id/btnCapture"
|
||||
android:layout_width="88dp"
|
||||
android:layout_height="88dp">
|
||||
|
||||
<!-- 外圈 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_capture_outer" />
|
||||
|
||||
<!-- 内圈 -->
|
||||
<View
|
||||
android:id="@+id/captureInner"
|
||||
android:layout_width="68dp"
|
||||
android:layout_height="68dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/bg_capture_inner" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 相册入口 - 放到右侧,变小 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/btnGallery"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:background="@drawable/bg_gallery_thumbnail">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivGalleryThumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="相册"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="10sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 底部导航栏 - 透明背景 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/bottomNav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="horizontal"
|
||||
android:background="@android:color/transparent">
|
||||
|
||||
<!-- 动态 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tabDynamic"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tabDynamicText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="动态"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 相机 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tabCamera"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tabCameraText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="相机"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 开直播 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tabLive"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tabLiveText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="开直播"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 录制时间显示 -->
|
||||
<TextView
|
||||
android:id="@+id/tvRecordTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_marginTop="80dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="6dp"
|
||||
android:background="@drawable/bg_record_time"
|
||||
android:text="00:00"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -1,22 +1,236 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white">
|
||||
|
||||
<!-- 相机拍摄内容 -->
|
||||
<FrameLayout
|
||||
android:id="@+id/contentCamera"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000000"
|
||||
android:visibility="gone">
|
||||
|
||||
<!-- 相机预览 -->
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/cameraCapturePreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- 顶部工具栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<ImageView
|
||||
android:id="@+id/btnCloseCamera"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/ic_close_24"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="关闭"
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 翻转摄像头 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/btnFlipCameraCapture"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:src="@drawable/ic_flip_camera_24"
|
||||
android:contentDescription="翻转"
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="翻转"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="10sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 底部控制区域 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="56dp"
|
||||
android:background="@drawable/bg_camera_bottom_gradient">
|
||||
|
||||
<!-- 模式切换:照片/视频 -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/bg_mode_selector"
|
||||
android:padding="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCaptureModePhoto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="照片"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold"
|
||||
android:background="@drawable/bg_mode_selected" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCaptureModeVideo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="视频"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#FFFFFF" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 拍照控制区 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="48dp">
|
||||
|
||||
<!-- 左侧占位 -->
|
||||
<View
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 拍照按钮 -->
|
||||
<FrameLayout
|
||||
android:id="@+id/btnCapturePhoto"
|
||||
android:layout_width="88dp"
|
||||
android:layout_height="88dp">
|
||||
|
||||
<!-- 外圈 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_capture_outer" />
|
||||
|
||||
<!-- 内圈 -->
|
||||
<View
|
||||
android:id="@+id/captureInnerView"
|
||||
android:layout_width="68dp"
|
||||
android:layout_height="68dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/bg_capture_inner" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 相册入口 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/btnCaptureGallery"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:background="@drawable/bg_gallery_thumbnail">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivCaptureGalleryThumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="相册"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="10sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 录制时间显示 -->
|
||||
<TextView
|
||||
android:id="@+id/tvCaptureRecordTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_marginTop="80dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="6dp"
|
||||
android:background="@drawable/bg_record_time"
|
||||
android:text="00:00"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- 发布动态内容 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/contentDynamic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:visibility="gone">
|
||||
|
||||
<!-- 顶部栏 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/topBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="@color/white"
|
||||
android:elevation="2dp">
|
||||
android:background="@color/white">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnBack"
|
||||
android:id="@+id/btnCloseDynamic"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_close_24"
|
||||
|
|
@ -28,179 +242,58 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="发布"
|
||||
android:text="发布动态"
|
||||
android:textSize="18sp"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="56dp"
|
||||
android:background="@color/white"
|
||||
app:tabMode="fixed"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicatorColor="#FF4757"
|
||||
app:tabIndicatorHeight="3dp"
|
||||
app:tabSelectedTextColor="#FF4757"
|
||||
app:tabTextColor="#666666"
|
||||
app:tabRippleColor="@android:color/transparent">
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="手机开播" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="发布动态" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="发布作品" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="电脑开播" />
|
||||
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="104dp">
|
||||
|
||||
<!-- 手机开播 -->
|
||||
<ScrollView
|
||||
android:id="@+id/contentMobileLive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="visible">
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="手机开播"
|
||||
android:textSize="20sp"
|
||||
android:text="“"
|
||||
android:textSize="48sp"
|
||||
android:textColor="#E0E0E0"
|
||||
android:fontFamily="serif" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etDynamicContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="分享你的想法"
|
||||
android:textColorHint="#BDBDBD"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="使用手机摄像头进行直播"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#999999" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:hint="直播标题"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etLiveTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:maxLength="30" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnStartLive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="开始直播"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="#FF4757"
|
||||
app:cornerRadius="26dp" />
|
||||
android:textSize="18sp"
|
||||
android:gravity="top|start"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLength="1000"
|
||||
android:padding="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<!-- 发布动态 -->
|
||||
<ScrollView
|
||||
android:id="@+id/contentDynamic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="发布动态"
|
||||
android:textSize="20sp"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="快速发布文字动态,可选择添加封面图片"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#999999" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:hint="说点什么..."
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="1000"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etDynamicContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine"
|
||||
android:minLines="5"
|
||||
android:gravity="top"
|
||||
android:maxLength="1000" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- 封面图片选择(可选) -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="封面图片(可选)"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<!-- 封面选择(隐藏) -->
|
||||
<FrameLayout
|
||||
android:id="@+id/dynamicCoverContainer"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@drawable/bg_dashed_border"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
android:focusable="true"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivDynamicCover"
|
||||
|
|
@ -217,102 +310,248 @@
|
|||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_add_photo_24"
|
||||
app:tint="#999999" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="添加封面"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="封面"
|
||||
android:textSize="10sp"
|
||||
android:textColor="#999999" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnRemoveDynamicCover"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_margin="2dp"
|
||||
android:src="@drawable/ic_close_circle_24"
|
||||
android:visibility="gone"
|
||||
android:contentDescription="移除封面" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnPublishDynamic"
|
||||
<!-- 底部工具栏 - 放在tab栏上方 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/dynamicToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="发布动态"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="#FF4757"
|
||||
app:cornerRadius="26dp" />
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:layout_marginBottom="56dp"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnAddImage"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_add_photo_24"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="添加图片"
|
||||
app:tint="#666666" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnPublishDynamic"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:gravity="center"
|
||||
android:text="发布"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="@drawable/bg_publish_button" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 发布作品 -->
|
||||
<ScrollView
|
||||
android:id="@+id/contentWork"
|
||||
<!-- 开直播内容 -->
|
||||
<FrameLayout
|
||||
android:id="@+id/contentLive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
android:background="@color/white"
|
||||
android:visibility="visible">
|
||||
|
||||
<!-- 手机直播内容 - 全屏相机预览 -->
|
||||
<FrameLayout
|
||||
android:id="@+id/contentMobileLive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000000"
|
||||
android:visibility="visible">
|
||||
|
||||
<!-- 相机预览容器 - 全屏 -->
|
||||
<FrameLayout
|
||||
android:id="@+id/previewContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="true"
|
||||
android:clipToPadding="true">
|
||||
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/cameraPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- 顶部工具栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:layout_gravity="top">
|
||||
|
||||
<TextView
|
||||
<!-- 关闭按钮 -->
|
||||
<ImageView
|
||||
android:id="@+id/btnBack"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/ic_close_24"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="关闭"
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 手机/电脑切换 -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="发布作品"
|
||||
android:textSize="20sp"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/bg_mode_selector"
|
||||
android:padding="3dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnModeMobile"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="6dp"
|
||||
android:text="手机"
|
||||
android:textSize="13sp"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold" />
|
||||
android:background="@drawable/bg_mode_selected" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnModePc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="6dp"
|
||||
android:text="电脑"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 翻转摄像头 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/btnFlipCamera"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:src="@drawable/ic_flip_camera_24"
|
||||
android:contentDescription="翻转"
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="发布图片或视频作品,支持添加标题、描述等"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#999999" />
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="翻转"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="10sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 底部控制区域 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp"
|
||||
android:layout_marginBottom="56dp">
|
||||
|
||||
<!-- 直播标题输入 -->
|
||||
<EditText
|
||||
android:id="@+id/etLiveTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/bg_live_title_input"
|
||||
android:hint="输入直播标题..."
|
||||
android:textColorHint="#999999"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:inputType="text"
|
||||
android:maxLength="30"
|
||||
android:singleLine="true" />
|
||||
|
||||
<!-- 开始直播按钮 -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnPublishWork"
|
||||
android:id="@+id/btnStartLive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="去发布作品"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="开始直播"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="#FF4757"
|
||||
app:cornerRadius="26dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
||||
|
||||
<!-- 电脑开播 -->
|
||||
<!-- 电脑直播内容 -->
|
||||
<ScrollView
|
||||
android:id="@+id/contentPcLive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
android:padding="20dp"
|
||||
android:paddingTop="72dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -330,24 +569,25 @@
|
|||
android:textSize="14sp"
|
||||
android:textColor="#999999" />
|
||||
|
||||
<!-- 直播标题 -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:hint="直播标题"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
app:boxStrokeColor="#FF4757"
|
||||
app:hintTextColor="#999999">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etPcLiveTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:maxLength="30" />
|
||||
android:maxLength="30"
|
||||
android:textColor="#333333" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- 分类选择 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -361,7 +601,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_spinner"
|
||||
android:background="@drawable/bg_spinner_light"
|
||||
android:paddingHorizontal="12dp" />
|
||||
|
||||
<ProgressBar
|
||||
|
|
@ -392,7 +632,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/bg_edit_text"
|
||||
android:background="@drawable/bg_edit_text_light"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
|
|
@ -400,7 +640,6 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text=""
|
||||
android:textSize="13sp"
|
||||
android:textColor="#333333"
|
||||
android:maxLines="2"
|
||||
|
|
@ -430,7 +669,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/bg_edit_text"
|
||||
android:background="@drawable/bg_edit_text_light"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
|
|
@ -438,7 +677,6 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text=""
|
||||
android:textSize="13sp"
|
||||
android:textColor="#333333"
|
||||
android:maxLines="2"
|
||||
|
|
@ -480,6 +718,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:text="1. 填写直播标题并选择分类\n2. 点击按钮创建直播间获取推流信息\n3. 下载并安装OBS Studio\n4. 打开OBS,点击设置 - 推流\n5. 服务选择自定义\n6. 将上方的推流地址和推流码填入\n7. 点击开始推流即可"
|
||||
android:textSize="13sp"
|
||||
android:textColor="#666666"
|
||||
|
|
@ -489,6 +728,146 @@
|
|||
|
||||
</ScrollView>
|
||||
|
||||
<!-- 电脑模式顶部栏(覆盖在上面) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/pcTopBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="@color/white"
|
||||
android:elevation="2dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnBackPc"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_close_24"
|
||||
android:contentDescription="关闭"
|
||||
app:tint="#333333" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 手机/电脑切换 -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/bg_mode_selector_light"
|
||||
android:padding="3dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnModeMobile2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="6dp"
|
||||
android:text="手机"
|
||||
android:textSize="13sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnModePc2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="6dp"
|
||||
android:text="电脑"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="@drawable/bg_mode_selected_dark" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<View
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
<!-- 底部导航栏 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/bottomNav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:elevation="8dp">
|
||||
|
||||
<!-- 动态 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tabDynamic"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tabDynamicText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="动态"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#999999" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 相机 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tabCamera"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tabCameraText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="作品"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#999999" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 开直播 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tabLive"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tabLiveText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="开直播"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#FF4757"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user