Android自定义View全解析:从基础绘制到复杂交互,实战多种自定义View实现

在Android开发中,自定义View的应用场景非常广泛。根据不同的需求,开发者可以创建各种类型的自定义View。以下是几种常见的自定义View类型及其实现思路和示例。
1. 自定义绘制View

自定义绘制View是最基础的自定义View类型,通常用于绘制图形、文本、图像等内容。开发者通过重写onDraw()方法,使用Canvas和Paint来实现绘制。

示例:自定义圆形View

复制代码
public class CircleView extends View {
    private Paint paint;

    public CircleView(Context context) {
        super(context);
        init();
    }

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true); // 抗锯齿
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int radius = Math.min(getWidth(), getHeight()) / 2;
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, paint);
    }
}

使用:

复制代码
<com.example.CircleView
    android:layout_width="100dp"
    android:layout_height="100dp"/>

2. 自定义组合View

自定义组合View是通过将多个现有的View组合在一起,形成一个新的复合View。这种方式通常用于创建复杂的UI组件。

示例:自定义标题栏

复制代码
public class CustomTitleBar extends RelativeLayout {
    private TextView titleText;
    private ImageView backButton;

    public CustomTitleBar(Context context) {
        super(context);
        init(context);
    }

    public CustomTitleBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        LayoutInflater.from(context).inflate(R.layout.custom_title_bar, this, true);
        titleText = findViewById(R.id.titleText);
        backButton = findViewById(R.id.backButton);

        backButton.setOnClickListener(v -> {
            // 返回按钮点击事件
            if (context instanceof Activity) {
                ((Activity) context).finish();
            }
        });
    }

    public void setTitle(String title) {
        titleText.setText(title);
    }
}

布局文件:custom_title_bar.xml

复制代码
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/colorPrimary">

    <ImageView
        android:id="@+id/backButton"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginStart="10dp"
        android:src="@drawable/ic_back" />

    <TextView
        android:id="@+id/titleText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="@android:color/white"
        android:textSize="18sp" />
</RelativeLayout>

使用:

复制代码
<com.example.CustomTitleBar
    android:id="@+id/titleBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

3. 自定义动画View

自定义动画View通常用于实现复杂的动画效果。开发者可以通过ValueAnimator、ObjectAnimator或PropertyValuesHolder等工具来实现动画。

示例:自定义旋转进度条

复制代码
public class RotatingProgressBar extends View {
    private Paint paint;
    private float rotationAngle = 0;

    public RotatingProgressBar(Context context) {
        super(context);
        init();
    }

    public RotatingProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(10);
        paint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = Math.min(centerX, centerY) - 10;

        canvas.save();
        canvas.rotate(rotationAngle, centerX, centerY);
        canvas.drawCircle(centerX, centerY, radius, paint);
        canvas.restore();
    }

    public void startRotation() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 360);
        animator.setDuration(1000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.addUpdateListener(animation -> {
            rotationAngle = (float) animation.getAnimatedValue();
            invalidate();
        });
        animator.start();
    }
}

使用:

复制代码
RotatingProgressBar progressBar = findViewById(R.id.rotatingProgressBar);
progressBar.startRotation();

4. 自定义触摸交互View

自定义触摸交互View通常用于处理用户的触摸事件,例如拖动、缩放、点击等。

示例:自定义可拖动View

复制代码
public class DraggableView extends View {
    private float lastX, lastY;

    public DraggableView(Context context) {
        super(context);
        init();
    }

    public DraggableView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        setBackgroundColor(Color.GREEN);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = event.getX();
                lastY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float dx = event.getX() - lastX;
                float dy = event.getY() - lastY;
                setX(getX() + dx);
                setY(getY() + dy);
                break;
        }
        return true;
    }
}

使用:

复制代码
<com.example.DraggableView
    android:layout_width="100dp"
    android:layout_height="100dp"/>

5. 自定义属性View

自定义属性View允许开发者在XML中定义和使用自定义属性,以增强View的灵活性。

示例:自定义带属性的TextView
在res/values/attrs.xml中定义属性

复制代码
<declare-styleable name="CustomTextView">
    <attr name="customTextColor" format="color" />
    <attr name="customTextSize" format="dimension" />
</declare-styleable>

在自定义View中解析属性:

复制代码
public class CustomTextView extends TextView {
    public CustomTextView(Context context) {
        super(context);
        init(null);
    }

    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CustomTextView);
            int textColor = a.getColor(R.styleable.CustomTextView_customTextColor, Color.BLACK);
            float textSize = a.getDimension(R.styleable.CustomTextView_customTextSize, 16);
            a.recycle();

            setTextColor(textColor);
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
        }
    }
}

在XML中使用:

复制代码
<com.example.CustomTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World"
    app:customTextColor="@color/red"
    app:customTextSize="20sp"/>

6. 自定义ViewGroup

自定义ViewGroup用于实现复杂的布局逻辑,例如瀑布流布局、环形布局等。

示例:自定义流式布局

复制代码
public class FlowLayout extends ViewGroup {
    public FlowLayout(Context context) {
        super(context);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int width = r - l;
        int x = 0, y = 0;

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (x + child.getMeasuredWidth() > width) {
                x = 0;
                y += child.getMeasuredHeight();
            }
            child.layout(x, y, x + child.getMeasuredWidth(), y + child.getMeasuredHeight());
            x += child.getMeasuredWidth();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

使用:

复制代码
<com.example.FlowLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <!-- 添加子View -->
</com.example.FlowLayout>

总结

自定义View的类型多种多样,开发者可以根据具体需求选择合适的实现方式。无论是简单的绘制、复杂的动画,还是自定义布局,掌握自定义View的开发技巧都能极大地提升应用的灵活性和用户体验。

相关推荐
参宿四南河三2 小时前
Android Compose SideEffect(副作用)实例加倍详解
android·app
火柴就是我3 小时前
mmkv的 mmap 的理解
android
没有了遇见3 小时前
Android之直播宽高比和相机宽高比不支持后动态获取所支持的宽高比
android
shenshizhong3 小时前
揭开 kotlin 中协程的神秘面纱
android·kotlin
vivo高启强3 小时前
如何简单 hack agp 执行过程中的某个类
android
沐怡旸4 小时前
【底层机制】 Android ION内存分配器深度解析
android·面试
你听得到114 小时前
肝了半个月,我用 Flutter 写了个功能强大的图片编辑器,告别image_cropper
android·前端·flutter
KevinWang_4 小时前
Android 原生 app 和 WebView 如何交互?
android
用户69371750013844 小时前
Android Studio中Gradle、AGP、Java 版本关系:不再被构建折磨!
android·android studio
杨筱毅5 小时前
【底层机制】Android低内存管理机制深度解析
android·底层机制