- View的分类
视图View主要分为两类:
单一视图
即一个View,不含子View
视图组
即多个View组成的View,比如LinerLayout,包含子View
- View类的简介
- View类是Android中所有组件的基类,如View是ViewGroup的基类
- View的构造函数
自定义View必须重写至少一个构造函数
public XiaoCaiView(Context context) {
super(context);
}
// 如果View是在.xml里声明的,则调用第二个构造函数
// 自定义属性是从AttributeSet参数传进来的
public XiaoCaiView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 不会自动调用
// 一般是在第二个构造函数里主动调用
// 如View有style属性时
public XiaoCaiView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//API21之后才使用
// 不会自动调用
// 一般是在第二个构造函数里主动调用
// 如View有style属性时
public XiaoCaiView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
- 测量View大小(onMeasure)
- 自定义View的测量方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
setMeasuredDimension(measureWidth, measureHeight);
//布局的宽高都是由这个方法指定
//指定控件的宽高,需要测量
//获取宽高的模式
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
//控件的宽
int width=MeasureSpec.getSize(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
//控件的高
int height=MeasureSpec.getSize(heightMeasureSpec);
//
if(widthMode==MeasureSpec.AT_MOST){
//在布局中定义warp_content
}
if(widthMode==MeasureSpec.EXACTLY){
//在布局中制定了具体的值:100dp
}
if(widthMode==MeasureSpec.UNSPECIFIED){
//尽可能的大
}
}
private int measureHeight(int heightMeasureSpec) {
int defaultHeight=0;
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
switch (heightMode) {
case MeasureSpec.UNSPECIFIED:
defaultHeight = height;
break;
case MeasureSpec.AT_MOST:
defaultHeight = height / 4;
break;
case MeasureSpec.EXACTLY:
defaultHeight = height / 5;
break;
default:
break;
}
return defaultHeight;
}
模式 | 二进制数值 | 描述 |
---|---|---|
UNSPECIFIED | 00 | 默认值,父控件没有给子View任何限制,子View可以设置为任意大小 |
EXACTLY | 01 | 父控件给子View限制了具体的数值 |
AT MOST | 10 | 表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。 |
- setMeasuredDimension
onMeasure方法最后需要调用setMeasuredDimension方法来保存测量的宽高值,如果不调用这个方法,可能会产生不可预测的问题。
- 确定View大小(onSizeChanged)
这个函数在视图发生改变的时候调用
因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。
onSizeChanged()函数如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
可以看出,它又四个参数,分别为 宽度,高度,上一次宽度,上一次高度。
这个函数比较简单,我们只需关注 宽度(w), 高度(h) 即可,这两个参数就是View最终的大小。
- 确定子View布局位置(onLayout)
- 自定义一个可以滑动的ViewGroup来体会一下OnLayout()吧
1.onMeasure()方法告诉子View去测量自己的大小/** * * 使用遍历的方式通知子view进行自测 * * */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int childCount=getChildCount(); for(int i=0;i
- onLayout():放置每个子View的位置
@Override protected void onLayout(boolean b, int left, int up, int right, int down) { // LinearLayout root=new LinearLayout(getContext()); // View child=new View(getContext()); // LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams // .WRAP_CONTENT); // lp.setMargins(10,10,10,10); // root.addView(child,lp); int childCout = getChildCount(); MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); mlp.height = childCout * mScreenHeight; //设置ViewGroup的高 setLayoutParams(mlp); for (int i = 0; i < childCout; i++) { View child = getChildAt(i); if (child.getVisibility() != View.GONE) { //设置每个child在ViewGroup中的位置 child.layout(left, i * mScreenHeight, right, (i + 1) * mScreenHeight); } } }
- OnTouchEvent()实现滑动逻辑:
@Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录触摸起点 mStart = getScrollY(); mLastY = y; break; case MotionEvent.ACTION_MOVE: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } //滑动的距离 int dy = mLastY - y; if (getScrollY() < 0 || getScrollY() > getHeight() - mScreenHeight) { dy = 0; } scrollBy(0, dy); mLastY = y; break; case MotionEvent.ACTION_UP: // 记录触摸终点 mEnd = getScrollY(); int dScrollY = mEnd - mStart; if (dScrollY > 0) { if (dScrollY < mScreenHeight / 3) { mScroller.startScroll( 0, getScrollY(), 0, -dScrollY); } else { mScroller.startScroll( 0, getScrollY(), 0, mScreenHeight - dScrollY); } } else { if (-dScrollY < mScreenHeight / 3) { mScroller.startScroll( 0, getScrollY(), 0, -dScrollY); } else { mScroller.startScroll( 0, getScrollY(), 0, -mScreenHeight - dScrollY); } } break; default: break; } postInvalidate(); return true; }
6. onDraw()(绘制内容)
这个是自定义控件最核心的一个类,需要画的东东都在这个里面。平时我们画什么东西的时候是不是需要一个本子,一个画笔。那么这个onDraw里面也需要画布(Canvas)和画笔(Paint)接下来就介绍一下Canvas和Paint:
- Canvas简介
Canvas我们可以称之为画布,能够在上面绘制各种东西,是安卓平台2D图形绘制的基础,非常强大。 - Canvas的常用操作速查表
操作类型 | 相关API | 备注 |
---|---|---|
绘制颜色 | drawColor, drawRGB, drawARGB | 使用单一颜色填充整个画布 |
绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧 |
绘制图片 | drawBitmap, drawPicture | 绘制位图和图片 |
绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字 |
绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数 |
顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
画布剪裁 | clipPath, clipRect | 设置画布的显示区域 |
画布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数 |
画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切 |
Matrix(矩阵) | getMatrix, setMatrix, concat | 实际画布的位移,缩放等操作的都是图像矩阵Matrix,只不过Matrix比较难以理解和使用,故封装了一些常用的方法。 |
- 简要介绍Paint
绘制的基本形状由Canvas确定,但绘制出来的颜色,具体效果则由Paint确定。
画笔有三种模式,如下:
STROKE //描边
FILL //填充
FILL_AND_STROKE //描边加填充
为了区分三者效果我们做如下实验:
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(40); //为了实验效果明显,特地设置描边宽度非常大
// 描边
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200,200,100,paint);
// 填充
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,500,100,paint);
// 描边加填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(200, 800, 100, paint);
7.Path简介
很多时候我们的一些API方法画出的达不到我们想要的一些酷炫效果,这时候就需要用到Path路径了,Path封装了由直线和曲线(二次,三次贝塞尔曲线)构成的几何路径。你能用Canvas中的drawPath来把这条路径画出来(同样支持Paint的不同绘制模式),也可以用于剪裁画布和根据路径绘制文字。我们有时会用Path来描述一个图像的轮廓,所以也会称为轮廓线(轮廓线仅是Path的一种使用方法,两者并不等价)。
- 最常用的方法:moveTo、 setLastPoint、 lineTo 和 close
moveTo:将点移动到相应的位置
setLastPoint:设置之前操作的最后一个点位置
lineTo:连接上一个点和该点构成线
close:形成闭合曲线
8.贝塞尔曲线简介
- 贝塞尔曲线主要是找到三个点,起始点,控制点,终止点。其中,起始点和终止点称为数据点。
类型 | 作用 |
---|---|
数据点 | 确定曲线的起始和结束位置 |
控制点 | 确定曲线的弯曲程度 |
- 贝塞尔曲线的应用:
- QQ小红点拖拽效果
- 一些炫酷的下拉刷新控件
- 阅读软件的翻书效果
- 一些平滑的折线图的制作
- 很多炫酷的动画效果
实例讲解
简单的QQ拖拽效果