Sunday 26 March 2017

app6 widgets

************widgets*************
1)BitmapCroppingWorkerTask.java
===============================
// "Therefore those skilled at the unorthodox// are infinite as heaven and earth,// inexhaustible as the great rivers.// When they come to an end,// they begin again,// like the days and months;// they die and are reborn,// like the four seasons."//// - Sun Tsu,// "The Art of War"
package com.eppico.widgets;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.Rect;import android.net.Uri;import android.os.AsyncTask;

import com.eppico.cropwindow.ImageViewUtil;
import java.lang.ref.WeakReference;
/** * Task to crop bitmap asynchronously from the UI thread. */class BitmapCroppingWorkerTask extends AsyncTask<Void, Void, BitmapCroppingWorkerTask.Result> {

    //region: Fields and Consts
    /**     * Use a WeakReference to ensure the ImageView can be garbage collected     */    private final WeakReference<CropImageView> mCropImageViewReference;
    /**     * the bitmap to crop     */    private final Bitmap mBitmap;
    /**     * The Android URI of the image to load     */    private final Uri mUri;
    /**     * The context of the crop image view widget used for loading of bitmap by Android URI     */    private final Context mContext;
    /**     * Required cropping rectangle     */    private final Rect mRect;
    /**     * The shape to crop the image     */    private final CropImageView.CropShape mCropShape;
    /**     * Degrees the image was rotated after loading     */    private final int mDegreesRotated;
    /**     * required width of the cropping image     */    private final int mReqWidth;
    /**     * required height of the cropping image     */    private final int mReqHeight;    //endregion
    public BitmapCroppingWorkerTask(CropImageView cropImageView, Bitmap bitmap, Rect rect, CropImageView.CropShape cropShape) {
        mCropImageViewReference = new WeakReference<>(cropImageView);        mContext = cropImageView.getContext();        mBitmap = bitmap;        mRect = rect;        mCropShape = cropShape;        mUri = null;        mDegreesRotated = 0;        mReqWidth = 0;        mReqHeight = 0;    }

    public BitmapCroppingWorkerTask(CropImageView cropImageView, Uri uri, Rect rect, CropImageView.CropShape cropShape, int degreesRotated, int reqWidth, int reqHeight) {
        mCropImageViewReference = new WeakReference<>(cropImageView);        mContext = cropImageView.getContext();        mUri = uri;        mRect = rect;        mCropShape = cropShape;        mDegreesRotated = degreesRotated;        mReqWidth = reqWidth;        mReqHeight = reqHeight;        mBitmap = null;    }

    /**     * The Android URI that this task is currently loading.     */    public Uri getUri() {
        return mUri;    }

    /**     * Crop image in background.     *     * @param params ignored     * @return the decoded bitmap data     */    @Override
    protected Result doInBackground(Void... params) {
        try {
            if (!isCancelled()) {
                Bitmap bitmap = null;                if (mUri != null) {
                    bitmap = ImageViewUtil.cropBitmap(
                            mContext,                            mUri,                            mRect,                            mDegreesRotated,                            mReqWidth,                            mReqHeight);                } else if (mBitmap != null) {
                    bitmap = ImageViewUtil.cropBitmap(mBitmap, mRect);                }
                if (bitmap != null && mCropShape == CropImageView.CropShape.OVAL) {
                    bitmap = ImageViewUtil.toOvalBitmap(bitmap);                }
                return new Result(bitmap);            }
            return null;        } catch (Exception e) {
            return new Result(e);        }
    }

    /**     * Once complete, see if ImageView is still around and set bitmap.     *     * @param result the result of bitmap cropping     */    @Override
    protected void onPostExecute(Result result) {
        if (result != null) {
            boolean completeCalled = false;            if (!isCancelled()) {
                CropImageView cropImageView = mCropImageViewReference.get();                if (cropImageView != null) {
                    completeCalled = true;                    cropImageView.onGetImageCroppingAsyncComplete(result);                }
            }
            if (!completeCalled && result.bitmap != null) {
                // fast release of unused bitmap                result.bitmap.recycle();            }
        }
    }

    //region: Inner class: Result
    /**     * The result of BitmapCroppingWorkerTask async loading.     */    public static final class Result {

        /**         * The cropped bitmap         */        public final Bitmap bitmap;
        /**         * The error that occurred during async bitmap cropping.         */        public final Exception error;
        Result(Bitmap bitmap) {
            this.bitmap = bitmap;            this.error = null;        }

        Result(Exception error) {
            this.bitmap = null;            this.error = error;        }
    }
    //endregion}
2)BitmapLoadingWorkerTask.java
==============================
// "Therefore those skilled at the unorthodox// are infinite as heaven and earth,// inexhaustible as the great rivers.// When they come to an end,// they begin again,// like the days and months;// they die and are reborn,// like the four seasons."//// - Sun Tsu,// "The Art of War"
package com.eppico.widgets;
import android.content.Context;import android.graphics.Bitmap;import android.net.Uri;import android.os.AsyncTask;import android.util.DisplayMetrics;

import com.eppico.cropwindow.ImageViewUtil;
import java.lang.ref.WeakReference;
/** * Task to load bitmap asynchronously from the UI thread. */class BitmapLoadingWorkerTask extends AsyncTask<Void, Void, BitmapLoadingWorkerTask.Result> {

    //region: Fields and Consts
    /**     * Use a WeakReference to ensure the ImageView can be garbage collected     */    private final WeakReference<CropImageView> mCropImageViewReference;
    /**     * The Android URI of the image to load     */    private final Uri mUri;
    /**     * Optional: if given use this rotation and not by exif     */    private final Integer mPreSetRotation;
    /**     * The context of the crop image view widget used for loading of bitmap by Android URI     */    private final Context mContext;
    /**     * required width of the cropping image after density adjustment     */    private final int mWidth;
    /**     * required height of the cropping image after density adjustment     */    private final int mHeight;    //endregion
    public BitmapLoadingWorkerTask(CropImageView cropImageView, Uri uri, Integer preSetRotation) {
        mUri = uri;        mPreSetRotation = preSetRotation;        mCropImageViewReference = new WeakReference<>(cropImageView);
        mContext = cropImageView.getContext();
        DisplayMetrics metrics = cropImageView.getResources().getDisplayMetrics();        double densityAdj = metrics.density > 1 ? 1 / metrics.density : 1;        mWidth = (int) (metrics.widthPixels * densityAdj);        mHeight = (int) (metrics.heightPixels * densityAdj);    }

    /**     * The Android URI that this task is currently loading.     */    public Uri getUri() {
        return mUri;    }

    /**     * Decode image in background.     *     * @param params ignored     * @return the decoded bitmap data     */    @Override
    protected Result doInBackground(Void... params) {
        try {
            if (!isCancelled()) {

                ImageViewUtil.DecodeBitmapResult decodeResult =
                        ImageViewUtil.decodeSampledBitmap(mContext, mUri, mWidth, mHeight);
                if (!isCancelled()) {

                    ImageViewUtil.RotateBitmapResult rotateResult = mPreSetRotation != null                            ? ImageViewUtil.rotateBitmapResult(decodeResult.bitmap, mPreSetRotation)
                            : ImageViewUtil.rotateBitmapByExif(mContext, decodeResult.bitmap, mUri);
                    return new Result(mUri, rotateResult.bitmap, decodeResult.sampleSize, rotateResult.degrees);                }
            }
            return null;        } catch (Exception e) {
            return new Result(mUri, e);        }
    }

    /**     * Once complete, see if ImageView is still around and set bitmap.     *     * @param result the result of bitmap loading     */    @Override
    protected void onPostExecute(Result result) {
        if (result != null) {
            boolean completeCalled = false;            if (!isCancelled()) {
                CropImageView cropImageView = mCropImageViewReference.get();                if (cropImageView != null) {
                    completeCalled = true;                    cropImageView.onSetImageUriAsyncComplete(result);                }
            }
            if (!completeCalled && result.bitmap != null) {
                // fast release of unused bitmap                result.bitmap.recycle();            }
        }
    }

    //region: Inner class: Result
    /**     * The result of BitmapLoadingWorkerTask async loading.     */    public static final class Result {

        /**         * The Android URI of the image to load         */        public final Uri uri;
        /**         * The loaded bitmap         */        public final Bitmap bitmap;
        /**         * The sample size used to load the given bitmap         */        public final int loadSampleSize;
        /**         * The degrees the image was rotated         */        public final int degreesRotated;
        /**         * The error that occurred during async bitmap loading.         */        public final Exception error;
        Result(Uri uri, Bitmap bitmap, int loadSampleSize, int degreesRotated) {
            this.uri = uri;            this.bitmap = bitmap;            this.loadSampleSize = loadSampleSize;            this.degreesRotated = degreesRotated;            this.error = null;        }

        Result(Uri uri, Exception error) {
            this.uri = uri;            this.bitmap = null;            this.loadSampleSize = 0;            this.degreesRotated = 0;            this.error = error;        }
    }
    //endregion}
3)CropImageView.java
=====================
/* * Copyright 2013, Edmodo, Inc.  * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. * You may obtain a copy of the License in the LICENSE file, or 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.eppico.widgets;
import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Matrix;import android.graphics.Rect;import android.media.ExifInterface;import android.net.Uri;import android.os.Bundle;import android.os.Parcelable;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.FrameLayout;import android.widget.ImageView;import android.widget.ProgressBar;import com.eppico.R;import com.eppico.cropwindow.ImageViewUtil;import com.eppico.cropwindow.edge.Edge;
import java.lang.ref.WeakReference;
/** * Custom view that provides cropping capabilities to an image. */public class CropImageView extends FrameLayout {

    //region: Fields and Consts
    /**     * Image view widget used to show the image for cropping.     */    private final ImageView mImageView;
    /**     * Overlay over the image view to show cropping UI.     */    private final CropOverlayView mCropOverlayView;
    /**     * Progress bar widget to show progress bar on async image loading and cropping.     */    private final ProgressBar mProgressBar;
    private Bitmap mBitmap;
    private int mDegreesRotated = 0;
    private int mLayoutWidth;
    private int mLayoutHeight;
    private int mImageResource = 0;
    /**     * if to show progress bar when image async loading/cropping is in progress.<br>
     * default: true, disable to provide custom progress bar UI.     */    private boolean mShowProgressBar = true;
    /**     * callback to be invoked when image async loading is complete     */    private WeakReference<OnSetImageUriCompleteListener> mOnSetImageUriCompleteListener;
    /**     * callback to be invoked when image async cropping is complete     */    private WeakReference<OnGetCroppedImageCompleteListener> mOnGetCroppedImageCompleteListener;
    /**     * The URI that the image was loaded from (if loaded from URI)     */    private Uri mLoadedImageUri;
    /**     * The sample size the image was loaded by if was loaded by URI     */    private int mLoadedSampleSize = 1;
    /**     * Task used to load bitmap async from UI thread     */    private WeakReference<BitmapLoadingWorkerTask> mBitmapLoadingWorkerTask;
    /**     * Task used to crop bitmap async from UI thread     */    private WeakReference<BitmapCroppingWorkerTask> mBitmapCroppingWorkerTask;    //endregion
    public CropImageView(Context context) {
        this(context, null);    }

    public CropImageView(Context context, AttributeSet attrs) {
        super(context, attrs);        int guidelines = Defaults.DEFAULT_GUIDELINES;        boolean fixAspectRatio = Defaults.DEFAULT_FIXED_ASPECT_RATIO;        int aspectRatioX = Defaults.DEFAULT_ASPECT_RATIO_X;        int aspectRatioY = Defaults.DEFAULT_ASPECT_RATIO_Y;        ImageView.ScaleType scaleType = Defaults.VALID_SCALE_TYPES[Defaults.DEFAULT_SCALE_TYPE_INDEX];        CropShape cropShape = CropShape.RECTANGLE;        float snapRadius = Defaults.SNAP_RADIUS_DP;
        if (attrs != null) {
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CropImageView, 0, 0);            try {
                guidelines = ta.getInteger(R.styleable.CropImageView_guidelines, guidelines);                fixAspectRatio = ta.getBoolean(R.styleable.CropImageView_fixAspectRatio, Defaults.DEFAULT_FIXED_ASPECT_RATIO);                aspectRatioX = ta.getInteger(R.styleable.CropImageView_aspectRatioX, Defaults.DEFAULT_ASPECT_RATIO_X);                aspectRatioY = ta.getInteger(R.styleable.CropImageView_aspectRatioY, Defaults.DEFAULT_ASPECT_RATIO_Y);                scaleType = Defaults.VALID_SCALE_TYPES[ta.getInt(R.styleable.CropImageView_scaleType, Defaults.DEFAULT_SCALE_TYPE_INDEX)];                cropShape = Defaults.VALID_CROP_SHAPES[ta.getInt(R.styleable.CropImageView_cropShape, Defaults.DEFAULT_CROP_SHAPE_INDEX)];                snapRadius = ta.getFloat(R.styleable.CropImageView_snapRadius, snapRadius);                mShowProgressBar = ta.getBoolean(R.styleable.CropImageView_showProgressBar, mShowProgressBar);            } finally {
                ta.recycle();            }
        }

        LayoutInflater inflater = LayoutInflater.from(context);        View v = inflater.inflate(R.layout.crop_image_view, this, true);
        mImageView = (ImageView) v.findViewById(R.id.ImageView_image);        mImageView.setScaleType(scaleType);
        mCropOverlayView = (CropOverlayView) v.findViewById(R.id.CropOverlayView);        mCropOverlayView.setInitialAttributeValues(guidelines, fixAspectRatio, aspectRatioX, aspectRatioY);        mCropOverlayView.setCropShape(cropShape);        mCropOverlayView.setSnapRadius(snapRadius);        mCropOverlayView.setVisibility(mBitmap != null ? VISIBLE : INVISIBLE);
        mProgressBar = (ProgressBar) v.findViewById(R.id.CropProgressBar);        setProgressBarVisibility();    }

    /**     * Set the scale type of the image in the crop view     */    public ImageView.ScaleType getScaleType() {
        return mImageView.getScaleType();    }

    /**     * Set the scale type of the image in the crop view     */    public void setScaleType(ImageView.ScaleType scaleType) {
        mImageView.setScaleType(scaleType);    }

    /**     * The shape of the cropping area - rectangle/circular.     */    public CropShape getCropShape() {
        return mCropOverlayView.getCropShape();    }

    /**     * The shape of the cropping area - rectangle/circular.     */    public void setCropShape(CropShape cropShape) {
        mCropOverlayView.setCropShape(cropShape);    }

    /**     * Sets whether the aspect ratio is fixed or not; true fixes the aspect ratio, while     * false allows it to be changed.     *     * @param fixAspectRatio Boolean that signals whether the aspect ratio should be     * maintained.     */    public void setFixedAspectRatio(boolean fixAspectRatio) {
        mCropOverlayView.setFixedAspectRatio(fixAspectRatio);    }

    /**     * Sets the guidelines for the CropOverlayView to be either on, off, or to show when     * resizing the application.     *     * @param guidelines Integer that signals whether the guidelines should be on, off, or     * only showing when resizing.     */    public void setGuidelines(int guidelines) {
        mCropOverlayView.setGuidelines(guidelines);    }

    /**     * Sets the both the X and Y values of the aspectRatio.     *     * @param aspectRatioX int that specifies the new X value of the aspect ratio     * @param aspectRatioY int that specifies the new Y value of the aspect ratio     */    public void setAspectRatio(int aspectRatioX, int aspectRatioY) {
        mCropOverlayView.setAspectRatioX(aspectRatioX);        mCropOverlayView.setAspectRatioY(aspectRatioY);    }

    /**     * An edge of the crop window will snap to the corresponding edge of a     * specified bounding box when the crop window edge is less than or equal to     * this distance (in pixels) away from the bounding box edge. (default: 3)     */    public void setSnapRadius(float snapRadius) {
        if (snapRadius >= 0) {
            mCropOverlayView.setSnapRadius(snapRadius);        }
    }

    /**     * if to show progress bar when image async loading/cropping is in progress.<br>
     * default: true, disable to provide custom progress bar UI.     */    public boolean isShowProgressBar() {
        return mShowProgressBar;    }

    /**     * if to show progress bar when image async loading/cropping is in progress.<br>
     * default: true, disable to provide custom progress bar UI.     */    public void setShowProgressBar(boolean showProgressBar) {
        mShowProgressBar = showProgressBar;        setProgressBarVisibility();    }

    /**     * Returns the integer of the imageResource     */    public int getImageResource() {
        return mImageResource;    }

    /**     * Get the URI of an image that was set by URI, null otherwise.     */    public Uri getImageUri() {
        return mLoadedImageUri;    }

    /**     * Gets the crop window's position relative to the source Bitmap (not the image     * displayed in the CropImageView).     *     * @return a RectF instance containing cropped area boundaries of the source Bitmap     */    public Rect getActualCropRect() {
        if (mBitmap != null) {
            final Rect displayedImageRect = ImageViewUtil.getBitmapRect(mBitmap, mImageView, mImageView.getScaleType());
            // Get the scale factor between the actual Bitmap dimensions and the displayed dimensions for width.            final float actualImageWidth = mBitmap.getWidth();            final float displayedImageWidth = displayedImageRect.width();            final float scaleFactorWidth = actualImageWidth / displayedImageWidth;
            // Get the scale factor between the actual Bitmap dimensions and the displayed dimensions for height.            final float actualImageHeight = mBitmap.getHeight();            final float displayedImageHeight = displayedImageRect.height();            final float scaleFactorHeight = actualImageHeight / displayedImageHeight;
            // Get crop window position relative to the displayed image.            final float displayedCropLeft = Edge.LEFT.getCoordinate() - displayedImageRect.left;            final float displayedCropTop = Edge.TOP.getCoordinate() - displayedImageRect.top;            final float displayedCropWidth = Edge.getWidth();            final float displayedCropHeight = Edge.getHeight();
            // Scale the crop window position to the actual size of the Bitmap.            float actualCropLeft = displayedCropLeft * scaleFactorWidth;            float actualCropTop = displayedCropTop * scaleFactorHeight;            float actualCropRight = actualCropLeft + displayedCropWidth * scaleFactorWidth;            float actualCropBottom = actualCropTop + displayedCropHeight * scaleFactorHeight;
            // Correct for floating point errors. Crop rect boundaries should not exceed the source Bitmap bounds.            actualCropLeft = Math.max(0f, actualCropLeft);            actualCropTop = Math.max(0f, actualCropTop);            actualCropRight = Math.min(mBitmap.getWidth(), actualCropRight);            actualCropBottom = Math.min(mBitmap.getHeight(), actualCropBottom);
            return new Rect((int) actualCropLeft, (int) actualCropTop, (int) actualCropRight, (int) actualCropBottom);        } else {
            return null;        }
    }

    /**     * Gets the crop window's position relative to the source Bitmap (not the image     * displayed in the CropImageView) and the original rotation.     *     * @return a RectF instance containing cropped area boundaries of the source Bitmap     */    @SuppressWarnings("SuspiciousNameCombination")
    public Rect getActualCropRectNoRotation() {
        if (mBitmap != null) {
            Rect rect = getActualCropRect();            int rotateSide = mDegreesRotated / 90;            if (rotateSide == 1) {
                rect.set(rect.top, mBitmap.getWidth() - rect.right, rect.bottom, mBitmap.getWidth() - rect.left);            } else if (rotateSide == 2) {
                rect.set(mBitmap.getWidth() - rect.right, mBitmap.getHeight() - rect.bottom, mBitmap.getWidth() - rect.left, mBitmap.getHeight() - rect.top);            } else if (rotateSide == 3) {
                rect.set(mBitmap.getHeight() - rect.bottom, rect.left, mBitmap.getHeight() - rect.top, rect.right);            }
            rect.set(rect.left * mLoadedSampleSize, rect.top * mLoadedSampleSize, rect.right * mLoadedSampleSize, rect.bottom * mLoadedSampleSize);            return rect;        } else {
            return null;        }
    }

    /**     * Gets the cropped image based on the current crop window.     *     * @return a new Bitmap representing the cropped image     */    public Bitmap getCroppedImage() {
        return getCroppedImage(0, 0);    }

    /**     * Gets the cropped image based on the current crop window.<br>
     * If image loaded from URI will use sample size to fit in the requested width and height down-sampling     * if required - optimization to get best size to quality.     *     * @return a new Bitmap representing the cropped image     */    public Bitmap getCroppedImage(int reqWidth, int reqHeight) {
        if (mBitmap != null) {
            if (mLoadedImageUri != null && mLoadedSampleSize > 1) {
                return ImageViewUtil.cropBitmap(
                        getContext(),                        mLoadedImageUri,                        getActualCropRectNoRotation(),                        mDegreesRotated,                        reqWidth,                        reqHeight);            } else {
                return ImageViewUtil.cropBitmap(mBitmap, getActualCropRect());            }
        } else {
            return null;        }
    }

    /**     * Gets the cropped circle image based on the current crop selection.<br>
     * Same as {@link #getCroppedImage()} (as the bitmap is rectangular by nature) but the pixels beyond the     * oval crop will be transparent.     *     * @return a new Circular Bitmap representing the cropped image     */    public Bitmap getCroppedOvalImage() {
        if (mBitmap != null) {
            Bitmap cropped = getCroppedImage();            return ImageViewUtil.toOvalBitmap(cropped);        } else {
            return null;        }
    }

    /**     * Gets the cropped image based on the current crop window.<br>
     * The result will be invoked to listener set by {@link #setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener)}.     */    public void getCroppedImageAsync() {
        getCroppedImageAsync(CropShape.RECTANGLE, 0, 0);    }

    /**     * Gets the cropped image based on the current crop window.<br>
     * If image loaded from URI will use sample size to fit in the requested width and height down-sampling     * if required - optimization to get best size to quality.<br>
     * The result will be invoked to listener set by {@link #setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener)}.     *     * @param cropShape the shape to crop the image     */    public void getCroppedImageAsync(CropShape cropShape, int reqWidth, int reqHeight) {
        if (mOnGetCroppedImageCompleteListener == null) {
            throw new IllegalArgumentException("OnGetCroppedImageCompleteListener is not set");        }
        BitmapCroppingWorkerTask currentTask = mBitmapCroppingWorkerTask != null ? mBitmapCroppingWorkerTask.get() : null;        if (currentTask != null) {
            // cancel previous cropping            currentTask.cancel(true);        }

        mBitmapCroppingWorkerTask = mLoadedImageUri != null && mLoadedSampleSize > 1                ? new WeakReference<>(new BitmapCroppingWorkerTask(this, mLoadedImageUri, getActualCropRectNoRotation(), cropShape, mDegreesRotated, reqWidth, reqHeight))
                : new WeakReference<>(new BitmapCroppingWorkerTask(this, mBitmap, getActualCropRect(), cropShape));        mBitmapCroppingWorkerTask.get().execute();        setProgressBarVisibility();    }

    /**     * Set the callback to be invoked when image async loading ({@link #setImageUriAsync(Uri)})     * is complete (successful or failed).     */    public void setOnSetImageUriCompleteListener(OnSetImageUriCompleteListener listener) {
        mOnSetImageUriCompleteListener = listener != null ? new WeakReference<>(listener) : null;    }

    /**     * Set the callback to be invoked when image async cropping ({@link #getCroppedImageAsync()})     * is complete (successful or failed).     */    public void setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener listener) {
        mOnGetCroppedImageCompleteListener = listener != null ? new WeakReference<>(listener) : null;    }

    /**     * Sets a Bitmap as the content of the CropImageView.     *     * @param bitmap the Bitmap to set     */    public void setImageBitmap(Bitmap bitmap) {
        setBitmap(bitmap, true);    }

    /**     * Sets a Bitmap and initializes the image rotation according to the EXIT data.<br>
     * <br>
     * The EXIF can be retrieved by doing the following:     * <code>ExifInterface exif = new ExifInterface(path);</code>
     *     * @param bitmap the original bitmap to set; if null, this     * @param exif the EXIF information about this bitmap; may be null     */    public void setImageBitmap(Bitmap bitmap, ExifInterface exif) {
        if (bitmap != null && exif != null) {
            ImageViewUtil.RotateBitmapResult result = ImageViewUtil.rotateBitmapByExif(bitmap, exif);            bitmap = result.bitmap;            mDegreesRotated = result.degrees;        }
        setBitmap(bitmap, true);    }

    /**     * Sets a Drawable as the content of the CropImageView.     *     * @param resId the drawable resource ID to set     */    public void setImageResource(int resId) {
        if (resId != 0) {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);            setBitmap(bitmap, true);            mImageResource = resId;        }
    }

    /**     * Sets a bitmap loaded from the given Android URI as the content of the CropImageView.<br>
     * Can be used with URI from gallery or camera source.<br>
     * Will rotate the image by exif data.<br>
     *     * @param uri the URI to load the image from     * @deprecated Use {@link #setImageUriAsync(Uri)} for better async handling     */    @Deprecated
    public void setImageUri(Uri uri) {
        if (uri != null) {

            DisplayMetrics metrics = getResources().getDisplayMetrics();            double densityAdj = metrics.density > 1 ? 1 / metrics.density : 1;            //mScreenWidth = getActivity().getWindowManager().getDefaultDisplay().getWidth();
            DisplayMetrics displayMetrics = getResources().getDisplayMetrics();            int width = displayMetrics.widthPixels;            int height = width;
//            int width = (int) (metrics.widthPixels * densityAdj);//            int height = (int) (metrics.heightPixels * densityAdj);
            ImageViewUtil.DecodeBitmapResult decodeResult =
                    ImageViewUtil.decodeSampledBitmap(getContext(), uri, width, height);
            ImageViewUtil.RotateBitmapResult rotateResult =
                    ImageViewUtil.rotateBitmapByExif(getContext(), decodeResult.bitmap, uri);
            setBitmap(rotateResult.bitmap, true);
            mLoadedImageUri = uri;            mLoadedSampleSize = decodeResult.sampleSize;            mDegreesRotated = rotateResult.degrees;        }
    }

    /**     * Sets a bitmap loaded from the given Android URI as the content of the CropImageView.<br>
     * Can be used with URI from gallery or camera source.<br>
     * Will rotate the image by exif data.<br>
     *     * @param uri the URI to load the image from     */    public void setImageUriAsync(Uri uri) {
        setImageUriAsync(uri, null);    }

    /**     * Clear the current image set for cropping.     */    public void clearImage() {
        clearImage(true);    }

    /**     * Rotates image by the specified number of degrees clockwise. Cycles from 0 to 360     * degrees.     *     * @param degrees Integer specifying the number of degrees to rotate.     */    public void rotateImage(int degrees) {
        if (mBitmap != null) {
            Matrix matrix = new Matrix();            matrix.postRotate(degrees);            Bitmap bitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, true);            setBitmap(bitmap, false);
            mDegreesRotated += degrees;            mDegreesRotated = mDegreesRotated % 360;        }
    }

    //region: Private methods
    /**     * Load image from given URI async using {@link BitmapLoadingWorkerTask}<br>
     * optionally rotate the loaded image given degrees, used for restore state.     */    private void setImageUriAsync(Uri uri, Integer preSetRotation) {
        if (uri != null) {
            BitmapLoadingWorkerTask currentTask = mBitmapLoadingWorkerTask != null ? mBitmapLoadingWorkerTask.get() : null;            if (currentTask != null) {
                // cancel previous loading (no check if the same URI because camera URI can be the same for different images)                currentTask.cancel(true);            }

            // either no existing task is working or we canceled it, need to load new URI            clearImage(true);            mBitmapLoadingWorkerTask = new WeakReference<>(new BitmapLoadingWorkerTask(this, uri, preSetRotation));            mBitmapLoadingWorkerTask.get().execute();            setProgressBarVisibility();        }
    }

    /**     * On complete of the async bitmap loading by {@link #setImageUriAsync(Uri)} set the result     * to the widget if still relevant and call listener if set.     *     * @param result the result of bitmap loading     */    void onSetImageUriAsyncComplete(BitmapLoadingWorkerTask.Result result) {

        mBitmapLoadingWorkerTask = null;        setProgressBarVisibility();
        if (result.error == null) {
            setBitmap(result.bitmap, true);            mLoadedImageUri = result.uri;            mLoadedSampleSize = result.loadSampleSize;            mDegreesRotated = result.degreesRotated;        }

        OnSetImageUriCompleteListener listener = mOnSetImageUriCompleteListener != null                ? mOnSetImageUriCompleteListener.get() : null;        if (listener != null) {
            listener.onSetImageUriComplete(this, result.uri, result.error);        }
    }

    /**     * On complete of the async bitmap cropping by {@link #getCroppedImageAsync()} call listener if set.     *     * @param result the result of bitmap cropping     */    void onGetImageCroppingAsyncComplete(BitmapCroppingWorkerTask.Result result) {
        mBitmapCroppingWorkerTask = null;        setProgressBarVisibility();
        OnGetCroppedImageCompleteListener listener = mOnGetCroppedImageCompleteListener != null                ? mOnGetCroppedImageCompleteListener.get() : null;        if (listener != null) {
            listener.onGetCroppedImageComplete(this, result.bitmap, result.error);        }
    }

    /**     * Set the given bitmap to be used in for cropping<br>
     * Optionally clear full if the bitmap is new, or partial clear if the bitmap has been manipulated.     */    private void setBitmap(Bitmap bitmap, boolean clearFull) {
        if (mBitmap != bitmap) {

            clearImage(clearFull);
            mBitmap = bitmap;            mImageView.setImageBitmap(mBitmap);            if (mCropOverlayView != null) {
                mCropOverlayView.resetCropOverlayView();                mCropOverlayView.setVisibility(VISIBLE);            }
        }
    }

    /**     * Clear the current image set for cropping.<br>
     * Full clear will also clear the data of the set image like Uri or Resource id while partial clear     * will only clear the bitmap and recycle if required.     */    private void clearImage(boolean full) {
        // if we allocated the bitmap, release it as fast as possible        if (mBitmap != null && (mImageResource > 0 || mLoadedImageUri != null)) {
            mBitmap.recycle();            mBitmap = null;        }

        if (full) {
            // clean the loaded image flags for new image            mImageResource = 0;            mLoadedImageUri = null;            mLoadedSampleSize = 1;            mDegreesRotated = 0;
            mImageView.setImageBitmap(null);
            if (mCropOverlayView != null) {
                mCropOverlayView.setVisibility(INVISIBLE);            }
        }
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();        bundle.putParcelable("instanceState", super.onSaveInstanceState());        bundle.putParcelable("LOADED_IMAGE_URI", mLoadedImageUri);        bundle.putInt("LOADED_IMAGE_RESOURCE", mImageResource);        if (mLoadedImageUri == null && mImageResource < 1) {
            bundle.putParcelable("SET_BITMAP", mBitmap);        }
        if (mBitmapLoadingWorkerTask != null) {
            BitmapLoadingWorkerTask task = mBitmapLoadingWorkerTask.get();            if (task != null) {
                bundle.putParcelable("LOADING_IMAGE_URI", task.getUri());            }
        }
        bundle.putInt("DEGREES_ROTATED", mDegreesRotated);        return bundle;    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            Bitmap bitmap = null;            Uri uri = bundle.getParcelable("LOADED_IMAGE_URI");            if (uri != null) {
                setImageUriAsync(uri, bundle.getInt("DEGREES_ROTATED"));            } else {
                int resId = bundle.getInt("LOADED_IMAGE_RESOURCE");                if (resId > 0) {
                    setImageResource(resId);                } else {
                    bitmap = bundle.getParcelable("SET_BITMAP");                    if (bitmap != null) {
                        setBitmap(bitmap, true);                    } else {
                        uri = bundle.getParcelable("LOADING_IMAGE_URI");                        if (uri != null) {
                            setImageUriAsync(uri);                        }
                    }
                }
            }

            mDegreesRotated = bundle.getInt("DEGREES_ROTATED");            if (mBitmap != null && bitmap == null) {
                // Fixes the rotation of the image when we reloaded it.                int tmpRotated = mDegreesRotated;                rotateImage(mDegreesRotated);                mDegreesRotated = tmpRotated;            }
            super.onRestoreInstanceState(bundle.getParcelable("instanceState"));        } else {
            super.onRestoreInstanceState(state);        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        if (mBitmap != null) {
            Rect bitmapRect = ImageViewUtil.getBitmapRect(mBitmap, this, mImageView.getScaleType());            mCropOverlayView.setBitmapRect(bitmapRect);        } else {
            mCropOverlayView.setBitmapRect(Defaults.EMPTY_RECT);        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (mBitmap != null) {

            // Bypasses a baffling bug when used within a ScrollView, where heightSize is set to 0.            if (heightSize == 0) {
                heightSize = mBitmap.getHeight();            }

            int desiredWidth;            int desiredHeight;
            double viewToBitmapWidthRatio = Double.POSITIVE_INFINITY;            double viewToBitmapHeightRatio = Double.POSITIVE_INFINITY;
            // Checks if either width or height needs to be fixed            if (widthSize < mBitmap.getWidth()) {
                viewToBitmapWidthRatio = (double) widthSize / (double) mBitmap.getWidth();            }
            if (heightSize < mBitmap.getHeight()) {
                viewToBitmapHeightRatio = (double) heightSize / (double) mBitmap.getHeight();            }

            // If either needs to be fixed, choose smallest ratio and calculate from there            if (viewToBitmapWidthRatio != Double.POSITIVE_INFINITY || viewToBitmapHeightRatio != Double.POSITIVE_INFINITY) {
                if (viewToBitmapWidthRatio <= viewToBitmapHeightRatio) {
                    desiredWidth = widthSize;                    desiredHeight = (int) (mBitmap.getHeight() * viewToBitmapWidthRatio);                } else {
                    desiredHeight = heightSize;                    desiredWidth = (int) (mBitmap.getWidth() * viewToBitmapHeightRatio);                }
            } else {
                // Otherwise, the picture is within frame layout bounds. Desired width is simply picture size                desiredWidth = mBitmap.getWidth();                desiredHeight = mBitmap.getHeight();            }

            int width = getOnMeasureSpec(widthMode, widthSize, desiredWidth);            int height = getOnMeasureSpec(heightMode, heightSize, desiredHeight);
            mLayoutWidth = width;            mLayoutHeight = height;
            Rect bitmapRect = ImageViewUtil.getBitmapRect(mBitmap.getWidth(),                    mBitmap.getHeight(),                    mLayoutWidth,                    mLayoutHeight,                    mImageView.getScaleType());            mCropOverlayView.setBitmapRect(bitmapRect);
            // MUST CALL THIS            setMeasuredDimension(mLayoutWidth, mLayoutHeight);
        } else {
            mCropOverlayView.setBitmapRect(Defaults.EMPTY_RECT);            setMeasuredDimension(widthSize, heightSize);        }
    }

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);        if (mLayoutWidth > 0 && mLayoutHeight > 0) {
            // Gets original parameters, and creates the new parameters            ViewGroup.LayoutParams origParams = this.getLayoutParams();            origParams.width = mLayoutWidth;            origParams.height = mLayoutHeight;            setLayoutParams(origParams);        }
    }

    /**     * Determines the specs for the onMeasure function. Calculates the width or height     * depending on the mode.     *     * @param measureSpecMode The mode of the measured width or height.     * @param measureSpecSize The size of the measured width or height.     * @param desiredSize The desired size of the measured width or height.     * @return The final size of the width or height.     */    private static int getOnMeasureSpec(int measureSpecMode, int measureSpecSize, int desiredSize) {
        // Measure Width        int spec;        if (measureSpecMode == MeasureSpec.EXACTLY) {
            // Must be this size            spec = measureSpecSize;        } else if (measureSpecMode == MeasureSpec.AT_MOST) {
            // Can't be bigger than...; match_parent value            spec = Math.min(desiredSize, measureSpecSize);        } else {
            // Be whatever you want; wrap_content            spec = desiredSize;        }

        return spec;    }

    /**     * Set visibility of progress bar when async loading/cropping is in process and show is enabled.     */    private void setProgressBarVisibility() {
        boolean visible =
                mShowProgressBar && (
                        (mBitmap == null && mBitmapLoadingWorkerTask != null) ||
                                (mBitmapCroppingWorkerTask != null));        mProgressBar.setVisibility(visible ? VISIBLE : INVISIBLE);    }
    //endregion
    //region: Inner class: CropShape
    /**     * The possible cropping area shape.     */    public enum CropShape {
        RECTANGLE,        OVAL
    }
    //endregion
    //region: Inner class: OnSetImageUriCompleteListener
    /**     * Interface definition for a callback to be invoked when image async loading is complete.     */    public interface OnSetImageUriCompleteListener {

        /**         * Called when a crop image view has completed loading image for cropping.<br>
         * If loading failed error parameter will contain the error.         *         * @param view The crop image view that loading of image was complete.         * @param uri the URI of the image that was loading         * @param error if error occurred during loading will contain the error, otherwise null.         */        void onSetImageUriComplete(CropImageView view, Uri uri, Exception error);    }
    //endregion
    //region: Inner class: OnGetCroppedImageCompleteListener
    /**     * Interface definition for a callback to be invoked when image async cropping is complete.     */    public interface OnGetCroppedImageCompleteListener {

        /**         * Called when a crop image view has completed loading image for cropping.<br>
         * If loading failed error parameter will contain the error.         *         * @param view The crop image view that cropping of image was complete.         * @param bitmap the cropped image bitmap (null if failed)         * @param error if error occurred during cropping will contain the error, otherwise null.         */        void onGetCroppedImageComplete(CropImageView view, Bitmap bitmap, Exception error);    }
    //endregion}
