Home | History | Annotate | Download | only in widgets
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.videoeditor.widgets;
     18 
     19 import com.android.videoeditor.R;
     20 
     21 import android.content.Context;
     22 import android.graphics.Canvas;
     23 import android.graphics.drawable.Drawable;
     24 import android.util.AttributeSet;
     25 import android.view.MotionEvent;
     26 import android.view.View;
     27 
     28 /**
     29  * The zoom control
     30  */
     31 public class ZoomControl extends View {
     32 
     33     private static final double MAX_ANGLE = Math.PI / 3;
     34     private static final double THUMB_RADIUS_CONTAINER_SIZE_RATIO = 0.432;
     35     private static final double THUMB_INTERNAL_RADIUS_CONTAINER_SIZE_RATIO = 0.24;
     36 
     37     // Instance variables
     38     private final Drawable mThumb;
     39     private double mRadius;
     40     private double mInternalRadius;
     41     private int mMaxProgress, mProgress;
     42     private OnZoomChangeListener mListener;
     43     private int mThumbX, mThumbY;
     44     private double mInterval;
     45 
     46     /**
     47      * The zoom change listener
     48      */
     49     public interface OnZoomChangeListener {
     50         /**
     51          * The progress value has changed
     52          *
     53          * @param progress The progress value
     54          * @param fromUser true if the user is changing the zoom
     55          */
     56         public void onProgressChanged(int progress, boolean fromUser);
     57     }
     58 
     59     public ZoomControl(Context context, AttributeSet attrs, int defStyle) {
     60         super(context, attrs, defStyle);
     61 
     62         // Set the default maximum progress
     63         mMaxProgress = 100;
     64         computeInterval();
     65 
     66         // Load the thumb selector
     67         mThumb = context.getResources().getDrawable(R.drawable.zoom_thumb_selector);
     68     }
     69 
     70     @Override
     71     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     72       super.onLayout(changed, left, top, right, bottom);
     73       double width = right - left;
     74       mRadius = width * THUMB_RADIUS_CONTAINER_SIZE_RATIO;
     75       mInternalRadius = width * THUMB_INTERNAL_RADIUS_CONTAINER_SIZE_RATIO;
     76     }
     77 
     78     public ZoomControl(Context context, AttributeSet attrs) {
     79         this(context, attrs, 0);
     80     }
     81 
     82     public ZoomControl(Context context) {
     83         this(context, null, 0);
     84     }
     85 
     86     @Override
     87     public void refreshDrawableState() {
     88         mThumb.setState(isPressed() ? PRESSED_WINDOW_FOCUSED_STATE_SET : ENABLED_STATE_SET);
     89         invalidate();
     90     }
     91 
     92     /**
     93      * @param max The maximum value
     94      */
     95     public void setMax(int max) {
     96         mMaxProgress = max;
     97         computeInterval();
     98     }
     99 
    100     /**
    101      * @param progress The progress
    102      */
    103     public void setProgress(int progress) {
    104         mProgress = progress;
    105 
    106         progressToPosition();
    107         invalidate();
    108     }
    109 
    110     /**
    111      * @param listener The listener
    112      */
    113     public void setOnZoomChangeListener(OnZoomChangeListener listener) {
    114         mListener = listener;
    115     }
    116 
    117     @Override
    118     protected void onDraw(Canvas canvas) {
    119         super.onDraw(canvas);
    120 
    121         if (mThumbX == 0 && mThumbY == 0) {
    122             progressToPosition();
    123         }
    124 
    125         final int halfWidth = mThumb.getIntrinsicWidth() / 2;
    126         final int halfHeight = mThumb.getIntrinsicHeight() / 2;
    127         mThumb.setBounds(mThumbX - halfWidth, mThumbY - halfHeight, mThumbX + halfWidth,
    128                 mThumbY + halfHeight);
    129         mThumb.setAlpha(isEnabled() ? 255 : 100);
    130         mThumb.draw(canvas);
    131     }
    132 
    133     @Override
    134     public boolean onTouchEvent(MotionEvent ev) {
    135         super.onTouchEvent(ev);
    136         switch (ev.getAction()) {
    137             case MotionEvent.ACTION_DOWN: {
    138                 if (isEnabled()) {
    139                     getParent().requestDisallowInterceptTouchEvent(true);
    140                 }
    141                 break;
    142             }
    143 
    144             case MotionEvent.ACTION_MOVE: {
    145                 if (isEnabled()) {
    146                     final float x = ev.getX() - (getWidth() / 2);
    147                     final float y = -(ev.getY() - (getHeight() / 2));
    148                     final double alpha = Math.atan((double)y / (double)x);
    149 
    150                     if (!checkHit(x, y, alpha)) {
    151                         return true;
    152                     }
    153 
    154                     final int progress;
    155                     if (x >= 0 && y >= 0) {
    156                         mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2));
    157                         mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha)));
    158                         progress = (int)((mMaxProgress / 2) - (alpha / mInterval));
    159                     } else if (x >= 0 && y <= 0) {
    160                         mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2));
    161                         mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha)));
    162                         progress = (int)((mMaxProgress / 2) - (alpha / mInterval));
    163                     } else if (x <= 0 && y >= 0) {
    164                         mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha)));
    165                         mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha)));
    166                         progress = -(int)(((alpha + MAX_ANGLE) / mInterval));
    167                     } else {
    168                         mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha)));
    169                         mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha)));
    170                         progress = (int)(mMaxProgress - ((alpha - MAX_ANGLE) / mInterval));
    171                     }
    172 
    173                     invalidate();
    174 
    175                     if (mListener != null) {
    176                         if (progress != mProgress) {
    177                             mProgress = progress;
    178                             mListener.onProgressChanged(mProgress, true);
    179                         }
    180                     }
    181                 }
    182                 break;
    183             }
    184 
    185             case MotionEvent.ACTION_CANCEL:
    186             case MotionEvent.ACTION_UP: {
    187                 break;
    188             }
    189 
    190             default: {
    191                 break;
    192             }
    193         }
    194 
    195         return true;
    196     }
    197 
    198     /**
    199      * Check if the user is touching the correct area
    200      *
    201      * @param x The horizontal coordinate
    202      * @param y The vertical coordinate
    203      * @param alpha The angle
    204      * @return true if there is a hit in the allowed area
    205      */
    206     private boolean checkHit(float x, float y, double alpha) {
    207         final double radius = Math.sqrt((x * x) + (y * y));
    208         if (radius < mInternalRadius) {
    209             return false;
    210         }
    211 
    212         if (x >= 0) {
    213             return true;
    214         } else if (y >= 0) {
    215             if ((alpha >= -(Math.PI / 2)) && (alpha <= -MAX_ANGLE)) {
    216                 return true;
    217             }
    218         } else {
    219             if ((alpha >= MAX_ANGLE) && (alpha <= (Math.PI / 2))) {
    220                 return true;
    221             }
    222         }
    223 
    224         return false;
    225     }
    226 
    227     /**
    228      * Compute the position of the thumb based on the progress values
    229      */
    230     private void progressToPosition() {
    231         if (getWidth() == 0) { // Layout is not yet complete
    232             return;
    233         }
    234 
    235         final double beta;
    236         if (mProgress <= mMaxProgress / 2) {
    237             beta = ((mMaxProgress / 2) - mProgress) * mInterval;
    238         } else {
    239             beta = ((mMaxProgress - mProgress) * mInterval) + Math.PI + MAX_ANGLE;
    240         }
    241 
    242         final double alpha;
    243         if (beta >= 0 && beta <= Math.PI / 2) {
    244             alpha = beta;
    245             mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2));
    246             mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha)));
    247         } else if (beta > Math.PI / 2 && beta < (Math.PI / 2) + MAX_ANGLE) {
    248             alpha = beta - Math.PI;
    249             mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha)));
    250             mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha)));
    251         } else if (beta <= 2 * Math.PI && beta > (3 * Math.PI) / 2) {
    252             alpha = beta - (2 * Math.PI);
    253             mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2));
    254             mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha)));
    255         } else {
    256             alpha = beta - Math.PI;
    257             mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha)));
    258             mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha)));
    259         }
    260     }
    261 
    262     /**
    263      * Compute the radians interval between progress values
    264      */
    265     private void computeInterval() {
    266         mInterval = (Math.PI - MAX_ANGLE) / (mMaxProgress / 2);
    267     }
    268 }
    269