You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 19:10:33