Android自定义View流程

文章目录

  • 前言
  • 一、创建类并继承View
    • 1、定义参数包含Context和AttributeSet的构造方法
    • 2、在activity_main.xml中使用自定义View
  • 二、设置自定义View的属性
    • 1、创建attr.xml
    • 2、获取attr.xml中自定义View的属性——obtainStyledAttributes()
  • 三、提供自定义View属性的getter和setter方法
  • 四、控制View的大小——重写onMeasure()
  • 五、当视图的大小发生变化——重写onSizeChanged()方法
  • 绘制View——重写onDraw()
  • 六、添加动画效果(高阶用法)
  • 七、响应用户手势操作——重写onTouchEvent()
  • 八、对外提供回调接口
    • 1、定义回调接口与接口方法
    • 2、添加用户点击动作响应事件逻辑
    • 3、Activity中实际调用
  • 总结

前言

在开发中,View视图具有非常重要的作用,它是直接呈现给使用者的,因此向用户展示精美高效的View视图很有意义。Android系统提供了丰富的视图组件,如TextView、ImageView、Button等,还提供了RelativeLayout、LinearLayout、FrameLayout等组合组件,使用这些组件搭配能实现良好的视图效果。但是,有时候我们需要实现更加个性化和有特点的视觉效果,使用系统提供的组件就比较难满足这种需求了,此时自定义View视图便派上用场了,本文将主要分析继承View类实现自定义View视图的流程,去创建符合特定需求的自定义View视图。

基本实现流程:
1、创建自定义类并继承View
2、设置自定义View的属性
3、控制自定义View的大小——重写onMeasure()
4、绘制自定义View——重写onDraw()

以下是完整流程

一、创建类并继承View

1、定义参数包含Context和AttributeSet的构造方法

创建一个类,并继承View,本示例创建一个名为CustomView的类,需要实现其构造方法,为了在XML布局中使用自定义View的属性,至少需要提供一个参数包含Context和AttributeSet的构造方法,如下所示:

public class CustomView extends View {
    public CustomView(Context context) {
        super(context, null);
        init(context, null, 0);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }
}

2、在activity_main.xml中使用自定义View

在XML布局中使用自定义属性,需要提供命名空间,命名空间的格式如:xmlns:[别名]="schemas.android.com/apk/res/[pa… name],
一种常用的命名空间:xmlns:app="schemas.android.com/apk/res-aut…

此时虽然能正常引用自定义View,但不包含任何属性


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity"
    android:orientation="vertical">
    
    <com.coolweather.uicustomviews.CustomView
        android:id="@+id/custom_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
LinearLayout>

二、设置自定义View的属性

1、创建attr.xml

为了像系统提供的组件那样,可以在XML布局中设置视图组件的属性,需要提供自定义View的属性设置,在res/values路径下新建一个attrs.xml文件,并在其中编辑属性名和格式,常用的格式有string:字符串,boolean:布尔值,color:颜色值, dimension:尺寸值,enum:枚举值,flags:位,float:浮点值,fraction:百分数,integer整数值,reference:引用资源ID。示例如下:


<resources>
    <declare-styleable name="CustomView">
        <attr name="customColor" format="color|reference" />
        <attr name="customText" format="string|reference" />
        <attr name="customSize" format="dimension|reference" />
    declare-styleable>
resources>

2、获取attr.xml中自定义View的属性——obtainStyledAttributes()

在XML布局中设置属性值后,接着便是在自定义的View中获取这些属性值,调用context.obtainStyledAttributes()返回TypedArray数组,TypedArray调用相应的方法获取属性值,如调用typedArray.getString(R.styleable.CustomView_textContent)获得字符串,TypedArray对象在调用之后要调用typedArray.recycle()回收资源,示例如下:

private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    // 初始化画笔、颜色、文本等属性
    // 获取自定义属性
    TypedArray typedArray = context.obtainStyledAttributes(attrs,
            R.styleable.CustomView, defStyleAttr, 0);

    customColor = typedArray.getColor(R.styleable.CustomView_customColor, Color.WHITE);
    customText = typedArray.getString(R.styleable.CustomView_customText);
    customDimension = typedArray.getDimension(R.styleable.CustomView_customSize, 30);

    typedArray.recycle();
}

三、提供自定义View属性的getter和setter方法

自定义View的属性不仅可以在XML布局中设置,还应提供getter和setter方法,以便在代码中更改属性,在调用setter方法更改属性时,View的外观发生变化时需要调用invalidate()方法使当前的视图失效,进而触发onDraw()方法重绘视图,如果View的大小和形状发生了变化,则需要调用requestLayout()请求重新布局,需要注意的是invalidate()方法要在UI线程中调用,在非UI线程中调用postInvalidate(),示例如下:

public void setTextContent(String textContent) {
    this.customText = textContent;
    //外观发生变化时,在UI线程中调用
    invalidate();
    //大小和形状发生了变化调用,非必要不调用,以提高性能
    requestLayout();
}

public String getCustomText() {
    return customText;
}

四、控制View的大小——重写onMeasure()