4)CropOverlayView.java
======================
/* * Copyright 2013, Edmodo, Inc.  * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. * You may obtain a copy of the License in the LICENSE file, or 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.eppico.widgets;
import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Path;import android.graphics.Rect;import android.graphics.Region;import android.os.Build;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Pair;import android.util.TypedValue;import android.view.MotionEvent;import android.view.View;import com.eppico.cropwindow.AspectRatioUtil;import com.eppico.cropwindow.PaintUtil;import com.eppico.cropwindow.edge.Edge;import com.eppico.cropwindow.handle.Handle;import com.eppico.cropwindow.HandleUtil;
/** * A custom View representing the crop window and the shaded background outside the crop window. */public class CropOverlayView extends View {
    //region: Fields and Consts    /**     * The Paint used to draw the white rectangle around the crop area.     */    private Paint mBorderPaint;
    /**     * The Paint used to draw the guidelines within the crop area when pressed.     */    private Paint mGuidelinePaint;
    /**     * The Paint used to draw the corners of the Border     */    private Paint mCornerPaint;
    /**     * The Paint used to darken the surrounding areas outside the crop area.     */    private Paint mBackgroundPaint;
    /**     * The bounding box around the Bitmap that we are cropping.     */    private Rect mBitmapRect;
    // The radius of the touch zone (in pixels) around a given Handle.    private float mHandleRadius;
    // An edge of the crop window will snap to the corresponding edge of a    // specified bounding box when the crop window edge is less than or equal to    // this distance (in pixels) away from the bounding box edge.    private float mSnapRadius;
    // Holds the x and y offset between the exact touch location and the exact    // handle location that is activated. There may be an offset because we    // allow for some leeway (specified by mHandleRadius) in activating a    // handle. However, we want to maintain these offset values while the handle    // is being dragged so that the handle doesn't jump.    private Pair<Float, Float> mTouchOffset;
    // The Handle that is currently pressed; null if no Handle is pressed.    private Handle mPressedHandle;
    // Flag indicating if the crop area should always be a certain aspect ratio    // (indicated by mTargetAspectRatio).    private boolean mFixAspectRatio = Defaults.DEFAULT_FIXED_ASPECT_RATIO;
    // Floats to save the current aspect ratio of the image    private int mAspectRatioX = Defaults.DEFAULT_ASPECT_RATIO_X;
    private int mAspectRatioY = Defaults.DEFAULT_ASPECT_RATIO_Y;
    // The aspect ratio that the crop area should maintain; this variable is    // only used when mMaintainAspectRatio is true.    private float mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
    /**     * Instance variables for customizable attributes     */    private int mGuidelines;
    /**     * The shape of the cropping area - rectangle/circular.     */    private CropImageView.CropShape mCropShape;
    // Whether the Crop View has been initialized for the first time    private boolean initializedCropWindow = false;
    // Instance variables for the corner values    private float mCornerExtension;
    private float mCornerOffset;
    private float mCornerLength;
    /**     * Used to set back LayerType after changing to software.     */    private Integer mOriginalLayerType;    //endregion
    public CropOverlayView(Context context) {
        super(context);        init(context);    }

    public CropOverlayView(Context context, AttributeSet attrs) {
        super(context, attrs);        init(context);    }

    /**     * Informs the CropOverlayView of the image's position relative to the     * ImageView. This is necessary to call in order to draw the crop window.     *     * @param bitmapRect the image's bounding box     */    public void setBitmapRect(Rect bitmapRect) {
        mBitmapRect = bitmapRect;        initCropWindow(mBitmapRect);    }

    /**     * Resets the crop overlay view.     */    public void resetCropOverlayView() {

        if (initializedCropWindow) {
            initCropWindow(mBitmapRect);            invalidate();        }
    }

    /**     * The shape of the cropping area - rectangle/circular.     */    public CropImageView.CropShape getCropShape() {
        return mCropShape;    }

    /**     * The shape of the cropping area - rectangle/circular.     */    public void setCropShape(CropImageView.CropShape cropShape) {
        if (mCropShape != cropShape) {
            mCropShape = cropShape;            if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 17) {
                if (mCropShape == CropImageView.CropShape.OVAL) {
                    mOriginalLayerType = getLayerType();                    if (mOriginalLayerType != View.LAYER_TYPE_SOFTWARE) {
                        // TURN off hardware acceleration                        setLayerType(View.LAYER_TYPE_SOFTWARE, null);                    } else {
                        mOriginalLayerType = null;                    }
                } else if (mOriginalLayerType != null) {
                    // return hardware acceleration back                    setLayerType(mOriginalLayerType, null);                    mOriginalLayerType = null;                }
            }
            invalidate();        }
    }

    /**     * Sets the guidelines for the CropOverlayView to be either on, off, or to     * show when resizing the application.     *     * @param guidelines Integer that signals whether the guidelines should be     * on, off, or only showing when resizing.     */    public void setGuidelines(int guidelines) {
        if (guidelines < 0 || guidelines > 2)
            throw new IllegalArgumentException("Guideline value must be set between 0 and 2. See documentation.");        else {
            mGuidelines = guidelines;
            if (initializedCropWindow) {
                initCropWindow(mBitmapRect);                invalidate();            }
        }
    }

    /**     * Sets whether the aspect ratio is fixed or not; true fixes the aspect     * ratio, while false allows it to be changed.     *     * @param fixAspectRatio Boolean that signals whether the aspect ratio     * should be maintained.     */    public void setFixedAspectRatio(boolean fixAspectRatio) {
        mFixAspectRatio = fixAspectRatio;
        if (initializedCropWindow) {
            initCropWindow(mBitmapRect);            invalidate();        }
    }

    /**     * Sets the X value of the aspect ratio; is defaulted to 1.     *     * @param aspectRatioX int that specifies the new X value of the aspect     * ratio     */    public void setAspectRatioX(int aspectRatioX) {
        if (aspectRatioX <= 0)
            throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");        else {
            mAspectRatioX = aspectRatioX;            mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
            if (initializedCropWindow) {
                initCropWindow(mBitmapRect);                invalidate();            }
        }
    }

    /**     * Sets the Y value of the aspect ratio; is defaulted to 1.     *     * @param aspectRatioY int that specifies the new Y value of the aspect     * ratio     */    public void setAspectRatioY(int aspectRatioY) {
        if (aspectRatioY <= 0)
            throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");        else {
            mAspectRatioY = aspectRatioY;            mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
            if (initializedCropWindow) {
                initCropWindow(mBitmapRect);                invalidate();            }
        }
    }

    /**     * An edge of the crop window will snap to the corresponding edge of a     * specified bounding box when the crop window edge is less than or equal to     * this distance (in pixels) away from the bounding box edge. (default: 3)     */    public void setSnapRadius(float snapRadius) {
        mSnapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, snapRadius, getResources().getDisplayMetrics());    }

    /**     * Sets all initial values, but does not call initCropWindow to reset the     * views. Used once at the very start to initialize the attributes.     *     * @param guidelines Integer that signals whether the guidelines should be     * on, off, or only showing when resizing.     * @param fixAspectRatio Boolean that signals whether the aspect ratio     * should be maintained.     * @param aspectRatioX float that specifies the new X value of the aspect     * ratio     * @param aspectRatioY float that specifies the new Y value of the aspect     * ratio     */    public void setInitialAttributeValues(int guidelines, boolean fixAspectRatio, int aspectRatioX, int aspectRatioY) {
        if (guidelines < 0 || guidelines > 2)
            throw new IllegalArgumentException("Guideline value must be set between 0 and 2. See documentation.");        else            mGuidelines = guidelines;
        mFixAspectRatio = fixAspectRatio;
        if (aspectRatioX <= 0)
            throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");        else {
            mAspectRatioX = aspectRatioX;            mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;        }

        if (aspectRatioY <= 0)
            throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");        else {
            mAspectRatioY = aspectRatioY;            mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;        }

    }

    //region: Private methods    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        // Initialize the crop window here because we need the size of the view        // to have been determined.        initCropWindow(mBitmapRect);    }

    @Override
    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);
        // Draw translucent background for the cropped area.        drawBackground(canvas, mBitmapRect);
        if (showGuidelines()) {
            // Determines whether guidelines should be drawn or not            if (mGuidelines == Defaults.GUIDELINES_ON) {
                drawRuleOfThirdsGuidelines(canvas);            } else if (mGuidelines == Defaults.GUIDELINES_ON_TOUCH) {
                // Draw only when resizing                if (mPressedHandle != null)
                    drawRuleOfThirdsGuidelines(canvas);            }
        }

        float w = mBorderPaint.getStrokeWidth();        float l = Edge.LEFT.getCoordinate() + w / 2;        float t = Edge.TOP.getCoordinate() + w / 2;        float r = Edge.RIGHT.getCoordinate() - w / 2;        float b = Edge.BOTTOM.getCoordinate() - w / 2;        if (mCropShape == CropImageView.CropShape.RECTANGLE) {
            // Draw rectangle crop window border.            canvas.drawRect(l, t, r, b, mBorderPaint);            drawCorners(canvas);        } else {
            // Draw circular crop window border            Defaults.EMPTY_RECT_F.set(l, t, r, b);            canvas.drawOval(Defaults.EMPTY_RECT_F, mBorderPaint);        }
    }

    @Override
    public boolean onTouchEvent(@SuppressWarnings("NullableProblems") MotionEvent event) {

        // If this View is not enabled, don't allow for touch interactions.        if (!isEnabled()) {
            return false;        }

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                onActionDown(event.getX(), event.getY());                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                getParent().requestDisallowInterceptTouchEvent(false);                onActionUp();                return true;
            case MotionEvent.ACTION_MOVE:
                onActionMove(event.getX(), event.getY());                getParent().requestDisallowInterceptTouchEvent(true);                return true;
            default:
                return false;        }
    }

    private void init(Context context) {

        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        mHandleRadius = HandleUtil.getTargetRadius(context);
        mSnapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, Defaults.SNAP_RADIUS_DP, displayMetrics);
        mBorderPaint = PaintUtil.newBorderPaint(context);        mGuidelinePaint = PaintUtil.newGuidelinePaint();        mBackgroundPaint = PaintUtil.newBackgroundPaint();        mCornerPaint = PaintUtil.newCornerPaint(context);
        // Sets the values for the corner sizes        mCornerOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                Defaults.DEFAULT_CORNER_OFFSET_DP,                displayMetrics);        mCornerExtension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                Defaults.DEFAULT_CORNER_EXTENSION_DP,                displayMetrics);        mCornerLength = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                Defaults.DEFAULT_CORNER_LENGTH_DP,                displayMetrics);
        // Sets guidelines to default until specified otherwise        mGuidelines = Defaults.DEFAULT_GUIDELINES;    }

    /**     * Set the initial crop window size and position. This is dependent on the     * size and position of the image being cropped.     *     * @param bitmapRect the bounding box around the image being cropped     */    private void initCropWindow(Rect bitmapRect) {

        if (bitmapRect.width() == 0 || bitmapRect.height() == 0) {
            return;        }

        // Tells the attribute functions the crop window has already been        // initialized        if (!initializedCropWindow) {
            initializedCropWindow = true;        }

        if (mFixAspectRatio
                && (bitmapRect.left != 0 || bitmapRect.right != 0                || bitmapRect.top != 0 || bitmapRect.bottom != 0)) {

            // If the image aspect ratio is wider than the crop aspect ratio,            // then the image height is the determining initial length. Else,            // vice-versa.            if (AspectRatioUtil.calculateAspectRatio(bitmapRect) > mTargetAspectRatio) {

                Edge.TOP.setCoordinate(bitmapRect.top);                Edge.BOTTOM.setCoordinate(bitmapRect.bottom);
                float centerX = getWidth() / 2f;
                //dirty fix for wrong crop overlay aspect ratio when using fixed aspect ratio                mTargetAspectRatio = (float) mAspectRatioX / mAspectRatioY;
                // Limits the aspect ratio to no less than 40 wide or 40 tall                float cropWidth = Math.max(Edge.MIN_CROP_LENGTH_PX,                        AspectRatioUtil.calculateWidth(Edge.TOP.getCoordinate(),                                Edge.BOTTOM.getCoordinate(),                                mTargetAspectRatio));
                // Create new TargetAspectRatio if the original one does not fit                // the screen                if (cropWidth == Edge.MIN_CROP_LENGTH_PX) {
                    mTargetAspectRatio = (Edge.MIN_CROP_LENGTH_PX) / (Edge.BOTTOM.getCoordinate() - Edge.TOP.getCoordinate());                }

                float halfCropWidth = cropWidth / 2f;                Edge.LEFT.setCoordinate(centerX - halfCropWidth);                Edge.RIGHT.setCoordinate(centerX + halfCropWidth);
            } else {

                Edge.LEFT.setCoordinate(bitmapRect.left);                Edge.RIGHT.setCoordinate(bitmapRect.right);
                float centerY = getHeight() / 2f;
                // Limits the aspect ratio to no less than 40 wide or 40 tall                float cropHeight = Math.max(Edge.MIN_CROP_LENGTH_PX,                        AspectRatioUtil.calculateHeight(Edge.LEFT.getCoordinate(),                                Edge.RIGHT.getCoordinate(),                                mTargetAspectRatio));
                // Create new TargetAspectRatio if the original one does not fit                // the screen                if (cropHeight == Edge.MIN_CROP_LENGTH_PX) {
                    mTargetAspectRatio = (Edge.RIGHT.getCoordinate() - Edge.LEFT.getCoordinate()) / Edge.MIN_CROP_LENGTH_PX;                }

                float halfCropHeight = cropHeight / 2f;                Edge.TOP.setCoordinate(centerY - halfCropHeight);                Edge.BOTTOM.setCoordinate(centerY + halfCropHeight);            }

        } else { // ... do not fix aspect ratio...
            // Initialize crop window to have 10% padding w/ respect to image.            float horizontalPadding = 0.1f * bitmapRect.width();            float verticalPadding = 0.1f * bitmapRect.height();
            Edge.LEFT.setCoordinate(bitmapRect.left + horizontalPadding);            Edge.TOP.setCoordinate(bitmapRect.top + verticalPadding);            Edge.RIGHT.setCoordinate(bitmapRect.right - horizontalPadding);            Edge.BOTTOM.setCoordinate(bitmapRect.bottom - verticalPadding);        }
    }

    /**     * Indicates whether the crop window is small enough that the guidelines     * should be shown. Public because this function is also used to determine     * if the center handle should be focused.     *     * @return boolean Whether the guidelines should be shown or not     */    public static boolean showGuidelines() {
        if ((Math.abs(Edge.LEFT.getCoordinate() - Edge.RIGHT.getCoordinate()) < Defaults.DEFAULT_SHOW_GUIDELINES_LIMIT)
                || (Math.abs(Edge.TOP.getCoordinate() - Edge.BOTTOM.getCoordinate()) < Defaults.DEFAULT_SHOW_GUIDELINES_LIMIT)) {
            return false;        } else {
            return true;        }
    }

    private void drawRuleOfThirdsGuidelines(Canvas canvas) {

        float sw = mBorderPaint.getStrokeWidth();        float l = Edge.LEFT.getCoordinate() + sw;        float t = Edge.TOP.getCoordinate() + sw;        float r = Edge.RIGHT.getCoordinate() - sw;        float b = Edge.BOTTOM.getCoordinate() - sw;
        float oneThirdCropWidth = Edge.getWidth() / 3;        float oneThirdCropHeight = Edge.getHeight() / 3;
        if (mCropShape == CropImageView.CropShape.OVAL) {

            float w = Edge.getWidth() / 2 - sw;            float h = Edge.getHeight() / 2 - sw;
            // Draw vertical guidelines.            float x1 = l + oneThirdCropWidth;            float x2 = r - oneThirdCropWidth;            float yv = (float) (h * Math.sin(Math.acos((w - oneThirdCropWidth) / w)));            canvas.drawLine(x1, t + h - yv, x1, b - h + yv, mGuidelinePaint);            canvas.drawLine(x2, t + h - yv, x2, b - h + yv, mGuidelinePaint);
            // Draw horizontal guidelines.            float y1 = t + oneThirdCropHeight;            float y2 = b - oneThirdCropHeight;            float xv = (float) (w * Math.cos(Math.asin((h - oneThirdCropHeight) / h)));            canvas.drawLine(l + w - xv, y1, r - w + xv, y1, mGuidelinePaint);            canvas.drawLine(l + w - xv, y2, r - w + xv, y2, mGuidelinePaint);        } else {

            // Draw vertical guidelines.            float x1 = l + oneThirdCropWidth;            float x2 = r - oneThirdCropWidth;            canvas.drawLine(x1, t, x1, b, mGuidelinePaint);            canvas.drawLine(x2, t, x2, b, mGuidelinePaint);
            // Draw horizontal guidelines.            float y1 = t + oneThirdCropHeight;            float y2 = b - oneThirdCropHeight;            canvas.drawLine(l, y1, r, y1, mGuidelinePaint);            canvas.drawLine(l, y2, r, y2, mGuidelinePaint);        }
    }

    private void drawBackground(Canvas canvas, Rect bitmapRect) {

        float l = Edge.LEFT.getCoordinate();        float t = Edge.TOP.getCoordinate();        float r = Edge.RIGHT.getCoordinate();        float b = Edge.BOTTOM.getCoordinate();
        if (mCropShape == CropImageView.CropShape.RECTANGLE) {
            canvas.drawRect(bitmapRect.left, bitmapRect.top, bitmapRect.right, t, mBackgroundPaint);            canvas.drawRect(bitmapRect.left, b, bitmapRect.right, bitmapRect.bottom, mBackgroundPaint);            canvas.drawRect(bitmapRect.left, t, l, b, mBackgroundPaint);            canvas.drawRect(r, t, bitmapRect.right, b, mBackgroundPaint);        } else {
            Path circleSelectionPath = new Path();            if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 17 && mCropShape == CropImageView.CropShape.OVAL) {
                Defaults.EMPTY_RECT_F.set(l + 2, t + 2, r - 2, b - 2);            } else {
                Defaults.EMPTY_RECT_F.set(l, t, r, b);            }
            circleSelectionPath.addOval(Defaults.EMPTY_RECT_F, Path.Direction.CW);            canvas.save();            canvas.clipPath(circleSelectionPath, Region.Op.XOR);            canvas.drawRect(bitmapRect.left, bitmapRect.top, bitmapRect.right, bitmapRect.bottom, mBackgroundPaint);            canvas.restore();        }
    }

    private void drawCorners(Canvas canvas) {

        float w = mBorderPaint.getStrokeWidth() * 1.5f + 1;        float l = Edge.LEFT.getCoordinate() + w;        float t = Edge.TOP.getCoordinate() + w;        float r = Edge.RIGHT.getCoordinate() - w;        float b = Edge.BOTTOM.getCoordinate() - w;
        // Top left        canvas.drawLine(l - mCornerOffset, t - mCornerExtension, l - mCornerOffset, t + mCornerLength, mCornerPaint);        canvas.drawLine(l, t - mCornerOffset, l + mCornerLength, t - mCornerOffset, mCornerPaint);
        // Top right        canvas.drawLine(r + mCornerOffset, t - mCornerExtension, r + mCornerOffset, t + mCornerLength, mCornerPaint);        canvas.drawLine(r, t - mCornerOffset, r - mCornerLength, t - mCornerOffset, mCornerPaint);
        // Bottom left        canvas.drawLine(l - mCornerOffset, b + mCornerExtension, l - mCornerOffset, b - mCornerLength, mCornerPaint);        canvas.drawLine(l, b + mCornerOffset, l + mCornerLength, b + mCornerOffset, mCornerPaint);
        // Bottom left        canvas.drawLine(r + mCornerOffset, b + mCornerExtension, r + mCornerOffset, b - mCornerLength, mCornerPaint);        canvas.drawLine(r, b + mCornerOffset, r - mCornerLength, b + mCornerOffset, mCornerPaint);    }

    /**     * Handles a {@link MotionEvent#ACTION_DOWN} event.     *     * @param x the x-coordinate of the down action     * @param y the y-coordinate of the down action     */    private void onActionDown(float x, float y) {
        float left = Edge.LEFT.getCoordinate();        float top = Edge.TOP.getCoordinate();        float right = Edge.RIGHT.getCoordinate();        float bottom = Edge.BOTTOM.getCoordinate();
        mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius, mCropShape);        if (mPressedHandle == null) {
            return;        }

        // Calculate the offset of the touch point from the precise location        // of the handle. Save these values in a member variable since we want        // to maintain this offset as we drag the handle.        mTouchOffset = HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom);        invalidate();    }

    /**     * Handles a {@link MotionEvent#ACTION_UP} or     * {@link MotionEvent#ACTION_CANCEL} event.     */    private void onActionUp() {
        if (mPressedHandle == null) {
            return;        }

        mPressedHandle = null;        invalidate();    }

    /**     * Handles a {@link MotionEvent#ACTION_MOVE} event.     *     * @param x the x-coordinate of the move event     * @param y the y-coordinate of the move event     */    private void onActionMove(float x, float y) {

        if (mPressedHandle == null) {
            return;        }

        // Adjust the coordinates for the finger position's offset (i.e. the        // distance from the initial touch to the precise handle location).        // We want to maintain the initial touch's distance to the pressed        // handle so that the crop window size does not "jump".        x += mTouchOffset.first;        y += mTouchOffset.second;
        // Calculate the new crop window size/position.        if (mFixAspectRatio) {
            mPressedHandle.updateCropWindow(x, y, mTargetAspectRatio, mBitmapRect, mSnapRadius);        } else {
            mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius);        }
        invalidate();    }
    //endregion}
