Android自定义View流程

这篇具有很好参考价值的文章主要介绍了Android自定义View流程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

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

基本实现流程:
1、创建自定义类并继承View,设置自定义View的属性
2、测量自定义View——重写onMeasure()
3、绘制自定义View——重写onDraw()
4、与用户进行交互——响应用户事件
5、优化自定义View

以下是具体流程

一、创建View

1、创建继承View的类

自定义的View是继承于View,当然如要自定义的View拥有某些Android已经提供的控件功能,可直接继承于已经提供的控件。

创建一个类,并继承View,本示例创建一个名为CustomView的类,

2、重写包含Context和AttributeSet参数的构造方法

重写其构造方法,为了在XML布局中通过attrs.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);
    }
}

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

<?xml version="1.0" encoding="utf-8"?>
<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,但不包含任何自定义属性

3、在attrs.xml中定义自定义属性——新建attrs.xml文件

为了像系统提供的组件那样,可以在XML布局中设置视图组件的属性,自定义属性通常写在res/values/attrs.xml文件中。声明自定义属性,都属于styleable,一般styleable的name和自定义控件的类名一样。

在res/values路径下新建一个attrs.xml文件,并在其中编辑属性名和格式,常用的格式有string:字符串,boolean:布尔值,color:颜色值, dimension:尺寸值,enum:枚举值,flags:位,float:浮点值,fraction:百分数,integer整数值,reference:引用资源ID。

<?xml version="1.0" encoding="utf-8"?>
<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>

4、获取attr.xml中自定义View的属性——Context的obtainStyledAttributes()方法

在attrs.xml布局中设置属性值后,接着便是在自定义的View中获取这些属性值:

  • 当在xml中创建一个view时,所有在xml中声明的属性都会被传入到view的构造方法中的AttributeSet类型的参数当中。
  • 通过调用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();
}

CustomView定义的字段如下:

private String customText;
private int customColor;
private float customSize;
private float textY;
private float centerX;
private float centerY;
private float maxCircleRadius;

5、提供自定义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就是为了能够让自定义的控件能够根据各种不同的情况以合适的宽高去展示。测量就必须要提到onMeasure方法。onMeasure方法是一个view确定宽高的地方。

重写onMeasure的固定伪代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int measureWidth = measure(widthMeasureSpec, true);
    int measureHeight = measure(heightMeasureSpec, false);
    setMeasuredDimension(measureWidth, measureHeight);
}

计算出height和width之后在onMeasure中要调用setMeasuredDimension()方法,否则会出现运行时异常。

通常,可以使用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);
}

三、绘制自定义View——重写onDraw()

  1. 自定义控件被创建并且测量代码写好后,接下来就调用onDraw()来绘制View

    • onDraw方法包含一个Canvas叫做画布的参数,onDraw()简单来说就两点:Canvas决定要去画什么;Paint决定怎么画
    • Canvas提供了画线方法,Paint就来决定线的颜色。Canvas提供了画矩形,Paint又可以决定让矩形是空心还是实心。
  2. 在onDraw方法中开始绘制之前

    • 画笔Paint对象信息要初始化完毕。因为View的重新绘制是比较频繁的,可能多次调用onDraw,所以初始化的代码不应该放在onDraw方法里。(重要)
  3. Paint画笔

    • 在绘图过程中起到了极其重要的作用,画笔主要保存颜色,样式等绘制信息,指定如何绘制文本和图形,画笔对象有很多设置方法,大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。
  4. Canvas画布

    • 当调整好画笔之后,需要绘制到画布上,这就得用Canvas类。Canvas画布可以绘制任何东西。还需要设置一些关于画布的属性,比如,画布的颜色、尺寸等。
  5. 常见绘制操作有哪些

    • drawRect(),drawRoundRect():绘制矩形。Rect的参数为int类型,而RectF的参数类型为float类型,从这一点上来看,RectF的精度更高一些,但是他们都是通过四个坐标参数来确定一个矩形的区域
    • drawOval(),drawCircle(),drawArc():绘制椭圆,圆,以及圆弧
    • drawText():绘制文本
    • drawLine():绘制线段
    • drawPoint():绘制点
    • drawBitmap():绘制图片
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    
    Paint paint = new Paint();// 此处Paint应在初始化阶段创建
    paint.setColor(customColor);
    paint.setTextSize(customSize);
    canvas.drawText(customText, 100, textY + 100, paint);
}

四、与用户进行交互——响应用户点击事件

某些情况自定义控件不仅只是展示漂亮的样式,还需要支持用户点击,拖动操作。自定义控件就需要做用户交互这一步。

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

View还会经常与使用者进行交互,因此还需要响应和处理用户的手势操作,一般来说,需要重写onTouchEvent(android.view.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、对外提供回调接口

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

private OnCustomViewClickListener onCustomViewClickListener;

public interface OnCustomViewClickListener {
    void onCustomViewClick();
}

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

3、添加用户点击事件响应事件逻辑

在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;
}

4、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

在上述步骤结束之后,其实一个较为完善的自定义控件已经出来。接下来需要确保自定义控件运行得流畅,官方说法是:为了避免控件体验迟缓,确保动画尽可能保持每秒60帧效果。

