/* * Copyright (C) 2010 The Android Open Source Project * * 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 com.example.android.videoeditor.widgets; import com.example.android.videoeditor.R; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /** * The zoom control */ public class ZoomControl extends View { private static final double MAX_ANGLE = Math.PI / 3; private static final double THUMB_RADIUS_CONTAINER_SIZE_RATIO = 0.432; private static final double THUMB_INTERNAL_RADIUS_CONTAINER_SIZE_RATIO = 0.24; // Instance variables private final Drawable mThumb; private double mRadius; private double mInternalRadius; private int mMaxProgress, mProgress; private OnZoomChangeListener mListener; private int mThumbX, mThumbY; private double mInterval; /** * The zoom change listener */ public interface OnZoomChangeListener { /** * The progress value has changed * * @param progress The progress value * @param fromUser true if the user is changing the zoom */ public void onProgressChanged(int progress, boolean fromUser); } public ZoomControl(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // Set the default maximum progress mMaxProgress = 100; computeInterval(); // Load the thumb selector mThumb = context.getResources().getDrawable(R.drawable.zoom_thumb_selector); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); double width = right - left; mRadius = width * THUMB_RADIUS_CONTAINER_SIZE_RATIO; mInternalRadius = width * THUMB_INTERNAL_RADIUS_CONTAINER_SIZE_RATIO; } public ZoomControl(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ZoomControl(Context context) { this(context, null, 0); } @Override public void refreshDrawableState() { mThumb.setState(isPressed() ? PRESSED_WINDOW_FOCUSED_STATE_SET : ENABLED_STATE_SET); invalidate(); } /** * @param max The maximum value */ public void setMax(int max) { mMaxProgress = max; computeInterval(); } /** * @param progress The progress */ public void setProgress(int progress) { mProgress = progress; progressToPosition(); invalidate(); } /** * @param listener The listener */ public void setOnZoomChangeListener(OnZoomChangeListener listener) { mListener = listener; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mThumbX == 0 && mThumbY == 0) { progressToPosition(); } final int halfWidth = mThumb.getIntrinsicWidth() / 2; final int halfHeight = mThumb.getIntrinsicHeight() / 2; mThumb.setBounds(mThumbX - halfWidth, mThumbY - halfHeight, mThumbX + halfWidth, mThumbY + halfHeight); mThumb.setAlpha(isEnabled() ? 255 : 100); mThumb.draw(canvas); } @Override public boolean onTouchEvent(MotionEvent ev) { super.onTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { if (isEnabled()) { getParent().requestDisallowInterceptTouchEvent(true); } break; } case MotionEvent.ACTION_MOVE: { if (isEnabled()) { final float x = ev.getX() - (getWidth() / 2); final float y = -(ev.getY() - (getHeight() / 2)); final double alpha = Math.atan((double)y / (double)x); if (!checkHit(x, y, alpha)) { return true; } final int progress; if (x >= 0 && y >= 0) { mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2)); mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha))); progress = (int)((mMaxProgress / 2) - (alpha / mInterval)); } else if (x >= 0 && y <= 0) { mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2)); mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha))); progress = (int)((mMaxProgress / 2) - (alpha / mInterval)); } else if (x <= 0 && y >= 0) { mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha))); mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha))); progress = -(int)(((alpha + MAX_ANGLE) / mInterval)); } else { mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha))); mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha))); progress = (int)(mMaxProgress - ((alpha - MAX_ANGLE) / mInterval)); } invalidate(); if (mListener != null) { if (progress != mProgress) { mProgress = progress; mListener.onProgressChanged(mProgress, true); } } } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { break; } default: { break; } } return true; } /** * Check if the user is touching the correct area * * @param x The horizontal coordinate * @param y The vertical coordinate * @param alpha The angle * @return true if there is a hit in the allowed area */ private boolean checkHit(float x, float y, double alpha) { final double radius = Math.sqrt((x * x) + (y * y)); if (radius < mInternalRadius) { return false; } if (x >= 0) { return true; } else if (y >= 0) { if ((alpha >= -(Math.PI / 2)) && (alpha <= -MAX_ANGLE)) { return true; } } else { if ((alpha >= MAX_ANGLE) && (alpha <= (Math.PI / 2))) { return true; } } return false; } /** * Compute the position of the thumb based on the progress values */ private void progressToPosition() { if (getWidth() == 0) { // Layout is not yet complete return; } final double beta; if (mProgress <= mMaxProgress / 2) { beta = ((mMaxProgress / 2) - mProgress) * mInterval; } else { beta = ((mMaxProgress - mProgress) * mInterval) + Math.PI + MAX_ANGLE; } final double alpha; if (beta >= 0 && beta <= Math.PI / 2) { alpha = beta; mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2)); mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha))); } else if (beta > Math.PI / 2 && beta < (Math.PI / 2) + MAX_ANGLE) { alpha = beta - Math.PI; mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha))); mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha))); } else if (beta <= 2 * Math.PI && beta > (3 * Math.PI) / 2) { alpha = beta - (2 * Math.PI); mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2)); mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha))); } else { alpha = beta - Math.PI; mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha))); mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha))); } } /** * Compute the radians interval between progress values */ private void computeInterval() { mInterval = (Math.PI - MAX_ANGLE) / (mMaxProgress / 2); } }