You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android中通过捏合手势调整View的LayoutParams实现贴纸缩放

修复捏合手势调整贴纸View尺寸的问题

我帮你排查了代码里几个导致缩放失效的核心问题,咱们一步步来修正:

问题根源分析

  1. 双指初始距离未正确捕获:你只在ACTION_DOWN里处理双指情况,但第二个手指按下的触发事件是ACTION_POINTER_DOWN,这导致olddistance从未被正确初始化,缩放比例计算直接失效。
  2. 宽高赋值颠倒:设置LayoutParams时,你把高度的计算结果赋给了宽度,宽度结果赋给了高度,这会让缩放后的宽高比例完全错乱。
  3. 缩放时位置偏移:直接修改View的宽高会让左上角位置固定,导致缩放时贴纸偏移,用户体验极差。
  4. 状态更新逻辑混乱:在ACTION_MOVE中不管单指还是双指都更新StartPT,干扰了缩放时的位置计算。

修正后的完整代码

final ImageView newSticker = new ImageView(getApplicationContext());
newSticker.setLayoutParams(new FrameLayout.LayoutParams(
    FrameLayout.LayoutParams.WRAP_CONTENT,
    FrameLayout.LayoutParams.WRAP_CONTENT
));
Bitmap sticker = BitmapFactory.decodeResource(getResources(), galleryList[position]);
newSticker.setImageBitmap(sticker);
// 根据需求设置scaleType:如果要让贴纸填满View,用FIT_XY;要保持比例用CENTER_INSIDE
newSticker.setScaleType(ImageView.ScaleType.FIT_XY);
viewGroup.addView(newSticker);

newSticker.setOnTouchListener(new View.OnTouchListener() {
    PointF downPT = new PointF(); // 记录单指按下时的触摸位置
    PointF startPT = new PointF(); // 记录贴纸初始位置
    float oldDistance;
    PointF lastFingerCenter = new PointF(); // 双指中心点,用于保持缩放中心

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        selectedSticker = newSticker;
        int action = event.getActionMasked(); // 用getActionMasked更可靠处理多点触摸
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                if (event.getPointerCount() == 1) {
                    // 单指移动逻辑
                    float newX = startPT.x + event.getX() - downPT.x;
                    float newY = startPT.y + event.getY() - downPT.y;
                    newSticker.setX(newX);
                    newSticker.setY(newY);
                } else if (event.getPointerCount() == 2) {
                    // 双指缩放逻辑
                    float dX = event.getX(0) - event.getX(1);
                    float dY = event.getY(0) - event.getY(1);
                    float newDistance = (float) Math.sqrt(dX * dX + dY * dY);

                    if (oldDistance > 0) { // 避免除以0的异常
                        float scaleRatio = newDistance / oldDistance;
                        int currentWidth = newSticker.getWidth();
                        int currentHeight = newSticker.getHeight();
                        // 计算新的宽高,保持原比例
                        int newWidth = (int) (currentWidth * scaleRatio);
                        int newHeight = (int) (currentHeight * scaleRatio);

                        // 计算当前双指的中心点
                        float currentCenterX = (event.getX(0) + event.getX(1)) / 2;
                        float currentCenterY = (event.getY(0) + event.getY(1)) / 2;

                        // 计算偏移量,确保缩放围绕双指中心进行
                        float offsetX = currentCenterX - (newSticker.getX() + currentWidth / 2);
                        float offsetY = currentCenterY - (newSticker.getY() + currentHeight / 2);
                        float newX = currentCenterX - (newWidth / 2) + offsetX;
                        float newY = currentCenterY - (newHeight / 2) + offsetY;

                        // 更新LayoutParams和位置
                        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) newSticker.getLayoutParams();
                        lp.width = newWidth;
                        lp.height = newHeight;
                        newSticker.setLayoutParams(lp);
                        newSticker.setX(newX);
                        newSticker.setY(newY);

                        // 更新旧距离,用于下一次缩放计算
                        oldDistance = newDistance;
                    }
                }
                break;
            case MotionEvent.ACTION_DOWN:
                // 单指按下初始化参数
                downPT.set(event.getX(), event.getY());
                startPT.set(newSticker.getX(), newSticker.getY());
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                // 双指按下时记录初始距离和中心点
                float odX = event.getX(0) - event.getX(1);
                float odY = event.getY(0) - event.getY(1);
                oldDistance = (float) Math.sqrt(odX * odX + odY * odY);
                lastFingerCenter.set(
                    (event.getX(0) + event.getX(1)) / 2,
                    (event.getY(0) + event.getY(1)) / 2
                );
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                // 抬起时重置状态,避免后续异常
                if (event.getPointerCount() == 1) {
                    oldDistance = 0;
                }
                break;
            default:
                break;
        }
        return true;
    }
});

关键修改说明

  • 改用ACTION_MASKED处理多点触摸:比直接用getAction()更准确,能正确识别多点触摸的动作类型。
  • 修复双指初始距离捕获:把双指初始距离的计算移到ACTION_POINTER_DOWN事件,确保第二个手指按下时能正确记录初始值。
  • 修正宽高赋值顺序:保证新宽高是原宽高乘以缩放比例,避免比例错乱。
  • 添加缩放中心保持逻辑:计算双指中心点,调整贴纸的X/Y位置,让缩放围绕双指中心进行,不会出现偏移。
  • 优化状态更新逻辑:只在单指移动时更新位置参数,避免干扰缩放逻辑。
  • 添加除以0保护:防止oldDistance为0时出现计算异常。

另外,根据你的需求(调整View尺寸而非缩放内部贴纸),记得给ImageView设置合适的scaleType

  • 如果希望贴纸填满整个View(跟着View尺寸同步变化),用FIT_XY
  • 如果希望保持贴纸原比例,只调整View尺寸适配贴纸,用CENTER_INSIDECENTER_CROP

内容的提问来源于stack exchange,提问作者Aleksakb24

火山引擎 最新活动