主题:添加样式

This commit is contained in:
xiao12feng8 2026-01-11 12:07:48 +08:00
parent c7eac54c04
commit b02fdf2ea7
23 changed files with 2144 additions and 325 deletions

View File

@ -100,6 +100,14 @@ dependencies {
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.viewpager2:viewpager2:1.0.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") implementation("com.github.bumptech.glide:glide:4.16.0")
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0") annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")

View File

@ -204,7 +204,8 @@
<activity <activity
android:name="com.example.livestreaming.PublishCenterActivity" android:name="com.example.livestreaming.PublishCenterActivity"
android:exported="false" android:exported="false"
android:screenOrientation="portrait" /> android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name="com.example.livestreaming.WishTreeActivity" android:name="com.example.livestreaming.WishTreeActivity"
@ -357,6 +358,13 @@
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<!-- 相机拍摄Activity -->
<activity
android:name="com.example.livestreaming.CameraCaptureActivity"
android:exported="false"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.NoActionBar" />
</application> </application>
</manifest> </manifest>

View File

@ -110,6 +110,14 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
setupUI(); setupUI();
setupSurface(); setupSurface();
// 接收传入的标题
String passedTitle = getIntent().getStringExtra("title");
if (!TextUtils.isEmpty(passedTitle)) {
binding.etTitle.setText(passedTitle);
// 如果有传入标题隐藏标题输入卡片让界面更简洁
binding.cardLiveInfo.setVisibility(View.GONE);
}
// 先检查主播资格通过后再检查权限 // 先检查主播资格通过后再检查权限
checkStreamerStatus(); checkStreamerStatus();
} }
@ -580,9 +588,16 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
String title = binding.etTitle.getText() != null ? String title = binding.etTitle.getText() != null ?
binding.etTitle.getText().toString().trim() : ""; binding.etTitle.getText().toString().trim() : "";
// 如果输入框为空尝试使用传入的标题
if (TextUtils.isEmpty(title)) { if (TextUtils.isEmpty(title)) {
Toast.makeText(this, "请输入直播标题", Toast.LENGTH_SHORT).show(); String passedTitle = getIntent().getStringExtra("title");
return; if (!TextUtils.isEmpty(passedTitle)) {
title = passedTitle;
}
}
if (TextUtils.isEmpty(title)) {
title = "我的直播间"; // 默认标题
} }
if (!cameraInitialized) { if (!cameraInitialized) {

View File

@ -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();
}
}
}

View File