5)CustomFont.java
===================
package com.eppico.widgets;
import android.content.Context;import android.graphics.Typeface;import android.util.AttributeSet;import android.widget.TextView;
public class CustomFont
        extends TextView {
    private Context c;    public CustomFont(Context c) {
        super(c);        this.c = c;        Typeface tfs = Typeface.createFromAsset(c.getAssets(),                "Roboto-Regular_3.ttf");        setTypeface(tfs);    }
    public CustomFont(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);        this.c = context;        Typeface tfs = Typeface.createFromAsset(c.getAssets(),                "Roboto-Regular_3.ttf");        setTypeface(tfs);    }
    public CustomFont(Context context, AttributeSet attrs)
    {
        super(context, attrs);        this.c = context;        Typeface tfs = Typeface.createFromAsset(c.getAssets(),                "Roboto-Regular_3.ttf");        setTypeface(tfs);    }
}
6)Defaults.java
===================
// "Therefore those skilled at the unorthodox// are infinite as heaven and earth,// inexhaustible as the great rivers.// When they come to an end,// they begin again,// like the days and months;// they die and are reborn,// like the four seasons."//// - Sun Tsu,// "The Art of War"
package com.eppico.widgets;
import android.graphics.Rect;import android.graphics.RectF;import android.widget.ImageView;
import com.eppico.cropwindow.PaintUtil;
/** * Defaults used in the library. */class Defaults {

    public static final Rect EMPTY_RECT = new Rect();
    public static final RectF EMPTY_RECT_F = new RectF();
    // Sets the default image guidelines to show when resizing    public static final int DEFAULT_GUIDELINES = 1;
    public static final boolean DEFAULT_FIXED_ASPECT_RATIO = false;
    public static final int DEFAULT_ASPECT_RATIO_X = 1;
    public static final int DEFAULT_ASPECT_RATIO_Y = 1;
    public static final int DEFAULT_SCALE_TYPE_INDEX = 0;
    public static final int DEFAULT_CROP_SHAPE_INDEX = 0;
    public static final float SNAP_RADIUS_DP = 3;
    public static final float DEFAULT_SHOW_GUIDELINES_LIMIT = 100;
    // Gets default values from PaintUtil, sets a bunch of values such that the    // corners will draw correctly    public static final float DEFAULT_CORNER_THICKNESS_DP = PaintUtil.getCornerThickness();
    public static final float DEFAULT_LINE_THICKNESS_DP = PaintUtil.getLineThickness();
    public static final float DEFAULT_CORNER_OFFSET_DP = (DEFAULT_CORNER_THICKNESS_DP / 2) - (DEFAULT_LINE_THICKNESS_DP / 2);
    public static final float DEFAULT_CORNER_EXTENSION_DP = DEFAULT_CORNER_THICKNESS_DP / 2 + DEFAULT_CORNER_OFFSET_DP;
    public static final float DEFAULT_CORNER_LENGTH_DP = 15;
    public static final int GUIDELINES_ON_TOUCH = 1;
    public static final int GUIDELINES_ON = 2;
    public static final ImageView.ScaleType[] VALID_SCALE_TYPES = new ImageView.ScaleType[]{ImageView.ScaleType.CENTER_INSIDE, ImageView.ScaleType.FIT_CENTER};
    public static final CropImageView.CropShape[] VALID_CROP_SHAPES = new CropImageView.CropShape[]{CropImageView.CropShape.RECTANGLE, CropImageView.CropShape.OVAL};}
