Android自定义View-自定义组件

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

Android自定义组件

  • android自定义组件一般有三种实现方式:
  • 一、组合控件:组合控件,顾名思义就是将一些小的控件组合起来形成一个新的控件,这些小的控件多是系统自带的控件。
  • 二、自绘控件: 何为自绘控件,就是完全用Paint和canvas画出来的,就是在onDraw()方法里面绘画,在onMeasure()方法里面进行测量,如果是容器在onLayout()方法中定位每个子组件。
  • 三、继承控件: 就是继承已有的控件,创建新控件,保留继承的父控件的特性,并且还可以引入新特性。

自绘控件也分两种,自定义组件和自定义容器,自定义组件是继承View类,自定义容器时继承ViewGrounp;今天主要分析下自定义组件;还是举个例子来的实际些,假如我们要画一个最简单的TextView,首先想到的就是canvas.drawText()方法,怎么画了?还是得一步一步来:
(1) )写一个MyTextView类继承View,重写三个构造方法,当然还有onDraw()和onMeasure()方法,如下代码:

public class MyTextView extends View{
/**
 * 文本颜色
 */
private int textColor;
/**
 * 字体大小
 */
private float textSize;
/**
 * 文本
 */
private String text;

/**
 * 绘制时控制
 */
private Paint mPaint;
private Rect mBoud;

public MyTextView(Context context) {
    this(context, null);
}

public MyTextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

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

(2) 接下来就要想既然是TextView,那肯定是有text,color,textSize等属性。在values目录下的attrs.xml中定义好这些属性,以便在xml中引入MyTextView能够直接操作这些值,然后在有三个参数的构造方法里面将这些属性与控件关联,如下:

attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
    <attr name="textColor" format="color|reference"/>
    <attr name="textSize" format="dimension|reference"/>
    <attr name="text" format="string|reference"/>
</declare-styleable>
</resources>
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextView, 0, 0);
    textColor = typedArray.getColor(R.styleable.MyTextView_textColor, 0);
    textSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 0);
    text = typedArray.getString(R.styleable.MyTextView_text);
    typedArray.recycle();
}

这里解释下三个构造方法,一般一个参数的调用两个参数的,两个参数调用三个参数的,属性与控件的关联写在三个参数的构造方法里面即可。

(3) 已经定义好了属性,那接下来在构造方法里用Paint画笔设置好这些属性,然后用onDraw方法里面的canvas画一个textview,如下:

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextView, 0, 0);
        textColor = typedArray.getColor(R.styleable.MyTextView_textColor, 0);
        textSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 0);
        text = typedArray.getString(R.styleable.MyTextView_text);
        typedArray.recycle();

        mPaint = new Paint();
        // 设置画笔为抗锯齿
        mPaint.setAntiAlias(true);
        mPaint.setColor(textColor);
        mPaint.setTextSize(textSize);
        //获取绘制文本的宽和高
        mBoud = new Rect();
        mPaint.getTextBounds(text, 0, text.length(), mBoud);
    }

@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //canvas.drawText(text, x, y, paint); //第一个参数是文本,第四个参数是画笔,第二个参数x默认是字符串的左边在屏幕的位置, 第三个参数y是这个字符文本baseline基线在屏幕上的位置,不是这个字符的中心在屏幕的位置
        Log.v("MyTextView", "getWidth:"+getWidth()/2);
        Log.v("MyTextView", "mBoud.width:"+mBoud.width()/2);
        Log.v("MyTextView", "getHeight"+getHeight()/2);
        Log.v("MyTextView", "mBoud.height:"+mBoud.height()/2);
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        int baseline = (getMeasuredHeight() / 2) - ((fontMetrics.bottom + fontMetrics.top) / 2);
        Log.v("MyTextView", "baseline:"+baseline);
        Log.v("MyTextView", "getMeasuredHeight:"+getMeasuredHeight());
        Log.v("MyTextView", "fontMetrics.top:"+fontMetrics.top);
        Log.v("MyTextView", "fontMetrics.bottom:"+fontMetrics.bottom);
        Log.v("MyTextView", "fontMetrics.ascent:"+fontMetrics.ascent);
        Log.v("MyTextView", "fontMetrics.descent:"+fontMetrics.descent);
        canvas.drawText(text, getWidth()/2 - mBoud.width()/2, baseline, mPaint);
    }

