Android通知中实现图片随进度条进度同步移动功能
Android通知实现图片随ProgressBar进度同步移动
需求
实现Android通知中的图片(iv_car1、iv_car2)随ProgressBar进度变化同步移动,效果类似SeekBar的滑块。
自定义通知布局
普通视图布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- Title and message body --> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="PickUp in 6 minutes" android:textColor="@color/black" android:textSize="12sp" android:textStyle="bold" tools:ignore="HardcodedText" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:progressTint="@color/black" android:layout_centerVertical="true" android:max="100" android:progress="50" /> <ImageView android:id="@+id/iv_car1" android:layout_width="30dp" android:layout_height="20dp" android:layout_centerVertical="true" android:src="@drawable/icon_car3" /> </RelativeLayout> </LinearLayout>
注:移除包裹ImageView的冗余LinearLayout,避免干扰偏移计算
可展开视图布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingVertical="@dimen/_5sdp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginVertical="@dimen/_5sdp" android:text="PickUp in 6 minutes" android:textColor="@color/black" android:textSize="12sp" android:textStyle="bold" /> <TextView android:id="@+id/message" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Meet at the pickup point for the ride you have booked." android:textColor="@color/black" android:textSize="10sp" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ProgressBar android:id="@+id/progressBar2" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:progressTint="@color/black" android:layout_centerVertical="true" android:max="100" android:progress="50" /> <ImageView android:id="@+id/iv_car2" android:layout_width="30dp" android:layout_height="20dp" android:layout_centerVertical="true" android:src="@drawable/icon_car3" /> </RelativeLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/ride_custome_notification_bg" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <ImageView android:layout_width="@dimen/_100sdp" android:layout_height="@dimen/_40sdp" android:layout_gravity="center" android:layout_marginVertical="@dimen/_5sdp" android:src="@drawable/car_photo" /> <ImageView android:id="@+id/user_image" android:layout_width="@dimen/_50sdp" android:layout_centerVertical="true" android:layout_height="@dimen/_50sdp" android:src="@drawable/profile" /> </RelativeLayout> <TextView android:id="@+id/numberPlate" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginVertical="@dimen/_2sdp" android:gravity="center" android:text="GAK 7522" android:textSize="12sp" android:textStyle="bold" /> <TextView android:id="@+id/carModel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:text="Grey Toyota corola" android:textSize="10sp" /> </LinearLayout> </LinearLayout>
注:移除包裹ImageView的冗余LinearLayout,确保margin设置生效
原代码问题分析
原实现存在以下问题:
- 进度偏移计算逻辑错误:
progress * (density+30)的计算方式完全不符合ProgressBar宽度比例,导致图片移动距离与进度不匹配。 - 主线程阻塞:
Thread.sleep(1000)会阻塞主线程,引发ANR风险。 - 布局层级冗余:ImageView被包裹在LinearLayout中,干扰margin的正确生效。
修正后的实现代码
fun notificationTest() { val channelId = "ride_driver_info" val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( channelId, "Ride Driver Info", NotificationManager.IMPORTANCE_LOW ) notificationManager.createNotificationChannel(channel) } val remoteViews = RemoteViews(packageName, R.layout.ride_notification_custome_layout) val expandableContent = RemoteViews(packageName, R.layout.ride_notification_expand_custome_layout) // 设置圆角头像 val sourceBitmap = BitmapFactory.decodeResource(resources, R.drawable.profile) val roundedBitmap = getRoundedBitmap(sourceBitmap) expandableContent.setImageViewBitmap(R.id.user_image, roundedBitmap) val notificationBuilder = NotificationCompat.Builder(this, channelId) .setSmallIcon(R.drawable.app_logo2) .setCustomContentView(remoteViews) .setCustomBigContentView(expandableContent) .setPriority(NotificationCompat.PRIORITY_LOW) .setAutoCancel(true) .setOngoing(true) var progress = 0 val maxProgress = 100 // 使用Coroutine实现非阻塞式进度更新 CoroutineScope(Dispatchers.IO).launch { while (progress <= maxProgress) { // 更新进度条 remoteViews.setProgressBar(R.id.progressBar1, maxProgress, progress, false) expandableContent.setProgressBar(R.id.progressBar2, maxProgress, progress, false) // 计算图片偏移:基于屏幕宽度比例,减去图片自身宽度的一半,避免图片超出进度条范围 val screenWidth = resources.displayMetrics.widthPixels val progressRatio = progress.toFloat() / maxProgress // 减去图片宽度的一半,让图片中心与进度位置对齐 val offsetPx = (screenWidth * progressRatio) - (30 * resources.displayMetrics.density / 2) val offsetDp = offsetPx / resources.displayMetrics.density // 更新图片margin remoteViews.setViewLayoutMargin(R.id.iv_car1, RemoteViews.MARGIN_START, offsetDp.toInt(), TypedValue.COMPLEX_UNIT_DIP) expandableContent.setViewLayoutMargin(R.id.iv_car2, RemoteViews.MARGIN_START, offsetDp.toInt(), TypedValue.COMPLEX_UNIT_DIP) // 更新通知 notificationManager.notify(1, notificationBuilder.build()) progress += 10 delay(1000) // 非阻塞延迟 } } } // 圆角图片工具方法 private fun getRoundedBitmap(bitmap: Bitmap): Bitmap { val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888) val canvas = Canvas(output) val paint = Paint() val rect = Rect(0, 0, bitmap.width, bitmap.height) val rectF = RectF(rect) paint.isAntiAlias = true canvas.drawRoundRect(rectF, bitmap.width.toFloat()/2, bitmap.height.toFloat()/2, paint) paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) canvas.drawBitmap(bitmap, rect, rect, paint) return output }
关键修正点
- 正确的偏移计算:基于屏幕宽度和进度比例计算偏移量,同时减去图片宽度的一半,让图片中心与进度位置对齐,避免超出进度条范围。
- 非阻塞更新:使用Coroutine的
delay替代Thread.sleep,避免阻塞主线程。 - 简化布局层级:移除包裹ImageView的LinearLayout,确保margin设置正确生效。
- 动态构建通知:每次更新通过NotificationBuilder重新构建通知,确保RemoteViews的修改能正确应用。
内容的提问来源于stack exchange,提问作者Umair Asim
相关产品推荐
相关产品推荐

