android 自定义控件

android 自定义控件学习整理

自定控件是每个android攻城狮都必需掌握的重要技能
一般步骤为1.创建控件的类文件,定义其功能逻辑。2.在res/values目录下创建attrs.xml文件,用于定义该控件的xml标签属性,方便在使用xml声明该控件时设置参数3.实现该控件的构造器,在构造器中把xml标签属性与后台代码中的变量相连接4.完成以上步骤之后,便可使用该控件

三个构造方法这里做一下简单的介绍:第一个构造器是不需要使用xml声明或者不需要使用inflate动态加载时候,第二个构造器是需要在xml中声明此控件时,第三个构造器是接受一个style资源

    public MyButton(Context context){
        super(context);
    }
    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public MyButton (Context context, AttributeSet attrs, int defStyle) {
    	super(context, attrs,defStyle);
    }

自定义控件可以大致的分为组合控件,绘制控件和继承控件

继承控件

比如我想给textview加一个边框,原来的textview是没有这个功能的,那么我想实现这个功能就需要使用自定义控件,由于只需要在原来的基础上加绘制边框即可,就使用继承控件,注意继承的时候要重写构造器。在构造器中完成必要对象的初始化工作,比如初始化画笔等

paint1=new Paint;
paint1.setColor(getResources().getColor(android.R.color.holo_blue_light));

接着我们就需要对控件进行绘制,获取控件的长和宽,绘制方面就需要重写onDraw方法。传入canvas参数,该参数的属性就是坐标,那我们就传入坐标。android的坐标可以分为绝对坐标和相对坐标,绝对坐标就是相对于手机左上角的坐标,相对坐标就是相对于父控件的坐标。这里我们要画边框,就调用canvas的drawline方法,该方法的参数就是(起始x,起始y,结束x,结束y),setColor(int color)设置绘制的颜色,setStrokeWidth(float width)设置绘制的粗细,具体的方法就不在这里展示了,只是做一个大概的整理。

@Override
protected void onDraw(Canvas canvas){
	//在回调父类方法前,实现自己的逻辑,对于TextView来说就是绘制文本前
	super.onDraw(canvas);
	//在回调父类方法后,实现自己的逻辑
}

这里做一个简单的例子

 paint.setStrokeWidth(20);
//  画TextView的上下两个边
canvas.drawLine(getLeft(),getX(),getWidth(),getX(),paint);
canvas.drawLine(getLeft(),getX()+getHeight(),getWidth(),getX()+getHeight(),paint);
super.onDraw(canvas);

如果想直接改变控件的形状等,可以不用重新自定义一个控件,可以在style中定义一个xml文件,将backgroud设置为这个文件。
绘制圆角控件

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
      android:shape="rectangle">                      //shape用于定义形状,有四种形状(矩形rectangle| 椭圆oval | 直线line | 圆形ring)
     <solid android:color="#ADD8E6"/>              //设置填充形状的颜色
<corners
    android:topLeftRadius="50dp"
    android:bottomLeftRadius="50dp"
    android:topRightRadius="0dp"
    android:bottomRightRadius="0dp"  />
 </shape>

复合控件

继承控件是最简单的一种,并不需要重写太多方法,接下来介绍复合控件,复合控件可以很好的创建出复用性很高的控件,一款app为了使界面得到统一,会使用相似的界面进行编辑,如果每个界面都去编写xml文件这无疑会使代码量大大增加,如果能使用一个模板来套用就会方便,复合控件就是这样的一个控件,它具有通用性和可定制性,有提供给调用者的丰富接口

首先需要定义控件的属性,在res资源目录下的values创建一个attrs.xml的属性定义文件,这里引用他人的代码作为范例

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 自定义的属性-->
    <declare-styleable name="Header">
        <attr name="titleTextSize" format="dimension" />
        <attr name="titleTextColor" format="color" />
        <attr name="titleText" format="string"/>
    </declare-styleable>
</resources>

declare-styleable用来声明使用了自定义属性,name就是它的名字
attr是具体的属性,format是它的类型,as ide在编写的时候会提醒你有哪些类型可选择。可以用“|”来分隔不同的属性。