7)EasyDialog.java
====================
package com.eppico.widgets;
import android.animation.Animator;import android.animation.AnimatorSet;import android.animation.ObjectAnimator;import android.annotation.SuppressLint;import android.app.Activity;import android.app.Dialog;import android.content.Context;import android.content.DialogInterface;import android.graphics.Color;import android.graphics.drawable.GradientDrawable;import android.graphics.drawable.LayerDrawable;import android.graphics.drawable.RotateDrawable;import android.os.Build;import android.util.DisplayMetrics;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.ViewTreeObserver;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.RelativeLayout;import android.widget.Toast;
import com.eppico.R;
import java.util.ArrayList;import java.util.List;
/** * Created by michael on 15/4/15. */public class EasyDialog {
    private Context context;    /**     * 内容在三角形上面     */    public static final int GRAVITY_TOP = 0;    /**     * 内容在三角形下面     */    public static final int GRAVITY_BOTTOM = 1;    /**     * 内容在三角形左面     */    public static final int GRAVITY_LEFT = 2;    /**     * 内容在三角形右面     */    public static final int GRAVITY_RIGHT = 3;    /**     * 对话框本身     */    private Dialog dialog;    /**     * 坐标     */    private int[] location;    /**     * 提醒框位置     */    private int gravity;    /**     * 外面传递进来的View     */    private View contentView;    /**     * 三角形     */    private ImageView ivTriangle;    /**     * 用来放外面传递进来的View     */    private LinearLayout llContent;    /**     * 触摸外面,是否关闭对话框     */    private boolean touchOutsideDismiss;    /**     * 提示框所在的容器     */    private RelativeLayout rlOutsideBackground;
    public EasyDialog(Context context) {
        initDialog(context);    }

    private void initDialog(final Context context) {
        this.context = context;        LayoutInflater layoutInflater = ((Activity) context).getLayoutInflater();        View dialogView = layoutInflater.inflate(R.layout.layout_dialog, null);        ViewTreeObserver viewTreeObserver = dialogView.getViewTreeObserver();        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                //当View可以获取宽高的时候,设置view的位置                relocation(location);
            }
        });        rlOutsideBackground = (RelativeLayout) dialogView.findViewById(R.id.rlOutsideBackground);        setTouchOutsideDismiss(true);        ivTriangle = (ImageView) dialogView.findViewById(R.id.ivTriangle);        llContent = (LinearLayout) dialogView.findViewById(R.id.llContent);        dialog = new Dialog(context, isFullScreen() ? android.R.style.Theme_Translucent_NoTitleBar_Fullscreen : android.R.style.Theme_Translucent_NoTitleBar);        dialog.setContentView(dialogView);        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                if (onEasyDialogDismissed != null) {
                    onEasyDialogDismissed.onDismissed();                }
            }
        });        dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                if (onEasyDialogShow != null) {
                    onEasyDialogShow.onShow();                }
            }
        });        animatorSetForDialogShow = new AnimatorSet();        animatorSetForDialogDismiss = new AnimatorSet();        objectAnimatorsForDialogShow = new ArrayList<>();        objectAnimatorsForDialogDismiss = new ArrayList<>();        ini();    }

    final View.OnTouchListener outsideBackgroundListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (touchOutsideDismiss && dialog != null) {
                onDialogDismiss();            }
            return false;        }
    };
    /**     * The Dialog instance     */    public Dialog getDialog() {
        return dialog;    }

    /**     * 初始化默认值     */    private void ini() {
        this.setLocation(new int[]{0, 0})
                .setGravity(GRAVITY_BOTTOM)
                .setTouchOutsideDismiss(true)
                .setOutsideColor(Color.TRANSPARENT)
                .setBackgroundColor(Color.BLUE)
                .setMatchParent(true)
                .setMarginLeftAndRight(24, 24);    }

    /**     * 设置提示框中要显示的内容     */    public EasyDialog setLayout(View layout) {
        if (layout != null) {
            this.contentView = layout;        }
        return this;    }

    /**     * 设置提示框中要显示的内容的布局Id     */    public EasyDialog setLayoutResourceId(int layoutResourceId) {
        View view = ((Activity) context).getLayoutInflater().inflate(layoutResourceId, null);        setLayout(view);        return this;    }

    /**     * 设置三角形所在的位置     */    public EasyDialog setLocation(int[] location) {
        this.location = location;        return this;    }

    /**     * 设置三角形所在的位置     * location.x坐标值为attachedView所在屏幕位置的中心     * location.y坐标值依据当前的gravity,如果gravity是top,则为控件上方的y值,如果是bottom,则为控件的下方的y值     *     * @param attachedView 在哪个View显示提示信息     */    public EasyDialog setLocationByAttachedView(View attachedView) {
        if (attachedView != null) {
            this.attachedView = attachedView;            int[] attachedViewLocation = new int[2];            attachedView.getLocationOnScreen(attachedViewLocation);            switch (gravity) {
                case GRAVITY_BOTTOM:
                    attachedViewLocation[0] += attachedView.getWidth() / 2;                    attachedViewLocation[1] += attachedView.getHeight();                    break;                case GRAVITY_TOP:
                    attachedViewLocation[0] += attachedView.getWidth() / 2;                    break;                case GRAVITY_LEFT:
                    attachedViewLocation[1] += attachedView.getHeight() / 2;                    break;                case GRAVITY_RIGHT:
                    attachedViewLocation[0] += attachedView.getWidth();                    attachedViewLocation[1] += attachedView.getHeight() / 2;            }
            setLocation(attachedViewLocation);        }
        return this;    }

    /**     * 对话框所依附的View     */    private View attachedView = null;
    public View getAttachedView() {
        return this.attachedView;    }

    /**     * 设置显示的内容在上方还是下方,如果设置错误,默认是在下方     */    public EasyDialog setGravity(int gravity) {
        if (gravity != GRAVITY_BOTTOM && gravity != GRAVITY_TOP && gravity != GRAVITY_LEFT && gravity != GRAVITY_RIGHT) {
            gravity = GRAVITY_BOTTOM;        }
        this.gravity = gravity;        switch (this.gravity) {
            case GRAVITY_BOTTOM:
                ivTriangle.setBackgroundResource(R.drawable.triangle_bottom);                break;            case GRAVITY_TOP:
                ivTriangle.setBackgroundResource(R.drawable.triangle_top);                break;            case GRAVITY_LEFT:
                ivTriangle.setBackgroundResource(R.drawable.triangle_left);                break;            case GRAVITY_RIGHT:
                ivTriangle.setBackgroundResource(R.drawable.triangle_right);                break;        }
        llContent.setBackgroundResource(R.drawable.round_corner_bg);        if (attachedView != null)//如果用户调用setGravity()之前就调用过setLocationByAttachedView,需要再调用一次setLocationByAttachedView        {
            this.setLocationByAttachedView(attachedView);        }
        this.setBackgroundColor(backgroundColor);        return this;    }

    /**     * 设置是否填充屏幕,如果不填充就适应布局内容的宽度,显示内容的位置会尽量随着三角形的位置居中     */    public EasyDialog setMatchParent(boolean matchParent) {
        ViewGroup.LayoutParams layoutParams = llContent.getLayoutParams();        layoutParams.width = matchParent ? ViewGroup.LayoutParams.MATCH_PARENT : ViewGroup.LayoutParams.WRAP_CONTENT;        llContent.setLayoutParams(layoutParams);        return this;    }

    /**     * 距离屏幕左右的边距     */    public EasyDialog setMarginLeftAndRight(int left, int right) {
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) llContent.getLayoutParams();        layoutParams.setMargins(left, 0, right, 0);        llContent.setLayoutParams(layoutParams);        return this;    }

    public EasyDialog setMarginTopAndBottom(int top, int bottom) {
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) llContent.getLayoutParams();        layoutParams.setMargins(0, top, 0, bottom);        llContent.setLayoutParams(layoutParams);        return this;    }

    /**     * 设置触摸对话框外面,对话框是否消失     */    public EasyDialog setTouchOutsideDismiss(boolean touchOutsideDismiss) {
        this.touchOutsideDismiss = touchOutsideDismiss;        if (touchOutsideDismiss) {
            rlOutsideBackground.setOnTouchListener(outsideBackgroundListener);        } else {
            rlOutsideBackground.setOnTouchListener(null);        }
        return this;    }

    /**     * 设置提醒框外部区域的颜色     */    public EasyDialog setOutsideColor(int color) {
        rlOutsideBackground.setBackgroundColor(color);        return this;    }

    private int backgroundColor;
    /**     * 设置对话框的颜色     * 三角形的图片是layer-list里面嵌套一个RotateDrawable,在设置颜色的时候需要特别处理     * http://stackoverflow.com/questions/24492000/set-color-of-triangle-on-run-time     * http://stackoverflow.com/questions/16636412/change-shape-solid-color-at-runtime-inside-drawable-xml-used-as-background     */    public EasyDialog setBackgroundColor(int color) {
        backgroundColor = color;        LayerDrawable drawableTriangle = (LayerDrawable) ivTriangle.getBackground();        GradientDrawable shapeTriangle = (GradientDrawable) (((RotateDrawable) drawableTriangle.findDrawableByLayerId(R.id.shape_id)).getDrawable());        if (shapeTriangle != null) {
            shapeTriangle.setColor(color);        } else {
            Toast.makeText(context, "shape is null", Toast.LENGTH_SHORT).show();        }
        GradientDrawable drawableRound = (GradientDrawable) llContent.getBackground();        if (drawableRound != null) {
            drawableRound.setColor(color);        }
        return this;    }

    /**     * 显示提示框     */    public EasyDialog show() {
        if (dialog != null) {
            if (contentView == null) {
                throw new RuntimeException("您是否未调用setLayout()或者setLayoutResourceId()方法来设置要显示的内容呢?");            }
            if (llContent.getChildCount() > 0) {
                llContent.removeAllViews();            }
            llContent.addView(contentView);            dialog.show();            onDialogShowing();        }
        return this;    }

    /**     * 显示对话框的View的parent,如果想自己写动画,可以获取这个实例来写动画     */    public View getTipViewInstance() {
        return rlOutsideBackground.findViewById(R.id.rlParentForAnimate);    }

    /**     * 横向     */    public static final int DIRECTION_X = 0;    /**     * 纵向     */    public static final int DIRECTION_Y = 1;

    /**     * 水平动画     *     * @param direction 动画的方向     * @param duration  动画执行的时间长度     * @param values    动画移动的位置     */    public EasyDialog setAnimationTranslationShow(int direction, int duration, float... values) {
        return setAnimationTranslation(true, direction, duration, values);    }

    /**     * 水平动画     *     * @param direction 动画的方向     * @param duration  动画执行的时间长度     * @param values    动画移动的位置     */    public EasyDialog setAnimationTranslationDismiss(int direction, int duration, float... values) {
        return setAnimationTranslation(false, direction, duration, values);    }

    private EasyDialog setAnimationTranslation(boolean isShow, int direction, int duration, float... values) {
        if (direction != DIRECTION_X && direction != DIRECTION_Y) {
            direction = DIRECTION_X;        }
        String propertyName = "";        switch (direction) {
            case DIRECTION_X:
                propertyName = "translationX";                break;            case DIRECTION_Y:
                propertyName = "translationY";                break;        }
        ObjectAnimator animator = ObjectAnimator.ofFloat(rlOutsideBackground.findViewById(R.id.rlParentForAnimate), propertyName, values)
                .setDuration(duration);        if (isShow) {
            objectAnimatorsForDialogShow.add(animator);        } else {
            objectAnimatorsForDialogDismiss.add(animator);        }
        return this;    }

    /**     * 对话框出现时候的渐变动画     *     * @param duration 动画执行的时间长度     * @param values   动画移动的位置     */    public EasyDialog setAnimationAlphaShow(int duration, float... values) {
        return setAnimationAlpha(true, duration, values);    }

    /**     * 对话框消失时候的渐变动画     *     * @param duration 动画执行的时间长度     * @param values   动画移动的位置     */    public EasyDialog setAnimationAlphaDismiss(int duration, float... values) {
        return setAnimationAlpha(false, duration, values);    }

    private EasyDialog setAnimationAlpha(boolean isShow, int duration, float... values) {
        ObjectAnimator animator = ObjectAnimator.ofFloat(rlOutsideBackground.findViewById(R.id.rlParentForAnimate), "alpha", values).setDuration(duration);        if (isShow) {
            objectAnimatorsForDialogShow.add(animator);        } else {
            objectAnimatorsForDialogDismiss.add(animator);        }
        return this;    }

    private AnimatorSet animatorSetForDialogShow;    private AnimatorSet animatorSetForDialogDismiss;    private List<Animator> objectAnimatorsForDialogShow;    private List<Animator> objectAnimatorsForDialogDismiss;

    private void onDialogShowing() {
        if (animatorSetForDialogShow != null && objectAnimatorsForDialogShow != null && objectAnimatorsForDialogShow.size() > 0) {
            animatorSetForDialogShow.playTogether(objectAnimatorsForDialogShow);            animatorSetForDialogShow.start();        }
        //TODO 缩放的动画效果不好,不能从控件所在的位置开始缩放//        ObjectAnimator.ofFloat(rlOutsideBackground.findViewById(R.id.rlParentForAnimate), "scaleX", 0.3f, 1.0f).setDuration(500).start();//        ObjectAnimator.ofFloat(rlOutsideBackground.findViewById(R.id.rlParentForAnimate), "scaleY", 0.3f, 1.0f).setDuration(500).start();    }

    @SuppressLint("NewApi")
    private void onDialogDismiss() {
        if (animatorSetForDialogDismiss.isRunning()) {
            return;        }
        if (animatorSetForDialogDismiss != null && objectAnimatorsForDialogDismiss != null && objectAnimatorsForDialogDismiss.size() > 0) {
            animatorSetForDialogDismiss.playTogether(objectAnimatorsForDialogDismiss);            animatorSetForDialogDismiss.start();            animatorSetForDialogDismiss.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    //这里有可能会有bug,当Dialog所依赖的Activity关闭的时候,如果这个时候,用户关闭对话框,由于对话框的动画关闭需要时间,当动画执行完毕后,对话框所依赖的Activity已经被销毁了,执行dismiss()就会报错                    if (context != null && context instanceof Activity) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                            if (!((Activity) context).isDestroyed()) {
                                dialog.dismiss();                            }
                        } else {
                            try {
                                dialog.dismiss();                            } catch (final IllegalArgumentException e) {

                            } catch (final Exception e) {

                            } finally {
                                dialog = null;                            }
                        }
                    }
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });        } else {
            dialog.dismiss();        }
    }

    /**     * 关闭提示框     */    public void dismiss() {
        if (dialog != null && dialog.isShowing()) {
            onDialogDismiss();        }
    }

    /**     * 根据x,y,重新设置控件的位置     * 因为setX setY为0的时候,都是在状态栏以下的,所以app不是全屏的话,需要扣掉状态栏的高度     */    private void relocation(int[] location) {
        float statusBarHeight = isFullScreen() ? 0.0f : getStatusBarHeight();
        ivTriangle.setX(location[0] - ivTriangle.getWidth() / 2);        ivTriangle.setY(location[1] - ivTriangle.getHeight() / 2 - statusBarHeight);        switch (gravity) {
            case GRAVITY_BOTTOM:
                llContent.setY(location[1] - ivTriangle.getHeight() / 2 - statusBarHeight + ivTriangle.getHeight());                break;            case GRAVITY_TOP:
                llContent.setY(location[1] - llContent.getHeight() - statusBarHeight - ivTriangle.getHeight() / 2);                break;            case GRAVITY_LEFT:
                llContent.setX(location[0] - llContent.getWidth() - ivTriangle.getWidth() / 2);                break;            case GRAVITY_RIGHT:
                llContent.setX(location[0] + ivTriangle.getWidth() / 2);                break;        }

        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) llContent.getLayoutParams();        switch (gravity) {
            case GRAVITY_BOTTOM:
            case GRAVITY_TOP:
                int triangleCenterX = (int) (ivTriangle.getX() + ivTriangle.getWidth() / 2);                int contentWidth = llContent.getWidth();                int rightMargin = getScreenWidth() - triangleCenterX;                int leftMargin = getScreenWidth() - rightMargin;                int availableLeftMargin = leftMargin - layoutParams.leftMargin;                int availableRightMargin = rightMargin - layoutParams.rightMargin;                int x = 0;                if (contentWidth / 2 <= availableLeftMargin && contentWidth / 2 <= availableRightMargin) {
                    x = triangleCenterX - contentWidth / 2;                } else {
                    if (availableLeftMargin <= availableRightMargin) {
                        x = layoutParams.leftMargin;                    } else {
                        x = getScreenWidth() - (contentWidth + layoutParams.rightMargin);                    }
                }
                llContent.setX(x);                break;            case GRAVITY_LEFT:
            case GRAVITY_RIGHT:
                int triangleCenterY = (int) (ivTriangle.getY() + ivTriangle.getHeight() / 2);                int contentHeight = llContent.getHeight();                int topMargin = triangleCenterY;                int bottomMargin = getScreenHeight() - topMargin;                int availableTopMargin = topMargin - layoutParams.topMargin;                int availableBottomMargin = bottomMargin - layoutParams.bottomMargin;                int y = 0;                if (contentHeight / 2 <= availableTopMargin && contentHeight / 2 <= availableBottomMargin) {
                    y = triangleCenterY - contentHeight / 2;                } else {
                    if (availableTopMargin <= availableBottomMargin) {
                        y = layoutParams.topMargin;                    } else {
                        y = getScreenHeight() - (contentHeight + layoutParams.topMargin);                    }
                }
                llContent.setY(y);                break;        }
    }

    /**     * 获取屏幕的宽度     */    private int getScreenWidth() {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();        return metrics.widthPixels;    }

    private int getScreenHeight() {
        int statusBarHeight = isFullScreen() ? 0 : getStatusBarHeight();        DisplayMetrics metrics = context.getResources().getDisplayMetrics();        return metrics.heightPixels - statusBarHeight;    }

    /**     * 获取状态栏的高度     */    private int getStatusBarHeight() {
        int result = 0;        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);        }
        return result;    }

    /**     * 判断下当前要显示对话框的Activity是否是全屏     */    public boolean isFullScreen() {
        int flg = ((Activity) context).getWindow().getAttributes().flags;        boolean flag = false;        if ((flg & 1024) == 1024) {
            flag = true;        }
        return flag;    }

    /**     * 设置是否可以按返回按钮取消     */    public EasyDialog setCancelable(boolean cancelable) {
        dialog.setCancelable(cancelable);        return this;    }

    private OnEasyDialogDismissed onEasyDialogDismissed;
    public EasyDialog setOnEasyDialogDismissed(OnEasyDialogDismissed onEasyDialogDismissed) {
        this.onEasyDialogDismissed = onEasyDialogDismissed;        return this;    }

    /**     * Dialog is dismissed     */    public interface OnEasyDialogDismissed {
        public void onDismissed();    }

    private OnEasyDialogShow onEasyDialogShow;
    public EasyDialog setOnEasyDialogShow(OnEasyDialogShow onEasyDialogShow) {
        this.onEasyDialogShow = onEasyDialogShow;        return this;    }

    /**     * Dialog is showing     */    public interface OnEasyDialogShow {
        public void onShow();    }
}
8)EdittextRobotoRegular.java
==============================
package com.eppico.widgets;
import android.content.Context;import android.content.res.TypedArray;import android.graphics.Typeface;import android.text.InputFilter;import android.util.AttributeSet;import android.widget.EditText;
import com.eppico.R;
public class EdittextRobotoRegular extends EditText {
    Context context;    boolean disableEmoticons=true; // Initial disable emoticons
    public EdittextRobotoRegular(Context context) {
        this(context,null);        /*super(context);        this.context = context;        init(context);*/    }
    public EdittextRobotoRegular(Context context, AttributeSet attrs) {
        this(context,attrs,android.R.attr.editTextStyle);        /*super(context, attrs);        this.context = context;        init(context);*/    }
    public EdittextRobotoRegular(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);        this.context = context;        init(context,attrs);    }

    private void init(Context context,AttributeSet attrs) {

        TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.MyEditText, 0, 0);        this.disableEmoticons=attr.getBoolean(R.styleable.MyEditText_disableEmoticons, disableEmoticons);        //this.disableEmoticons=attr.getBoolean(R.styleable.MyEdittext_disableEmoticons, disableEmoticons);
        Typeface tf = Typeface.createFromAsset(getContext().getAssets(), "Roboto-Regular_3.ttf");        setTypeface(tf);
        /*InputFilter[] filterArray = new InputFilter[2];        filterArray[0] = new InputFilter.LengthFilter(maxLength);        filterArray[1] = new EmojiExcludeFilter(context);
        setFilters(filterArray);*/

        if(disableEmoticons)
        {
            InputFilter curFilters[] = getFilters();            if (curFilters != null) {
                InputFilter newFilters[] = new InputFilter[curFilters.length + 1];                System.arraycopy(curFilters, 0, newFilters, 0, curFilters.length);                newFilters[curFilters.length] = new EmojiExcludeFilter(context);                setFilters(newFilters);            }
        }


    }

    /** Default it is enabled     * @param disableEmoticons true: Disable emoticons, false: enable emoticons     * */    public void setDisableEmoticons(boolean disableEmoticons)
    {
        this.disableEmoticons=disableEmoticons;        invalidate();    }


    /*private class EmojiExcludeFilter implements InputFilter {
        @Override        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {            for (int i = start; i < end; i++) {                int type = Character.getType(source.charAt(i));                if (type == Character.SURROGATE || type == Character.OTHER_SYMBOL) {
                    //Show message only one time                    if(firstTime)                    {                        firstTime=false;                        ToastHelper.getInstance().showToast(context,context.getString(R.string.msgEmoticonsNotAllowed));                    }                    return "";                }            }            return null;        }    }*/}