官网给出的优化建议:

1、避免不必要的代码
2、在onDraw()方法中不应该有会导致垃圾回收的代码
3、尽可能少让onDraw()方法调用,大多数onDraw()方法调用都伴随调用invalidate(),所以不是必须,不要调用invalidate()方法。

(可选)当视图的大小发生变化——重写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更有吸引力和自然,还需要添加一些动画效果,这时候使用属性动画修改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);
    }
});

总结

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

到了这里,关于Android自定义View流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android:绘制自定义View人脸识别框

    项目开发需要自定义View实现一个人脸框,代码实现很平常,一些细节记录一下,方便以后查阅。 代码实现: FaceView.java 注意: 这里我把FaceView的layout_width、layout_height都设置成了\\\"match_parent\\\" 这里面有个 坑 容易踩到 本次开发时,xml中FaceView控件往上一直到第一层父布局宽、高

    2024年02月09日
    浏览(64)
  • Android 自定义View之圆形进度条

    很多场景下都用到这种进度条,有的还带动画效果, 今天我也来写一个。 写之前先拆解下它的组成: 底层圆形 上层弧形 中间文字 那我们要做的就是: 绘制底层圆形; 在同位置绘制上层弧形,但颜色不同; 在中心点绘制文本,显示进度。 按照这个目标,学习下自定义Vi

    2024年02月09日
    浏览(44)
  • Android 自定义View 之 Dialog弹窗

      在日常开发中用到弹窗是比较多的,常用于提示作用,比如错误操作提示,余额不足提示,退出登录提示等,还有用于数据展示的弹窗,上拉弹窗等等,主要为了简化在日常开发中的使用。   Android中的Dialog弹窗是一种用于展示特定信息或者在用户需要进行某些操作时

    2024年02月16日
    浏览(43)
  • [Android]自定义RecyclerView中View的动画

    官方有一个默认Item动画类DafaultItemAnimator,其中 DefaultItemAnimator 继承了SimpleItemAnimator 继承了 RecyclerView.ItemAnimator SimpleItemAnimator 它是一个包装类,用来判断当前的ViewHolder到底是执行移动、移除、添加或者改变等行为。 DefaultItemAnimator 是执行具体动画类,它负责将viewHolder初始化

    2024年02月11日
    浏览(67)
  • Android自定义View之游戏摇杆键盘实现(一)

    public class RemoteViewBg { private Bitmap bitmapBg; public RemoteViewBg(Bitmap bitmap) { bitmapBg = bitmap; } //背景的绘图函数 public void draw(Canvas canvas, Paint paint, Rect src0 ,Rect dst0 ) { canvas.drawBitmap(bitmapBg, src0, dst0, paint); } } 重写系统的触摸时间,判断触摸点在背景范围内还是背景范围外 @Override public b

    2024年04月17日
    浏览(54)
  • Android 自定义View实战—制作一个简易输入框

    这次我们来做一个简易输入框,可以用于密码输入和验证码输入。 依然在EasyView中进行创建,在 com.easy.view 下新建一个 EasyEditText ,继承自 View ,实现里面的构造方法。 ① 构造方法 然后我们继承自 View ,重写里面的构造方法,代码如下: 下面就可以增加样式了。 ② XML样式

    2024年02月10日
    浏览(37)
  • Android自定义View之游戏摇杆键盘实现(一),快手android面试经验

    public class RemoteViewBg { private Bitmap bitmapBg; public RemoteViewBg(Bitmap bitmap) { bitmapBg = bitmap; } //背景的绘图函数 public void draw(Canvas canvas, Paint paint, Rect src0 ,Rect dst0 ) { canvas.drawBitmap(bitmapBg, src0, dst0, paint); } } 重写系统的触摸时间,判断触摸点在背景范围内还是背景范围外 @Override public b

    2024年04月12日
    浏览(46)
  • Android音视频剪辑器自定义View实战!

    Android音视频剪辑器自定义View实战! - 掘金   Android音视频剪辑器自定义View实战! - 掘金 话不多说,先上一个代码完成效果。 动图好像录成横屏的了,也没找到调整反转 GIF 的位置,下面再补一张设计稿静态图吧 最近这几年音视频应用越来越广泛,随之而来的音视频相关的需

    2024年02月12日
    浏览(42)
  • 【Android】自定义View onDraw()方法会调用两次

    自定义了View后,在构造函数中设置画笔颜色,发现它没起效,但是在onDraw()里设置颜色就会起效,出问题的代码如下: 我在构造函数中设置的画笔颜色是红色,但是实际画出的线是黑色的(画笔默认颜色是黑色),分析了一下发现是paint.reset()的问题,reset就是画笔重置设置嘛

    2024年01月19日
    浏览(32)
  • Android 自定义view中根据状态修改drawable图片

    原文地址:Android 自定义view中根据状态修改drawable图片 - Stars-One的杂货小窝 本文涉及知识点: Android里的selector图片使用 底部导航栏的使用 自定义view的步骤了解 建议有以上基础有助于帮助你理解本篇文章.... 起因,由于UI那边的实现,不是按照的Material Design风格设计的,设计的底部

    2024年02月13日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包