自Material Design的設計理念推出以來,整個安卓的UI跟UE界幾乎發生了翻天覆地的變化,由此推出的設計理念也在一步一步的被引入到開發者的具體項目中,良心來說Google提出的這個設計理念跟相關的支持包確實也給開發者帶來極大的便利,但是不足的是,向下兼容做的不是太好,好多效果使用起來一般都會要求API的版本在至少21以上,所以有些效果想要引入到項目中還是需要開發者做一些定制的,今天我來跟大家分享下MD風格中的點擊水波蕩漾的效果實現。
對自定義View比較熟的開發者肯定不陌生,定制一個View的時候,肯定會先定制一些相關的屬性,一般會在attrs.xml文件中通過declare-styleable來自定義相關屬性跟name
<?xml version="1.0" encoding="utf-8"?><resources> <!--****************引入MD風格的點擊水波蕩漾效果***************--> <declare-styleable name="MaterialLayout"> <attr name="alpha" format="integer" /> <attr name="alpha_step" format="integer" /> <attr name="framerate" format="integer" /> <attr name="duration" format="integer" /> <attr name="mycolor" format="color" /> <attr name="scale" format="float" /> </declare-styleable></resources>然后自定義MaterialLayout文件繼承自RelativeLayout/** * 引入Material Design風格的點擊水波蕩漾效果布局 * 2017年2月6日10:30:19 */public class MaterialLayout extends RelativeLayout { PRivate static final int DEFAULT_RADIUS = 10; private static final int DEFAULT_FRAME_RATE = 10; private static final int DEFAULT_DURATION = 200; private static final int DEFAULT_ALPHA = 255; private static final float DEFAULT_SCALE = 0.8f; private static final int DEFAULT_ALPHA_STEP = 5; /** * 動畫幀率 */ private int mFrameRate = DEFAULT_FRAME_RATE; /** * 漸變動畫持續時間 */ private int mDuration = DEFAULT_DURATION; /** * */ private Paint mPaint = new Paint(); /** * 被點擊的視圖的中心點 */ private Point mCenterPoint = null; /** * 視圖的Rect */ private RectF mTargetRectf; /** * 起始的圓形背景半徑 */ private int mRadius = DEFAULT_RADIUS; /** * 最大的半徑 */ private int mMaxRadius = DEFAULT_RADIUS; /** * 漸變的背景色 */ private int mCirclelColor = Color.LTGRAY; /** * 每次重繪時半徑的增幅 */ private int mRadiusStep = 1; /** * 保存用戶設置的alpha值 */ private int mBackupAlpha; /** * 圓形半徑針對于被點擊視圖的縮放比例,默認為0.8 */ private float mCircleScale = DEFAULT_SCALE; /** * 顏色的alpha值, (0, 255) */ private int mColorAlpha = DEFAULT_ALPHA; /** * 每次動畫Alpha的漸變遞減值 */ private int mAlphaStep = DEFAULT_ALPHA_STEP; private View mTargetView; /** * @param context */ public MaterialLayout(Context context) { this(context, null); } public MaterialLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public MaterialLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } private void init(Context context, AttributeSet attrs) { if (isInEditMode()) { return; } if (attrs != null) { initTypedArray(context, attrs); } initPaint(); this.setWillNotDraw(false); this.setDrawingCacheEnabled(true); } private void initTypedArray(Context context, AttributeSet attrs) { final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialLayout); mCirclelColor = typedArray.getColor(R.styleable.MaterialLayout_mycolor, Color.LTGRAY); mDuration = typedArray.getInteger(R.styleable.MaterialLayout_duration, DEFAULT_DURATION); mFrameRate = typedArray.getInteger(R.styleable.MaterialLayout_framerate, DEFAULT_FRAME_RATE); mColorAlpha = typedArray.getInteger(R.styleable.MaterialLayout_alpha, DEFAULT_ALPHA); mCircleScale = typedArray.getFloat(R.styleable.MaterialLayout_scale, DEFAULT_SCALE); typedArray.recycle(); } private void initPaint() { mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mCirclelColor); mPaint.setAlpha(mColorAlpha); // 備份alpha屬性用于動畫完成時重置 mBackupAlpha = mColorAlpha; } /** * 點擊的某個坐標點是否在View的內部 * * @param touchView * @param x 被點擊的x坐標 * @param y 被點擊的y坐標 * @return 如果點擊的坐標在該view內則返回true, 否則返回false */ private boolean isInFrame(View touchView, float x, float y) { initViewRect(touchView); return mTargetRectf.contains(x, y); } /** * 獲取點中的區域,屏幕絕對坐標值,這個高度值也包含了狀態欄和標題欄高度 * * @param touchView */ private void initViewRect(View touchView) { int[] location = new int[2]; touchView.getLocationOnScreen(location); // 視圖的區域 mTargetRectf = new RectF(location[0], location[1], location[0] + touchView.getWidth(), location[1] + touchView.getHeight()); } /** * 減去狀態欄和標題欄的高度 */ private void removeExtraHeight() { int[] location = new int[2]; this.getLocationOnScreen(location); // 減去兩個該布局的top,這個top值就是狀態欄的高度 mTargetRectf.top -= location[1]; mTargetRectf.bottom -= location[1]; // 計算中心點坐標 int centerHorizontal = (int) (mTargetRectf.left + mTargetRectf.right) / 2; int centerVertical = (int) ((mTargetRectf.top + mTargetRectf.bottom) / 2); // 獲取中心點 mCenterPoint = new Point(centerHorizontal, centerVertical); } private View findTargetView(ViewGroup viewGroup, float x, float y) { int childCount = viewGroup.getChildCount(); // 迭代查找被點擊的目標視圖 for (int i = 0; i < childCount; i++) { View childView = viewGroup.getChildAt(i); if (childView instanceof ViewGroup) { return findTargetView((ViewGroup) childView, x, y); } else if (isInFrame(childView, x, y)) { // 否則判斷該點是否在該View的frame內 return childView; } } return null; } private boolean isAnimEnd() { return mRadius >= mMaxRadius; } private void calculateMaxRadius(View view) { // 取視圖的最長邊 int maxLength = Math.max(view.getWidth(), view.getHeight()); // 計算Ripple圓形的半徑 mMaxRadius = (int) ((maxLength / 2) * mCircleScale); int redrawCount = mDuration / mFrameRate; // 計算每次動畫半徑的增值 mRadiusStep = (mMaxRadius - DEFAULT_RADIUS) / redrawCount; // 計算每次alpha遞減的值 mAlphaStep = (mColorAlpha - 100) / redrawCount; } /** * 處理ACTION_DOWN觸摸事件, 注意這里獲取的是Raw x, y, * 即屏幕的絕對坐標,但是這個當屏幕中有狀態欄和標題欄時就需要去掉這些高度,因此得到mTargetRectf后其高度需要減去該布局的top起點 * ,也就是標題欄和狀態欄的總高度. * * @param event */ private void deliveryTouchDownEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { mTargetView = findTargetView(this, event.getRawX(), event.getRawY()); if (mTargetView != null) { removeExtraHeight(); // 計算相關數據 calculateMaxRadius(mTargetView); // 重繪視圖 invalidate(); } } } @Override public boolean onInterceptTouchEvent(MotionEvent event) { deliveryTouchDownEvent(event); return super.onInterceptTouchEvent(event); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); // 繪制Circle drawRippleIfNecessary(canvas); } private void drawRippleIfNecessary(Canvas canvas) { if (isFoundTouchedSubView()) { // 計算新的半徑和alpha值 mRadius += mRadiusStep; mColorAlpha -= mAlphaStep; // 裁剪一塊區域,這塊區域就是被點擊的View的區域.通過clipRect來獲取這塊區域,使得繪制操作只能在這個區域范圍內的進行, // 即使繪制的內容大于這塊區域,那么大于這塊區域的繪制內容將不可見. 這樣保證了背景層只能繪制在被點擊的視圖的區域 canvas.clipRect(mTargetRectf); mPaint.setAlpha(mColorAlpha); // 繪制背景圓形,也就是 canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mRadius, mPaint); } if (isAnimEnd()) { reset(); } else { invalidateDelayed(); } } /** * 發送重繪消息 */ private void invalidateDelayed() { this.postDelayed(new Runnable() { @Override public void run() { invalidate(); } }, mFrameRate); } /** * 判斷是否找到被點擊的子視圖 * * @return */ private boolean isFoundTouchedSubView() { return mCenterPoint != null && mTargetView != null; } private void reset() { mCenterPoint = null; mTargetRectf = null; mRadius = DEFAULT_RADIUS; mColorAlpha = mBackupAlpha; mTargetView = null; invalidate(); }}在布局中使用此效果的時候只需要把要實現該效果的View包裹在MaterialLayout節點下即可<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> <!--***********Material Design風格的點擊水波蕩漾效果****--> <com.zhuandian.qxe.utils.MyView.MaterialLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe_refresh" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="2dp"></ListView> </android.support.v4.widget.SwipeRefreshLayout> </com.zhuandian.qxe.utils.MyView.MaterialLayout> <android.support.design.widget.FloatingActionButton android:id="@+id/search" android:layout_width="60dp" android:layout_height="60dp" android:layout_gravity="bottom|right" android:layout_margin="10dp" android:src="@drawable/qy_taobao" app:backgroundTint="#631263" /> <include layout="@layout/load_animation" /></FrameLayout>當然你也可以根據attrs.xml文件中自定義的屬性值在MaterialLayout節點下通過引入屬性來調節此效果的具體細節包括顏色,透明度的漸變效果等
新聞熱點
疑難解答