1 /* 2 * The MIT License (MIT) 3 * 4 * Copyright (c) 2014 Robin Chutaux 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 */ 24 25 package com.maomao.beautymovie.widget; 26 27 28 import com.maomao.beautymovie.R; 29 30 import android.content.Context; 31 import android.content.res.TypedArray; 32 import android.graphics.Bitmap; 33 import android.graphics.Canvas; 34 import android.graphics.Paint; 35 import android.graphics.PorterDuff; 36 import android.graphics.PorterDuffXfermode; 37 import android.graphics.Rect; 38 import android.os.Handler; 39 import android.util.AttributeSet; 40 import android.view.GestureDetector; 41 import android.view.MotionEvent; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.view.animation.Animation; 45 import android.view.animation.ScaleAnimation; 46 import android.widget.RelativeLayout; 47 48 /** 49 * Author : Chutaux Robin 50 * Date : 10/8/2014 51 */ 52 public class RippleView extends RelativeLayout 53 { 54 private int WIDTH; 55 private int HEIGHT; 56 private int FRAME_RATE = 10; 57 private int DURATION = 400; 58 private int PAINT_ALPHA = 90; 59 private Handler canvasHandler; 60 private float radiusMax = 0; 61 private boolean animationRunning = false; 62 private int timer = 0; 63 private int timerEmpty = 0; 64 private int durationEmpty = -1; 65 private float x = -1; 66 private float y = -1; 67 private int zoomDuration; 68 private float zoomScale; 69 private ScaleAnimation scaleAnimation; 70 private Boolean hasToZoom; 71 private Boolean isCentered; 72 private Integer rippleType; 73 private Paint paint; 74 private Bitmap originBitmap; 75 private int rippleColor; 76 private View childView; 77 private int ripplePadding; 78 private GestureDetector gestureDetector; 79 private Runnable runnable = new Runnable() 80 { 81 @Override 82 public void run() 83 { 84 invalidate(); 85 } 86 }; 87 88 public RippleView(Context context) 89 { 90 super(context); 91 } 92 93 public RippleView(Context context, AttributeSet attrs) 94 { 95 super(context, attrs); 96 init(context, attrs); 97 } 98 99 public RippleView(Context context, AttributeSet attrs, int defStyle) 100 { 101 super(context, attrs, defStyle); 102 init(context, attrs); 103 } 104 105 private void init(final Context context, final AttributeSet attrs) 106 { 107 if (isInEditMode()) 108 return; 109 110 final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleView); 111 rippleColor = typedArray.getColor(R.styleable.RippleView_rv_color, getResources().getColor(R.color.rippelColor)); 112 rippleType = typedArray.getInt(R.styleable.RippleView_rv_type, 0); 113 hasToZoom = typedArray.getBoolean(R.styleable.RippleView_rv_zoom, false); 114 isCentered = typedArray.getBoolean(R.styleable.RippleView_rv_centered, false); 115 DURATION = typedArray.getInteger(R.styleable.RippleView_rv_rippleDuration, DURATION); 116 FRAME_RATE = typedArray.getInteger(R.styleable.RippleView_rv_framerate, FRAME_RATE); 117 PAINT_ALPHA = typedArray.getInteger(R.styleable.RippleView_rv_alpha, PAINT_ALPHA); 118 ripplePadding = typedArray.getDimensionPixelSize(R.styleable.RippleView_rv_ripplePadding, 0); 119 canvasHandler = new Handler(); 120 zoomScale = typedArray.getFloat(R.styleable.RippleView_rv_zoomScale, 1.03f); 121 zoomDuration = typedArray.getInt(R.styleable.RippleView_rv_zoomDuration, 200); 122 paint = new Paint(); 123 paint.setAntiAlias(true); 124 paint.setStyle(Paint.Style.FILL); 125 paint.setColor(rippleColor); 126 paint.setAlpha(PAINT_ALPHA); 127 this.setWillNotDraw(false); 128 129 gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() 130 { 131 @Override 132 public boolean onSingleTapConfirmed(MotionEvent e) 133 { 134 return true; 135 } 136 137 @Override 138 public boolean onSingleTapUp(MotionEvent e) 139 { 140 return true; 141 } 142 }); 143 144 this.setDrawingCacheEnabled(true); 145 } 146 147 @Override 148 public void addView(View child, int index, ViewGroup.LayoutParams params) 149 { 150 childView = child; 151 super.addView(child, index, params); 152 } 153 154 @Override 155 public void draw(Canvas canvas) 156 { 157 super.draw(canvas); 158 if (animationRunning) 159 { 160 if (DURATION <= timer * FRAME_RATE) 161 { 162 animationRunning = false; 163 timer = 0; 164 durationEmpty = -1; 165 timerEmpty = 0; 166 canvas.restore(); 167 invalidate(); 168 return; 169 } 170 else 171 canvasHandler.postDelayed(runnable, FRAME_RATE); 172 173 if (timer == 0) 174 canvas.save(); 175 176 177 canvas.drawCircle(x, y, (radiusMax * (((float) timer * FRAME_RATE) / DURATION)), paint); 178 179 paint.setColor(getResources().getColor(android.R.color.holo_red_light)); 180 181 if (rippleType == 1 && originBitmap != null && (((float) timer * FRAME_RATE) / DURATION) > 0.4f) 182 { 183 if (durationEmpty == -1) 184 durationEmpty = DURATION - timer * FRAME_RATE; 185 186 timerEmpty++; 187 final Bitmap tmpBitmap = getCircleBitmap((int) ((radiusMax) * (((float) timerEmpty * FRAME_RATE) / (durationEmpty)))); 188 canvas.drawBitmap(tmpBitmap, 0, 0, paint); 189 tmpBitmap.recycle(); 190 } 191 192 paint.setColor(rippleColor); 193 194 if (rippleType == 1) 195 { 196 if ((((float) timer * FRAME_RATE) / DURATION) > 0.6f) 197 paint.setAlpha((int) (PAINT_ALPHA - ((PAINT_ALPHA) * (((float) timerEmpty * FRAME_RATE) / (durationEmpty))))); 198 else 199 paint.setAlpha(PAINT_ALPHA); 200 } 201 else 202 paint.setAlpha((int) (PAINT_ALPHA - ((PAINT_ALPHA) * (((float) timer * FRAME_RATE) / DURATION)))); 203 204 timer++; 205 } 206 } 207 208 @Override 209 protected void onSizeChanged(int w, int h, int oldw, int oldh) 210 { 211 super.onSizeChanged(w, h, oldw, oldh); 212 WIDTH = w; 213 HEIGHT = h; 214 215 scaleAnimation = new ScaleAnimation(1.0f, zoomScale, 1.0f, zoomScale, w / 2, h / 2); 216 scaleAnimation.setDuration(zoomDuration); 217 scaleAnimation.setRepeatMode(Animation.REVERSE); 218 scaleAnimation.setRepeatCount(1); 219 } 220 221 @Override 222 public boolean onTouchEvent(MotionEvent event) 223 { 224 if (gestureDetector.onTouchEvent(event) && !animationRunning) 225 { 226 if (hasToZoom) 227 this.startAnimation(scaleAnimation); 228 229 radiusMax = Math.max(WIDTH, HEIGHT); 230 231 if (rippleType != 2) 232 radiusMax /= 2; 233 234 radiusMax -= ripplePadding; 235 236 if (isCentered || rippleType == 1) 237 { 238 this.x = getMeasuredWidth() / 2; 239 this.y = getMeasuredHeight() / 2; 240 } 241 else 242 { 243 this.x = event.getX(); 244 this.y = event.getY(); 245 } 246 247 animationRunning = true; 248 249 if (rippleType == 1 && originBitmap == null) 250 originBitmap = getDrawingCache(true); 251 252 invalidate(); 253 this.performClick(); 254 } 255 256 childView.onTouchEvent(event); 257 return true; 258 } 259 260 @Override 261 public boolean onInterceptTouchEvent(MotionEvent event) 262 { 263 return true; 264 } 265 266 private Bitmap getCircleBitmap(final int radius) { 267 final Bitmap output = Bitmap.createBitmap(originBitmap.getWidth(), originBitmap.getHeight(), Bitmap.Config.ARGB_8888); 268 final Canvas canvas = new Canvas(output); 269 final Paint paint = new Paint(); 270 final Rect rect = new Rect((int)(x - radius), (int)(y - radius), (int)(x + radius), (int)(y + radius)); 271 272 paint.setAntiAlias(true); 273 canvas.drawARGB(0, 0, 0, 0); 274 canvas.drawCircle(x, y, radius, paint); 275 276 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 277 canvas.drawBitmap(originBitmap, rect, rect, paint); 278 279 return output; 280 } 281 }
View Code
xml中声明:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout android:orientation="vertical" 3 android:padding="5.0dip" 4 android:fitsSystemWindows="true" 5 android:clipToPadding="true" 6 android:layout_width="fill_parent" 7 android:layout_height="fill_parent" 8 xmlns:android="http://schemas.android.com/apk/res/android" 9 xmlns:ripple="http://schemas.android.com/apk/res-auto" 10 xmlns:tools="http://schemas.android.com/tools" 11 tools:context=".MainActivity"> 12 <EditText 13 android:textSize="16.0sp" 14 android:id="@+id/edit_email" 15 android:layout_width="fill_parent" 16 android:layout_height="wrap_content" 17 android:layout_marginTop="10.0dip" 18 android:hint="您的邮箱(选填)" 19 android:singleLine="true" /> 20 <EditText 21 android:textSize="16.0sp" 22 android:id="@+id/edit_advice" 23 android:layout_width="fill_parent" 24 android:layout_height="wrap_content" 25 android:layout_marginTop="20.0dip" 26 android:hint="您的意见或建议" /> 27 28 29 30 <com.maomao.beautymovie.widget.RippleView 31 android:id="@+id/rect" 32 android:layout_width="fill_parent" 33 android:layout_marginTop="20dp" 34 android:layout_marginLeft="10dp" 35 android:layout_marginRight="10dp" 36 android:layout_height="wrap_content" 37 ripple:rv_type="rectangle" 38 ripple:rv_zoom="true"> 39 40 <TextView 41 android:id="@+id/rect_child" 42 android:layout_width="fill_parent" 43 android:layout_height="50dp" 44 android:layout_centerInParent="true" 45 android:textColor="@android:color/white" 46 android:textSize="20sp" 47 android:gravity="center" 48 android:text="提交" 49 android:background="@android:color/holo_blue_light"/> 50 51 </com.maomao.beautymovie.widget.RippleView> 52 53 54 55 </LinearLayout>
activity中监听使用:
1 package com.maomao.beautymovie; 2 3 4 5 import com.maomao.beautymovie.widget.RippleView; 6 7 8 public class FeedbackActivity extends BaseActivity { 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 setContentView(R.layout.activity_feedback); 14 final RippleView rippleView = (RippleView) findViewById(R.id.rect); 15 16 rippleView.setOnClickListener(new View.OnClickListener() 17 { 18 @Override 19 public void onClick(View v) 20 { 21 Toast.makeText(FeedbackActivity.this, "这是一个Toast提示", Toast.LENGTH_LONG).show(); 22 } 23 }); 24 25 26 } 27 28 @Override 29 public boolean onOptionsItemSelected(MenuItem item) { 30 if (item.getItemId() == android.R.id.home) { 31 finish(); 32 return true; 33 } 34 return super.onOptionsItemSelected(item); 35 } 36 37 38 39 }