android之View和LinearLayout的重写(实现背景气泡和波纹效果)

前两天看了仿android L里面水波纹效果的两篇博客

Android L中水波纹点击效果的实现

Android自定义组件系列【14】——Android5.0按钮波纹效果实现

第一篇是实现了一个水波纹布局,放在里面的所有控件点击后都会出现波纹效果

第二篇是实现了一个水波纹view,点击之后自身会出现波纹效果

根据对这两篇博客的理解,我自己实现了一个类似的东西,没找到合适的录屏软件,只好把波纹的速度调快了很多才录下来,能看出来啥意思,不调速度的话还算比较优雅。

就是像上面这样一个控件,里面的背景用的是一个重写的TextView,背景就一直有一个不断“呼吸”的气泡。

这里就联系前面两篇博客(建议先去看下),介绍下在View和ViewGroup中实现背景动画,同时纪录下自己对里面知识点的理解。

就暂时把这种效果命名成会呼吸的气泡。(后来发现这个图不动,好吧不知为啥不浪费时间了,就自己想象下这个整个背景有一个圆,不停的放大缩小放大缩小,圆心随机,这个刚好是随机到左上角位置了)

首先是ViewGroup

这里就直接以第一篇博客为例,纪录下自己的理解

1.获取坐标,这个是比较重要的一步,之后判断点击的控件还有绘制波纹都需要用到

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

super.onLayout(changed, l, t, r, b);

this.getLocationOnScreen(mLocationInScreen);

}

这里获取到的是当前布局左上角在整个屏幕中的坐标

2.获取点击到的控件,这里的获取就需要根据坐标挨个判断了,在dispatchTouchEvent方法里面会传入当前点击事件MotionEvent,他会带入当前点击的坐标,这里有两种获取方式,一种是getX,一种是getRawX,在view的坐标系里面说到这两种的区别了,为了计算点击事件,这里要获取的是相对屏幕的坐标,其实在布局的重写里面整片都是使用相对屏幕的坐标,因为布局会出现嵌套,嵌套之后相对坐标就不对了,所以全都使用相对屏幕的坐标去计算。获取到点击事件的坐标,就可以拿坐标去找到所点击的控件了。整个过程博客里面已经很详细了。

3.拿到点击的控件之后就要在控件上面绘制波纹了,这里代码还是贴一下

// view绘制流程:先绘制背景,再绘制自己(onDraw),接着绘制子元素(dispatchDraw),最后绘制一些装饰等比如滚动条(onDrawScrollBars)

// 为了防止绘制绘制的子元素把波纹挡住,这里选择在子元素绘制完成再绘制波纹

@Override

protected void dispatchDraw(Canvas canvas) {

super.dispatchDraw(canvas);

if (!mShouldDoAnimation || mTargetWidth < 0 || mTouchTarget == null) {

return;

}

if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {// 当半径超过短边之后,增加扩散速度尽快完成扩散

mRevealRadius += mRevealRadiusGap * 4;

} else {// 波纹当半径递增扩散

mRevealRadius += mRevealRadiusGap;

}

this.getLocationOnScreen(mLocationInScreen);// 获取本布局的坐标---1⃣️

int[] location = new int[2];

mTouchTarget.getLocationOnScreen(location);// 获取点击控件的坐标---2⃣️

int left = location[0] - mLocationInScreen[0];//---3⃣️

int top = location[1] - mLocationInScreen[1];

int right = left + mTouchTarget.getMeasuredWidth();

int bottom = top + mTouchTarget.getMeasuredHeight();

canvas.save();

canvas.clipRect(left, top, right, bottom);//---4⃣️

canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);

canvas.restore();

if (mRevealRadius <= mMaxRevealRadius) {

postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);//---5⃣️

} else if (!mIsPressed) {

mShouldDoAnimation = false;

postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);

}

}

整个绘制的过程就是上面这样,还是重点关注下画布的切割和坐标的计算,绘制流程和半径的计算之类的看看就明白了

1⃣️获取的是布局左上角的坐标

