Android自定义view原理及自定义View示例
自定义view如何分类
- 自定义View:只需要重写onMeasure()和onDraw(),在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View
- 自定义ViewGroup:只需要重写onMeasure()和onLayout(),一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout
view的构造函数:共有4个
// 如果View是在Java代码里面new的,则调用第一个构造函数public CustomView(Context context) {super(context);}// 如果View是在.xml里声明的,则调用第二个构造函数// 自定义属性是从AttributeSet参数传进来的public CustomView(Context context, AttributeSet attrs) {super(context, attrs);}// 不会自动调用 // 一般是在第二个构造函数里主动调用 // 如View有style属性时 public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}//API21之后才使用 // 不会自动调用 // 一般是在第二个构造函数里主动调用 // 如View有style属性时 public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}
LayoutInflater 的基本用法
LayoutInflater.from(this).inflate()inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
- 如果 root 为 null,attachToRoot 将失去作用,设置任何值都没有意义。
- 如果 root 不为 null,attachToRoot 设为 true,则会给加载的布局文件的指定一个父布局,即 root。
- 如果 root 不为 null,attachToRoot 设为 false,则会将布局文件最外层的所有 layout 属性进行设置,当该 view 被添加到父 view 当中时,这些 layout 属性会自动生效。
- 在不设置 attachToRoot 参数的情况下,如果 root 不为 null,attachToRoot 参数默认为 true。
Android两种坐标系
- View的静态坐标方法
View的静态坐标方法 | 解释 |
---|---|
getLeft() | 返回View自身左边到父布局左边的距离 |
getTop() | 返回View自身顶边到父布局顶边的距离 |
getRight() | 返回View自身右边到父布局左边的距离 |
getBottom() | 返回View自身底边到父布局顶边的距离 |
getX() | 返回值为getLeft()+getTranslationX(),当setTranslationX()时getLeft()不变,getX()变。 |
getY() | 返回值为getTop()+getTranslationY(),当setTranslationY()时getTop()不变,getY()变。 |
- 手指触摸屏幕时MotionEvent
MotionEvent坐标方法 | 解释 |
---|---|
getX() | 当前触摸事件距离当前View左边的距离 |
getY() | 当前触摸事件距离当前View顶边的距离 |
getRawX() | 当前触摸事件距离整个屏幕左边的距离 |
getRawY() | 当前触摸事件距离整个屏幕顶边的距离 |
- 获取宽高
View宽高方法 | 解释 |
---|---|
getWidth() | layout后有效,返回值是mRight-mLeft,一般会参考measure的宽度(measure可能没用),但不是必须的。 |
getHeight() | layout后有效,返回值是mBottom-mTop,一般会参考measure的高度(measure可能没用),但不是必须的。 |
getMeasuredWidth() | 返回measure过程得到的mMeasuredWidth值,供layout参考,或许没用。 |
getMeasuredHeight() | 返回measure过程得到的mMeasuredHeight值,供layout参考,或许没用。 |
- 获取view位置
View的方法 | 结论描述 |
---|---|
getLocalVisibleRect() | 获取View自身可见的坐标区域,坐标以自己的左上角为原点(0,0),另一点为可见区域右下角相对自己(0,0)点的坐标,其实View2当前height为550,可见height为470。 |
getGlobalVisibleRect() | 获取View在屏幕绝对坐标系中的可视区域,坐标以屏幕左上角为原点(0,0),另一个点为可见区域右下角相对屏幕原点(0,0)点的坐标。 |
getLocationOnScreen() | 坐标是相对整个屏幕而言,Y坐标为View左上角到屏幕顶部的距离。 |
getLocationInWindow() | 如果为普通Activity则Y坐标为View左上角到屏幕顶部(此时Window与屏幕一样大);如果为对话框式的Activity则Y坐标为当前Dialog模式Activity的标题栏顶部到View左上角的距离。 |
- View滑动相关坐标系
View的scrollTo()和scrollBy()是用于滑动View中的内容,而不是改变View的位置;改变View在屏幕中的位置可以使用offsetLeftAndRight()和offsetTopAndBottom()方法,他会导致getLeft()等值改变
View的滑动方法 | 效果及描述 |
---|---|
offsetLeftAndRight(int offset) | 水平方向挪动View,offset为正则x轴正向移动,移动的是整个View,getLeft()会变的,自定义View很有用。 |
offsetTopAndBottom(int offset) | 垂直方向挪动View,offset为正则y轴正向移动,移动的是整个View,getTop()会变的,自定义View很有用。 |
scrollTo(int x, int y) | 将**View中内容(不是整个View)**滑动到相应的位置,参考坐标原点为ParentView左上角,x,y为正则向xy轴反方向移动,反之同理。 |
scrollBy(int x, int y) | 在scrollTo()的基础上继续滑动xy。 |
setScrollX(int value) | 实质为scrollTo(),只是只改变Y轴滑动。 |
setScrollY(int value) | 实质为scrollTo(),只是只改变X轴滑动。 |
getScrollX()/getScrollY() | 获取当前滑动位置偏移量。 |
Android 视图绘制流程
view的层级结构
一. onMeasure() 决定View的大小;
源码:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
public class MyView extends View { @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(200, 200);}}
getMeasureWidth与getWidth的区别
- getMeasureWidth:在measure()过程结束后就可以获取到对应的值; 通过setMeasuredDimension()方法来进行设置的.
- getWidth:在layout()过程结束后才能获取到; 通过视图右边的坐标减去左边的坐标计算出来的.
二. onLayout() 决定View在ViewGroup中的位置;
源码:protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
ViewGroup中的源码:@Overrideprotected abstract void onLayout(boolean changed,int l, int t, int r, int b);
三. onDraw() 决定绘制这个View。
public void draw(Canvas canvas) {if (ViewDebug.TRACE_HIERARCHY) {ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);}final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;// Step 1, draw the background, if needed int saveCount;if (!dirtyOpaque) {final Drawable background = mBGDrawable;if (background != null) {final int scrollX = mScrollX;final int scrollY = mScrollY;if (mBackgroundSizeChanged) {background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);mBackgroundSizeChanged = false;}if ((scrollX | scrollY) == 0) {background.draw(canvas);} else {canvas.translate(scrollX, scrollY);background.draw(canvas);canvas.translate(-scrollX, -scrollY);}}}final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the content if (!dirtyOpaque) onDraw(canvas);// Step 4, draw the children dispatchDraw(canvas);// Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas);// we're done...return;}}
自定义绘制API
Canvas常用方法
- 绘制图形(点、线、矩形、椭圆、圆等)
- 绘制文本(文本的居中问题,需要Paint知识)
- 画布的基本变化(平移、缩放、旋转、倾斜)
- 画布的裁剪
- 画布的保存
Paint类主要用于设置绘制风格:包括画笔的颜色画笔触笔粗细、填充风格及文字的特征
- Paint常用方法
- 颜色
- 类型(填充、描边)
- 字体大小
- 宽度
- 对齐方式
- 文字位置属性测量
- 文字宽度测量
3.Path常用方法
- 添加路径
- 移动起点
- 贝塞尔(二阶、三阶)
- 逻辑运算
- 重置路径
- PathEffect
- Matrix
- PathMeasure
PorterDuffXfermode
Matrix
平移矩阵
缩放矩阵
旋转矩阵
ColorMatrix
类别 | API | 描述 |
---|---|---|
旋转 | setRotate | 设置(非输入轴颜色的)色调 |
饱和度 | setSaturation | 设置饱和度 |
缩放 | setScale | 三原色的取值的比例 |
设置 | set、setConcat | 设置颜色矩阵、两个颜色矩阵的乘积 |
重置 | reset | 重置颜色矩阵为初始状态 |
矩阵运算 | preConcat、postConcat | 颜色矩阵的前乘、后乘 |
4.动画
- ObjectAnimator
- ValueAnimator
- AnimatorSet
- 差值器
- 估值器
自定义View示例:实现类似水波扩散效果一共分为六步
1.
public SpreadView(Context context) {this(context,null,0);
}public SpreadView(Context context, @Nullable AttributeSet attrs) {this(context, attrs,0);
}public SpreadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initPaints();
}private void initPaints() {}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);
}
2.实现画笔paint类
本文一共两只画笔
private void initPaints() {//画笔1:centerPaint = new Paint();centerPaint.setColor(Color.YELLOW);centerPaint.setAntiAlias(true);//抗锯齿效果//最开始不透明且扩散距离为0alphas.add(255);spreadRadius.add(0);//画笔2:spreadPaint = new Paint();spreadPaint.setAntiAlias(true);spreadPaint.setAlpha(255);spreadPaint.setColor(Color.RED);
}
3.覆写onMeasure(…)方法
实现这个方法告诉了母容器如何放弃自定义View,可以通过提供的measureSpecs来决定你的View的高和宽,以下是一个正方形,确认它的宽和高是一样的。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int w = MeasureSpec.getSize(widthMeasureSpec);int h = MeasureSpec.getSize(heightMeasureSpec);int size = Math.min(w, h);setMeasuredDimension(size, size);
}
注意:
这个方法需要至少保证一个setMeasuredDimension(..)调用,否则会报IllegalStateException错误。
4.实现onSizeChanged(…)方法
这个方法是你获取View现在的宽和高. 这里我们计算的是中心和半径
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//圆心位置centerX = w / 2;centerY = h / 2;
}
5.实现onDraw(…)方法
这个方法提供了如何绘制view,它提供的Canvas类可以进行绘制。
@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);for (int i = 0; i < spreadRadius.size(); i++) {int alpha = alphas.get(i);spreadPaint.setAlpha(alpha);int width = spreadRadius.get(i);//绘制扩散的圆canvas.drawCircle(centerX, centerY, radius + width, spreadPaint);//每次扩散圆半径递增,圆透明度递减if (alpha > 0 && width < 300) {alpha = alpha - distance > 0 ? alpha - distance : 1;alphas.set(i, alpha);spreadRadius.set(i, width + distance);}}//当最外层扩散圆半径达到最大半径时添加新扩散圆if (spreadRadius.get(spreadRadius.size() - 1) > maxRadius) {spreadRadius.add(0);alphas.add(255);}//超过8个扩散圆,删除最先绘制的圆,即最外层的圆if (spreadRadius.size() >= 8) {alphas.remove(0);spreadRadius.remove(0);}//中间的圆canvas.drawCircle(centerX, centerY, radius, centerPaint);//TODO 可以在中间圆绘制文字或者图片//延迟更新,达到扩散视觉差效果postInvalidateDelayed(delayMilliseconds);
}
6.添加你的View
<?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:gravity="center"android:orientation="vertical"><com.qinqu.spreadviewdemo.SpreadViewandroid:id="@+id/spreadView"android:layout_width="match_parent"android:layout_height="wrap_content"app:spread_center_color="@color/colorAccent"app:spread_delay_milliseconds="35"app:spread_distance="5"app:spread_max_radius="90"app:spread_radius="100"app:spread_spread_color="@color/colorAccent" /></LinearLayout>
问题
demo:https://download.csdn.net/download/haoxuhong/10457408
Android自定义view原理及自定义View示例相关推荐
- android 仿360浮动,Android仿360悬浮小球自定义view实现示例
Android仿360悬浮小球自定义view实现示例 效果图如下: 实现当前这种类似的效果 和360小球 悬浮桌面差不错类似.这种效果是如何实现的呢.废话不多说 ,直接上代码. 1.新建工程,添加悬浮 ...
- java 贝塞尔曲线_贝塞尔曲线:原理、自定义贝塞尔曲线View、使用!!!
一.原理 转自:http://www.2cto.com/kf/201401/275838.html Android动画学习Demo(3) 沿着贝塞尔曲线移动的Property Animation Pr ...
- Android软件开发之盘点自定义View界面大合集(二)
Android软件开发之盘点自定义View界面大合集(二) - 雨松MOMO的程序世界 - 51CTO技术博客 雨松MOMO带大家盘点Android 中的自定义View界面的绘制 今天我用自己写的一个 ...
- Android初级教程初谈自定义view自定义属性
有些时候,自己要在布局文件中重复书写大量的代码来定义一个布局.这是最基本的使用,当然要掌握:但是有些场景都去对应的布局里面写对应的属性,就显得很无力.会发现,系统自带的控件无法满足我们的要求,这个时候 ...
- Carson带你学Android:源码解析自定义View Draw过程
前言 自定义View是Android开发者必须了解的基础 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化 等 今天,我将全面总结自定义View ...
- android录音波浪动画_Android 自定义 view 实现波浪动画进度条
最近在做项目时需要实现这样一种动画,类似于波浪形的进度动画,粗略的看了一下,发现好像类似于正余弦曲线实现的,但是Android 没有相关的API,所以需要我们动手画出来,所以现在在此记录一下学习过程, ...
- Android自定义view之(刻度尺view)
前言: 最近一直在做h5,感觉学的东西多了还真有点混淆了,再来看anroid的时候,觉得有点点陌生了,难道真的是鱼与熊掌不可兼得吗? 好吧,也罢- 在技术群中看到一个小伙伴有一个这样的需求,所以在不是 ...
- android canvas绘制圆角_Android自定义View撸一个渐变的温度指示器(TmepView)
秦子帅明确目标,每天进步一点点..... 作者 | andy 地址 | blog.csdn.net/Andy_l1/article/details/82910061 1.概述 自定义View对需要 ...
- android绘制心形_Android自定义View系列(一)——打造一个爱心进度条
写作原因:Android进阶过程中有一个绕不开的话题--自定义View.这一块是安卓程序员更好地实现功能自主化必须迈出的一步.下面这个系列博主将通过实现几个例子来认识安卓自定义View的方法.从自定义 ...
最新文章
- gitlab的搭建与汉化
- Node.js 使用axios读写influxDB
- 原 Linux搭建SVN 服务器2
- php如何查看上传的文件大小,PHP设置最大上传文件大小
- maven命令mvn package指定jar包名称
- 分布式光伏贷款欲破冰 多家银行推出相关业务
- java final对象_java面向对象基础_final详细介绍
- 7.8 服务暴露总结
- Java中构造函数,静态代码块,构造代码块的执行顺序
- CSS基本选择器之类选择器多类名(CSS、HTML)
- 分布式系统关注点(3)——过去这几十年,分布式系统的「数据一致性」精华都在这了!...
- 计算器是不是电子计算机,计算器和计算机的区别?
- oracle中给予权限,Oracle给予用户权限
- Nginx不停机升级
- android minui fb显示相关函数
- 【C#】WinForm 之 SQL Server 服务监控器(避免开机启动服务)
- simulink中找不到CarSim S-Function图标
- Android开发AndroidStudio与eclipse安装与使用
- | + logger
- 【机器学习】缺失值的处理方法总结
热门文章
- redis漏洞利用总结
- linux rpm下载网站,推荐几个rpm下载站点
- 26-时尚精品服饰网店响应式网页模板 (HTML css JavaScript)
- deallocate mysql_MySQL 预处理语句prepare、execute、deallocate的使用
- WebRTC:stun/turn服务器搭建
- 氚云1,熟悉氚云并学会如何自建流程
- 向mysql数据库发送指令_常用的MySQL数据库命令大全
- excel值查找公式 - Vlookup
- 马化腾要1千年做成的事他十年完成了,还骗了马云每年投10亿!
- 诸暨机器人餐厅价格_诸暨写字楼食堂承包收费标准,承包饭堂