Search code examples
androidlayoutimage-scalingmagnification

Magnification based on touch location


I'm having an issue developing a magnification based on touch location in Android. The result I want is similar to the older mac book application bar magnification; the icon where the mouse is hovered over is the largest magnified and the icons surround it slow scale down back to their original size.

I've included an image of what it should like if the user touch an icon in the center of the icons bar.

enter image description here

Currently I have code that somewhat works. I'm using a cos equation to create the scale factor but when you touch near the yellow icons, it causes the last green icon to magnify. Also the sloping affect isn't coming out properly. I've attached a photo with sample touches and the code. Thank you for any help you can provide. enter image description here

Java class preforming the scaling

public class Layout extends RelativeLayout implements View.OnTouchListener{
    private Camera mCamera = new Camera();
    private Matrix mMatrix = new Matrix();
    private ArrayList<Layer> layers = new ArrayList<Layer>();
    private final String TAG = this.getClass().getSimpleName();
    private int xTouch = 0;
    private int yTouch = 0;
    private boolean isUserInput = false;

    private int childHeight = 1;
    private Context context;

    public TrackerLayout(Context context) {
        super(context);
        setupView(context);
    }

    public TrackerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setupView(context);
    }

    public TrackerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setupView(context);
    }

    private void setupView(Context context){
        this.context = context;
        setOnTouchListener(this);
        setWillNotDraw(false);        
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (layers.size() != getChildCount()) {
            getChildrenViews();
        }
        //getChild's order
        sortChildrenByDist();
        super.onDraw(canvas);

    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        childHeight = h / 8;
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        if(isUserInput && yTouch >= 0) {
            Bitmap bitmap = child.getDrawingCache();

            if (bitmap == null) {
                child.setDrawingCacheEnabled(true);
                child.buildDrawingCache();
                bitmap = child.getDrawingCache();
            }

            //Get a reference for the child layer that's being modified
            Layer layer = null;
            for (Layer tempLayer : layers) {
                if (tempLayer.child.getId() == child.getId()) {
                    layer = tempLayer;
                    break;
                }
            }

            canvas.save();
            if(layer.coordinates.scale < 1){
                layer.coordinates.scale = 1;
            }
            canvas.scale(layer.coordinates.scale, layer.coordinates.scale, layer.coordinates.pivotX, layer.coordinates.pivotY);

            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setFilterBitmap(true);

            if (mCamera == null) {
                mCamera = new Camera();
            }

            mCamera.save();
            mCamera.rotateY(layer.coordinates.rotation);

            if (mMatrix == null) {
                mMatrix = new Matrix();
            }

            mCamera.getMatrix(mMatrix);
            mCamera.restore();

            mMatrix.preTranslate(-layer.coordinates.centerX, -layer.coordinates.centerY);
            mMatrix.postScale(layer.coordinates.scale, layer.coordinates.scale);
            mMatrix.postTranslate(layer.coordinates.pivotX, layer.coordinates.pivotY);
            canvas.drawBitmap(bitmap, layer.coordinates.left, layer.coordinates.top, paint);

            canvas.restore();
            return false;
        }
        return super.drawChild(canvas, child, drawingTime);

    }

    private void getChildrenViews() {
        int childrenCount = this.getChildCount();

        for (int i = 0; i < childrenCount; i++) {
            if (getChildAt(i) instanceof ImageView) {
                Layer layer = new Layer();
                ImageView child = (ImageView) getChildAt(i);
                int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, context.getResources().getDisplayMetrics());
                RelativeLayout.LayoutParams params = (LayoutParams) child.getLayoutParams();
                params.height = childHeight;
                child.setLayoutParams(params );
                layer.child = (child);

                layers.add(layer);
            }
        }
    }

    private void sortChildrenByDist() {
        ArrayList<Layer> negDistLayers = new ArrayList<Layer>();
        ArrayList<Layer> postDistLayers = new ArrayList<Layer>();


        for (Layer layer : layers) {
            layer.coordinates = calculateCoordinates(layer.child);
            if(layer.coordinates.distFromCenter >= 0 ){
                postDistLayers.add(layer);
            }else{
                negDistLayers.add(layer);
            }
        }

        Collections.sort(negDistLayers);
        Collections.sort(postDistLayers);

        layers.clear();
        layers.addAll(postDistLayers);
        layers.addAll(negDistLayers);

        drawOrderedLayers();
    }

    private synchronized void drawOrderedLayers() {
        for (int i = (layers.size() - 1); i > -1; i--) {
            this.bringChildToFront(layers.get(i).child);

        }
    }

    /**
     * This method calculates the coordinates used to create the rotates, camera, and scaling on each child view
     *
     * @param child - the view the coordinates will be calculated from
     * @return
     */

    private Coordinates calculateCoordinates(View child) {
        Coordinates coordinates = new Coordinates();

        coordinates.left = child.getLeft();
        coordinates.top = child.getTop();

        coordinates.centerX = child.getWidth() / 2;
        coordinates.centerY = child.getHeight() / 2;

        coordinates.pivotX = coordinates.left + coordinates.centerX;
        coordinates.pivotY = coordinates.top + coordinates.centerY;

        coordinates.userYInput = yTouch;
        coordinates.distFromCenter = (yTouch == 0) ? coordinates.pivotY : ((coordinates.pivotY - coordinates.userYInput) / coordinates.userYInput);


        double cosValue = (Math.cos(coordinates.distFromCenter));
        coordinates.scale = (float) (2 * cosValue);

        coordinates.rotation = coordinates.distFromCenter;
        return coordinates;

    }    

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        final int action = event.getAction();
        Log.i(TAG, " on touch detected");
        switch (action & MotionEvent.ACTION_MASK) {

            case MotionEvent.ACTION_DOWN: {
                xTouch = (int) event.getX();
                yTouch = (int) event.getY();
                isUserInput = true;
                return true;
            }

            case MotionEvent.ACTION_MOVE: {

                xTouch = (int) event.getX();
                yTouch = (int) event.getY();
                isUserInput = true;
                return true;
            }

            default:
                isUserInput = false;
                return false;
        }

    }

    private class Coordinates {
        int left;
        int top;
        int centerX;
        int centerY;
        float pivotX;
        float pivotY;
        float userYInput;
        float distFromCenter;
        float scale;
        float rotation;

    }

    //This class will hold a reference to the child and it's coordinates to 
    //help imitate layers
    private class Layer implements Comparable<Layer> {
        View child;
        Coordinates coordinates;

        //All values need to be sort towards ) so there's an if case to determine if the input is
        //comparing the list of positive values or negative values
        public int compareTo(Layer compareItem) {
            if(coordinates.distFromCenter >= 0) {
                return Float.compare(coordinates.distFromCenter, compareItem.coordinates.distFromCenter);
            }else{
                return Float.compare(compareItem.coordinates.distFromCenter, coordinates.distFromCenter);
            }
        }

    }

}

