/* * Copyright (C) 2013 Peng fei Pan <sky@xiaopan.me> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.xiaopan.sketch.feature; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Build; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.widget.Scroller; import me.xiaopan.sketch.SLog; import me.xiaopan.sketch.SketchImageView; /** * 显示按下状态,按下后会在ImageView上显示一个黑色半透明的蒙层,松手后小时 */ public class ShowPressedFunction extends SketchImageView.Function { private static final int DEFAULT_PRESSED_STATUS_COLOR = 0x33000000; protected String logName = "ShowPressedFunction"; private View view; private ImageShapeFunction imageShapeFunction; protected int touchX; protected int touchY; protected int pressedStatusColor = DEFAULT_PRESSED_STATUS_COLOR; protected int rippleRadius; protected boolean allowShowPressedStatus; protected boolean animationRunning; protected Paint pressedStatusPaint; protected GestureDetector gestureDetector; protected boolean showRect; public ShowPressedFunction(View view, ImageShapeFunction imageShapeFunction) { this.view = view; this.imageShapeFunction = imageShapeFunction; this.gestureDetector = new GestureDetector(view.getContext(), new PressedStatusManager()); } @Override public boolean onTouchEvent(MotionEvent event) { if (view.isClickable()) { gestureDetector.onTouchEvent(event); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_OUTSIDE: allowShowPressedStatus = false; view.invalidate(); break; } } return false; } @Override public void onDraw(Canvas canvas) { if (allowShowPressedStatus || animationRunning || showRect) { boolean applyMaskClip = imageShapeFunction.getClipPath() != null; if (applyMaskClip) { canvas.save(); try { canvas.clipPath(imageShapeFunction.getClipPath()); } catch (UnsupportedOperationException e) { SLog.e(logName, "The current environment doesn't support clipPath has shut down automatically hardware acceleration"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } e.printStackTrace(); } } if (pressedStatusPaint == null) { pressedStatusPaint = new Paint(); pressedStatusPaint.setColor(pressedStatusColor); pressedStatusPaint.setAntiAlias(true); } if (allowShowPressedStatus || animationRunning) { canvas.drawCircle(touchX, touchY, rippleRadius, pressedStatusPaint); } else if (showRect) { canvas.drawRect(view.getPaddingLeft(), view.getPaddingTop(), view.getWidth() - view.getPaddingRight(), view.getHeight() - view.getPaddingBottom(), pressedStatusPaint); } if (applyMaskClip) { canvas.restore(); } } } public void setPressedStatusColor(int pressedStatusColor) { this.pressedStatusColor = pressedStatusColor; if (pressedStatusPaint != null) { pressedStatusPaint.setColor(pressedStatusColor); } } private class PressedStatusManager extends GestureDetector.SimpleOnGestureListener implements Runnable { private boolean showPress; private Scroller scroller; private Runnable cancelRunnable; public PressedStatusManager() { scroller = new Scroller(view.getContext()); } @Override public void run() { animationRunning = scroller.computeScrollOffset(); if (animationRunning) { rippleRadius = scroller.getCurrX(); view.post(this); } view.invalidate(); } @Override public boolean onDown(MotionEvent event) { if (!scroller.isFinished()) { scroller.forceFinished(true); view.removeCallbacks(this); animationRunning = false; view.invalidate(); } touchX = (int) event.getX(); touchY = (int) event.getY(); showPress = false; return false; } @Override public void onShowPress(MotionEvent e) { allowShowPressedStatus = true; showPress = true; startAnimation(1000); } @Override public void onLongPress(MotionEvent e) { super.onLongPress(e); } @Override public boolean onSingleTapUp(MotionEvent e) { if (!showPress) { showRect = true; view.invalidate(); if (cancelRunnable == null) { cancelRunnable = new Runnable() { @Override public void run() { showRect = false; view.invalidate(); } }; } view.postDelayed(cancelRunnable, 200); } return super.onSingleTapUp(e); } private void startAnimation(int duration) { if (scroller == null) { scroller = new Scroller(view.getContext(), new DecelerateInterpolator()); } scroller.startScroll(0, 0, computeRippleRadius(), 0, duration); view.post(this); } /** * 计算涟漪的半径 * * @return 涟漪的半径 */ private int computeRippleRadius() { // 先计算按下点到四边的距离 int toLeftDistance = touchX - view.getPaddingLeft(); int toTopDistance = touchY - view.getPaddingTop(); int toRightDistance = Math.abs(view.getWidth() - view.getPaddingRight() - touchX); int toBottomDistance = Math.abs(view.getHeight() - view.getPaddingBottom() - touchY); // 当按下位置在第一或第四象限的时候,比较按下位置在左上角到右下角这条线上距离谁最远就以谁为半径,否则在左下角到右上角这条线上比较 int centerX = view.getWidth() / 2; int centerY = view.getHeight() / 2; if ((touchX < centerX && touchY < centerY) || (touchX > centerX && touchY > centerY)) { int toLeftTopDistance = (int) Math.sqrt((toLeftDistance * toLeftDistance) + (toTopDistance * toTopDistance)); int toRightBottomDistance = (int) Math.sqrt((toRightDistance * toRightDistance) + (toBottomDistance * toBottomDistance)); return toLeftTopDistance > toRightBottomDistance ? toLeftTopDistance : toRightBottomDistance; } else { int toLeftBottomDistance = (int) Math.sqrt((toLeftDistance * toLeftDistance) + (toBottomDistance * toBottomDistance)); int toRightTopDistance = (int) Math.sqrt((toRightDistance * toRightDistance) + (toTopDistance * toTopDistance)); return toLeftBottomDistance > toRightTopDistance ? toLeftBottomDistance : toRightTopDistance; } } } }