美文网首页
Android 自定义View流程解析

Android 自定义View流程解析

作者: QiShare | 来源:发表于2022-03-03 14:14 被阅读0次

    1.简介

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

    2.自定义View流程

    2.1 创建类并继承View

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

    public class CustomView extends View {
        public CustomView(Context context) {
            this(context,null);
        }
    
        public CustomView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    }
    

    2.2 提供自定义属性

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

    <resources>
        <declare-styleable name="CustomView">
            <attr name="textContent" format="string|reference" />
            <attr name="textSize" format="dimension|reference" />
            <attr name="textColor" format="color|reference" />
            <attr name="circleColor" format="color|reference" />
        </declare-styleable>
    </resources>
    

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

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <com.android.viewdemo.CustomView
            android:id="@+id/cv_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            app:textContent="Android"
            app:textSize="50sp"
            app:textColor="@color/teal_200"
            app:circleColor="@color/purple_500"/>
    </RelativeLayout>
    

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

    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0);
    try {
        textContent = typedArray.getString(R.styleable.CustomView_textContent);
        textSize = typedArray.getDimensionPixelSize(R.styleable.CustomView_textSize, 50);
        textColor = typedArray.getColor(R.styleable.CustomView_textColor, 0);
        circleColor = typedArray.getColor(R.styleable.CustomView_circleColor, 0);
    } finally {
        typedArray.recycle();
    }
    

    2.3 提供属性的getter和setter方法

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

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

    2.4 重写onMeasure()方法

    此方法主要是用来控制View的大小,让父视图知道View希望的大小,在方法内计算得出希望View显示的大小后,调用setMeasuredDimension()方法将计算出的宽高传入,示例如下;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = 0;
        int height = 0;
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        if (modeWidth == MeasureSpec.EXACTLY) {
            width = sizeWidth;
        } else if (modeWidth == MeasureSpec.AT_MOST) {
            width = Math.min(defaultWidth, sizeWidth);
        } else {
            width = defaultWidth;
        }
    
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
        if (modeHeight == MeasureSpec.EXACTLY) {
            height = sizeHeight;
        } else if (modeHeight == MeasureSpec.AT_MOST) {
            height = Math.min(defaultHeight, sizeHeight);
        } else {
            height = defaultHeight;
        }
        setMeasuredDimension(width, height);
    }
    

    2.5 重写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;
    }
    

    2.6 初始化画笔Paint

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

    paintText = new Paint();
    paintText.setAntiAlias(true);
    paintText.setTextSize(textSize);
    paintText.setColor(textColor);
    paintText.setStyle(Paint.Style.FILL);
    
    paintCircle = new Paint();
    paintCircle.setAntiAlias(true);
    paintCircle.setColor(circleColor);
    paintCircle.setStyle(Paint.Style.STROKE);
    paintCircle.setStrokeWidth(10);
    

    2.7 重写onDraw()方法绘制View

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(textContent, 30, textY + 30, paintText);
        canvas.drawCircle(centerX, centerY, circleRadius, paintCircle);
    }
    

    2.8 响应用户手势操作

    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类实现更多的手势检测,如双击、长按、滚动等。

    2.9 添加动画效果

    为了让自定义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);
        }
    });
    

    2.10 对外提供回调接口

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

    public interface OnCircleAnimationStartListener {
        void onCircleAnimationStart();
    }
    
    public void setOnCircleAnimationStartListener(OnCircleAnimationStartListener onCircleAnimationStartListener) {
        this.onCircleAnimationStartListener = onCircleAnimationStartListener;
    }
    
    cv_view.setOnCircleAnimationStartListener(new CustomView.OnCircleAnimationStartListener() {
        @Override
        public void onCircleAnimationStart() {
    
        }
    });
    

    3.总结

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

    相关文章

      网友评论

          本文标题:Android 自定义View流程解析

          本文链接:https://www.haomeiwen.com/subject/inzyrrtx.html