按钮点击水波纹效果
2016-01-07 10:50
225 查看
水波纹的出现给我们的错觉是直接将波纹绘制在button上面的,但是这样能做到吗?首先button自己有background和src,如果把半透明的水波纹当作background或者src绘制到button上面,肯定是会损失button原有的样式的。可能有朋友猜想那就把水波纹绘制在屏幕上呗,恭喜这位朋友答对了,至少我是这么干的,具体思路就是,我们自己实现一个layout,在layout中捕捉事件,并对事件进行相应的处理,在down事件中寻找当前用户点击的是哪个view,找出view所在的矩形区域,将一个透明的圆环绘制到这个矩形区域。
2、重写layout的dispatchTouchEvent方法,在down事件中找出被点击的view;
3、接着找出view所在的矩形区域,因为要将波纹绘制到该区域;
4、矩形区域找到之后,这个区域就是我们要绘制的博波纹所在地,上面也说过了,波纹其实就是圆环,绘制圆的画是需要知道圆心坐标和圆的半径,圆心坐标肯定就是down时候的x和y了,然后计算半径
5、半径算出来了,虽说圆心就是down时的x和y,但是有个地方还是需要注意的,在绘制圆环的时候提供的圆心坐标的x和y是在本文中是相对于layout的,所以在计算y的时候是需要进行一定处理的:
6、圆心坐标和半径都计算好了,记下来就可以绘制圆形波纹了,那么在哪里绘制这个波纹比较合适呢?有朋友立马就说肯定是在onDraw方法里面绘制了,那么恭喜你在我看来你是答错了,我们的layout中是很有很多childview的,而layout是个viewGroup,viewGroup在绘制的时候,是先绘制自身的背景,再绘制自身,再绘制childview,如果在onDraw中绘制波纹,也就意味者后面绘制出来的childView会将我们的波纹遮盖,所以我们就应该等到childview绘制完毕后再来绘制波纹,这样可以保证childview在最顶层。
实现思路
1、自己实现一个layout:2、重写layout的dispatchTouchEvent方法,在down事件中找出被点击的view;
3、接着找出view所在的矩形区域,因为要将波纹绘制到该区域;
4、矩形区域找到之后,这个区域就是我们要绘制的博波纹所在地,上面也说过了,波纹其实就是圆环,绘制圆的画是需要知道圆心坐标和圆的半径,圆心坐标肯定就是down时候的x和y了,然后计算半径
5、半径算出来了,虽说圆心就是down时的x和y,但是有个地方还是需要注意的,在绘制圆环的时候提供的圆心坐标的x和y是在本文中是相对于layout的,所以在计算y的时候是需要进行一定处理的:
6、圆心坐标和半径都计算好了,记下来就可以绘制圆形波纹了,那么在哪里绘制这个波纹比较合适呢?有朋友立马就说肯定是在onDraw方法里面绘制了,那么恭喜你在我看来你是答错了,我们的layout中是很有很多childview的,而layout是个viewGroup,viewGroup在绘制的时候,是先绘制自身的背景,再绘制自身,再绘制childview,如果在onDraw中绘制波纹,也就意味者后面绘制出来的childView会将我们的波纹遮盖,所以我们就应该等到childview绘制完毕后再来绘制波纹,这样可以保证childview在最顶层。
自定义控件代码:
package com.example.viewresult; import java.util.ArrayList; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; /** * * 1. 如何得知用户点击了哪个元素 2. 如何取得被点击元素的信息 3. 如何通过layout进行重绘绘制水波纹 4. 如果延迟up事件的分发 * */ @SuppressLint("NewApi") public class CustomViewResult extends LinearLayout { private View mTargetTouchView; private Paint mHalfTransPaint; private Paint mTransPaint; private float[] mDownPositon;// 手指点击的坐标,也就是圆环的中心点 private float rawRadius;// 原始的圆环半径 private float drawedRadius;// 正在慢慢绘制的圆环半径 private float drawingRadiusDegrees = 10;// 慢慢绘制圆环的时候,半径的递增百分比 private static final long INVALID_DURATION = 30; private static postUpEventDelayed delayedRunnable; public void init() { setOrientation(VERTICAL);// 设置方向 mHalfTransPaint = new Paint(); mHalfTransPaint.setColor(Color.parseColor("#55ffffff")); mHalfTransPaint.setAntiAlias(true); mTransPaint = new Paint(); mTransPaint.setColor(Color.parseColor("#00ffffff")); mTransPaint.setAntiAlias(true);// 抗锯齿 mDownPositon = new float[2]; delayedRunnable = new postUpEventDelayed(); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mTargetTouchView = null; drawedRadius = 0; float x = ev.getRawX(); float y = ev.getRawY(); mTargetTouchView = findTargetView(x, y, this); if (mTargetTouchView != null) { Button msg = (Button) mTargetTouchView; RectF targetTouchRectF = getViewRectF(mTargetTouchView); mDownPositon = getCircleCenterPostion(x, y); // 所要绘制的圆环的中心点 float circleCenterX = mDownPositon[0]; float circleCenterY = mDownPositon[1]; /** * 圆环的半径: 圆环的中心点圆心当然是点击的那个点,但是半径是变化的 * 圆心可能落在mTargetTouchView区域的任意个方位之内,所以要想圆环绘制覆盖整个mTargetTouchView * 则radius的取值为圆心的横坐标到mTargetTouchView的四个点的距离中的最大值 */ float left = circleCenterX - targetTouchRectF.left; float right = targetTouchRectF.right - circleCenterX; float top = circleCenterY - targetTouchRectF.top; float bottom = targetTouchRectF.bottom - circleCenterY; // 计算出最大的值则为半径 rawRadius = Math.max(bottom, Math.max(Math.max(left, right), top)); // Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate, // 其中前者是在UI线程自身中使用,而后者在非UI线程中使用。 // postInvalidate还支持延迟刷新 postInvalidateDelayed(INVALID_DURATION); } } else if (ev.getAction() == MotionEvent.ACTION_UP) { // 需要让波纹绘制完毕后再执行在up中执行的方法 // if(drawedRadius==0){ // return false; // } // long totalTime = (long) (INVALID_DURATION * // (drawingRadiusDegrees+5)); // // 离波纹结束的时间 // long time = (long) (totalTime - drawedRadius*totalTime / // rawRadius); delayedRunnable.event = ev; return true; } return super.dispatchTouchEvent(ev); } class postUpEventDelayed implements Runnable { private MotionEvent event; @Override public void run() { if (mTargetTouchView != null && mTargetTouchView.isClickable() && event != null) { mTargetTouchView.dispatchTouchEvent(event); } } } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); /** * 绘制完子元素后开始绘制波纹 */ if (mTargetTouchView != null) { RectF clipRectF = clipRectF(mTargetTouchView); canvas.save(); // 为了不让绘制的圆环超出所要绘制的范围 canvas.clipRect(clipRectF); if (drawedRadius < rawRadius) { drawedRadius += rawRadius / drawingRadiusDegrees; canvas.drawCircle(mDownPositon[0], mDownPositon[1], drawedRadius, mHalfTransPaint); postInvalidateDelayed(INVALID_DURATION); } else { canvas.drawCircle(mDownPositon[0], mDownPositon[1], rawRadius, mTransPaint); post(delayedRunnable); } canvas.restore(); } } /** * 获取圆环的中心坐标 */ public float[] getCircleCenterPostion(float x, float y) { int[] location = new int[2]; float[] mDownPositon = new float[2]; getLocationOnScreen(location);// 获取当前控件在屏幕中的绝对位置 mDownPositon[0] = x; mDownPositon[1] = y - location[1]; return mDownPositon; } /** * 获取要剪切的区域 * * @param mTargetTouchView * @return */ public RectF clipRectF(View mTargetTouchView) { RectF clipRectF = getViewRectF(mTargetTouchView); int[] location = new int[2]; getLocationOnScreen(location); clipRectF.top -= location[1]; clipRectF.bottom -= location[1]; return clipRectF; } /** * 寻找目标view * * @param x * @param y * @param anchorView * 从哪个view开始往下寻找 * @return */ public View findTargetView(float x, float y, View anchorView) { ArrayList<View> touchablesView = anchorView.getTouchables(); View targetView = null; for (View child : touchablesView) { // 1、精度不一样,Rect是使用int类型作为数值,RectF是使用float类型作为数值 // 2、两个类型提供的方法也不是完全一致 RectF rectF = getViewRectF(child); if (rectF.contains(x, y) && child.isClickable()) { // 这说明被点击的view找到了 targetView = child; break; } } return targetView; } public RectF getViewRectF(View view) { int[] location = new int[2]; // View.getLocationInWindow(int[] location) // 一个控件在其父窗口中的坐标位置 // View.getLocationOnScreen(int[] location) // 一个控件在其整个屏幕上的坐标位置 view.getLocationOnScreen(location); int childLeft = location[0]; int childTop = location[1]; int childRight = childLeft + view.getMeasuredWidth(); int childBottom = childTop + view.getMeasuredHeight(); return new RectF(childLeft, childTop, childRight, childBottom); } public CustomViewResult(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public CustomViewResult(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomViewResult(Context context) { this(context, null, 0); } }
自定义控件的使用:
1.布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <com.example.viewresult.CustomViewResult android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ff0000" /> <Button android:id="@+id/button2" android:layout_width="180dp" android:layout_height="wrap_content" android:background="#ff00ff" android:layout_marginLeft="50dp" android:layout_marginTop="10dp" /> <Button android:id="@+id/button3" android:layout_width="220dp" android:layout_height="wrap_content" android:background="#000000" android:layout_marginLeft="20dp" android:layout_marginTop="10dp" /> </com.example.viewresult.CustomViewResult> </RelativeLayout>
2.活动类
package com.example.viewresult; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button1).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub Toast.makeText(getApplicationContext(), "点击", 0).show(); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
相关文章推荐
- [转]讲给普通人听的分布式数据存储
- 深度遍历搜索
- linux安装dns
- JAVA序列化
- JAVA序列化
- Hibernate框架详解(五)面向对象查询语言和锁
- 解决has leaked ServiceConnection com.baidu.location.c@427b6780
- 常用快捷键
- 【c/c++】函数模板和模板函数
- SSD: Single Shot MultiBox Detector
- Web服务器父与子 Apache和Tomcat区别
- linux 内存使用
- 数学 + 带权中位数 - SGU 114 Telecasting station
- Python多线程同步---互斥锁
- Java classloader 原理
- 微软自拍应用iOS版发布
- 放出百度接口框架设计思路
- Unity中一键创建常用文件夹
- Unity中一键创建常用文件夹
- eclipse 去掉js验证