**onDraw方法里面的baseline是基线,在Android中,文字的绘制都是从Baseline处开始的,所以第三个参数y不是这个文本中心在屏幕的位置,而是文本基线在屏幕的位置,感兴趣的可移至BaseLine

到现在其实我们的画的MyTextView已经可以用了,额?不是还有一个onMeasure方法什么都没有写吗,不急我们先在xml中引入试试看效果**

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:id="@+id/activity_mtext_view"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.yilei.ownerdraw.activity.MTextViewActivity">

    <com.yilei.ownerdraw.view.MTextView
        android:layout_width="wrap_parent"
        android:layout_height="wrap_content"
        app:texts="Android自定义TextView"
        app:text_colors="@color/chart_green"
        app:text_sizes="14sp"/>
</RelativeLayout>

这里写图片描述

可以看到,我们设置textview的宽高都是wrap_content,但是运行之后都占满了屏幕,之前重写的onMeasure方法就就派上用场了

  • 1.首先要了解测量组件的宽高时,是由组件的父容器调用的,首先容器他会遍历容器内所有的子组件,一个一个的测量宽高,这里的我们自定义textView的父容器就是最外层RelativeLayout
  • 2.然后要了解MeasureSpec类,这个类为我们提供了两个方法,getMode(int measureSpec)和getSize(int measureSpec)可以获得宽和高的尺寸和模式,这个类还为我们提供了三个常量MeasureSpec.EXACTLY、MeasureSpec.AT_MOST、MeasureSpec.UNSPECIFIED;
  • MeasureSpec.EXACTLY:已经为TextView指定确定尺寸或者为math_parent时
  • MeasureSpec.AT_MOST:当TextView为wrap_content时
  • MeasureSpec.UNSPECIFIED:TextView未定义宽高

上面我们设置了高为wrap_content,但是我们没有测量textview的高,如果指定了为wrap_content,那么此时的高应该是TextView本身的高,所以需要测量然后返回给父容器,下面我们重写onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = measureWidth(widthMeasureSpec);
    int height = measureHeight(heightMeasureSpec);
    setMeasuredDimension(width, height);
}

private int measureWidth(int widthMeasureSpec){
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    int size = MeasureSpec.getSize(widthMeasureSpec);
    int width = 0;

    if(mode == MeasureSpec.EXACTLY){
        //如果是math_parent或确定尺寸
        width = size;
    }else if(mode == MeasureSpec.AT_MOST){
        //如果是wrap_parent
        width = getPaddingLeft() + mBound.width() + getPaddingRight();
    }
    return width;
}

private int measureHeight(int heightMeasureSpec){
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    int size = MeasureSpec.getSize(heightMeasureSpec);

    int height = 0;
    if(mode == MeasureSpec.EXACTLY){
        //如果是math_parent或确定尺寸
        height = size;
    }else if(mode == MeasureSpec.AT_MOST){
        //如果是wrap_parent
        height = getPaddingTop() + mBound.height() + getPaddingBottom();
    }
    return height;
}

再来试试效果
这里写图片描述

现在TextView测量完成了,已经可以设置宽高了,宽高设置为wrap_content则是TextView本身的宽高,因为我们还考虑了Padding,所以也可以设置TextView的Padding,如果测量时没考虑Padding在xml中设置是不起作用的,有兴趣的的可以试一试

当然,这只是一个很简单的自定义TextView组件,仅为简单的理解自绘View,另外还有自定义容器,自定义容器时就要考虑onLayout方法了,因为要定位每个子组件在容器中位置。想要熟练使用自定义View,必须熟练使用Paint和canvas,这是自定义View的基本。下一篇介绍自定义容器

自定义组件和自定义容器的一些小demo,有兴趣的可以移至OwnerDraw