此方法主要是用来控制View的大小,需要根据实际情况确定自定义View的宽度和高度。通常,可以使用MeasureSpec类的getSize()getMode()方法来获取宽度和高度的建议值以及测量模式。,调用setMeasuredDimension()方法将计算出的宽高传入,示例如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int measuredWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    int measuredHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    switch (widthMode) {
        case MeasureSpec.EXACTLY:
            measuredWidth = widthSize;
            break;
        case MeasureSpec.AT_MOST:
            // 计算宽度的最大值,通常基于内容和内边距
            measuredWidth = Math.min(widthMeasureSpec, widthSize);
            break;
        case MeasureSpec.UNSPECIFIED:
            // 宽度可以是任意值,通常基于内容和内边距
            measuredWidth = widthMeasureSpec;
            break;
    }

    switch (heightMode) {
        case MeasureSpec.EXACTLY:
            measuredHeight = heightSize;
            break;
        case MeasureSpec.AT_MOST:
            // 计算高度的最大值,通常基于内容和内边距
            measuredHeight = Math.min(heightMeasureSpec, heightSize);
            break;
        case MeasureSpec.UNSPECIFIED:
            // 高度可以是任意值,通常基于内容和内边距
            measuredHeight = heightMeasureSpec;
            break;
    }

    setMeasuredDimension(measuredWidth, measuredHeight);
}

五、当视图的大小发生变化——重写onSizeChanged()方法

当视图的大小发生变化时,onSizeChanged()方法会被调用,onSizeChanged()方法会携带4个参数,分别是新的宽度、新的高度、旧的宽度、旧的高度,这对正确地绘制View至关重要,绘制需要的位置和尺寸等参数需要在此方法内进行计算,示例如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    textY = (float) h / 2;
    centerX = (float) w / 2;
    centerY = (float) h / 2;
    maxCircleRadius = (float) (w - 20) / 2;
}

绘制View——重写onDraw()

绘制View需要用到画布Canvas和画笔Paint,Canvas负责处理绘制什么,如点、线、圆、矩形等,Paint负责处理如何绘制,如绘制的颜色、是否填充、透明度等,画布Canvas可以在重写onDraw()方法后获取,而画笔Paint则需要在初始化阶段新建一个或多个Paint对象。示例如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    
    Paint paint = new Paint();
    paint.setColor(customColor);
    paint.setTextSize(customSize);
    canvas.drawText(customText, 100, textY + 100, paint);
}

绘制View是重要的一环,它将可见的界面呈现给使用者,重写onDraw()方法后,它将提供一个画布Canvas,它将和画笔Paint一起执行绘制,Canvas提供了丰富的绘制方法,如drawLine()绘制线段、drawText()绘制文本、drawPoint()绘制点、drawRect()绘制矩形等,传入计算好的参数和画笔,便可绘制出相应的图形。

六、添加动画效果(高阶用法)

为了让自定义View更有吸引力和自然,还需要添加一些动画效果,这时候使用属性动画修改View的属性,可以产生动画效果,示例如下:

ObjectAnimator textAlpha = ObjectAnimator.ofInt(this, "textAlpha", 255, 50);
textAlpha.setDuration(2000);
textAlpha.setRepeatCount(ValueAnimator.INFINITE);
textAlpha.setRepeatMode(ValueAnimator.RESTART);
textAlpha.start();
textAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int animatedValue = (int) animation.getAnimatedValue();
        setTextAlpha(animatedValue);
    }
});

ObjectAnimator circle = ObjectAnimator.ofFloat(this, "circleRadius", 0.0f, maxCircleRadius);
circle.setDuration(2000);
circle.setRepeatCount(ValueAnimator.INFINITE);
circle.setRepeatMode(ValueAnimator.RESTART);
circle.start();
circle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float animatedValue = (float) animation.getAnimatedValue();
        setCircleRadius(animatedValue);
    }
});

七、响应用户手势操作——重写onTouchEvent()

View还会经常与使用者进行交互,因此还需要响应和处理用户的手势操作,一般来说,需要重写onTouchEvent(MotionEvent event),在此方法内处理手势操作,常见的手势操作有按下、滑动、抬起等,在此方法内加上业务逻辑,示例如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return true;
}

此外,还可以借助GestureDetector类实现更多的手势检测,如双击、长按、滚动等。

八、对外提供回调接口

1、定义回调接口与接口方法

自定义View还应对外提供回调接口,以传递一些事件和数据,方便调用方处理相应的逻辑,常见的操作是在View内定义一些接口,在接口内部定义一些事件,并对外提供回调接口的方法,示例如下:

private OnCustomViewClickListener onCustomViewClickListener;

public interface OnCustomViewClickListener {
    void onCustomViewClick();
}

public void setOnCustomViewClickListener(OnCustomViewClickListener onCustomViewClickListener) {
    this.onCustomViewClickListener = onCustomViewClickListener;
}

2、添加用户点击动作响应事件逻辑

在onTouchEvent()中添加对应动作后执行的方法

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            onCustomViewClickListener.onCustomViewClick();
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return true;
}

3、Activity中实际调用

CustomView customView = (CustomView) findViewById(R.id.custom_view);
customView.setOnCustomViewClickListener(new CustomView.OnCustomViewClickListener() {
    @Override
    public void onCustomViewClick() {
        Toast.makeText(MainActivity.this, "clicked the view", Toast.LENGTH_SHORT).show();
    }
});

总结

自定义View很有实用意义,在系统组件不能实现需求时,我们可以通过自定义View来达到目的。本文分析了实现自定义View的流程,包括自定义View属性、提供属性的getter和setter方法、重写onMeasure()、重写onSizeChanged()、初始化画笔Paint、重写onDraw()、响应用户手势操作、添加动画效果、对外提供回调接口。根据实际的需要,这些环节可能不需要都实现,或者增加别的环节。

你可能感兴趣的:(android,android)