能够显示1x~8x 范围的放大缩小动画,移动单位为每缩放0.1x的时候移动一格,因此共有80格,这个例子是使用触摸位置的x轴占整个x轴多少来控制倍数控件的展现。具体运算主要看initView方法便可。实际效果会比图片好,图片跳帧很严重,实际上每一格移动的时候都会有一个小的跳跃感动画,能够下载下来试试:java
代码文件和测试apkandroid
package com.cjz.zoombar; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.example.chenjiezhu.myapplication.R; public class MainActivity extends Activity { private MzZoomCircleView zoomCircleView; private float prevZoomVal, currentZoomVal; private TextView btn_zoom_val; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.zoom_bar); zoomCircleView = findViewById(R.id.zoomCircleView); btn_zoom_val = findViewById(R.id.tv_zoom_val); zoomCircleView.setVisible(true); } @Override public boolean onTouchEvent(MotionEvent event) { boolean val = super.onTouchEvent(event); Log.i("cjztest", event.toString()); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: zoomCircleView.setCurrentValue((8f * event.getX() / 1080f), false); btn_zoom_val.setText((8f * event.getX() / 1080f) + "x"); break; case MotionEvent.ACTION_UP: prevZoomVal = currentZoomVal; break; } return val; } }
动画控件:canvas
package com.cjz.zoombar; import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.view.animation.PathInterpolator; import java.util.ArrayList; import java.util.List; public class MzZoomCircleView extends View { private int mCircleRadius; private int mCircleStartPadding; private int mCirclePadding; private Paint mCirclePaint; private int mIndicatorRadius; private boolean mVisible; private int mWidth; private int mHeight; private static final float MIN_ZOOM_VALUE = 1.0f; /** * value range: [1.0f ~ 8.0f] */ private float mMaxZoomValue = 8.0f; private float mPrevMaxZoomValue = Float.MIN_VALUE; private float mCurrentValue = 1.0f; private float mLastValue = 1.0f; private float mMoveX = 0; private float mIndicatorModeX; private ValueAnimator mValueAnimator; private ValueAnimator mAlphaAnimator; private boolean mIsClickZoomIndicator = false; private Rect mValidDrawBounds = new Rect(); private List<Calibration> mShowCirclePoints = new ArrayList<>(); private List<Calibration> mShowWithAlphaPoints = new ArrayList<>(); /** * record the circle point and alpha */ class Calibration { private float x; private float y; private float mAlpha; private int defaultX; public void setX(float x) { this.x = x; } public void setY(float y) { this.y = y; } public void setAlpha(float alpha) { mAlpha = alpha; } public void setDefaultX(int defaultX) { this.defaultX = defaultX; } } public MzZoomCircleView(Context context) { this(context, null); } public MzZoomCircleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MzZoomCircleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initValue(); initPaint(); } private void initValue() { mCircleRadius = dip2px(getContext(), 1); mCircleStartPadding = dip2px(getContext(), 5); mCirclePadding = dip2px(getContext(), 6); mIndicatorRadius = dip2px(getContext(), 16); // mIndicatorRadius = 16; } private void initPaint() { mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); Typeface typeface = Typeface.create("SFDIN-medium", Typeface.NORMAL); mCirclePaint.setTypeface(typeface); mCirclePaint.setColor(Color.RED); } private void initView(float offset, boolean isTarget) { float positionOffset = offset; mShowWithAlphaPoints.clear(); mValidDrawBounds.set(getPaddingStart() - 10, getPaddingTop(), mWidth - getPaddingEnd() + 10, mHeight - getPaddingBottom()); int circleSize = 2 * mCircleRadius + mCirclePadding; //create points array if (mShowCirclePoints.isEmpty() || !floatEquals(mPrevMaxZoomValue, mMaxZoomValue)) { mShowCirclePoints.clear(); for (int i = 0; i < (mMaxZoomValue - 1f) / 0.1f; i++) { Calibration calibration = new Calibration(); calibration.setDefaultX(i * circleSize + mWidth / 2); //mWidth / 2 右半轴为起始点,每一个加上空位的大小是(2 * mCircleRadius + mCirclePadding) calibration.setY(mHeight / 2); calibration.setAlpha(0); mShowCirclePoints.add(calibration); } mPrevMaxZoomValue = mMaxZoomValue; } //give them position for (int i = 0; i < mShowCirclePoints.size(); i++) { Calibration calibration = mShowCirclePoints.get(i); if (!isTarget) { calibration.x = calibration.defaultX - ((int) ((positionOffset - 1f) / (mMaxZoomValue) * mShowCirclePoints.size())) * circleSize; } else { calibration.x = calibration.defaultX + positionOffset; } calibration.y = mHeight / 2; calibration.setAlpha(0); //绕开中间的按钮,从中线开始左右靠 if (calibration.x > mWidth / 2) { calibration.x += mIndicatorRadius; } else if (calibration.x < mWidth / 2) { calibration.x -= mIndicatorRadius; } //set alpha values if (calibration.x < (mWidth / 2 - (mIndicatorRadius + mCircleStartPadding))) { float startPoint = mWidth / 2 - circleSize * 10 - (mIndicatorRadius + mCircleStartPadding); if (calibration.x > startPoint) { calibration.setAlpha((calibration.x - startPoint) / (mWidth / 2 - startPoint) * 255f); } } else if (calibration.x > (mWidth / 2 + (mIndicatorRadius + mCircleStartPadding))) { float endPoint = mWidth / 2 + circleSize * 10 + (mIndicatorRadius + mCircleStartPadding); if (calibration.x < endPoint) { calibration.setAlpha((1 - (calibration.x - mWidth / 2) / (endPoint - mWidth / 2)) * 255f); } } mShowWithAlphaPoints.add(calibration); } } public void updateMaxZoomValue(float maxZoomValue) { mMaxZoomValue = maxZoomValue; } public float getCurrentValue() { return mCurrentValue; } public void setVisible(boolean visible) { mVisible = visible; this.setVisibility(mVisible ? VISIBLE : INVISIBLE); } public void resetCircleValue() { setCurrentValue(MIN_ZOOM_VALUE, false); } public void handleZoomCircleViewAnim(final boolean show, final boolean resetCircleValue) { if (mAlphaAnimator != null && mAlphaAnimator.isRunning()) { mAlphaAnimator.end(); } if (show) { mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 0.0f, 1.0f); mAlphaAnimator.setDuration(80); } else { mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 1.0f, 0.0f); mAlphaAnimator.setDuration(100); } mAlphaAnimator.setInterpolator(new PathInterpolator(0.3f, 0f, 0.66f, 1f)); mAlphaAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { if (show) { setVisible(true); } } @Override public void onAnimationEnd(Animator animation) { if (!show) { setVisible(false); } if (resetCircleValue) { if (!floatEquals(MIN_ZOOM_VALUE, mCurrentValue)) { mMoveX = (mCurrentValue - mLastValue) * 10 * (2 * mCircleRadius + mCirclePadding); } initView(mCurrentValue, false); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mAlphaAnimator.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!mVisible) { return; } mWidth = getWidth(); mHeight = getHeight(); for (Calibration calibration : mShowWithAlphaPoints) { mCirclePaint.setAlpha((int) calibration.mAlpha); canvas.drawCircle(calibration.x, calibration.y, mCircleRadius, mCirclePaint); } Paint indicatorPaint = new Paint(); indicatorPaint.setColor(Color.BLACK); indicatorPaint.setStyle(Paint.Style.STROKE); indicatorPaint.setAntiAlias(true); indicatorPaint.setStrokeWidth(3f); indicatorPaint.setTextSize(dip2px(getContext(), 10)); canvas.drawCircle(mWidth / 2f, mHeight / 2f, mIndicatorRadius, indicatorPaint); canvas.drawText(String.format("%.1fx", mCurrentValue), mWidth / 2 - dip2px(getContext(), 10) / 2 - dip2px(getContext(), 4), mHeight / 2 + dip2px(getContext(), 4), indicatorPaint); if (mValueAnimator != null && !mValueAnimator.isRunning() && mIsClickZoomIndicator) { handleZoomCircleViewAnim(false, true); mIsClickZoomIndicator = false; } } public synchronized void setCurrentValue(float value, boolean clickIndicator) { value = Float.parseFloat(String.format("%.1f", value)); if (floatEquals(value, mCurrentValue)) { return; } Log.i("cjztest", "value:" + value); mIsClickZoomIndicator = clickIndicator; if (value < MIN_ZOOM_VALUE) { value = MIN_ZOOM_VALUE; } else if (value > mMaxZoomValue) { value = mMaxZoomValue; } if (mValueAnimator != null && mValueAnimator.isRunning()) { mValueAnimator.end(); } if (floatEquals(mCurrentValue, value)) { return; } mLastValue = mCurrentValue; mCurrentValue = value; if (mIsClickZoomIndicator) { if (mCurrentValue > mLastValue) { mIndicatorModeX = mCirclePadding * 3 + mCircleRadius * 6; } else { mIndicatorModeX = -(mCirclePadding * 3 + mCircleRadius * 6); if (floatEquals(MIN_ZOOM_VALUE, mCurrentValue)) { initView(mCurrentValue, false); } } scrollToTarget(mIndicatorModeX); } else { mMoveX = (mCurrentValue - mLastValue) * 10 * (2 * mCircleRadius + mCirclePadding); scrollToTarget(mMoveX); } } private void scrollToTarget(final float x) { final int circleSize = (2 * mCircleRadius + mCirclePadding); mValueAnimator = new ValueAnimator().ofFloat(0, x); if (mIsClickZoomIndicator) { mValueAnimator.setDuration(240); } else { mValueAnimator.setDuration(150); } mValueAnimator.setInterpolator(new DecelerateInterpolator()); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (mIsClickZoomIndicator) { initView((float) animation.getAnimatedValue() - mWidth, true); //loop anim } else { //0.1f means a point, so is zoom value is 1, the left side have 1 / 0.1f = 10 points int endX = Math.round((mCurrentValue - 1f) * 10) * circleSize; int startX = Math.round((mLastValue - 1f) * 10) * circleSize; float targetVal = -(startX + (endX - startX) * (Math.abs((float) animation.getAnimatedValue() / x))); initView(Math.round(targetVal), true); } invalidate(); } }); mValueAnimator.start(); } private static boolean floatEquals(float a, float b){ if(Math.abs(a - b) <= 0.00001f){ return true; } else { return false; } } private static boolean floatEquals(double a, double b){ if(Math.abs(a - b) <= 0.0000001f){ return true; } else { return false; } } private int dip2px(Context context, float dipValue) { float m = context.getResources().getDisplayMetrics().density ; return (int) (dipValue * m) ; } }