自定义控件——初识自定义控件 本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。开发的时候,因为业务需求或者封装需要,我们会进行自定义控件。 说在前面,本篇涉及到一些东西 * onMeasure * onLayout * onDraw * MeasureSpec (32位二进制数,头两位模式(Mode),后两位大小(Size)) * onFinishInflate * ViewGr 阅读详情

相关推荐

Android中的自定义组件(view)

Android App开发过程中,很多时候会遇到系统框架中提供的控件无法满足我们产品的设计需求,那么这时候我们可以选择先Google下有没有比较成熟的开源项目可以让我们用.所以我们要自定义view满足功能需求.

bruce_lee0的博客 874

Android自定义组件:2、如何实现和使用自定义组件自定义属性

声明:本教程不收取任何费用,欢迎转载,尊重作者劳动成果,不得用于商业用途,侵权必究!!! 目录 一、前言 二、如何实现自定义组件 步骤1:写 attrs.xml 资源文件 1、attrs.xml 文件 和 R 文件对应关系 2、attrs.xml 文件重点注意 (1)declare-styleable子元素 (2)attrs.xml 文件只做属性的描述 步骤2:写自定义类 步骤3:使用自定义组件自定义属性 三、效果展示-简单小结 1、效果展示 2、执行流程 3、可以不写 attrs.xml 文件 4、问题 四、把代码移植到 Android Studio中去 1、Android Studio中

Androidandroid自定义view (UI组件),三个绘图工具类详解】

自定义View的实现方式有以下几种: 组合控件,继承控件,自绘控件。 详细可分为:自定义组合控件,继承系统View控件,继承系统ViewGroup,自绘View控件,自绘 ViewGroup控件。 绘图相关的 一些API,他们分别是Canvas(画布),Paint(画笔),Path(路径) ,同时也是自定义View的基础。...

m0_63658317的博客 1233

Android基础篇-自定义组件

Android自定义组件布局

sandmswift的博客 2223

Android自定义控件

invalidate方法用于在UI线程中请求重绘视图。当我们希望在主线程中更新UI时,可以在UI线程中直接调用invalidate()方法。内部实现:调用了invalidate方法后,为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,开始View树重绘流程(只绘制需要重绘的视图)postInvalidate方法用于在非UI线程中请求重绘视图。

RandowWrite 1655

Android自定义组件:1、什么是自定义组件自定义组件的方式、定义自定义属性

一、什么是自定义组件 Android为我们提供了丰富的 UI组件,基本的应用已经足够了,但在实际开发中,如果我们想构建更加复杂的 UI视图,此时就需要使用自定义组件了。 二、自定义组件的方式 自定义组件的三种方式: 1、组合现有组件 组合现有Android默认提供的组件,继承ViewGroup或其子Layout类等布局类进行组合。 2、在某一个组件上进行扩展 调整现有Android默......

鲁迷那的专栏—坚持实践后再写出来! 2559

Android笔记62】Android自定义View组件

Android中,所有的UI组件都是继承自View类的,所以我们自定义View组件的时候,也需要自己编写一个类继承自View类,然后重写其中的对应方法即可。第一步:添加自定义控件属性。第二步:自定义组件类,继承自View类。第三步:将自定义的控件属性和自定义View关联起来。第四步:绘制自定义View控件,重写【onDraw()】方法。第五步:设置自定义View控件的显示位置,重写【onMeasure()】方法。第六步:添加自定义View控件的事件。

✅ Java 开发工程师 1212

android构建自定义View自定义组件

