public class FirmwareUpdateActivity extends BaseMvvmActivity<ActivityFirmwareUpdateBinding> {
private static final String TAG_DOWNLOAD_FRAGMENT = "download_fragment";
private static final String TAG_INSTALL_FRAGMENT = "install_fragment";
@Nullable
@Override
protected ActivityFirmwareUpdateBinding bindContentView(@Nullable Bundle bundle) {
return ActivityFirmwareUpdateBinding.inflate(getLayoutInflater());
}
@Override
protected void subscribeViewModel(@Nullable Bundle bundle) {
initViews();
startDownloadPhase();
}
private void initViews() {
// 设置Toolbar
viewBinding.ivBack.setOnClickListener(v -> onBackPressed());
// 设置固定的描述文本
viewBinding.tvDescription.setText("The new firmware will be downloaded by your\n" +
"Deco system, not this app. No mobile data or\n" +
"storage on this device is required.\n" +
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXX");
}
private void startDownloadPhase() {
// 显示下载Fragment
FirmwareDownloadFragment downloadFragment = new FirmwareDownloadFragment();
downloadFragment.setOnDownloadCompleteListener(this::onDownloadComplete);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, downloadFragment, TAG_DOWNLOAD_FRAGMENT)
.commit();
}
private void onDownloadComplete() {
// 下载完成,切换到安装Fragment
FirmwareInstallFragment installFragment = new FirmwareInstallFragment();
installFragment.setOnInstallCompleteListener(this::onInstallComplete);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, installFragment, TAG_INSTALL_FRAGMENT)
.addToBackStack(null)
.commit();
}
private void onInstallComplete() {
// 安装完成,可以返回或显示完成页面
// 这里可以显示成功提示或自动返回
finish();
}
@Override
public void onBackPressed() {
// 升级过程中禁止返回,或显示确认对话框
showCancelUpgradeDialog();
}
private void showCancelUpgradeDialog() {
// 显示取消升级确认对话框
// 这里可以实现确认对话框逻辑
}
}
在创建主Activity的布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<!-- Toolbar -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:elevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_back"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_menu"
android:contentDescription="@string/back" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="TP WIFI"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/iv_add"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_add"
android:contentDescription="@string/add" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
<!-- Description Text -->
<TextView
android:id="@+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginHorizontal="32dp"
android:text="The new firmware will be downloaded by your\nDeco system, not this app. No mobile data or\nstorage on this device is required.\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\nXXXXXXXXXXXX"
android:textColor="@color/black"
android:textSize="16sp"
android:lineSpacingExtra="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<!-- Fragment Container -->
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="32dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_description" />
</androidx.constraintlayout.widget.ConstraintLayout>
在修改下载Fragment,添加完成回调:
public class FirmwareDownloadFragment extends BaseMvvmFragment<FragmentFirmwareDownloadBinding> {
private Handler progressHandler;
private Runnable progressRunnable;
private int currentProgress = 0;
private static final int TOTAL_DURATION = 60000; // 1分钟 = 60秒
private static final int UPDATE_INTERVAL = 600; // 每600ms更新一次
private static final int PROGRESS_STEP = 1; // 每次增加1%
// 下载完成回调接口
public interface OnDownloadCompleteListener {
void onDownloadComplete();
}
private OnDownloadCompleteListener downloadCompleteListener;
@Nullable
@Override
protected FragmentFirmwareDownloadBinding bindView(@NonNull LayoutInflater layoutInflater,
@Nullable ViewGroup viewGroup,
@Nullable Bundle bundle) {
return FragmentFirmwareDownloadBinding.inflate(layoutInflater, viewGroup, false);
}
@Override
protected void subscribeViewModel(@Nullable Bundle bundle) {
// 初始化进度处理器
initProgressHandler();
// 开始进度动画
startProgressAnimation();
}
public void setOnDownloadCompleteListener(OnDownloadCompleteListener listener) {
this.downloadCompleteListener = listener;
}
private void initProgressHandler() {
progressHandler = new Handler(Looper.getMainLooper());
progressRunnable = new Runnable() {
@Override
public void run() {
if (currentProgress <= 100) {
// 更新进度条
updateProgress(currentProgress);
currentProgress += PROGRESS_STEP;
// 如果还没有达到100%,继续下一次更新
if (currentProgress <= 100) {
progressHandler.postDelayed(this, UPDATE_INTERVAL);
} else {
// 进度完成,处理完成逻辑
onProgressCompleted();
}
}
}
};
}
private void startProgressAnimation() {
currentProgress = 0;
progressHandler.post(progressRunnable);
}
private void updateProgress(int progress) {
if (getViewDataBinding() != null && getViewDataBinding().progressWv != null) {
// 更新TPWaveBallProgressIndicator的进度
getViewDataBinding().progressWv.setWaveProgress(progress);
}
}
private void onProgressCompleted() {
// 进度完成后的处理逻辑
if (getViewDataBinding() != null && getViewDataBinding().tvDownloading != null) {
getViewDataBinding().tvDownloading.setText("Download Completed!");
}
// 延迟500ms后调用完成回调,切换到安装阶段
if (progressHandler != null) {
progressHandler.postDelayed(() -> {
if (downloadCompleteListener != null) {
downloadCompleteListener.onDownloadComplete();
}
}, 500);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
// 清理Handler,避免内存泄漏
if (progressHandler != null && progressRunnable != null) {
progressHandler.removeCallbacks(progressRunnable);
}
}
@Override
public void onPause() {
super.onPause();
// 暂停时停止进度更新
if (progressHandler != null && progressRunnable != null) {
progressHandler.removeCallbacks(progressRunnable);
}
}
@Override
public void onResume() {
super.onResume();
// 恢复时继续进度更新(如果还没完成)
if (currentProgress < 100) {
progressHandler.post(progressRunnable);
}
}
}
下载Fragment的布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<!-- Progress Indicator -->
<com.tplink.design.indicator.TPWaveBallProgressIndicator
android:id="@+id/progress_wv"
style="@style/Widget.TPDesign.WaveBallProgressIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:waveProgress="0" />
<!-- Downloading Text -->
<TextView
android:id="@+id/tv_downloading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="Downloading..."
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progress_wv" />
</androidx.constraintlayout.widget.ConstraintLayout>
在创建安装Fragment:
public class FirmwareInstallFragment extends BaseMvvmFragment<FragmentFirmwareInstallBinding> {
private CountDownTimer countDownTimer;
private static final long TOTAL_TIME_MILLIS = 180000; // 3分钟 = 180秒
private ObjectAnimator progressAnimator;
// 安装完成回调接口
public interface OnInstallCompleteListener {
void onInstallComplete();
}
private OnInstallCompleteListener installCompleteListener;
@Nullable
@Override
protected FragmentFirmwareInstallBinding bindView(@NonNull LayoutInflater layoutInflater,
@Nullable ViewGroup viewGroup,
@Nullable Bundle bundle) {
return FragmentFirmwareInstallBinding.inflate(layoutInflater, viewGroup, false);
}
@Override
protected void subscribeViewModel(@Nullable Bundle bundle) {
initViews();
startInstallation();
}
public void setOnInstallCompleteListener(OnInstallCompleteListener listener) {
this.installCompleteListener = listener;
}
private void initViews() {
// 设置进度条初始状态
getViewDataBinding().circularProgressBar.setMax(100);
getViewDataBinding().circularProgressBar.setProgress(0);
// 设置初始文本
getViewDataBinding().tvRemainingTime.setText("00:03:00");
getViewDataBinding().tvStatus.setText("Installing new firmware...");
}
private void startInstallation() {
// 启动进度条动画
progressAnimator = ObjectAnimator.ofInt(
getViewDataBinding().circularProgressBar,
"progress",
0,
100
);
progressAnimator.setDuration(TOTAL_TIME_MILLIS);
progressAnimator.setInterpolator(new LinearInterpolator());
progressAnimator.start();
// 启动倒计时
countDownTimer = new CountDownTimer(TOTAL_TIME_MILLIS, 1000) {
@Override
public void onTick(long millisUntilFinished) {
// 更新倒计时显示
updateRemainingTime(millisUntilFinished);
}
@Override
public void onFinish() {
// 安装完成
getViewDataBinding().tvRemainingTime.setText("00:00:00");
getViewDataBinding().tvStatus.setText("Firmware update completed!");
onInstallationComplete();
}
};
countDownTimer.start();
}
private void updateRemainingTime(long millisUntilFinished) {
long minutes = (millisUntilFinished / 1000) / 60;
long seconds = (millisUntilFinished / 1000) % 60;
String timeFormatted = String.format("00:%02d:%02d", minutes, seconds);
getViewDataBinding().tvRemainingTime.setText(timeFormatted);
}
private void onInstallationComplete() {
// 延迟1秒后调用完成回调
if (getViewDataBinding() != null) {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (installCompleteListener != null) {
installCompleteListener.onInstallComplete();
}
}, 1000);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
// 清理资源
if (countDownTimer != null) {
countDownTimer.cancel();
}
if (progressAnimator != null) {
progressAnimator.cancel();
}
}
@Override
public void onPause() {
super.onPause();
// 暂停时停止计时器
if (countDownTimer != null) {
countDownTimer.cancel();
}
if (progressAnimator != null) {
progressAnimator.pause();
}
}
@Override
public void onResume() {
super.onResume();
// 注意:实际项目中可能需要保存和恢复进度状态
// 这里简化处理,如果需要可以实现状态保存逻辑
}
}
安装Fragment的布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<!-- 圆形进度条容器 -->
<RelativeLayout
android:id="@+id/progress_container"
android:layout_width="280dp"
android:layout_height="280dp"
android:layout_marginTop="80dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- 自定义圆形进度条 -->
<com.yourpackage.widget.CircularProgressBar
android:id="@+id/circular_progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 中心内容 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remaining Time"
android:textColor="#999999"
android:textSize="16sp"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tv_remaining_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:03:00"
android:textColor="@color/black"
android:textSize="36sp"
android:textStyle="bold" />
</LinearLayout>
</RelativeLayout>
<!-- 状态文字 -->
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="Installing new firmware..."
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progress_container" />
</androidx.constraintlayout.widget.ConstraintLayout>
后创建自定义圆形进度条:
package com.yourpackage.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
public class CircularProgressBar extends View {
private Paint backgroundPaint;
private Paint progressPaint;
private RectF rectF;
private float strokeWidth = 20;
private int progress = 0;
private int max = 100;
public CircularProgressBar(Context context) {
super(context);
init();
}
public CircularProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 背景圆环画笔
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backgroundPaint.setColor(Color.parseColor("#E0E0E0"));
backgroundPaint.setStyle(Paint.Style.STROKE);
backgroundPaint.setStrokeWidth(strokeWidth);
// 进度圆环画笔
progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
progressPaint.setColor(Color.parseColor("#00BCD4")); // 青色
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setStrokeWidth(strokeWidth);
progressPaint.setStrokeCap(Paint.Cap.ROUND);
rectF = new RectF();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int radius = Math.min(width, height) / 2;
int centerX = width / 2;
int centerY = height / 2;
rectF.set(centerX - radius + strokeWidth / 2,
centerY - radius + strokeWidth / 2,
centerX + radius - strokeWidth / 2,
centerY + radius - strokeWidth / 2);
// 绘制背景圆环
canvas.drawArc(rectF, -90, 360, false, backgroundPaint);
// 绘制进度圆环
float angle = 360 * progress / (float) max;
canvas.drawArc(rectF, -90, angle, false, progressPaint);
}
public void setProgress(int progress) {
this.progress = Math.min(progress, max);
invalidate();
}
public int getProgress() {
return progress;
}
public void setMax(int max) {
this.max = max;
}
public int getMax() {
return max;
}
public void setStrokeWidth(float width) {
this.strokeWidth = width;
backgroundPaint.setStrokeWidth(width);
progressPaint.setStrokeWidth(width);
invalidate();
}
public void setProgressColor(int color) {
progressPaint.setColor(color);
invalidate();
}
public void setBackgroundColor(int color) {
backgroundPaint.setColor(color);
invalidate();
}
}
美!我已经为你设计了完整的两阶段固件升级流程。这个设计包含:
🏗️ 架构设计
两阶段升级流程:
- 下载阶段:波浪进度条(TPWaveBallProgressIndicator)- 1分钟
- 安装阶段:圆形进度条(自定义CircularProgressBar)- 3分钟
📱 组件说明
1. FirmwareUpdateActivity - 主控制器
- 继承BaseMvvmActivity
- 管理两个Fragment的切换
- 共享Toolbar和描述文本
2. FirmwareDownloadFragment - 下载阶段
- 波浪进度条动画(1分钟)
- 完成后自动切换到安装阶段
- 使用回调接口通知Activity
3. FirmwareInstallFragment - 安装阶段
- 圆形进度条 + 倒计时(3分钟)
- 显示"Remaining Time"和"Installing..."
- 完成后可返回或显示成功提示
4. CircularProgressBar - 自定义控件
- 青色(#00BCD4)进度环
- 支持动画和进度设置
- 圆角线条效果
🔄 工作流程
用户点击升级按钮
↓
FirmwareUpdateActivity启动
↓
显示FirmwareDownloadFragment (1分钟波浪进度)
↓
下载完成,切换到FirmwareInstallFragment (3分钟圆形进度)
↓
安装完成,返回或显示完成页面
📋 使用步骤
- 将所有Java文件放入对应的包中
- 将布局文件放入
res/layout - 将
CircularProgressBar.java放入widget包 - 在AndroidManifest.xml中注册
FirmwareUpdateActivity - 确保你的项目包含TPWaveBallProgressIndicator组件
✨ 特色功能
- 无缝切换:两个阶段自动衔接,用户体验流畅
- 共享布局:复用Toolbar和描述文本,节省资源
- 进度同步:倒计时与进度条完美同步
- 生命周期管理:正确处理Fragment切换和资源清理
- 回调机制:阶段完成时自动触发下一步
现在你只需要在viewBinding.btnUpdateAll的点击事件中启动FirmwareUpdateActivity就可以实现完整的升级流程了!