9)EmojiExcludeFilter.java
==========================
package com.eppico.widgets;
import android.content.Context;import android.text.InputFilter;import android.text.Spanned;import android.widget.Toast;
import com.eppico.R;import com.eppico.utility.ToastHelper;
public class EmojiExcludeFilter implements InputFilter {

    Context context;    private boolean firstTime=true;
    public EmojiExcludeFilter(Context context) {
        this.context= context;    }

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        for (int i = start; i < end; i++) {
            int type = Character.getType(source.charAt(i));            if (type == Character.SURROGATE || type == Character.OTHER_SYMBOL) {

                //Show message only one time                if (firstTime) {
                    firstTime = false;                    ToastHelper.getInstance().showToast(context, context.getString(R.string.msgEmoticonsNotAllowed), Toast.LENGTH_LONG);                }
                return "";            }
        }
        return null;    }
}
10)MyEditText.java
=======================
package com.eppico.widgets;
import android.content.Context;import android.content.res.TypedArray;import android.graphics.Typeface;import android.text.InputFilter;import android.util.AttributeSet;import android.widget.EditText;
import com.eppico.R;
public class MyEditText extends EditText {
    Context context;    boolean disableEmoticons=true; // Initial disable emoticons    int fontType;    String fontNames[];
    public MyEditText(Context context) {
        this(context,null);    }