定义好属性以后就需要获取属性来设置,系统提供了TypedArray来获取自定义属性集

TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.Header);
titleText=a.getString(R.styleable.Header_titleText);
titleTextColor=a.getColor(R.styleable.Header_titleTextColor, Color.WHITE);
titleTextSize=a.getDimension(R.styleable.Header_titleTextSize,20f);

通过TypedArray对象的getString(),getColor()方法就可以获取属性值,属性的第二个值就是初始值。获取到属性值后需要调用TypedArray的recyle方法来完成资源的回收。这些语句都是在控件的第三个构造器中完成。

a.recycle();

在获取到属性后就开始组合控件,将获取到的属性值加入控件中,比如调用textView.setText将文本内容插入textView中,组合控件就用到了之前分析的动态添加控件的代码,这里不做详细分析。

定义接口就相当于做一个模板给使用者,那么就需要用到接口回调,使用使用者自己定义的接口,具体原理参照我后面对于接口回调的详细分析,这里就只贴出部分代码

public interface mClickListener{
	void mClick();
}//定义一个接口,里面是抽象的方法,具体的实现由使用者去定义

//在代码中注册好监听事件,即确定好在哪使用,但不写出怎么使用,如上面强调的那样,具体的实现由使用者去定义,比如这里就是在button中触发这个接口
button.setOnClickListener(new OnClickListener(){
	@Override
	public void onClick(View v){
		mclickListener.mClick();
	}
}
//在代码中暴露一个接口来给使用者注册接口回调,使用者调用这个方法可以将自己实现了接口的类传进去,就注册了接口,所以之前的代码也是使用的是mclickListener,而不是mListener。
public void setListener(mClickListener mListener){
	mclickListener=mListener
}

写好接口后在相应的代码中实现接口,就完成了接口回调。最后一步就是引用UI模板,在引用前需要指定第三方控件的名字空间,在xml文件中经常使用android:来引用属性,在as中,第三方控件都要使用

xmlns:自己控件空间名字="http://schemas.android.com/apk/res-auto"

接下来就可以将UI模板写入到布局文件中了

绘制控件

绘制控件需要重写它的onDraw(),onMeasure()等方法来实现绘制逻辑,通过重写onTouchEvent等触控事件来实现交互逻辑,这里暂时还没有接触到需要完全重写的view,等到日后遇到了再来更新。


没错,我来更新了 2019-4-20
首先明确一点:canvas是决定画什么,paint是决定怎么画,比如填充什么,要不要设置渐变,画的顺序方向等等。
最近学习到了完全自定义绘制的view,基本的图形可以用canvas.drawxxxx来绘制,不同的图形有不同的参数,这里就不一一阐述了,如果想画组合图形
在这里插入图片描述
这种就需要调用Path来组合图形,path有几大类方法,第一大类是直接描述路径,这个又分为两小种,一是组合基本图形,调用addxxxx就可以组合各种基本图形,另外一种就是画线,可以画直线和贝塞尔曲线。这里整理了一篇他人的文章作为笔记
https://www.jianshu.com/p/9ad3aaae0c63
第二大类就是辅助设置,比如设置图形交汇的方式,设置文字等等


Paint详解:

颜色

Paint可以直接绘制颜色,也可以设置渐变颜色,在xml文件中经常用到的shape定义控件的外表中常用的渐变的java表现形式就是这种,也可以直接将bitmap作为颜色填入。直接填入颜色这里就不整理了,渐变和bitmap着色都是利用的shader(着色器)

public LinearGradient (float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile);
Shader shader =new LinearGradient(......);
Paint paint=new Paint();
paint.setShader(shader);

这里只做一个简单的例子:线性渐变的方法,类似的还有辐射渐变,扫描渐变等等,这里也不一一列举了。

圆形头像

在app的设计中经常会碰到要实现圆形头像,我整理出来有两种实现方法,一是先将图片截取成圆形,再将图片放入控件中,二是绘制圆形的控件,再将图片与空间取交集得到圆形的头像。
首先介绍第二种方法:

mSrcBitmap = Bitmap.createScaledBitmap(mBitmap, mWidth, mHeight, false);//对图片进行缩放
mShader = new BitmapShader(mSrcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);//设置shader
mPaint = new Paint();
mPaint.setShader(mShader);//将shader存入画笔
//最后再绘制一个圆,将画笔传入即可

这里引用一张图片作为讲解
在这里插入图片描述
第一种方法重点则是如何绘制出圆形的图片,实际上也是利用了两个图像的重叠取交集的方法来设计的,这里整理了一篇博客

https://www.jianshu.com/p/57025c3491c1?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq

第一种方法用到的一个重要的参数就是PorterDuff.Mode,这个参数定义了两个图形交互的绘制方式,比如取交集,或者取并集,这里引用一张经常看到的图片作为整理,注意绘制的时候要先画dst部分再画src部分,即先画红色再画蓝色

在这里插入图片描述
实际上Shader中也可以用这个参数,ComposeShader:混合着色器,首先创建两个着色器,再new ComposeShader赋值给新的shader即可

滤镜

同样也是在paint里面设置ColorFilter,用它的子类来具体的实现,分为LightingColorFilter,PorterDuffColorFilter和ColorMatrixColorFilter,LightingColorFilter是模拟光照效果,对颜色进行简单的处理,PorterDuffColorFilter 的作用是使用一个指定的颜色和一种指定的PorterDuff.Mode 来与绘制对象进行合成,ColorMatrixColorFilter则是利用矩阵进行更复杂的处理,这里没有遇到使用滤镜,就不多做介绍了。

Xfermode

这就是绘制圆形头像的第二种方法所用到的核心方法。以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,选取一个 PorterDuff.Mode 作为绘制内容的颜色处理方案。
用离屏缓冲

int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(rectBitmap, 0, 0, paint); // 画方
paint.setXfermode(xfermode); // 设置 Xfermode
canvas.drawBitmap(circleBitmap, 0, 0, paint); // 画圆
paint.setXfermode(null); // 用完及时清除 Xfermode
canvas.restoreToCount(saved);
//这里都是先画dst,再画src

控制透明区域:透明区域不要太小,要让它足够覆盖到要和它结合绘制的内容


裁剪变换
canvas

首先明确一个小知识:
canvas的sava 和 restore

save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。

对canvas中特定元素的旋转平移等操作实际上是对整个画布进行了操作,所以如果不对canvas进行save以及restore,那么每一次绘图都会在上一次的基础上进行操作,最后导致错位。比如说你相对于起始点每次30度递增旋转,30,60,90.如果不使用save 以及 restore 就会变成30, 90, 150,每一次在前一次基础上进行了旋转。save是入栈,restore是出栈。

所以在每次对canvas处理之前都要对canvas进行save和restore操作。

裁剪

canvas.clipxxx,可以是Rect,可以是path,这里就不多做介绍,需要的时候再翻翻就可以。

几何变换

可以进行平移,旋转,缩放,错切,这里对错切做个整理,因为其他几个都比较好理解,这里引用一个图片作为理解
在这里插入图片描述

canvas.save();
canvas.skew(0, 0.5f);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();
使用 Matrix 来做变换

setTranslate(float dx,float dy):控制Matrix进行位移。
setSkew(float kx,float ky):控制Matrix进行倾斜,kx、ky为X、Y方向上的比例。
setSkew(float kx,float ky,float px,float py):控制Matrix以px、py为轴心进行倾斜,kx、ky为X、Y方向上的倾斜比例。
setRotate(float degrees):控制Matrix进行depress角度的旋转,轴心为(0,0)。
setRotate(float degrees,float px,float py):控制Matrix进行depress角度的旋转,轴心为(px,py)。
setScale(float sx,float sy):设置Matrix进行缩放,sx、sy为X、Y方向上的缩放比例。
setScale(float sx,float sy,float px,float py):设置Matrix以(px,py)为轴心进行缩放,sx、sy为X、Y方向上的缩放比例。
同样也需要canvas.save保存状态

canvas.save();
camera.save(); // 保存 Camera 的状态
camera.rotateX(30); // 旋转 Camera 的三维空间
camera.applyToCanvas(canvas); // 把旋转投影到 Canvas
camera.restore(); // 恢复 Camera 的状态
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();

绘制就整理到这里,有些添加文字等等就没有在这里整理出来了,如果需要再搜索一下项目即可。


2019-5-15 更新

动画

动画可以分为逐帧动画,补间动画,属性动画
首先整理自己最先接触到的属性动画。

属性动画

属性动画就是通过不断的修改view的属性达到动画的目的,比如我想将一个view移动到某个位置,如果直接一下移动过去就没有动画,但我可以每次只移动一小点,通过不断的移动达到动画的目的,那么我就需要用到一个方法:postdelayed,这个方法就是延时启动一个线程,在for循环中不断的传入延时进去,启动不同的线程让view移动就达到了动画的目的,而这个实际上就是调用setter方法改变了view的属性,所以叫属性动画。
属性动画的api实现原理就是这样。

ViewPropertyAnimator

使用方法 View.animate()+方法 View.animate()会得到ViewPropertyAnimator对象
具体的方法在这里借鉴一个表
在这里插入图片描述
setDuration(int duration) 设置动画时长
setInterpolator(Interpolator interpolator) 速度设置器
https://www.cnblogs.com/onone/articles/6588335.html 具体的几种速度类型
PathInterpolator 自定义动画完成度 / 时间完成度曲线。用这个 Interpolator 你可以定制出任何你想要的速度模型。定制的方式是使用一个 Path 对象来绘制出你要的动画完成度 / 时间完成度曲线。
也可设置监听,对动画的使用进行监听。

ObjectAnimator

区别:对自定义的属性进行动画操作

ObjectAnimator animator=ObjectAnimator.ofXXX(view对象,属性名(String),(目标值,中间值),最终值);
animator.start();//动画开始
//XXX是方法返回值的类型,比如translationx就是float类型

注意因为是对view里面的setter进行操作,所以自定义view里面一定要有相应属性的setter方法,名字也要对好,get方法也要准备好,因为如果没有目标值,只有最终值,会调用getter方法去获取view属性的初始值。
setter方法中也要调用invalidate()通知view重绘

像有一些属性不是简单的直接加就行,比如argb颜色的调整,从一个颜色到另一个颜色,不是把整个数据看作一个整体进行调整,而是只调整其中的一部分,这样基础属性就不能满足了,需要使用自定义的,安卓sdk21以上自带了ofArgb调整,所以可以直接用,如果都没有,就需要完全自定义,自定义一个类实现TypeEvaluator<属性的类型>,然后重写evaluate方法,里面重写过程计算。
-----------------------------------------------------------分割线-----------------------------------------------------------
2019.7.9 更新
摸了许久的?,继续来更新整理。
这次正在学习测量和布局部分。首先整理一下自定义view测量,布局的过程,不论是测量还是布局都是从外向内,从上到下依次传递的,如果父布局是个viewgroup,那么他的mesure和layout都是通知子view负责调用自己的onMesure,onLayout进行测量布局,所以如果布局是view,没有子view,那么它的layout,mesure就为空,要重写测量,布局方法也是重写onMeasure,onLayout
方法,onMeasure的作用是测量并且调用measure,layout的作用是传递父布局传递过来的位置和尺寸并且调用layout

举个简单的例子,如果想修改控件的大小,那么就可以重写onMeasure,调用 super.onMeasure() ,触发原有的自
我测量,然后用 getMeasuredWidth() 和getMeasuredHeight() 来获取到之前的测量结果,并使用自己的算法,根据测
量结果计算出新的结果,最后调用 setMeasuredDimension() 来保存新的结果。如果想完全自己测量控件的高度,就不要super,完全由自己计算高度,再setMeasuredDimension() 保存结果,不过这是没有加上父view的限制,在写xml文件的时候通常会加上layout_xxx,这些就是父view的限制,所以在完全自定义onMeasure时需要使用 resolveSize() 来让子 View 的计算结果符合父 View 的限制(如果用自己的方式来满足父 View 的限制也行)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值