Android中的自定义View(一)
来源:互联网 发布:数据分析属于什么行业 编辑:程序博客网 时间:2024/06/02 05:34
我们在实际开发过程中,如果想要在界面做出酷炫的效果,单单靠系统提供的控件是远远不够的,所以这时候就需要自定义View了。在Google下也有好多比较成熟的开源项目/开源控件可下载来使用,但是毕竟代码的设计都是原作者的思路,我们在使用起来也有可能受到意外的限制或多余功能的冗余。自己掌握自定义View的制作还是很有必要的,毕竟有时项目中还是可能需要独一无二的样式控件,而且自己通过自定义View绘制出来的效果还会带来更多的成就感以及提高后期可维护性。
一般地自定义View分几种方式:
一、继承现有View,扩展现有View的一些功能
例如我们要实现一个支持配置字体的TextView,那么可以继承TextView,并扩展一个叫typeface属性,可以使在XML界面布局时直接配置该属性便可出现特定字体的TextView。
又例如我们要实现一个支持背景颜色渐变动画的容器View,当设置它的背景色变化时它不会立即改变颜色,而是会有一个过渡的渐变过程,这时就要继承LinearLayout 或 RelativeLayout 或 FrameLayout等(根据实现情况定),然后在其内部实现一个当前颜色值,并使循环改变当前背景使实现每帧都不一样的颜色。
二、直接继承View
这种方式主要用于实现一些不规则的效果。采用这种方式一般都是要重写onDraw方法,而且最好对wrap_content和padding做支持,因为不对其进行特殊处理的话,那么当外界在布局中使用wrap_content和padding时就会无法达到预期的效果。wrap_content的支持可以重写onMeasure方法,而padding的支持可在onDraw方法中实现。
三、直接继承ViewGroup
这种方式主要用于实现自定义的布局。采用这种方式稍微有些复杂,它需要合适地处理ViewGroup的measure、layout这两个过程,并同时处理子元素的measure和layout过程,而且还要考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效。
示例一、直接继承View
步骤1、修改activity_main.xml布局文件:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical" > <project.text.com.myapplication.MyView android:id="@+id/myView1" android:layout_width="wrap_content" android:layout_height="100dp" android:layout_margin="20dp" android:padding="10dp" android:background="#ff0000" app:circle_color="#ffff00" /> </LinearLayout>
说明:
1、xmlns:app=http://schemas.android.com/apk/res-auto这行是使用自定义属性必须要的,其中app是属性前缀,可自定义,也可以写成:xmlns:app=http://schemas.android.com/apk/res/包名
2、app:circle_color="#ffff00"这行就是自定义的属性
步骤2、在values目录下创建自定义属性的XML,比如attrs.xml,也可以选择以attrs_开头的文件名:
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="MyView"> <attr name="circle_color" format="color" /> </declare-styleable></resources>
说明:
这里定义了一个格式为“color”的属性“circle_color”,自定义属性还有其他格式,如: reference是指资源id,dimension是指尺寸,而像string、integer和boolean这种是指基本数据类型,等。
步骤3、创建我们的自定义控件类MyView:
public class MyView extends View { private final static int DEF_WIDTH = 200; private final static int DEF_HEIGHT = 200; private int mColor = Color.YELLOW; private Paint mPaint; public MyView(Context context) { this(context, null, 0); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(mColor); // 获取自定义属性的值 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView); mColor = typedArray.getColor(R.styleable.MyView_circle_color, Color.YELLOW); typedArray.recycle(); } // 重写onDraw @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 加入padding值的绘制,使padding属性起作用 final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingLeft(); final int paddingBottom = getPaddingLeft(); int width = getWidth() - paddingLeft - paddingRight; int height = getHeight() - paddingTop - paddingBottom; int radius = Math.min(width, height) / 2; // 绘圆 canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint); } // 重写onMeasure为了能使wrap_content起作用 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(DEF_WIDTH, DEF_HEIGHT); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(DEF_WIDTH, heightSpaceSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpaceSize, DEF_HEIGHT); } }}
说明:
1、MyView直接继承View,可以看到在构造函数中通过TypedArray对象获得原来在XML中定义的自定义属性的值;
2、重写onDraw方法,该方法参数是一个Canvas(画布)对象,本例中只是简单地通过drawCircle方法绘制了一个圆形,其中,第一、二参数是圆的圆心坐标,第三参数是圆的半径,第四参数是一个Paint(画笔)对象,Paint决定绘出的方式,包括笔的粗细、颜色等,相关的内容知识会很多,我们这里暂不详细介绍;
3、上面提到,如若直接继承View的自定义控件最好对wrap_content和padding做支持,可以看到onDraw方法中,我们通过getPadding一系列方法获得padding的值,然后再通过这些值来算出圆心和半径;
4、关于wrap_content的支持,就是重写了onMeasure方法中实现,否则wrap_content的值就相当于match_parent。因为如果View在布局上使用wrap_content,那么它的specMode是AT_MOST模式,在这模式下,它的宽/高等于specSize,而specSize就是parentSize,这种效果就跟使用match_parent完全一样。详细原理可以回顾《View的工作原理(一)之 View的三大过程 和 认识MeasureSpec》 。
5、onMeasure方法接收宽和高的MeasureSpec值,我们在《View的工作原理(一)之 View的三大过程 和 认识MeasureSpec》 中介绍也过MeasureSpec高2位代表SpecMode,低30位代表SpecSize,此两个值就是用于测量出View的宽和高。所以在omMeasure的实现中先获得View的SpecMode,然后再判断若果是AT_MOST,则传递一个我们想要的值,此值可根据实际开发情况决定,如TextView就是按字的长度来对应变大,本示例中我们给定死了两个值DEF_WIDTH 和 DEF_HEIGHT。
自定义控件onDetachedFromWindow和onAttachedToWindow方法
在自定义View中如果有线程或者动画,需要及时停止,那么onDetachedFromWindow是一个很好的时机。当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法会被调用,和此方法对应的是onAttachedToWindow,当包含此View的Activity启动时,View的onAttachedToWindow方法会被调用。同时,当View变得不可见时我们也需要停止线程和动画,如果不及时处理这种问题,有可能会造成内存泄漏。
- Android中的自定义View(一)
- Android 自定义View(一)
- android-自定义View(一)
- android 自定义view(一)
- Android:自定义View(一)
- Android自定义View(一)
- android 自定义view(一)
- android自定义view(一)
- Android自定义View(一)
- Android自定义View(一)
- Android自定义View(一)
- Android 自定义View(一)
- Android 自定义View(一)
- Android 自定义View(一)
- Android自定义View(一)
- Android 自定义View (一)
- Android 自定义View (一)
- Android自定义View(一)
- OIDC--对 JWT标准的id_token进行验证和解密。
- 洛谷Oj-封锁阳光大学-BFS + 染色
- RFX2401C是 RFaxis开发的第二代CMOS 的SOC射频前端集成IC
- bzoj 1966: [Ahoi2005]VIRUS 病毒检测
- myrocks build 镜像
- Android中的自定义View(一)
- [剑指offer]面试题16:反转链表
- Nexus OSS私服仓库的安装和配置以及与Maven整合配置
- 论文发表流程有哪些
- React 中的 定义组件的 两种方式
- nginx 代理请求图片文件等简单使用
- NRF51802 是NRF51822cost down版本,NRF51802是一款低功耗2.4G无线SOC芯片
- Codeforces Round #436 (Div. 2) ABCDEF
- Centos7 静态地址配置