2⃣️获取的是控件左上角的坐标

3⃣️控件的坐标减去布局的坐标,就是布局在控件上的相对坐标,这里的相对坐标其实就是getLeft和getTop的概念,但是不能这么用,因为有可能控件与布局之间还有嵌套别的布局

4⃣️在3⃣️中已经获取到了控件相对于布局的坐标,这里就在布局的画布上把控件对应的位置切割下来,然后在上面画圆,切割是为了提高性能

5⃣️这里画完圆之后要马上画下一个半径更大的圆,从而达到扩散的效果,所以要postInvalidateDelayed去刷新,刷新的时候只刷新控件所对应的那个区域,也是为了提高性能

基本上就这些吧,原博已经讲得比较详细了,我这里只是针对自己的理解纪录下。

然后是View

看一下分几个步骤

1.获取当前控件的宽高信息(用来初始化气泡的半径等信息)

2.获取点击事件(用来作为气泡的圆心,在没有点击事件的时候它是随机坐标作为圆心的,点击则移动到点击的位置)

3.绘制气泡

下面是重写的BreathTextView代码

public class JasonBreathTextView extends TextView {

private JasonBreathCircle breathCircle;

public JasonBreathTextView(Context context) {

super(context);

}

public JasonBreathTextView(Context context, AttributeSet attrs) {

super(context, attrs);

breathCircle = new JasonBreathCircle(context);

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

// TODO Auto-generated method stub

super.onSizeChanged(w, h, oldw, oldh);

breathCircle.initParameters(this);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

breathCircle.setCircleCenter((int) event.getX(), (int) event.getY());

return super.onTouchEvent(event);

}

@Override

protected void onDraw(Canvas canvas) {

breathCircle.draw(canvas);

super.onDraw(canvas);

}

/**

* 开始水纹效果

*/

public void startReveal() {

breathCircle.start();

}

/**

* 停止水纹效果

*/

public void stopReveal() {

breathCircle.stop();

}

}

这个就是图中使用的继承自TextView的控件。

为了使用方便,我把气泡的实现和控件的实现分开了,这样之后不管实现哪种View都可以直接使用分离出来的气泡类,使用方法就像上面这样,只需要把view控件本身传给气泡,气泡就会在控件上绘制了。

气泡BreathCircle的代码如下:

