| package com.android.camera.ui; |
| |
| import static com.android.camera.ui.GLRootView.dpToPixel; |
| import android.content.Context; |
| import android.graphics.Color; |
| import android.graphics.Rect; |
| import android.view.MotionEvent; |
| |
| import com.android.camera.R; |
| import com.android.camera.Util; |
| |
| import java.text.DecimalFormat; |
| import java.util.Arrays; |
| |
| import javax.microedition.khronos.opengles.GL11; |
| |
| public class ZoomController extends GLView { |
| private static final int LABEL_COLOR = Color.WHITE; |
| |
| private static final DecimalFormat sZoomFormat = new DecimalFormat("#.#x"); |
| private static final int INVALID_POSITION = Integer.MAX_VALUE; |
| |
| private static final float LABEL_FONT_SIZE = 18; |
| private static final int HORIZONTAL_PADDING = 3; |
| private static final int VERTICAL_PADDING = 3; |
| private static final int MINIMAL_HEIGHT = 150; |
| private static final float TOLERANCE_RADIUS = 30; |
| |
| private static float sLabelSize; |
| private static int sHorizontalPadding; |
| private static int sVerticalPadding; |
| private static int sMinimalHeight; |
| private static float sToleranceRadius; |
| |
| private static NinePatchTexture sBackground; |
| private static Texture sSlider; |
| private static Texture sTickMark; |
| private static Texture sFineTickMark; |
| |
| private StringTexture mTickLabels[]; |
| private float mRatios[]; |
| private int mIndex; |
| |
| private int mFineTickStep; |
| private int mLabelStep; |
| |
| private int mMaxLabelWidth; |
| private int mMaxLabelHeight; |
| |
| private int mSliderTop; |
| private int mSliderBottom; |
| private int mSliderLeft; |
| private int mSliderPosition = INVALID_POSITION; |
| private float mValueGap; |
| private ZoomListener mZoomListener; |
| |
| public interface ZoomListener { |
| public void onZoomChanged(int index, float ratio, boolean isMoving); |
| } |
| |
| public ZoomController(Context context) { |
| initializeStaticVariable(context); |
| } |
| |
| private void onSliderMoved(int position, boolean isMoving) { |
| position = Util.clamp(position, |
| mSliderTop, mSliderBottom - sSlider.getHeight()); |
| mSliderPosition = position; |
| invalidate(); |
| |
| int index = mRatios.length - 1 - (int) |
| ((position - mSliderTop) / mValueGap + .5f); |
| if (index != mIndex || !isMoving) { |
| mIndex = index; |
| if (mZoomListener != null) { |
| mZoomListener.onZoomChanged(mIndex, mRatios[mIndex], isMoving); |
| } |
| } |
| } |
| |
| private static void initializeStaticVariable(Context context) { |
| if (sBackground != null) return; |
| |
| sLabelSize = dpToPixel(context, LABEL_FONT_SIZE); |
| sHorizontalPadding = dpToPixel(context, HORIZONTAL_PADDING); |
| sVerticalPadding = dpToPixel(context, VERTICAL_PADDING); |
| sMinimalHeight = dpToPixel(context, MINIMAL_HEIGHT); |
| sToleranceRadius = dpToPixel(context, TOLERANCE_RADIUS); |
| |
| sBackground = new NinePatchTexture(context, R.drawable.zoom_background); |
| sSlider = new ResourceTexture(context, R.drawable.zoom_slider); |
| sTickMark = new ResourceTexture(context, R.drawable.zoom_tickmark); |
| sFineTickMark = new ResourceTexture( |
| context, R.drawable.zoom_finetickmark); |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| if (!changed) return; |
| Rect p = mPaddings; |
| int height = b - t - p.top - p.bottom; |
| int margin = Math.max(sSlider.getHeight(), mMaxLabelHeight); |
| mValueGap = (float) (height - margin) / (mRatios.length - 1); |
| |
| mSliderLeft = p.left + mMaxLabelWidth + sHorizontalPadding |
| + sTickMark.getWidth() + sHorizontalPadding; |
| |
| mSliderTop = p.top + margin / 2 - sSlider.getHeight() / 2; |
| mSliderBottom = mSliderTop + height - margin + sSlider.getHeight(); |
| } |
| |
| private boolean withInToleranceRange(float x, float y) { |
| float sx = mSliderLeft + sSlider.getWidth() / 2; |
| float sy = mSliderTop + (mRatios.length - 1 - mIndex) * mValueGap |
| + sSlider.getHeight() / 2; |
| float dist = Util.distance(x, y, sx, sy); |
| return dist <= sToleranceRadius; |
| } |
| |
| @Override |
| protected boolean onTouch(MotionEvent e) { |
| float x = e.getX(); |
| float y = e.getY(); |
| switch (e.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| if (withInToleranceRange(x, y)) { |
| onSliderMoved((int) (y - sSlider.getHeight()), true); |
| } |
| return true; |
| case MotionEvent.ACTION_MOVE: |
| if (mSliderPosition != INVALID_POSITION) { |
| onSliderMoved((int) (y - sSlider.getHeight()), true); |
| } |
| return true; |
| case MotionEvent.ACTION_UP: |
| if (mSliderPosition != INVALID_POSITION) { |
| onSliderMoved((int) (y - sSlider.getHeight()), false); |
| mSliderPosition = INVALID_POSITION; |
| } |
| return true; |
| } |
| return true; |
| } |
| |
| public void setAvailableZoomRatios(float ratios[]) { |
| if (Arrays.equals(ratios, mRatios)) return; |
| mRatios = ratios; |
| mLabelStep = getLabelStep(ratios.length); |
| mTickLabels = new StringTexture[ |
| (ratios.length + mLabelStep - 1) / mLabelStep]; |
| for (int i = 0, n = mTickLabels.length; i < n; ++i) { |
| mTickLabels[i] = StringTexture.newInstance( |
| sZoomFormat.format(ratios[i * mLabelStep]), |
| sLabelSize, LABEL_COLOR); |
| } |
| mFineTickStep = mLabelStep % 3 == 0 |
| ? mLabelStep / 3 |
| : mLabelStep %2 == 0 ? mLabelStep / 2 : 0; |
| |
| int maxHeight = 0; |
| int maxWidth = 0; |
| int labelCount = mTickLabels.length; |
| for (int i = 0; i < labelCount; ++i) { |
| maxWidth = Math.max(maxWidth, mTickLabels[i].getWidth()); |
| maxHeight = Math.max(maxHeight, mTickLabels[i].getHeight()); |
| } |
| |
| mMaxLabelHeight = maxHeight; |
| mMaxLabelWidth = maxWidth; |
| invalidate(); |
| } |
| |
| private int getLabelStep(final int valueCount) { |
| if (valueCount < 5) return 1; |
| for (int step = valueCount / 5;; ++step) { |
| if (valueCount / step <= 5) return step; |
| } |
| } |
| |
| @Override |
| protected void onMeasure(int widthSpec, int heightSpec) { |
| int labelCount = mTickLabels.length; |
| int ratioCount = mRatios.length; |
| |
| int height = (mMaxLabelHeight + sVerticalPadding) |
| * (labelCount - 1) * ratioCount / (mLabelStep * labelCount) |
| + Math.max(sSlider.getHeight(), mMaxLabelHeight); |
| |
| int width = mMaxLabelWidth + sHorizontalPadding + sTickMark.getWidth() |
| + sHorizontalPadding + sBackground.getIntrinsicWidth(); |
| height = Math.max(sMinimalHeight, height); |
| |
| new MeasureHelper(this) |
| .setPreferredContentSize(width, height) |
| .measure(widthSpec, heightSpec); |
| } |
| |
| @Override |
| protected void render(GLRootView root, GL11 gl) { |
| renderTicks(root, gl); |
| renderSlider(root, gl); |
| } |
| |
| private void renderTicks(GLRootView root, GL11 gl) { |
| float gap = mValueGap; |
| int labelStep = mLabelStep; |
| |
| // render the tick labels |
| int xoffset = mPaddings.left + mMaxLabelWidth; |
| float yoffset = mSliderBottom - sSlider.getHeight() / 2; |
| for (int i = 0, n = mTickLabels.length; i < n; ++i) { |
| Texture t = mTickLabels[i]; |
| t.draw(root, xoffset - t.getWidth(), |
| (int) (yoffset - t.getHeight() / 2)); |
| yoffset -= labelStep * gap; |
| } |
| |
| // render the main tick marks |
| Texture tickMark = sTickMark; |
| xoffset += sHorizontalPadding; |
| yoffset = mSliderBottom - sSlider.getHeight() / 2; |
| int halfHeight = tickMark.getHeight() / 2; |
| for (int i = 0, n = mTickLabels.length; i < n; ++i) { |
| tickMark.draw(root, xoffset, (int) (yoffset - halfHeight)); |
| yoffset -= labelStep * gap; |
| } |
| |
| if (mFineTickStep > 0) { |
| // render the fine tick marks |
| tickMark = sFineTickMark; |
| xoffset += sTickMark.getWidth() - tickMark.getWidth(); |
| yoffset = mSliderBottom - sSlider.getHeight() / 2; |
| halfHeight = tickMark.getHeight() / 2; |
| for (int i = 0, n = mRatios.length; i < n; ++i) { |
| if (i % mLabelStep != 0) { |
| tickMark.draw(root, xoffset, (int) (yoffset - halfHeight)); |
| } |
| yoffset -= gap; |
| } |
| } |
| } |
| |
| private void renderSlider(GLRootView root, GL11 gl) { |
| int left = mSliderLeft; |
| int bottom = mSliderBottom; |
| int top = mSliderTop; |
| sBackground.setSize(sBackground.getIntrinsicWidth(), bottom - top); |
| sBackground.draw(root, left, top); |
| |
| if (mSliderPosition == INVALID_POSITION) { |
| sSlider.draw(root, left, (int) |
| (top + mValueGap * (mRatios.length - 1 - mIndex))); |
| } else { |
| sSlider.draw(root, left, mSliderPosition); |
| } |
| } |
| |
| public void setZoomListener(ZoomListener listener) { |
| mZoomListener = listener; |
| } |
| |
| public void setZoomIndex(int index) { |
| index = Util.clamp(index, 0, mRatios.length - 1); |
| if (mIndex == index) return; |
| mIndex = index; |
| if (mZoomListener != null) { |
| mZoomListener.onZoomChanged(mIndex, mRatios[mIndex], false); |
| } |
| } |
| } |