xml layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background_color">

    <com.layouts.Layout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_alignParentEnd="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop=“true”
        android:orientation="vertical">


        <ImageView
            android:id="@+id/icon_level_1"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="right"
            android:layout_alignParentRight="true"
            android:background="#FFF66C"
            android:layout_alignParentTop="true"/>

        <ImageView
            android:id="@+id/icon_level_2"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="right"
            android:background="#E7EE6C"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/ticon_level_1"/>

        <ImageView
            android:id="@+id/icon_level_3"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="right"
            android:background="#CCE46D"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/icon_level_2"/>

        <ImageView
            android:id="@+id/icon_level_4"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="right"
            android:background="#B2E07B"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/icon_level_3"/>

        <ImageView
            android:id="@+id/icon_level_5"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="right"
            android:background="#8BDB91"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/icon_level_4"/>

        <ImageView
            android:id="@+id/icon_level_6"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="right"
            android:background="#65BE91"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/icon_level_5"/>

        <ImageView
            android:id="@+id/icon_level_7"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="right"
            android:background="#4E8F77"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/icon_level_6"/>

        <ImageView
            android:id="@+id/icon_level_8"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="right"
            android:background="#3A655F"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/icon_level_7"/>

    </com.layouts.Layout>

Solution

  • My fix for this was a much simpler equation.
    I used the center pivot of each square to get their distance for the center and then divided the distance from the center by the layout height.

     distFromCenter = (yTouch == 0) ? pivotY : Math.abs((pivotY - userYInput));
     scale = (distFromCenter / layoutHieght);
     scale = scale == 0 ? 2 : 2 - scale. 
    

    The close the center, the larger the scale, other wise its reduced but never goes below 1.