    public MyEditText(Context context, AttributeSet attrs) {
        this(context,attrs,android.R.attr.editTextStyle);    }

    public MyEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);        this.context = context;        fontNames=context.getResources().getStringArray(R.array.fontNames);        init(context,attrs);    }


    private void init(Context context,AttributeSet attrs) {

        TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.MyEditText, 0, 0);        this.disableEmoticons=attr.getBoolean(R.styleable.MyEditText_disableEmoticons, disableEmoticons);        this.fontType=attr.getInt(R.styleable.MyEditText_fontType, 0);
        // Set TypeFace        Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), fontNames[fontType]);        setTypeface(typeface);
        if(disableEmoticons)
        {
            InputFilter curFilters[] = getFilters();            if (curFilters != null) {
                InputFilter newFilters[] = new InputFilter[curFilters.length + 1];                System.arraycopy(curFilters, 0, newFilters, 0, curFilters.length);                newFilters[curFilters.length] = new EmojiExcludeFilter(context);                setFilters(newFilters);            }
        }
    }

    /** Default it is enabled     * @param disableEmoticons true: Disable emoticons, false: enable emoticons     * */    public void setDisableEmoticons(boolean disableEmoticons)
    {
        this.disableEmoticons=disableEmoticons;        invalidate();    }

}
11)MyTextView.java
========================
package com.eppico.widgets;
import android.content.Context;import android.content.res.TypedArray;import android.graphics.Typeface;import android.text.InputFilter;import android.util.AttributeSet;import android.widget.EditText;import android.widget.TextView;
import com.eppico.R;
public class MyTextView extends TextView {
    Context context;    int fontType;    String fontNames[];
    public MyTextView(Context context) {
        this(context,null);
    }

    public MyTextView(Context context, AttributeSet attrs) {
        this(context,attrs,android.R.attr.textViewStyle);
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);        this.context = context;        fontNames=context.getResources().getStringArray(R.array.fontNames);        init(context,attrs);    }


    private void init(Context context,AttributeSet attrs) {

        TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.MyEditText, 0, 0);        this.fontType=attr.getInt(R.styleable.MyTextView_fontType, 0);
        // Set TypeFace        Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), fontNames[fontType]);        setTypeface(typeface);
    }
}
12)ShapedImageView.java
========================
package com.eppico.widgets;
import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.RectF;import android.graphics.drawable.shapes.RoundRectShape;import android.graphics.drawable.shapes.Shape;import android.os.Build;import android.util.AttributeSet;import android.widget.ImageView;

import com.eppico.R;
import java.util.Arrays;
public class ShapedImageView extends ImageView {

    public static final int SHAPE_MODE_ROUND_RECT = 1;    public static final int SHAPE_MODE_CIRCLE = 2;
    private static final int LAYER_FLAGS = Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG;
    private int mShapeMode = 0;    private float mRadius = 0;    private int mStrokeColor = 0x26000000;    private float mStrokeWidth = 0;
    private Path mPath;    private Shape mShape, mStrokeShape;    private Paint mPaint, mStrokePaint, mPathPaint;    private Bitmap mStrokeBitmap;
    private PathExtension mExtension;
    public ShapedImageView(Context context) {
        super(context);        init(null);    }

    public ShapedImageView(Context context, AttributeSet attrs) {
        super(context, attrs);        init(attrs);    }

