按鈕點選水波紋效果
水波紋的出現給我們的錯覺是直接將波紋繪製在button上面的,但是這樣能做到嗎?首先button自己有background和src,如果把半透明的水波紋當作background或者src繪製到button上面,肯定是會損失button原有的樣式的。可能有朋友猜想那就把水波紋繪製在螢幕上唄,恭喜這位朋友答對了,至少我是這麼幹的,具體思路就是,我們自己實現一個layout,在layout中捕捉事件,並對事件進行相應的處理,在down事件中尋找當前使用者點選的是哪個view,找出view所在的矩形區域,將一個透明的圓環繪製到這個矩形區域。
實現思路
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;
}
}