package com.marshalchen.common.uimodule.simplemodule; import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.text.TextPaint; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.BounceInterpolator; import android.widget.RelativeLayout; import com.marshalchen.common.commonUtils.logUtils.Logs; import com.marshalchen.common.commonUtils.uiUtils.BasicUiUtils; import com.marshalchen.ultimateandroiduicomponent.R; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.AnimatorListenerAdapter; import com.nineoldandroids.animation.ValueAnimator; /** * Author: wangjie * Email: tiantian.china.2@gmail.com * Github: https://github.com/wangjiegulu/DraggableFlagView * Date: 12/23/14. */ public class DraggableFlagView extends View { private static final String TAG = DraggableFlagView.class.getSimpleName(); public static interface OnDraggableFlagViewListener { /** * 拖拽销毁圆点后的回调 * * @param view */ void onFlagDismiss(DraggableFlagView view); } private OnDraggableFlagViewListener onDraggableFlagViewListener; public void setOnDraggableFlagViewListener(OnDraggableFlagViewListener onDraggableFlagViewListener) { this.onDraggableFlagViewListener = onDraggableFlagViewListener; } public DraggableFlagView(Context context) { super(context); init(context); } private int patientColor = Color.RED; public DraggableFlagView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DraggableFlagView); int indexCount = a.getIndexCount(); for (int i = 0; i < indexCount; i++) { int attrIndex = a.getIndex(i); if (attrIndex == R.styleable.DraggableFlagView_df_color) { patientColor = a.getColor(attrIndex, Color.RED); } } a.recycle(); init(context); } public DraggableFlagView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private Context context; private int originRadius; // 初始的圆的半径 private int originWidth; private int originHeight; private int maxMoveLength; // 最大的移动拉长距离 private boolean isArrivedMaxMoved; // 达到了最大的拉长距离(松手可以触发事件) private int curRadius; // 当前点的半径 private int touchedPointRadius; // touch的圆的半径 private Point startPoint = new Point(); private Point endPoint = new Point(); private Paint paint; // 绘制圆形图形 private TextPaint textPaint; // 绘制圆形图形 private Paint.FontMetrics textFontMetrics; private int[] location; private boolean isTouched; // 是否是触摸状态 private Triangle triangle = new Triangle(); private String text = ""; // 正常状态下显示的文字 private void init(Context context) { this.context = context; setBackgroundColor(Color.TRANSPARENT); // 设置绘制flag的paint paint = new Paint(); paint.setColor(patientColor); paint.setAntiAlias(true); // 设置绘制文字的paint textPaint = new TextPaint(); textPaint.setAntiAlias(true); textPaint.setColor(Color.WHITE); textPaint.setTextSize(BasicUiUtils.dp2px(context, 12)); textPaint.setTextAlign(Paint.Align.CENTER); textFontMetrics = textPaint.getFontMetrics(); } RelativeLayout.LayoutParams originLp; // 实际的layoutparams RelativeLayout.LayoutParams newLp; // 触摸时候的LayoutParams private boolean isFirst = true; @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // Logger.d(TAG, String.format("onSizeChanged, w: %s, h: %s, oldw: %s, oldh: %s", w, h, oldw, oldh)); if (isFirst && w > 0 && h > 0) { isFirst = false; originWidth = w; originHeight = h; originRadius = Math.min(originWidth, originHeight) / 2; curRadius = originRadius; touchedPointRadius = originRadius; maxMoveLength = BasicUiUtils.getDeviceHeight(context) / 6; refreshStartPoint(); ViewGroup.LayoutParams lp = this.getLayoutParams(); if (RelativeLayout.LayoutParams.class.isAssignableFrom(lp.getClass())) { originLp = (RelativeLayout.LayoutParams) lp; } newLp = new RelativeLayout.LayoutParams(lp.width, lp.height); } } @Override public void setLayoutParams(ViewGroup.LayoutParams params) { super.setLayoutParams(params); refreshStartPoint(); } /** * 修改layoutParams后,需要重新设置startPoint */ private void refreshStartPoint() { location = new int[2]; this.getLocationInWindow(location); // Logger.d(TAG, "location on screen: " + Arrays.toString(location)); // startPoint.set(location[0], location[1] + h); try { location[1] = location[1] - BasicUiUtils.getTopBarHeight((Activity) context); } catch (Exception ex) { } startPoint.set(location[0], location[1] + getMeasuredHeight()); // Logger.d(TAG, "startPoint: " + startPoint); } Path path = new Path(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.TRANSPARENT); int startCircleX = 0, startCircleY = 0; if (isTouched) { // 触摸状态 startCircleX = startPoint.x + curRadius; startCircleY = startPoint.y - curRadius; // 绘制原来的圆形(触摸移动的时候半径会不断变化) canvas.drawCircle(startCircleX, startCircleY, curRadius, paint); // 绘制手指跟踪的圆形 int endCircleX = endPoint.x; int endCircleY = endPoint.y; canvas.drawCircle(endCircleX, endCircleY, originRadius, paint); if (!isArrivedMaxMoved) { // 没有达到拉伸最大值 path.reset(); double sin = triangle.deltaY / triangle.hypotenuse; double cos = triangle.deltaX / triangle.hypotenuse; // A点 path.moveTo( (float) (startCircleX - curRadius * sin), (float) (startCircleY - curRadius * cos) ); // B点 path.lineTo( (float) (startCircleX + curRadius * sin), (float) (startCircleY + curRadius * cos) ); // C点 path.quadTo( (startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2, (float) (endCircleX + originRadius * sin), (float) (endCircleY + originRadius * cos) ); // D点 path.lineTo( (float) (endCircleX - originRadius * sin), (float) (endCircleY - originRadius * cos) ); // A点 path.quadTo( (startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2, (float) (startCircleX - curRadius * sin), (float) (startCircleY - curRadius * cos) ); canvas.drawPath(path, paint); } // 绘制文字 float textH = -textFontMetrics.ascent - textFontMetrics.descent; canvas.drawText(text, endCircleX, endCircleY + textH / 2, textPaint); } else { // 非触摸状态 if (curRadius > 0) { startCircleX = curRadius; startCircleY = originHeight - curRadius; canvas.drawCircle(startCircleX, startCircleY, curRadius, paint); if (curRadius == originRadius) { // 只有在恢复正常的情况下才显示文字 // 绘制文字 float textH = -textFontMetrics.ascent - textFontMetrics.descent; canvas.drawText(text, startCircleX, startCircleY + textH / 2, textPaint); } } } // Logger.d(TAG, "circleX: " + startCircleX + ", circleY: " + startCircleY + ", curRadius: " + curRadius); } float downX = Float.MAX_VALUE; float downY = Float.MAX_VALUE; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); // Logger.d(TAG, "onTouchEvent: " + event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isTouched = true; this.setLayoutParams(newLp); endPoint.x = (int) downX; endPoint.y = (int) downY; changeViewHeight(this, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); postInvalidate(); downX = event.getX() + location[0]; downY = event.getY() + location[1]; // Logger.d(TAG, String.format("downX: %f, downY: %f", downX, downY)); break; case MotionEvent.ACTION_MOVE: // 计算直角边和斜边(用于计算绘制两圆之间的填充去) triangle.deltaX = event.getX() - downX; triangle.deltaY = -1 * (event.getY() - downY); // y轴方向相反,所有需要取反 double distance = Math.sqrt(triangle.deltaX * triangle.deltaX + triangle.deltaY * triangle.deltaY); triangle.hypotenuse = distance; // Logger.d(TAG, "triangle: " + triangle); refreshCurRadiusByMoveDistance((int) distance); endPoint.x = (int) event.getX(); endPoint.y = (int) event.getY(); postInvalidate(); break; case MotionEvent.ACTION_UP: isTouched = false; this.setLayoutParams(originLp); if (isArrivedMaxMoved) { // 触发事件 changeViewHeight(this, originWidth, originHeight); postInvalidate(); if (null != onDraggableFlagViewListener) { onDraggableFlagViewListener.onFlagDismiss(this); } Logs.d(TAG, "触发事件..."); resetAfterDismiss(); } else { // 还原 changeViewHeight(this, originWidth, originHeight); startRollBackAnimation(500/*ms*/); } downX = Float.MAX_VALUE; downY = Float.MAX_VALUE; break; } return true; } /** * 触发事件之后重置 */ private void resetAfterDismiss() { this.setVisibility(GONE); text = ""; isArrivedMaxMoved = false; curRadius = originRadius; postInvalidate(); } /** * 根据移动的距离来刷新原来的圆半径大小 * * @param distance */ private void refreshCurRadiusByMoveDistance(int distance) { if (distance > maxMoveLength) { isArrivedMaxMoved = true; curRadius = 0; } else { isArrivedMaxMoved = false; float calcRadius = (1 - 1f * distance / maxMoveLength) * originRadius; float maxRadius = BasicUiUtils.dip2px(context, 2); curRadius = (int) Math.max(calcRadius, maxRadius); // Logger.d(TAG, "[refreshCurRadiusByMoveDistance]curRadius: " + curRadius + ", calcRadius: " + calcRadius + ", maxRadius: " + maxRadius); } } /** * 改变某控件的高度 * * @param view * @param height */ private void changeViewHeight(View view, int width, int height) { ViewGroup.LayoutParams lp = view.getLayoutParams(); if (null == lp) { lp = originLp; } lp.width = width; lp.height = height; view.setLayoutParams(lp); } /** * 回滚状态动画 */ private void startRollBackAnimation(long duration) { ValueAnimator rollBackAnim = ValueAnimator.ofFloat(curRadius, originRadius); rollBackAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (float) animation.getAnimatedValue(); curRadius = (int) value; postInvalidate(); } }); rollBackAnim.setInterpolator(new BounceInterpolator()); // 反弹效果 rollBackAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); DraggableFlagView.this.clearAnimation(); } }); rollBackAnim.setDuration(duration); rollBackAnim.start(); } /** * 计算四个坐标的三角边关系 */ class Triangle { double deltaX; double deltaY; double hypotenuse; @Override public String toString() { return "Triangle{" + "deltaX=" + deltaX + ", deltaY=" + deltaY + ", hypotenuse=" + hypotenuse + '}'; } } public String getText() { return text; } public void setText(String text) { this.text = text; this.setVisibility(VISIBLE); postInvalidate(); } }