    public ShapedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);        init(attrs);    }

    private void init(AttributeSet attrs) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            setLayerType(LAYER_TYPE_HARDWARE, null);        }
        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ShapedImageView);            mShapeMode = a.getInt(R.styleable.ShapedImageView_shape_mode, 2);            mRadius = a.getDimension(R.styleable.ShapedImageView_round_radius, 0);
            mStrokeWidth = a.getDimension(R.styleable.ShapedImageView_stroke_width, 0);            mStrokeColor = a.getColor(R.styleable.ShapedImageView_stroke_color, mStrokeColor);            a.recycle();        }
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setFilterBitmap(true);        mPaint.setColor(Color.BLACK);        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mStrokePaint.setFilterBitmap(true);        mStrokePaint.setColor(mStrokeColor);
        mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPathPaint.setFilterBitmap(true);        mPathPaint.setColor(Color.BLACK);        mPathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
        mPath = new Path();    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);        if (changed) {
            switch (mShapeMode) {
                case SHAPE_MODE_ROUND_RECT:
                    break;                case SHAPE_MODE_CIRCLE:
                    int min = Math.min(getWidth(), getHeight());                    mRadius = (float) min / 2;                    break;            }

            if (mShape == null) {
                float[] radius = new float[8];                Arrays.fill(radius, mRadius);                mShape = new RoundRectShape(radius, null, null);                mStrokeShape = new RoundRectShape(radius, null, null);            }
            mShape.resize(getWidth(), getHeight());            mStrokeShape.resize(getWidth() - mStrokeWidth * 2, getHeight() - mStrokeWidth * 2);
            if (mStrokeWidth > 0 && mStrokeBitmap == null) {
                mStrokeBitmap = makeStrokeBitmap(getWidth(), getHeight());            }

            if (mExtension != null) {
                mExtension.onLayout(mPath, getWidth(), getHeight());            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mStrokeWidth > 0 && mStrokeShape != null && mStrokeBitmap != null) {
            int i = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, LAYER_FLAGS);            mStrokePaint.setXfermode(null);            canvas.drawBitmap(mStrokeBitmap, 0, 0, mStrokePaint);            canvas.translate(mStrokeWidth, mStrokeWidth);            mStrokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));            mStrokeShape.draw(canvas, mStrokePaint);            canvas.restoreToCount(i);        }

        if (mExtension != null) {
            canvas.drawPath(mPath, mPathPaint);        }

        switch (mShapeMode) {
            case SHAPE_MODE_ROUND_RECT:
            case SHAPE_MODE_CIRCLE:
                if (mShape != null) {
                    mShape.draw(canvas, mPaint);                }
                break;        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();        if (mStrokeWidth > 0 && mStrokeBitmap == null && mStrokeShape != null) {
            mStrokeBitmap = makeStrokeBitmap(getWidth(), getHeight());        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();        if (mStrokeBitmap != null) {
            mStrokeBitmap.recycle();            mStrokeBitmap = null;        }
    }

    private Bitmap makeStrokeBitmap(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);        Canvas c = new Canvas(bm);        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);        p.setColor(mStrokeColor);        c.drawRect(new RectF(0, 0, w, h), p);        return bm;    }

    public void setExtension(PathExtension extension) {
        mExtension = extension;        requestLayout();    }

    public interface PathExtension {
        void onLayout(Path path, int width, int height);    }

    public void setShapeMode(int shapeMode)
    {
        this.mShapeMode=shapeMode;        invalidate();    }


}
13)TextViewRobotoBold.java
==========================
package com.eppico.widgets;
import android.content.Context;import android.graphics.Typeface;import android.util.AttributeSet;import android.widget.TextView;
public class TextViewRobotoBold extends TextView {
    Context context;
    public TextViewRobotoBold(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);        this.context = context;        init(context);    }

    public TextViewRobotoBold(Context context, AttributeSet attrs) {
        super(context, attrs);        this.context = context;        init(context);    }

    public TextViewRobotoBold(Context context) {
        super(context);        this.context = context;        init(context);    }

    private void init(Context context) {//        if (!isInEditMode()) {        Typeface tf = Typeface.createFromAsset(getContext().getAssets(), "Roboto-Bold_3.ttf");        setTypeface(tf);//        }    }}
14)TouchImageView.java
==============================
package com.eppico.widgets;
import android.annotation.TargetApi;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Matrix;import android.graphics.PointF;import android.graphics.drawable.Drawable;import android.net.Uri;import android.os.Build;import android.os.Build.VERSION;import android.os.Build.VERSION_CODES;import android.os.Bundle;import android.os.Parcelable;import android.util.AttributeSet;import android.util.Log;import android.view.GestureDetector;import android.view.MotionEvent;import android.view.ScaleGestureDetector;import android.view.View;import android.view.animation.AccelerateDecelerateInterpolator;import android.widget.ImageView;import android.widget.Scroller;
public class TouchImageView extends ImageView {

   private static final String DEBUG = "DEBUG";
   // SuperMin and SuperMax multipliers. Determine how much the image can be   // zoomed below or above the zoom boundaries, before animating back to the   // min/max zoom boundary.   //   private static final float SUPER_MIN_MULTIPLIER = .75f;   private static final float SUPER_MAX_MULTIPLIER = 1.25f;
   // Scale of image ranges from minScale to maxScale, where minScale == 1   // when the image is stretched to fit view.   //   private float normalizedScale;
   // Matrix applied to image. MSCALE_X and MSCALE_Y should always be equal.   // MTRANS_X and MTRANS_Y are the other values used. prevMatrix is the matrix   // saved prior to the screen rotating.   //   private Matrix matrix, prevMatrix;
   public static enum State {
      NONE, DRAG, ZOOM, FLING, ANIMATE_ZOOM
   };
   private State state;
   private float minScale;   private float maxScale;   private float superMinScale;   private float superMaxScale;   private float[] m;
   private Context context;   private Fling fling;
   //   // Size of view and previous view size (ie before rotation)   //   private int viewWidth, viewHeight, prevViewWidth, prevViewHeight;
   //   // Size of image when it is stretched to fit view. Before and After   // rotation.   //   private float matchViewWidth, matchViewHeight, prevMatchViewWidth,         prevMatchViewHeight;
   //   // After setting image, a value of true means the new image should maintain   // the zoom of the previous image. False means it should be resized within   // the view.   //   private boolean maintainZoomAfterSetImage;
   //   // True when maintainZoomAfterSetImage has been set to true and setImage has   // been called.   //   private boolean setImageCalledRecenterImage;
   private ScaleGestureDetector mScaleDetector;   private GestureDetector mGestureDetector;
   public TouchImageView(Context context) {
      super(context);      sharedConstructing(context);   }

   public TouchImageView(Context context, AttributeSet attrs) {
      super(context, attrs);      sharedConstructing(context);   }