public class JasonBreathCircle {

private static int DIFFUSE_GAP = 2; // 扩散半径增量

private static final int INVALIDATE_DURATION = 10; // 每次刷新的时间间隔

private Context mContext;

private boolean needToDrawReveal = false;// 绘画标志位

// 圆形自身的一些属性

private boolean isLargerMode = true;// 呼吸模式

private Paint mPaintReveal;// 画笔

private int mCircleCenterX;// 圆心x

private int mCircleCenterY;// 圆心y

private int mCurRadius;// 当前半径

private int mMaxRadius;// 最大半径

// 依附的控件的一些属性,利用高度宽度计算当前触摸点的位置

private View mParentView;// 依附的控件

private int mParentHeight;// 控件高度

private int mParentWidth;// 控件宽度

// ================初始化方法(必须调用)===============

/**

* 实例化一个圆,之后要调用initParameters初始化该圆的属性,再之后就可以draw了

*

* @param context

*/

public JasonBreathCircle(Context context) {

mContext = context;

initPaint();

}

/**

* 传入view,用来初始化坐标,半径,默认以中心为圆心开始画圆

*

* @param view

*/

public void initParameters(View view) {

this.mParentView = view;

// 获取当前依附控件的属性

mParentHeight = mParentView.getHeight();

mParentWidth = mParentView.getWidth();

// 初始化圆的属性

mMaxRadius = (int) Math.hypot(view.getHeight(), view.getWidth()) / 2;

// 控件的宽度高度求出初始圆心

mCircleCenterX = mParentWidth / 2;

mCircleCenterY = mParentHeight / 2;

}

/**

* 传入画布

*

* @param canvas

*/

public void draw(Canvas canvas) {

if (needToDrawReveal) {

canvas.save();

canvas.drawCircle(mCircleCenterX, mCircleCenterY, mCurRadius,

mPaintReveal);

canvas.restore();

if (isLargerMode && mCurRadius < mMaxRadius) {

mCurRadius += DIFFUSE_GAP;// 波纹递增

postRevealInvalidate();

} else if (mCurRadius > 0 && !isLargerMode) {

// 画完一个周期从头再画

mCurRadius -= DIFFUSE_GAP;// 波纹递增

postRevealInvalidate();

} else {// 转换模式

isLargerMode = !isLargerMode;

// 随机选择坐标作为圆心,从0到最右边中间取x,从0到底边取y

setCircleCenter(JasonRandomUtil.nextInt(0, mParentWidth),

JasonRandomUtil.nextInt(0, mParentHeight));

// 圆心更换后,缩小前,把当前半径设为最大,防止边上出现空白覆盖不满

if (!isLargerMode) {

mCurRadius = mMaxRadius;

}

postRevealInvalidate();

}

}

}

// ===============对外接口===============

/**

* 开始呼吸

*/

public void start() {

if (needToDrawReveal) {

return;

}

needToDrawReveal = true;

postRevealInvalidate();

}

/**

* 停止呼吸

*/

public void stop() {

if (!needToDrawReveal) {

return;

}

needToDrawReveal = false;

reset();

postRevealInvalidate();

}

/**

* 设置圆心

*

* @param x

* @param y

*/

public void setCircleCenter(int x, int y) {

mCircleCenterX = x;

mCircleCenterY = y;

mMaxRadius = JasonRadiusUtil.getMaxRadius(mCircleCenterX,

mCircleCenterY, mParentWidth, mParentHeight);

}

/**

* 设置画圆为空心还是实心,默认实心

*

* @param isHollow

*/

public void setHollow(boolean isHollow) {

mPaintReveal.setStyle(isHollow ? Paint.Style.STROKE : Paint.Style.FILL);

}

// ================内部实现===============

/**

* 重置

*/

private void reset() {

mCurRadius = 0;

isLargerMode = true;

}

/**

* 初始化画笔

*/

private void initPaint() {

mPaintReveal = new Paint();

mPaintReveal.setColor(mContext.getResources().getColor(

R.color.jason_bg_common_green_light));

mPaintReveal.setAntiAlias(true);

}

/**

* 重绘

*/

private void postRevealInvalidate() {

mParentView.postInvalidateDelayed(INVALIDATE_DURATION);

}

}

获取控件的宽高信息是在onSizeChanged这个方法中,这里会有宽高信息可以去获取

获取点击事件是在onTouchEvent里面

最后绘制这里选择了onDraw方法

如果看过前面两篇博客了,这里还是纪录了两个地方:

1⃣️关于绘制其实有好几个方法可以选用,看下view绘制流程:先绘制背景,再绘制自己(onDraw),接着绘制子元素(dispatchDraw),最后绘制一些装饰等比如滚动条(onDrawScrollBars)

因为这里是要把绘制出来的气泡做背景,所以要在气泡绘制完成才去绘制view自身的一些东西,所以在onDraw里面最合适了

2⃣️关于坐标,由于受前面第一篇博客里面布局重写时对坐标处理的影响,在这里浪费了些时间,其实在view的重写里面,重绘的时候使用坐标,只需要知道view的宽高就够了,因为onDraw传进来的canvas就是控件本身的大小,

所以不需要像布局里面那样对画布进行裁剪,只要直接在上面画就行了。坐标系直接自己按照宽高去建立就行了,左上角是原点。

作者:jason0539

微博:http://weibo.com/2553717707

博客:http://blog.csdn.net/jason0539(转载请说明出处)