Android中,你的应用程序程序与View组件有着一种固定的联系,例如按钮(Button)、文本框(TextView),可编辑文本框(EditText),列表框(ListView),复选框(CheckBox),单选框(RadioButton),滚动条(Gallery),微调器(...

chilongkun8891的博客 330

Android Studio开发基础之自定义View组件

一般情况下,不直接使用ViewViewGroup类,而是使用使用其子类。例如要显示一张图片可以用View类的子类ImageView,开发自定义View组件可分为两个主要步骤: 一、创建一个继承自android.view.View类的View类,并且重写构造方法。 如下,新建一个名为MyView.Java的Java类文件,重写一个带Context的构造方法和onDraw()方法(用来重新绘制A

智慧空间实验室 1万+

Android自定义View:打造个性化UI组件

自定义View可以让开发者根据自己的需求设计出独特的UI组件,从而提高应用界面的美观度。相比系统自带的View自定义View具有更高的灵活性和扩展性,可以更好地满足个性化需求。继承现有View自定义View的一种常见方式。开发者可以继承系统自带的View,然后重写其绘制方法(onDraw())和触摸事件处理方法(onTouchEvent()),实现个性化的UI组件。@Override // 自定义绘制逻辑 @Override // 自定义触摸事件处理逻辑 。

2501_92487499的博客 456

Android-自定义View组件切换Activity界面

一般情况下,开发Android应用程序时,不直接使用ViewViewGroup类,而是使用这两个类的子类。显示图片就可以使用ImageView类,。虽然Android提供了很多继承了View类的UI组件,但在实际开发中,出现不满足程序需要的情况时,可以通过继承View类来开发自己的组件。开发自定义View组件: 创建一个继承Android.view.View类的View类,并重写构造方法。

eva1994的博客 778

从0到1Android自定义View(三)画图的五个元素组件

前言迟来的第三篇,这篇还是知识点,不过没有细讲每个方法的运用等等,因为我个人觉得,API 方法是变的,且死记硬背效果不佳,当然能记住是好事,我们必需学会在编程中猜想和查找到对应的方法,所以这里提供的只是一些方法的概况,下一篇就是进行实践来完善这些知识点。目录一、画图五个元素组件简介Android Framework 提供了一些 2D 画图的 API,android.graphics 包就是其中之一。

GitHubClub 2113

Android 自定义view(二) 如何实现自定义组件

Android 自定义组件(一) 基本实现方式和自定义属性:http://blog.csdn.net/jjwwmlp456/article/details/38728519

打渔还是晒网 —— stone 4008

安卓自定义View组件

1.创建一个自定义类组建这个类是我们自定义view组件的类,它一定是继承了view类或view的子类。而且这个类必须实现一个带参数(Context context和AttributeSet Attributes)的构造方法。定义自定义Attributes例如:res/values/attrs.xml<resources> <declare-styleable name=""> <

zfireear的博客 674

android自定义View组件封装jar包

通常情况下自定义View在公司其他相似的项目中复用性级别高的,我们都会做一些封装,省去粘贴复制而且有可能被修改的的情况,封装成jar也是有弊端的,所以根据情况而定; 个人总结的两个弊端: 1.一般jar看不见源码,如果没有很好的保存源码,在做扩展上就有局限性了。 2.没有demo或者文档资料,jar中对外的接口展示不直观; 今天拿到朋友给的一个ViewPagerCycle轮播图实现了循环

Daisy的专栏 3307

android自定义view (UI组件

自定义View的实现方式有以下几种: 组合控件,继承控件,自绘控件 详细可分为:自定义组合控件,继承系统View控件,继承系统ViewGroup,自绘View控件,自绘 ViewGroup控件组合控件就是将多个控件组合成一个新的控件,可以重复使用。应用场景:在项目中经常会遇到一些比较复杂的UI块需要用在多处使用,那么我们就可以通过五大布局 和基本控件组合成一个新的布局View,这样就可以方便的将该UI用在项目的不同页面中,比如一个标题 栏。...

suiyue010211的博客 480

Android开发中自定义View设定到FrameLayout布局中实现多组件显示

<br />在Android开发中,如果我们想自定义View视图组件,并实现在FrameLayout布局中多个组件的同时显示呢?<br />举例来说想在自定义View上面显示Button 等View组件需要完成如下任务<br />1.在自定义View的类中覆盖父类的构造(注意是2个参数的)1 2 3 4 5 6 7 8 9 10 publicclass MyView2 extendsView{   public MyView2(Context context,Attribute

wylwhd的专栏 856
上一篇: NoScrollViewPager
five-lei
博客等级 码龄11年 1粉丝 2原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值