   public TouchImageView(Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);      sharedConstructing(context);   }

   private void sharedConstructing(Context context) {
      super.setClickable(true);      this.context = context;      mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());      mGestureDetector = new GestureDetector(context, new GestureListener());      matrix = new Matrix();      prevMatrix = new Matrix();      m = new float[9];      normalizedScale = 1;      minScale = 1;      maxScale = 3;      superMinScale = SUPER_MIN_MULTIPLIER * minScale;      superMaxScale = SUPER_MAX_MULTIPLIER * maxScale;      maintainZoomAfterSetImage = true;      setImageMatrix(matrix);      setScaleType(ScaleType.MATRIX);      setState(State.NONE);      setOnTouchListener(new TouchImageViewListener());   }

   public void enableZoom(boolean enable)
   {
      if(enable)
         setOnTouchListener(new TouchImageViewListener());      else         setOnTouchListener(null);
   }

   @Override
   public void setImageResource(int resId) {
      super.setImageResource(resId);      setImageCalled();      savePreviousImageValues();      fitImageToView();   }

   @Override
   public void setImageBitmap(Bitmap bm) {
      super.setImageBitmap(bm);      setImageCalled();      savePreviousImageValues();      fitImageToView();   }

   @Override
   public void setImageDrawable(Drawable drawable) {
      super.setImageDrawable(drawable);      setImageCalled();      savePreviousImageValues();      fitImageToView();   }

   @Override
   public void setImageURI(Uri uri) {
      super.setImageURI(uri);      setImageCalled();      savePreviousImageValues();      fitImageToView();   }

   private void setImageCalled() {
      if (!maintainZoomAfterSetImage) {
         setImageCalledRecenterImage = true;      }
   }

   /**    * Save the current matrix and view dimensions in the prevMatrix and    * prevView variables.    */   private void savePreviousImageValues() {
      if (matrix != null) {
         matrix.getValues(m);         prevMatrix.setValues(m);         prevMatchViewHeight = matchViewHeight;         prevMatchViewWidth = matchViewWidth;         prevViewHeight = viewHeight;         prevViewWidth = viewWidth;      }
   }

   @Override
   public Parcelable onSaveInstanceState() {
      Bundle bundle = new Bundle();      bundle.putParcelable("instanceState", super.onSaveInstanceState());      bundle.putFloat("saveScale", normalizedScale);      bundle.putFloat("matchViewHeight", matchViewHeight);      bundle.putFloat("matchViewWidth", matchViewWidth);      bundle.putInt("viewWidth", viewWidth);      bundle.putInt("viewHeight", viewHeight);      matrix.getValues(m);      bundle.putFloatArray("matrix", m);      return bundle;   }

   @Override
   public void onRestoreInstanceState(Parcelable state) {
      if (state instanceof Bundle) {
         Bundle bundle = (Bundle) state;         normalizedScale = bundle.getFloat("saveScale");         m = bundle.getFloatArray("matrix");         prevMatrix.setValues(m);         prevMatchViewHeight = bundle.getFloat("matchViewHeight");         prevMatchViewWidth = bundle.getFloat("matchViewWidth");         prevViewHeight = bundle.getInt("viewHeight");         prevViewWidth = bundle.getInt("viewWidth");         super.onRestoreInstanceState(bundle.getParcelable("instanceState"));         return;      }

      super.onRestoreInstanceState(state);   }

   /**    * Get the max zoom multiplier.    *     * @return max zoom multiplier.    */   public float getMaxZoom() {
      return maxScale;   }

   /**    * Set the max zoom multiplier. Default value: 3.    *     * @param max    *            max zoom multiplier.    */   public void setMaxZoom(float max) {
      maxScale = max;      superMaxScale = SUPER_MAX_MULTIPLIER * maxScale;   }

   /**    * Get the min zoom multiplier.    *     * @return min zoom multiplier.    */   public float getMinZoom() {
      return minScale;   }

   /**    * After setting image, a value of true means the new image should maintain    * the zoom of the previous image. False means the image should be resized    * within the view. Defaults value is true.    *     * @param maintainZoom    */   public void maintainZoomAfterSetImage(boolean maintainZoom) {
      maintainZoomAfterSetImage = maintainZoom;   }

   /**    * Get the current zoom. This is the zoom relative to the initial scale, not    * the original resource.    *     * @return current zoom multiplier.    */   public float getCurrentZoom() {
      return normalizedScale;   }

   /**    * Set the min zoom multiplier. Default value: 1.    *     * @param min    *            min zoom multiplier.    */   public void setMinZoom(float min) {
      minScale = min;      superMinScale = SUPER_MIN_MULTIPLIER * minScale;   }

   /**    * For a given point on the view (ie, a touch event), returns the point    * relative to the original drawable's coordinate system.    *     * @param x    * @param y    * @return PointF relative to original drawable's coordinate system.    */   public PointF getDrawablePointFromTouchPoint(float x, float y) {
      return transformCoordTouchToBitmap(x, y, true);   }

   /**    * For a given point on the view (ie, a touch event), returns the point    * relative to the original drawable's coordinate system.    *     * @param p    * @return PointF relative to original drawable's coordinate system.    */   public PointF getDrawablePointFromTouchPoint(PointF p) {
      return transformCoordTouchToBitmap(p.x, p.y, true);   }

   /**    * Performs boundary checking and fixes the image matrix if it is out of    * bounds.    */   private void fixTrans() {
      matrix.getValues(m);      float transX = m[Matrix.MTRANS_X];      float transY = m[Matrix.MTRANS_Y];
      float fixTransX = getFixTrans(transX, viewWidth, getImageWidth());      float fixTransY = getFixTrans(transY, viewHeight, getImageHeight());
      if (fixTransX != 0 || fixTransY != 0) {
         matrix.postTranslate(fixTransX, fixTransY);      }
   }

   /**    * When transitioning from zooming from focus to zoom from center (or vice    * versa) the image can become unaligned within the view. This is apparent    * when zooming quickly. When the content size is less than the view size,    * the content will often be centered incorrectly within the view.    * fixScaleTrans first calls fixTrans() and then makes sure the image is    * centered correctly within the view.    */   private void fixScaleTrans() {
      fixTrans();      matrix.getValues(m);      if (getImageWidth() < viewWidth) {
         m[Matrix.MTRANS_X] = (viewWidth - getImageWidth()) / 2;      }

      if (getImageHeight() < viewHeight) {
         m[Matrix.MTRANS_Y] = (viewHeight - getImageHeight()) / 2;      }
      matrix.setValues(m);   }

   private float getFixTrans(float trans, float viewSize, float contentSize) {
      float minTrans, maxTrans;
      if (contentSize <= viewSize) {
         minTrans = 0;         maxTrans = viewSize - contentSize;
      } else {
         minTrans = viewSize - contentSize;         maxTrans = 0;      }

      if (trans < minTrans)
         return -trans + minTrans;      if (trans > maxTrans)
         return -trans + maxTrans;      return 0;   }

   private float getFixDragTrans(float delta, float viewSize, float contentSize) {
      if (contentSize <= viewSize) {
         return 0;      }
      return delta;   }

   private float getImageWidth() {
      return matchViewWidth * normalizedScale;   }

   private float getImageHeight() {
      return matchViewHeight * normalizedScale;   }

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      Drawable drawable = getDrawable();      if (drawable == null || drawable.getIntrinsicWidth() == 0            || drawable.getIntrinsicHeight() == 0) {
         setMeasuredDimension(0, 0);         return;      }

      int drawableWidth = drawable.getIntrinsicWidth();      int drawableHeight = drawable.getIntrinsicHeight();      int widthSize = MeasureSpec.getSize(widthMeasureSpec);      int widthMode = MeasureSpec.getMode(widthMeasureSpec);      int heightSize = MeasureSpec.getSize(heightMeasureSpec);      int heightMode = MeasureSpec.getMode(heightMeasureSpec);      viewWidth = setViewSize(widthMode, widthSize, drawableWidth);      viewHeight = setViewSize(heightMode, heightSize, drawableHeight);
      //      // Set view dimensions      //      setMeasuredDimension(viewWidth, viewHeight);
      //      // Fit content within view      //      fitImageToView();   }

   /**    * If the normalizedScale is equal to 1, then the image is made to fit the    * screen. Otherwise, it is made to fit the screen according to the    * dimensions of the previous image matrix. This allows the image to    * maintain its zoom after rotation.    */   private void fitImageToView() {
      Drawable drawable = getDrawable();      if (drawable == null || drawable.getIntrinsicWidth() == 0            || drawable.getIntrinsicHeight() == 0) {
         return;      }
      if (matrix == null || prevMatrix == null) {
         return;      }

      int drawableWidth = drawable.getIntrinsicWidth();      int drawableHeight = drawable.getIntrinsicHeight();
      //      // Scale image for view      //      float scaleX = (float) viewWidth / drawableWidth;      float scaleY = (float) viewHeight / drawableHeight;      float scale = Math.min(scaleX, scaleY);
      //      // Center the image      //      float redundantYSpace = viewHeight - (scale * drawableHeight);      float redundantXSpace = viewWidth - (scale * drawableWidth);      matchViewWidth = viewWidth - redundantXSpace;      matchViewHeight = viewHeight - redundantYSpace;      if (normalizedScale == 1 || setImageCalledRecenterImage) {
         //         // Stretch and center image to fit view         //         matrix.setScale(scale, scale);         matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2);         normalizedScale = 1;         setImageCalledRecenterImage = false;
      } else {
         prevMatrix.getValues(m);
         //         // Rescale Matrix after rotation         //         m[Matrix.MSCALE_X] = matchViewWidth / drawableWidth
               * normalizedScale;         m[Matrix.MSCALE_Y] = matchViewHeight / drawableHeight
               * normalizedScale;
         //         // TransX and TransY from previous matrix         //         float transX = m[Matrix.MTRANS_X];         float transY = m[Matrix.MTRANS_Y];
         //         // Width         //         float prevActualWidth = prevMatchViewWidth * normalizedScale;         float actualWidth = getImageWidth();         translateMatrixAfterRotate(Matrix.MTRANS_X, transX,               prevActualWidth, actualWidth, prevViewWidth, viewWidth,               drawableWidth);
         //         // Height         //         float prevActualHeight = prevMatchViewHeight * normalizedScale;         float actualHeight = getImageHeight();         translateMatrixAfterRotate(Matrix.MTRANS_Y, transY,               prevActualHeight, actualHeight, prevViewHeight, viewHeight,               drawableHeight);
         // Set the matrix to the adjusted scale and translate values.         //         matrix.setValues(m);      }
      setImageMatrix(matrix);   }

   /**    * Set view dimensions based on layout params    *     * @param mode    * @param size    * @param drawableWidth    * @return    */   private int setViewSize(int mode, int size, int drawableWidth) {
      int viewSize;      switch (mode) {
      case MeasureSpec.EXACTLY:
         viewSize = size;         break;
      case MeasureSpec.AT_MOST:
         viewSize = Math.min(drawableWidth, size);         break;
      case MeasureSpec.UNSPECIFIED:
         viewSize = drawableWidth;         break;
      default:
         viewSize = size;         break;      }
      return viewSize;   }

   /**    * After rotating, the matrix needs to be translated. This function finds    * the area of image which was previously centered and adjusts translations    * so that is again the center, post-rotation.    *     * @param axis    *            Matrix.MTRANS_X or Matrix.MTRANS_Y    * @param trans    *            the value of trans in that axis before the rotation    * @param prevImageSize    *            the width/height of the image before the rotation    * @param imageSize    *            width/height of the image after rotation    * @param prevViewSize    *            width/height of view before rotation    * @param viewSize    *            width/height of view after rotation    * @param drawableSize    *            width/height of drawable    */   private void translateMatrixAfterRotate(int axis, float trans,         float prevImageSize, float imageSize, int prevViewSize,         int viewSize, int drawableSize) {
      if (imageSize < viewSize) {
         //         // The width/height of image is less than the view's width/height.         // Center it.         //         m[axis] = (viewSize - (drawableSize * m[Matrix.MSCALE_X])) * 0.5f;
      } else if (trans > 0) {
         //         // The image is larger than the view, but was not before rotation.         // Center it.         //         m[axis] = -((imageSize - viewSize) * 0.5f);
      } else {
         //         // Find the area of the image which was previously centered in the         // view. Determine its distance         // from the left/top side of the view as a fraction of the entire         // image's width/height. Use that percentage         // to calculate the trans in the new view width/height.         //         float percentage = (Math.abs(trans) + (0.5f * prevViewSize))
               / prevImageSize;         m[axis] = -((percentage * imageSize) - (viewSize * 0.5f));      }
   }

   private void setState(State state) {
      this.state = state;   }

   /**    * Gesture Listener detects a single click or long click and passes that on    * to the view's listener.    *     * @author Ortiz    *     */   private class GestureListener extends         GestureDetector.SimpleOnGestureListener {

      @Override
      public boolean onSingleTapConfirmed(MotionEvent e) {
         return performClick();      }

      @Override
      public void onLongPress(MotionEvent e) {
         performLongClick();      }

      @Override
      public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,            float velocityY) {
         if (fling != null) {
            //            // If a previous fling is still active, it should be cancelled            // so that two flings            // are not run simultaenously.            //            fling.cancelFling();         }
         fling = new Fling((int) velocityX, (int) velocityY);         compatPostOnAnimation(fling);         return super.onFling(e1, e2, velocityX, velocityY);      }

      @Override
      public boolean onDoubleTap(MotionEvent e) {
         boolean consumed = false;         if (state == State.NONE) {
            float targetZoom = (normalizedScale == minScale) ? maxScale
                  : minScale;            DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom,                  e.getX(), e.getY(), false);            compatPostOnAnimation(doubleTap);            consumed = true;         }
         return consumed;      }
   }

   /**    * Responsible for all touch events. Handles the heavy lifting of drag and    * also sends touch events to Scale Detector and Gesture Detector.    *     * @author Ortiz    *     */   private class TouchImageViewListener implements OnTouchListener {

      //      // Remember last point position for dragging      //      private PointF last = new PointF();
      @Override
      public boolean onTouch(View v, MotionEvent event) {
         mScaleDetector.onTouchEvent(event);         mGestureDetector.onTouchEvent(event);         PointF curr = new PointF(event.getX(), event.getY());
         if (state == State.NONE || state == State.DRAG || state == State.FLING) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
               last.set(curr);               if (fling != null)
                  fling.cancelFling();               setState(State.DRAG);               break;
            case MotionEvent.ACTION_MOVE:
               if (state == State.DRAG) {
                  float deltaX = curr.x - last.x;                  float deltaY = curr.y - last.y;                  float fixTransX = getFixDragTrans(deltaX, viewWidth,                        getImageWidth());                  float fixTransY = getFixDragTrans(deltaY, viewHeight,                        getImageHeight());                  matrix.postTranslate(fixTransX, fixTransY);                  fixTrans();                  last.set(curr.x, curr.y);               }
               break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
               setState(State.NONE);               break;            }
         }

         setImageMatrix(matrix);         //         // indicate event was handled         //         return true;      }
   }

   /**    * ScaleListener detects user two finger scaling and scales image.    *     * @author Ortiz    *     */   private class ScaleListener extends         ScaleGestureDetector.SimpleOnScaleGestureListener {
      @Override
      public boolean onScaleBegin(ScaleGestureDetector detector) {
         setState(State.ZOOM);         return true;      }

      @Override
      public boolean onScale(ScaleGestureDetector detector) {
         scaleImage(detector.getScaleFactor(), detector.getFocusX(),               detector.getFocusY(), true);         return true;      }

      @Override
      public void onScaleEnd(ScaleGestureDetector detector) {
         super.onScaleEnd(detector);         setState(State.NONE);         boolean animateToZoomBoundary = false;         float targetZoom = normalizedScale;         if (normalizedScale > maxScale) {
            targetZoom = maxScale;            animateToZoomBoundary = true;
         } else if (normalizedScale < minScale) {
            targetZoom = minScale;            animateToZoomBoundary = true;         }

         if (animateToZoomBoundary) {
            DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom,                  viewWidth / 2, viewHeight / 2, true);            compatPostOnAnimation(doubleTap);         }
      }
   }

   private void scaleImage(float deltaScale, float focusX, float focusY,         boolean stretchImageToSuper) {

      float lowerScale, upperScale;      if (stretchImageToSuper) {
         lowerScale = superMinScale;         upperScale = superMaxScale;
      } else {
         lowerScale = minScale;         upperScale = maxScale;      }

      float origScale = normalizedScale;      normalizedScale *= deltaScale;      if (normalizedScale > upperScale) {
         normalizedScale = upperScale;         deltaScale = upperScale / origScale;      } else if (normalizedScale < lowerScale) {
         normalizedScale = lowerScale;         deltaScale = lowerScale / origScale;      }

      matrix.postScale(deltaScale, deltaScale, focusX, focusY);      fixScaleTrans();   }

   /**    * DoubleTapZoom calls a series of runnables which apply an animated zoom    * in/out graphic to the image.    *     * @author Ortiz    *     */   private class DoubleTapZoom implements Runnable {

      private long startTime;      private static final float ZOOM_TIME = 500;      private float startZoom, targetZoom;      private float bitmapX, bitmapY;      private boolean stretchImageToSuper;      private AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator();      private PointF startTouch;      private PointF endTouch;
      DoubleTapZoom(float targetZoom, float focusX, float focusY,            boolean stretchImageToSuper) {
         setState(State.ANIMATE_ZOOM);         startTime = System.currentTimeMillis();         this.startZoom = normalizedScale;         this.targetZoom = targetZoom;         this.stretchImageToSuper = stretchImageToSuper;         PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY,               false);         this.bitmapX = bitmapPoint.x;         this.bitmapY = bitmapPoint.y;
         //         // Used for translating image during scaling         //         startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY);         endTouch = new PointF(viewWidth / 2, viewHeight / 2);      }

      @Override
      public void run() {
         float t = interpolate();         float deltaScale = calculateDeltaScale(t);         scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper);         translateImageToCenterTouchPosition(t);         fixScaleTrans();         setImageMatrix(matrix);
         if (t < 1f) {
            //            // We haven't finished zooming            //            compatPostOnAnimation(this);
         } else {
            //            // Finished zooming            //            setState(State.NONE);         }
      }

      /**       * Interpolate between where the image should start and end in order to       * translate the image so that the point that is touched is what ends up       * centered at the end of the zoom.       *        * @param t       */      private void translateImageToCenterTouchPosition(float t) {
         float targetX = startTouch.x + t * (endTouch.x - startTouch.x);         float targetY = startTouch.y + t * (endTouch.y - startTouch.y);         PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY);         matrix.postTranslate(targetX - curr.x, targetY - curr.y);      }

      /**       * Use interpolator to get t       *        * @return       */      private float interpolate() {
         long currTime = System.currentTimeMillis();         float elapsed = (currTime - startTime) / ZOOM_TIME;         elapsed = Math.min(1f, elapsed);         return interpolator.getInterpolation(elapsed);      }

      /**       * Interpolate the current targeted zoom and get the delta from the       * current zoom.       *        * @param t       * @return       */      private float calculateDeltaScale(float t) {
         float zoom = startZoom + t * (targetZoom - startZoom);         return zoom / normalizedScale;      }
   }

   /**    * This function will transform the coordinates in the touch event to the    * coordinate system of the drawable that the imageview contain    *     * @param x    *            x-coordinate of touch event    * @param y    *            y-coordinate of touch event    * @param clipToBitmap    *            Touch event may occur within view, but outside image content.    *            True, to clip return value to the bounds of the bitmap size.    * @return Coordinates of the point touched, in the coordinate system of the    *         original drawable.    */   private PointF transformCoordTouchToBitmap(float x, float y,         boolean clipToBitmap) {
      matrix.getValues(m);      float origW = getDrawable().getIntrinsicWidth();      float origH = getDrawable().getIntrinsicHeight();      float transX = m[Matrix.MTRANS_X];      float transY = m[Matrix.MTRANS_Y];      float finalX = ((x - transX) * origW) / getImageWidth();      float finalY = ((y - transY) * origH) / getImageHeight();
      if (clipToBitmap) {
         finalX = Math.min(Math.max(x, 0), origW);         finalY = Math.min(Math.max(y, 0), origH);      }

      return new PointF(finalX, finalY);   }

   /**    * Inverse of transformCoordTouchToBitmap. This function will transform the    * coordinates in the drawable's coordinate system to the view's coordinate    * system.    *     * @param bx    *            x-coordinate in original bitmap coordinate system    * @param by    *            y-coordinate in original bitmap coordinate system    * @return Coordinates of the point in the view's coordinate system.    */   private PointF transformCoordBitmapToTouch(float bx, float by) {
      matrix.getValues(m);      float origW = getDrawable().getIntrinsicWidth();      float origH = getDrawable().getIntrinsicHeight();      float px = bx / origW;      float py = by / origH;      float finalX = m[Matrix.MTRANS_X] + getImageWidth() * px;      float finalY = m[Matrix.MTRANS_Y] + getImageHeight() * py;      return new PointF(finalX, finalY);   }

   /**    * Fling launches sequential runnables which apply the fling graphic to the    * image. The values for the translation are interpolated by the Scroller.    *     * @author Ortiz    *     */   private class Fling implements Runnable {

      Scroller scroller;      int currX, currY;
      Fling(int velocityX, int velocityY) {
         setState(State.FLING);         scroller = new Scroller(context);         matrix.getValues(m);
         int startX = (int) m[Matrix.MTRANS_X];         int startY = (int) m[Matrix.MTRANS_Y];         int minX, maxX, minY, maxY;
         if (getImageWidth() > viewWidth) {
            minX = viewWidth - (int) getImageWidth();            maxX = 0;
         } else {
            minX = maxX = startX;         }

         if (getImageHeight() > viewHeight) {
            minY = viewHeight - (int) getImageHeight();            maxY = 0;
         } else {
            minY = maxY = startY;         }

         scroller.fling(startX, startY, (int) velocityX, (int) velocityY,               minX, maxX, minY, maxY);         currX = startX;         currY = startY;      }

      public void cancelFling() {
         if (scroller != null) {
            setState(State.NONE);            scroller.forceFinished(true);         }
      }

      @Override
      public void run() {
         if (scroller.isFinished()) {
            scroller = null;            return;         }

         if (scroller.computeScrollOffset()) {
            int newX = scroller.getCurrX();            int newY = scroller.getCurrY();            int transX = newX - currX;            int transY = newY - currY;            currX = newX;            currY = newY;            matrix.postTranslate(transX, transY);            fixTrans();            setImageMatrix(matrix);            compatPostOnAnimation(this);         }
      }
   }

   @TargetApi(VERSION_CODES.JELLY_BEAN)
   private void compatPostOnAnimation(Runnable runnable) {
      if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
         postOnAnimation(runnable);
      } else {
         postDelayed(runnable, 1000 / 60);      }
   }

   private void printMatrixInfo() {
      matrix.getValues(m);      Log.d(DEBUG, "Scale: " + m[Matrix.MSCALE_X] + " TransX: "            + m[Matrix.MTRANS_X] + " TransY: " + m[Matrix.MTRANS_Y]);   }
}

No comments:

Post a Comment