一個紅色小球,跟隨手指觸摸滑動在屏幕上自由移動,且不會超出屏幕邊界。
public class DrawCircle extends View {
private int circleX = 100; // 圓心初始 X
private int circleY = 100; // 圓心初始 Y
private static final int RADIUS = 20;
private int screenWidth, screenHeight;
public DrawCircle(Context context) {
this(context, null);
}
public DrawCircle(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawCircle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
screenWidth = wm.getDefaultDisplay().getWidth();
screenHeight = wm.getDefaultDisplay().getHeight();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true); // 抗鋸齒
canvas.drawCircle(circleX, circleY, RADIUS, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
// 更新圓心座標為觸點座標
circleX = (int) event.getX();
circleY = (int) event.getY();
break;
}
// 邊界約束:保證整個圓在可見區域內
if (circleX >= RADIUS && circleX <= screenWidth - RADIUS &&
circleY >= RADIUS && circleY <= screenHeight - RADIUS - 90) { // 90 為狀態欄/導航欄預留
invalidate(); // 觸發重繪
}
return true; // 消費事件,不再向父佈局傳遞
}
}
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.customview.DrawCircle
android:id="@+id/circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout container = findViewById(R.id.rl);
DrawCircle circle = new DrawCircle(this);
container.addView(circle);
}
}
| 知識點 | 實現細節 |
|---|---|
| 自定義 View 三大構造器 | 覆寫三個構造器,統一調用 init(),兼容 XML/代碼兩種創建方式 |
| 繪製流程 | onDraw(Canvas) 中創建 Paint、設置顏色/抗鋸齒、調用 canvas.drawCircle() |
| 觸摸事件處理 | 重寫 onTouchEvent,處理 DOWN/MOVE/UP 統一更新座標,返回 true 表示消費事件 |
| 邊界約束 | 根據屏幕寬高與圓半徑計算合法範圍,超出不調用 invalidate() |
| 性能優化 | Paint 對象可提升為成員變量避免重複創建;invalidate() 僅在合法區域調用 |
ACTION_POINTER_DOWN/UP 支持多手指canvas.drawBitmap() 替換圓形,做拖拽頭像/貼紙自定義 View 核心三步走:構造器初始化 → onDraw 繪製 → onTouchEvent 交互。本例通過 50 行核心代碼實現了完整的觸控跟隨組件,是 Android 自定義 View 入門的最佳練手項目。