@ -1,20 +1,46 @@
package com.example.livestreaming; package com.example.livestreaming;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; 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.bumptech.glide.Glide;
import com.example.livestreaming.databinding.ActivityPublishCenterBinding; 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.Room;
import com.example.livestreaming.net.StreamUrls; import com.example.livestreaming.net.StreamUrls;
import com.example.livestreaming.net.WorksRequest; 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.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.MediaType;
import okhttp3.MultipartBody; import okhttp3.MultipartBody;
@ -43,13 +74,34 @@ import retrofit2.Response;
public class PublishCenterActivity extends AppCompatActivity { public class PublishCenterActivity extends AppCompatActivity {
private static final String TAG = "PublishCenterActivity";
private ActivityPublishCenterBinding binding; 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 List<CategoryResponse> categoryList = new ArrayList<>();
private int selectedCategoryId = -1; private int selectedCategoryId = -1;
private Uri dynamicCoverUri = null; private Uri dynamicCoverUri = null;
private ActivityResultLauncher<Intent> imagePickerLauncher; 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) { public static void start(Context context) {
context.startActivity(new Intent(context, PublishCenterActivity.class)); context.startActivity(new Intent(context, PublishCenterActivity.class));
} }
@ -64,11 +116,102 @@ public class PublishCenterActivity extends AppCompatActivity {
binding = ActivityPublishCenterBinding.inflate(getLayoutInflater()); binding = ActivityPublishCenterBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
setupImagePicker(); setupImagePicker();
setupToolbar(); setupPermissionLauncher();
setupTabs(); setupGalleryLauncher();
setupClickListeners(); setupClickListeners();
loadCategories(); 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() { 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() { 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.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.btnCopyStreamUrl.setOnClickListener(v -> copyToClipboard("推流地址", binding.tvStreamUrl.getText().toString()));
binding.btnCopyStreamKey.setOnClickListener(v -> copyToClipboard("推流码", binding.tvStreamKey.getText().toString())); binding.btnCopyStreamKey.setOnClickListener(v -> copyToClipboard("推流码", binding.tvStreamKey.getText().toString()));
binding.btnGetStreamInfo.setOnClickListener(v -> createPcLiveRoom()); 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) { private void showBottomTab(int position) {
currentTab = position; currentBottomTab = position;
binding.contentMobileLive.setVisibility(View.GONE);
binding.contentDynamic.setVisibility(View.GONE); // 切换tab时隐藏键盘
binding.contentWork.setVisibility(View.GONE); hideKeyboard();
binding.contentPcLive.setVisibility(View.GONE);
switch (position) { // 根据tab切换底部导航栏样式
case 0: binding.contentMobileLive.setVisibility(View.VISIBLE); break; if (position == 1 || (position == 2 && isMobileLiveMode)) {
case 1: binding.contentDynamic.setVisibility(View.VISIBLE); break; // 相机模式或手机直播模式透明背景白色字体
case 2: binding.contentWork.setVisibility(View.VISIBLE); break; binding.bottomNav.setBackgroundColor(0x00000000); // 透明
case 3: binding.contentPcLive.setVisibility(View.VISIBLE); break; 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() { private void loadCategories() {
@ -203,11 +487,8 @@ public class PublishCenterActivity extends AppCompatActivity {
binding.btnPublishDynamic.setText("发布中..."); binding.btnPublishDynamic.setText("发布中...");
if (dynamicCoverUri != null) { if (dynamicCoverUri != null) {
// 用户选择了封面上传后发布
uploadDynamicCoverAndPublish(content); uploadDynamicCoverAndPublish(content);
} else { } else {
// 用户没选封面使用特殊标记纯文字动态
// 使用 "text_only" 作为标记在展示时识别为纯文字动态
doPublishDynamic(content, "text_only"); doPublishDynamic(content, "text_only");
} }
} }
@ -253,7 +534,6 @@ public class PublishCenterActivity extends AppCompatActivity {
request.setDescription(content); request.setDescription(content);
request.setType("IMAGE"); request.setType("IMAGE");
request.setImageUrls(new ArrayList<>()); request.setImageUrls(new ArrayList<>());
// 如果没有封面设置特殊标识URL表示这是纯文字动态
if (coverUrl == null || coverUrl.isEmpty()) { if (coverUrl == null || coverUrl.isEmpty()) {
request.setCoverUrl("TEXT_ONLY_DYNAMIC_PLACEHOLDER"); request.setCoverUrl("TEXT_ONLY_DYNAMIC_PLACEHOLDER");
} else { } else {
@ -283,7 +563,7 @@ public class PublishCenterActivity extends AppCompatActivity {
private void resetDynamicButton() { private void resetDynamicButton() {
binding.btnPublishDynamic.setEnabled(true); binding.btnPublishDynamic.setEnabled(true);
binding.btnPublishDynamic.setText("发布动态"); binding.btnPublishDynamic.setText("发布");
} }
private File uriToFile(Uri uri) { private File uriToFile(Uri uri) {
@ -317,10 +597,9 @@ public class PublishCenterActivity extends AppCompatActivity {
binding.btnGetStreamInfo.setText("创建中..."); binding.btnGetStreamInfo.setText("创建中...");
binding.streamInfoLoading.setVisibility(View.VISIBLE); binding.streamInfoLoading.setVisibility(View.VISIBLE);
// 获取当前用户昵称作为主播名称
String streamerName = com.example.livestreaming.net.AuthStore.getNickname(this); String streamerName = com.example.livestreaming.net.AuthStore.getNickname(this);
if (TextUtils.isEmpty(streamerName)) { if (TextUtils.isEmpty(streamerName)) {
streamerName = title; // 如果没有昵称使用标题 streamerName = title;
} }
CreateRoomRequest request = new CreateRoomRequest(title, streamerName, "pc"); CreateRoomRequest request = new CreateRoomRequest(title, streamerName, "pc");
@ -375,4 +654,282 @@ public class PublishCenterActivity extends AppCompatActivity {
Toast.makeText(this, label + "已复制", Toast.LENGTH_SHORT).show(); 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();
}
}
} }

View File

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

View File

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

View 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="#FF4757" />
<corners android:radius="8dp" />
</shape>

View File

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

View File

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

View File

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

View File

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

View 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="#FFFFFF" />
<corners android:radius="16dp" />
</shape>

View 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="#333333" />
<corners android:radius="16dp" />
</shape>

View 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="#33FFFFFF" />
<corners android:radius="20dp" />
</shape>

View 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="#F0F0F0" />
<corners android:radius="20dp" />
</shape>

View File

@ -1,6 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <selector xmlns:android="http://schemas.android.com/apk/res/android">
android:shape="rectangle"> <item android:state_pressed="true">
<solid android:color="#FF4757" /> <shape android:shape="rectangle">
<corners android:radius="8dp" /> <solid android:color="#E53E4F" />
<corners android:radius="16dp" />
</shape> </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>

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

View File

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

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

View File

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

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

View File

@ -1,22 +1,236 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/white"> 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 <LinearLayout
android:id="@+id/topBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="56dp"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:background="@color/white" android:background="@color/white">
android:elevation="2dp">
<ImageView <ImageView
android:id="@+id/btnBack" android:id="@+id/btnCloseDynamic"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:src="@drawable/ic_close_24" android:src="@drawable/ic_close_24"
@ -28,179 +242,58 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:text="发布" android:text="发布动态"
android:textSize="18sp" android:textSize="18sp"
android:textColor="#333333" android:textColor="#333333"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:padding="20dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="手机开播" android:text="&#8220;"
android:textSize="20sp" 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:textColor="#333333"
android:textStyle="bold" /> android:textSize="18sp"
android:gravity="top|start"
<TextView android:inputType="textMultiLine"
android:layout_width="wrap_content" android:maxLength="1000"
android:layout_height="wrap_content" android:padding="0dp" />
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" />
</LinearLayout> </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 <FrameLayout
android:id="@+id/dynamicCoverContainer" android:id="@+id/dynamicCoverContainer"
android:layout_width="120dp" android:layout_width="80dp"
android:layout_height="120dp" android:layout_height="80dp"
android:layout_marginTop="8dp" android:layout_marginStart="20dp"
android:layout_marginBottom="12dp"
android:background="@drawable/bg_dashed_border" android:background="@drawable/bg_dashed_border"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true"
android:visibility="gone">
<ImageView <ImageView
android:id="@+id/ivDynamicCover" android:id="@+id/ivDynamicCover"
@ -217,102 +310,248 @@
android:gravity="center"> android:gravity="center">
<ImageView <ImageView
android:layout_width="32dp" android:layout_width="24dp"
android:layout_height="32dp" android:layout_height="24dp"
android:src="@drawable/ic_add_photo_24" android:src="@drawable/ic_add_photo_24"
app:tint="#999999" /> app:tint="#999999" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="2dp"
android:text="添加封面" android:text="封面"
android:textSize="12sp" android:textSize="10sp"
android:textColor="#999999" /> android:textColor="#999999" />
</LinearLayout> </LinearLayout>
<ImageView <ImageView
android:id="@+id/btnRemoveDynamicCover" android:id="@+id/btnRemoveDynamicCover"
android:layout_width="24dp" android:layout_width="20dp"
android:layout_height="24dp" android:layout_height="20dp"
android:layout_gravity="top|end" android:layout_gravity="top|end"
android:layout_margin="4dp" android:layout_margin="2dp"
android:src="@drawable/ic_close_circle_24" android:src="@drawable/ic_close_circle_24"
android:visibility="gone" android:visibility="gone"
android:contentDescription="移除封面" /> android:contentDescription="移除封面" />
</FrameLayout> </FrameLayout>
<com.google.android.material.button.MaterialButton <!-- 底部工具栏 - 放在tab栏上方 -->
android:id="@+id/btnPublishDynamic" <LinearLayout
android:id="@+id/dynamicToolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="52dp" android:layout_height="48dp"
android:layout_marginTop="32dp" android:orientation="horizontal"
android:text="发布动态" android:gravity="center_vertical"
android:textSize="16sp" android:paddingHorizontal="16dp"
app:backgroundTint="#FF4757" android:layout_marginBottom="56dp"
app:cornerRadius="26dp" /> 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> </LinearLayout>
</ScrollView> </LinearLayout>
<!-- 发布作品 --> <!-- 开直播内容 -->
<ScrollView <FrameLayout
android:id="@+id/contentWork" android:id="@+id/contentLive"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="horizontal"
android:padding="20dp"> 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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="发布作品" android:orientation="horizontal"
android:textSize="20sp" 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: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 <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="2dp"
android:text="发布图片或视频作品,支持添加标题、描述等" android:text="翻转"
android:textSize="14sp" android:textColor="@android:color/white"
android:textColor="#999999" /> 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 <com.google.android.material.button.MaterialButton
android:id="@+id/btnPublishWork" android:id="@+id/btnStartLive"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="52dp" android:layout_height="52dp"
android:layout_marginTop="32dp" android:layout_marginTop="16dp"
android:text="去发布作品" android:text="开始直播"
android:textSize="16sp" android:textSize="16sp"
app:backgroundTint="#FF4757" app:backgroundTint="#FF4757"
app:cornerRadius="26dp" /> app:cornerRadius="26dp" />
</LinearLayout> </LinearLayout>
</ScrollView> </FrameLayout>
<!-- 电脑开播 --> <!-- 电脑直播内容 -->
<ScrollView <ScrollView
android:id="@+id/contentPcLive" android:id="@+id/contentPcLive"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/white"
android:visibility="gone"> android:visibility="gone">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:padding="20dp"
android:paddingTop="72dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -330,24 +569,25 @@
android:textSize="14sp" android:textSize="14sp"
android:textColor="#999999" /> android:textColor="#999999" />
<!-- 直播标题 -->
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:hint="直播标题" 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 <com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPcLiveTitle" android:id="@+id/etPcLiveTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="text" android:inputType="text"
android:maxLength="30" /> android:maxLength="30"
android:textColor="#333333" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<!-- 分类选择 -->
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -361,7 +601,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:background="@drawable/bg_spinner" android:background="@drawable/bg_spinner_light"
android:paddingHorizontal="12dp" /> android:paddingHorizontal="12dp" />
<ProgressBar <ProgressBar
@ -392,7 +632,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:orientation="horizontal" android:orientation="horizontal"
android:background="@drawable/bg_edit_text" android:background="@drawable/bg_edit_text_light"
android:padding="12dp"> android:padding="12dp">
<TextView <TextView
@ -400,7 +640,6 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text=""
android:textSize="13sp" android:textSize="13sp"
android:textColor="#333333" android:textColor="#333333"
android:maxLines="2" android:maxLines="2"
@ -430,7 +669,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:orientation="horizontal" android:orientation="horizontal"
android:background="@drawable/bg_edit_text" android:background="@drawable/bg_edit_text_light"
android:padding="12dp"> android:padding="12dp">
<TextView <TextView
@ -438,7 +677,6 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text=""
android:textSize="13sp" android:textSize="13sp"
android:textColor="#333333" android:textColor="#333333"
android:maxLines="2" android:maxLines="2"
@ -480,6 +718,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="80dp"
android:text="1. 填写直播标题并选择分类\n2. 点击按钮创建直播间获取推流信息\n3. 下载并安装OBS Studio\n4. 打开OBS点击设置 - 推流\n5. 服务选择自定义\n6. 将上方的推流地址和推流码填入\n7. 点击开始推流即可" android:text="1. 填写直播标题并选择分类\n2. 点击按钮创建直播间获取推流信息\n3. 下载并安装OBS Studio\n4. 打开OBS点击设置 - 推流\n5. 服务选择自定义\n6. 将上方的推流地址和推流码填入\n7. 点击开始推流即可"
android:textSize="13sp" android:textSize="13sp"
android:textColor="#666666" android:textColor="#666666"
@ -489,6 +728,146 @@
</ScrollView> </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> </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>