Android如何从已有View程序化生成适配Shimmer的动态占位视图?
动态生成与原View匹配的Shimmer占位视图
需求说明
需要从已有Android View生成形状完全一致的占位视图,用于Facebook Shimmer动画。原View可能是矩形、圆形、椭圆形等任意形状,此前自定义View仅能绘制矩形,无法适配所有形状,需替代静态的shimmer_placeholder_layout实现动态生成。
解决方案
1. 自定义通用占位View
实现一个可适配任意形状的占位View,支持通过ShapeDrawable或Path绘制对应形状:
public class ShimmerPlaceholderView extends View { private Paint mPaint; private ShapeDrawable mShapeDrawable; private Path mShapePath; public ShimmerPlaceholderView(Context context) { super(context); init(); } public ShimmerPlaceholderView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ShimmerPlaceholderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.parseColor("#E0E0E0")); // 占位底色,可自定义 mPaint.setStyle(Paint.Style.FILL); } // 设置从原View提取的ShapeDrawable public void setShapeDrawable(ShapeDrawable shapeDrawable) { mShapeDrawable = shapeDrawable; if (mShapeDrawable != null) { mShapeDrawable.setBounds(0, 0, getWidth(), getHeight()); } invalidate(); } // 设置自定义形状的Path public void setShapePath(Path path) { mShapePath = path; invalidate(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mShapeDrawable != null) { mShapeDrawable.setBounds(0, 0, w, h); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mShapeDrawable != null) { mShapeDrawable.draw(canvas); } else if (mShapePath != null) { canvas.drawPath(mShapePath, mPaint); } else { // 默认绘制与View大小一致的矩形 canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); } } }
2. 提取原View的形状并生成占位视图
根据原View的类型(系统View/自定义View),提取其形状信息,创建对应占位View并添加到Shimmer容器中:
// 获取目标原View与Shimmer容器 View originalView = findViewById(R.id.target_original_view); ShimmerFrameLayout shimmerLayout = findViewById(R.id.shimmerLayout); LinearLayout placeholderContainer = shimmerLayout.findViewById(R.id.placeholder_container); // 创建占位视图 ShimmerPlaceholderView placeholder = new ShimmerPlaceholderView(this); // 匹配原View的布局参数 ViewGroup.LayoutParams originalParams = originalView.getLayoutParams(); placeholder.setLayoutParams(new ViewGroup.LayoutParams(originalParams.width, originalParams.height)); // 提取原View形状并设置给占位视图 if (originalView.getBackground() instanceof ShapeDrawable) { // 情况1:原View背景是ShapeDrawable(圆角矩形、圆形、椭圆等) ShapeDrawable originalShape = (ShapeDrawable) originalView.getBackground(); // 复制形状属性并替换为占位色 ShapeDrawable copiedShape = new ShapeDrawable(originalShape.getShape()); copiedShape.getPaint().setColor(Color.parseColor("#E0E0E0")); copiedShape.getPaint().setStyle(Paint.Style.FILL); placeholder.setShapeDrawable(copiedShape); } else if (originalView instanceof CustomShapeView) { // 情况2:原View是自定义View,需暴露获取形状Path的方法 Path shapePath = ((CustomShapeView) originalView).getShapePath(); placeholder.setShapePath(shapePath); } else { // 情况3:通过Outline提取系统View的形状(如带圆角的CardView) Outline outline = new Outline(); originalView.getOutline(outline); if (outline.isRoundRect()) { RectF rect = new RectF(); outline.getRoundRect(rect); float radius = outline.getRadius(); ShapeDrawable roundedRect = new ShapeDrawable( new RoundRectShape(new float[]{radius, radius, radius, radius, radius, radius, radius, radius}, null, null) ); roundedRect.getPaint().setColor(Color.parseColor("#E0E0E0")); placeholder.setShapeDrawable(roundedRect); } else if (outline.isOval()) { ShapeDrawable oval = new ShapeDrawable(new OvalShape()); oval.getPaint().setColor(Color.parseColor("#E0E0E0")); placeholder.setShapeDrawable(oval); } } // 添加占位视图到Shimmer容器 placeholderContainer.addView(placeholder); // 启动Shimmer动画 shimmerLayout.startShimmer();
3. 修改Shimmer布局
移除静态include,给内部LinearLayout添加id用于动态添加视图:
<com.facebook.shimmer.ShimmerFrameLayout android:id="@+id/shimmerLayout" android:layout_width="match_parent" android:layout_height="match_parent" tools:ignore="MissingConstraints"> <LinearLayout android:id="@+id/placeholder_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- 动态生成的占位视图将添加到这里 --> </LinearLayout> </com.facebook.shimmer.ShimmerFrameLayout>
补充说明
- 对于自定义View,建议在内部添加
getShapePath()方法暴露绘制路径,方便占位视图复用形状; - 占位色、动画效果可根据需求自定义调整;
- 如果原View在布局中尚未测量完成,需等待View测量完成后再提取形状信息(可通过
ViewTreeObserver.OnGlobalLayoutListener监听)。
内容的提问来源于stack exchange,提问作者Vijay Dhas
相关产品推荐
相关产品推荐