android背景气泡,android之View跟LinearLayout的重写(实现背景气泡和波纹效果)相关推荐

  1. android自定义view背景透明度,Android 背景透明度设置总结

    一.写在前面的 在需求上遇到背景设置透明度还是比较常见的,设置透明度有几种方式,但是不同的场景应用下,不同的方式可能会出现一些问题.针对开发过程中的需求做以下总结. 二.先看效果图 图1. 图2. 图 ...

  2. Android 设置view透明度,广告标题透明背景

    android:alpha="0.8" <ImageViewandroid:layout_width="match_parent"android:layo ...

  3. android surfaceview 背景颜色,android – 设置Surface View的背景颜色

    有一个解决方法来做到这一点. >为surfaceview添加父视图组,将背景颜色设置为此视图组而不是surfaceview; android:layout_width="match_p ...

  4. android 水印背景功能,Android 给View添加剧复的文字水印背景 相似钉钉通信录的背景效果...

    从新写了一种更好的实现方式能够点下边的连接去看下 效果图以下 首先来讲下思路 由于咱们是要显示一个重复显示不少个的水印背景图,个人思路是绘制出一个文字是斜着的矩形图片,而后做为背景平铺.canvas ...

  5. 【Android 应用开发】自定义View 和 ViewGroup

    一. 自定义View介绍 自定义View时, 继承View基类, 并实现其中的一些方法. (1) ~ (2) 方法与构造相关 (3) ~ (5) 方法与组件大小位置相关 (6) ~ (9) 方法与触摸 ...

  6. android字符显示流程图,Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

  7. android应用的界面编程----View与ViewGroup的概念

    1 UI OverView Android中所有的UI元素都是通过View与ViewGroup来构建的,View是指屏幕中一块可与用户进行交互的空白,类似于java界面编程中的JPanel.为了界面布 ...

  8. android 滑动取值_Android View篇之调整字体大小滑杆的实现

    小伙伴们大家好呀,这次介绍一个稍微有点意思的View,在很多阅读类.新闻类的APP上都标配的字体大小调整功能.100多行代码就可以实现,来看看效果吧! 思路分析 1.刻度线代表着每个字体的大小取值,是 ...

  9. android textview背景透明度,Android TextView文字透明度和背景透明度设置

    textview1.setTextColor(Color.argb(255, 0, 255, 0)); //文字透明度 控件设为半透明: 控件名.getBackground().setAlpha(in ...

最新文章

  1. vue中组件在不同页面中渲染出错
  2. 判断手势_手势密码有几种?
  3. python缓冲区_如何在Python中使用Google的协议缓冲区
  4. 乐华娱乐前训练生黄智博卖口罩诈骗案宣判:获刑三年三个月
  5. 最近为A公司提炼的经营理念之合作理念
  6. Flink 集群搭建
  7. 2017北理复试机试题
  8. DOA算法1:MUSIC算法(一)
  9. 21天学通C语言-学习笔记(7)
  10. html怎么让图片在左侧文字在右边,网页设计 怎么让图片在左 文字在右
  11. Hibernate——缓存
  12. 摩斯密码基础知识介绍
  13. DHCP协议以及其配置
  14. 数字孪生|数字孪生装备-关键技术和发展阶段
  15. WebRTC系列-RTCDataChannel发送非音视频数据
  16. 电源设计经验谈1-9
  17. 惯性室内导航入门之PDR (步行者航位推算)
  18. linux镜像文件没有gho,【iso文件中没有gho文件】iso变gho_iso文件里没有gho-系统城...
  19. 文存阅刊杂志文存阅刊杂志社文存阅刊编辑部2023年第1期目录
  20. 点评国内SNS网站-转帖

热门文章

  1. 雨林木风 Ghost Vista SP1 电脑公司装机版 V1.0
  2. html5结合flash实现视频文件在所有主流浏览器兼容播放
  3. 开源项目-房屋租赁管理系统
  4. C++入门、STL学习、二级C++等
  5. 基于B/S的网络考试系统的设计与实现(附:源码 论文 课件)
  6. 11月最新编程排行榜出炉,这个语言超过了C蝉联榜首~
  7. Python内置函数!
  8. Cadence全家桶Capture+Allegro流程-1-创建原理图库和元器件库
  9. Linux之网络性能测试工具netperf实践
  10. 神马笔记 版本2.11.0——歌词笔记