Android Studio中如何通过拖拽边缘或角调整View大小
多锚点拖拽调整View大小解决方案
问题描述
我有一个ImageView,想要通过拖拽它的边缘或角来调整大小,但遇到两个问题:
- 仅能拖拽右下角;
- 拖拽角时整个ImageView会缩放。
需要实现所有角和边均可拖拽,且仅拖拽单一边或角时不缩放整个View,每个锚点拖拽时仅调整对应方向的边界或尺寸。
核心实现思路
要完成需求,需要三个关键步骤:
- 给目标View添加8个拖拽锚点(四个角+四条边);
- 为每个锚点定义专属的拖拽逻辑(不同锚点对应调整View的位置、宽度、高度的组合不同);
- 在触摸监听中识别锚点类型,执行对应的调整逻辑。
具体代码实现
1. 定义锚点类型枚举
创建枚举类标记每个锚点的类型,方便后续逻辑判断:
public enum AnchorType { TOP_LEFT, TOP_RIGHT, TOP_CENTER, BOTTOM_LEFT, BOTTOM_RIGHT, BOTTOM_CENTER, CENTER_LEFT, CENTER_RIGHT }
2. 修改MainActivity,添加所有锚点
封装锚点添加逻辑,为目标View批量添加8个拖拽锚点,并绑定带类型标识的触摸监听:
public class MainActivity extends AppCompatActivity { private int dpToPx(int dp) { return (int) (dp * getResources().getDisplayMetrics().density); } private static final int CIRCLE_SIZE_DP = 20; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Drawable circle = ContextCompat.getDrawable(this, R.drawable.circle); int circleSize = dpToPx(CIRCLE_SIZE_DP); RelativeLayout viewToBeResized = findViewById(R.id.customView); TextView status = findViewById(R.id.status); // 批量添加8个锚点 addAnchor(viewToBeResized, circle, circleSize, RelativeLayout.ALIGN_PARENT_START, RelativeLayout.ALIGN_PARENT_TOP, AnchorType.TOP_LEFT, status); addAnchor(viewToBeResized, circle, circleSize, RelativeLayout.ALIGN_PARENT_END, RelativeLayout.ALIGN_PARENT_TOP, AnchorType.TOP_RIGHT, status); addAnchor(viewToBeResized, circle, circleSize, RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.ALIGN_PARENT_TOP, AnchorType.TOP_CENTER, status); addAnchor(viewToBeResized, circle, circleSize, RelativeLayout.ALIGN_PARENT_START, RelativeLayout.ALIGN_PARENT_BOTTOM, AnchorType.BOTTOM_LEFT, status); addAnchor(viewToBeResized, circle, circleSize, RelativeLayout.ALIGN_PARENT_END, RelativeLayout.ALIGN_PARENT_BOTTOM, AnchorType.BOTTOM_RIGHT, status); addAnchor(viewToBeResized, circle, circleSize, RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.ALIGN_PARENT_BOTTOM, AnchorType.BOTTOM_CENTER, status); addAnchor(viewToBeResized, circle, circleSize, RelativeLayout.ALIGN_PARENT_START, RelativeLayout.CENTER_VERTICAL, AnchorType.CENTER_LEFT, status); addAnchor(viewToBeResized, circle, circleSize, RelativeLayout.ALIGN_PARENT_END, RelativeLayout.CENTER_VERTICAL, AnchorType.CENTER_RIGHT, status); } // 封装锚点添加方法,减少重复代码 private void addAnchor(RelativeLayout parent, Drawable drawable, int size, int ruleX, int ruleY, AnchorType type, TextView status) { ImageView anchor = new ImageView(this); anchor.setImageDrawable(drawable); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(size, size); params.addRule(ruleX, parent.getId()); params.addRule(ruleY, parent.getId()); anchor.setTag(type); // 给锚点标记类型 parent.addView(anchor, params); anchor.setOnTouchListener(new AnchorTouchListener(parent, status)); } }
3. 重写AnchorTouchListener,实现分类型拖拽逻辑
根据锚点类型执行对应的尺寸/位置调整,同时添加最小宽高限制避免View消失:
public class AnchorTouchListener implements View.OnTouchListener { private View viewToResize; private TextView lblStatus; // 初始状态参数 private int initialLeft; private int initialTop; private int initialWidth; private int initialHeight; private int initialTouchX; private int initialTouchY; public AnchorTouchListener(View viewToResize, TextView lblStatus) { this.viewToResize = viewToResize; this.lblStatus = lblStatus; } @Override public boolean onTouch(View view, MotionEvent event) { AnchorType anchorType = (AnchorType) view.getTag(); if (anchorType == null) return false; final int touchX = (int) event.getRawX(); final int touchY = (int) event.getRawY(); RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) viewToResize.getLayoutParams(); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: lblStatus.setText("拖拽开始"); // 记录初始状态 initialLeft = lp.leftMargin; initialTop = lp.topMargin; initialWidth = viewToResize.getWidth(); initialHeight = viewToResize.getHeight(); initialTouchX = touchX; initialTouchY = touchY; break; case MotionEvent.ACTION_MOVE: int deltaX = touchX - initialTouchX; int deltaY = touchY - initialTouchY; // 根据锚点类型执行对应调整逻辑 switch (anchorType) { case TOP_LEFT: // 左上锚点:调整左边界+上边界,同时修改宽高 lp.leftMargin = initialLeft + deltaX; lp.topMargin = initialTop + deltaY; lp.width = initialWidth - deltaX; lp.height = initialHeight - deltaY; break; case TOP_RIGHT: // 右上锚点:调整上边界+宽度 lp.topMargin = initialTop + deltaY; lp.width = initialWidth + deltaX; lp.height = initialHeight - deltaY; break; case TOP_CENTER: // 顶部中间:仅调整上边界+高度 lp.topMargin = initialTop + deltaY; lp.height = initialHeight - deltaY; break; case BOTTOM_LEFT: // 左下锚点:调整左边界+高度 lp.leftMargin = initialLeft + deltaX; lp.width = initialWidth - deltaX; lp.height = initialHeight + deltaY; break; case BOTTOM_RIGHT: // 右下锚点:仅调整宽高(不移动位置) lp.width = initialWidth + deltaX; lp.height = initialHeight + deltaY; break; case BOTTOM_CENTER: // 底部中间:仅调整高度 lp.height = initialHeight + deltaY; break; case CENTER_LEFT: // 左侧中间:调整左边界+宽度 lp.leftMargin = initialLeft + deltaX; lp.width = initialWidth - deltaX; break; case CENTER_RIGHT: // 右侧中间:仅调整宽度 lp.width = initialWidth + deltaX; break; } // 限制最小宽高,避免缩到消失 lp.width = Math.max(lp.width, dpToPx(50)); lp.height = Math.max(lp.height, dpToPx(50)); viewToResize.setLayoutParams(lp); lblStatus.setText(String.format("宽:%d,高:%d", lp.width, lp.height)); break; case MotionEvent.ACTION_UP: lblStatus.setText("拖拽结束"); break; } return true; } // dp转px工具方法 private int dpToPx(int dp) { return (int) (dp * viewToResize.getContext().getResources().getDisplayMetrics().density); } }
4. 额外注意事项
- 确保目标View的布局依赖
margin(原XML中layout_centerHorizontal和layout_centerVertical会自动计算初始margin,无需修改); - 如果是ImageView,根据需求设置合适的
scaleType(比如fitXY或centerCrop),避免图片变形; - 可根据需求调整最小宽高的阈值。
内容的提问来源于stack exchange,提问作者Julian Docev
相关产品推荐
相关产品推荐

