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

前两天看了仿android L里面水波纹效果的两篇博客Android L中水波纹点击效果的实现Android自定义组件系列【14】——Android5.0按钮波纹效果实现第一篇是实现了一个水波纹布局,放在里面的所有控件点击后都会出现波纹效果第二篇是实现了一个水波纹view,点击之后自身会出现波纹效果根据对这两篇博客的理解,我自己实现了一个

大家好,又见面了,我是全栈君。

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

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

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

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

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

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

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

就是像上面这样一个控件,里面的背景用的是一个重写的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(转载请说明出处)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/121332.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • pycharm使用技巧及常用快捷键_pycharm快捷键大全图

    pycharm使用技巧及常用快捷键_pycharm快捷键大全图Pycharm常用快捷键1、编辑(Editing)Ctrl+Space基本的代码完成(类、方法、属性)Ctrl+Alt+Space快速导入任意类Ctrl+Shift+Enter语句完成Ctrl+P参数信息(在方法中调用参数)Ctrl+Q快速查看文档F1外部文档Shift+F1外部文档,进入web文档主页Ctrl+Shift+Z–>Redo重做Ctrl+鼠标简介/进入代码定义Ctrl+F1显示错误描述或警告.

  • python读取txt文件内容(python怎么读取excel)

    python读取txt文件的方法:首先打开文件,代码为【f=open(‘/tmp/test.txt’)】;然后进行读取,代码为【本教程操作环境:windows7系统、python3.9版,该方法适用于所有品牌电脑。python读取txt文件的方法:一、文件的打开和创建>>>f=open(‘/tmp/test.txt’)>>>f.read()’hell…

  • getParameter方法的用法[通俗易懂]

    getParameter方法的用法[通俗易懂]html核心代码<body><fontsize=”5″color=”blue”>圆面积计算</font><br><formaction=”home/CCarea”method=”post”> 请输入半径r:<inputtype=”text”name=”radius”v…

  • linux清除隐藏的挖矿程序

    linux清除隐藏的挖矿程序1.找出cpu高的程序,top找不到的话,用下面命令ps-aux–sort=-pcpu|head-102.杀掉相关进程kill-9pid3.查看crontab是否有定时任务4.删除相关命令[root@dbserverlib]#lsattrlibiacpkmn.so.3—-i——–e–libiacpkmn.so.3[root@dbserverlib]#chattr-ilibiacpkmn.so.3[root@dbserver

  • lcd电子时钟怎么调_keil液晶显示程序

    lcd电子时钟怎么调_keil液晶显示程序第11周上机程序-LCD12864显示-操作示范结果展示取模软件软件图片软件下载百度网盘下载钉钉群下载软件使用方法(文字取模)软件使用方法(字符取模)程序修改导入原本程序修改原程序修改文字修改学号完整程序结果展示取模软件软件图片软件下载百度网盘下载链接:link.提取码:houh钉钉群下载软件使用方法(文字取模)点击C51后字符便会自动生成。保存为记事本形式,如下所示软件使用方法(字符取模)一样的操作,输入学号,生成16进制数组,保存于桌面即可。程

  • LLDP 链路发现协议「建议收藏」

    LLDP 链路发现协议「建议收藏」LLDP链路发现协议公有标准协议作用:在网络设备之间运行后,可以直接查看到设备之间的互联端口以及对方设备的简要信息配置:在每个设备的系统试图下开启LLDP就可以了。配置命令:[SW1]lldpenable//开启LLDP功能[SW2]lldpenable//开启LLDP功能displaylldpneighborbrief//查看设备上的LLDP邻居表LocalIntfNeighborDevNeighborIn

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号