Android 自定义imageview实现图片缩放实例详解
程序员文章站
2022-08-02 14:28:55
android 自定义imageview实现图片缩放实例详解
觉得这个自定义的imageview很好用 性能不错 所以拿出来分享给大家 ...
android 自定义imageview实现图片缩放实例详解
觉得这个自定义的imageview很好用 性能不错 所以拿出来分享给大家 因为不会做gif图 所以项目效果 就不好贴出来了 把代码贴出来
1.项目结构图
2.compat.class
package com.suo.image; import android.os.build.version; import android.os.build.version_codes; import android.view.view; public class compat { private static final int sixty_fps_interval = 1000 / 60; public static void postonanimation(view view, runnable runnable) { if (version.sdk_int >= version_codes.jelly_bean) { sdk16.postonanimation(view, runnable); } else { view.postdelayed(runnable, sixty_fps_interval); } } }
3.hackyviewpager.class
package com.suo.image; import android.content.context; import android.support.v4.view.viewpager; import android.util.attributeset; import android.view.motionevent; /** * hacky fix for issue #4 and * http://code.google.com/p/android/issues/detail?id=18990 * * scalegesturedetector seems to mess up the touch events, which means that * viewgroups which make use of onintercepttouchevent throw a lot of * illegalargumentexception: pointerindex out of range. * * there's not much i can do in my code for now, but we can mask the result by * just catching the problem and ignoring it. * * @author chris banes */ public class hackyviewpager extends viewpager { public hackyviewpager(context context) { super(context); } public hackyviewpager(context context, attributeset attrs) { super(context, attrs); // todo auto-generated constructor stub } @override public boolean onintercepttouchevent(motionevent ev) { try { return super.onintercepttouchevent(ev); } catch (illegalargumentexception e) { e.printstacktrace(); return false; } } }
4.iscaleview.class
/******************************************************************************* * copyright 2011, 2012 chris banes. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. *******************************************************************************/ package com.suo.image; import android.graphics.rectf; import android.view.view; import android.widget.imageview; public interface iscaleview { /** * returns true if the scaleview is set to allow zooming of scales. * * @return true if the scaleview allows zooming. */ boolean canzoom(); /** * gets the display rectangle of the currently displayed drawable. the * rectangle is relative to this view and includes all scaling and * translations. * * @return - rectf of displayed drawable */ rectf getdisplayrect(); /** * @return the current minimum scale level. what this value represents depends on the current {@link android.widget.imageview.scaletype}. */ float getminscale(); /** * @return the current middle scale level. what this value represents depends on the current {@link android.widget.imageview.scaletype}. */ float getmidscale(); /** * @return the current maximum scale level. what this value represents depends on the current {@link android.widget.imageview.scaletype}. */ float getmaxscale(); /** * returns the current scale value * * @return float - current scale value */ float getscale(); /** * return the current scale type in use by the imageview. */ imageview.scaletype getscaletype(); /** * whether to allow the imageview's parent to intercept the touch event when the scale is scroll to it's horizontal edge. */ void setallowparentinterceptonedge(boolean allow); /** * sets the minimum scale level. what this value represents depends on the current {@link android.widget.imageview.scaletype}. */ void setminscale(float minscale); /** * sets the middle scale level. what this value represents depends on the current {@link android.widget.imageview.scaletype}. */ void setmidscale(float midscale); /** * sets the maximum scale level. what this value represents depends on the current {@link android.widget.imageview.scaletype}. */ void setmaxscale(float maxscale); /** * register a callback to be invoked when the scale displayed by this view is long-pressed. * * @param listener - listener to be registered. */ void setonlongclicklistener(view.onlongclicklistener listener); /** * register a callback to be invoked when the matrix has changed for this * view. an example would be the user panning or scaling the scale. * * @param listener - listener to be registered. */ void setonmatrixchangelistener(scaleviewattacher.onmatrixchangedlistener listener); /** * register a callback to be invoked when the scale displayed by this view * is tapped with a single tap. * * @param listener - listener to be registered. */ void setonscaletaplistener(scaleviewattacher.onscaletaplistener listener); /** * register a callback to be invoked when the view is tapped with a single * tap. * * @param listener - listener to be registered. */ void setonviewtaplistener(scaleviewattacher.onviewtaplistener listener); /** * controls how the image should be resized or moved to match the size of * the imageview. any scaling or panning will happen within the confines of * this {@link android.widget.imageview.scaletype}. * * @param scaletype - the desired scaling mode. */ void setscaletype(imageview.scaletype scaletype); /** * allows you to enable/disable the zoom functionality on the imageview. * when disable the imageview reverts to using the fit_center matrix. * * @param zoomable - whether the zoom functionality is enabled. */ void setzoomable(boolean zoomable); /** * zooms to the specified scale, around the focal point given. * * @param scale - scale to zoom to * @param focalx - x focus point * @param focaly - y focus point */ void zoomto(float scale, float focalx, float focaly); }
5.scaleview
/******************************************************************************* * copyright 2011, 2012 chris banes. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. *******************************************************************************/ package com.suo.image; import com.suo.image.scaleviewattacher.onmatrixchangedlistener; import com.suo.image.scaleviewattacher.onscaletaplistener; import com.suo.image.scaleviewattacher.onviewtaplistener; import android.content.context; import android.graphics.rectf; import android.graphics.drawable.drawable; import android.net.uri; import android.util.attributeset; import android.widget.imageview; public class scaleview extends imageview implements iscaleview { @suppresswarnings("unused") private static final string tag = "scaleview"; private final scaleviewattacher mattacher; private scaletype mpendingscaletype; public scaleview(context context) { this(context, null); setzoomable(false); } public scaleview(context context, attributeset attr) { this(context, attr, 0); } public scaleview(context context, attributeset attr, int defstyle) { super(context, attr, defstyle); super.setscaletype(scaletype.matrix); mattacher = new scaleviewattacher(this); if (null != mpendingscaletype) { setscaletype(mpendingscaletype); mpendingscaletype = null; } } public void setonclicklistener(onclicklistener listener){ mattacher.setonclicklinstener(listener); } @override public boolean canzoom() { return mattacher.canzoom(); } @override public rectf getdisplayrect() { return mattacher.getdisplayrect(); } @override public float getminscale() { return mattacher.getminscale(); } @override public float getmidscale() { return mattacher.getmidscale(); } @override public float getmaxscale() { return mattacher.getmaxscale(); } @override public float getscale() { return mattacher.getscale(); } @override public scaletype getscaletype() { return mattacher.getscaletype(); } @override public void setallowparentinterceptonedge(boolean allow) { mattacher.setallowparentinterceptonedge(allow); } @override public void setminscale(float minscale) { mattacher.setminscale(minscale); } @override public void setmidscale(float midscale) { mattacher.setmidscale(midscale); } @override public void setmaxscale(float maxscale) { mattacher.setmaxscale(maxscale); } @override // setimagebitmap calls through to this method public void setimagedrawable(drawable drawable) { super.setimagedrawable(drawable); if (null != mattacher) { mattacher.update(); } } @override public void setimageresource(int resid) { super.setimageresource(resid); if (null != mattacher) { mattacher.update(); } } @override public void setimageuri(uri uri) { super.setimageuri(uri); if (null != mattacher) { mattacher.update(); } } @override public void setonmatrixchangelistener(onmatrixchangedlistener listener) { mattacher.setonmatrixchangelistener(listener); } @override public void setonlongclicklistener(onlongclicklistener l) { mattacher.setonlongclicklistener(l); } @override public void setonscaletaplistener(onscaletaplistener listener) { mattacher.setonscaletaplistener(listener); } @override public void setonviewtaplistener(onviewtaplistener listener) { mattacher.setonviewtaplistener(listener); } @override public void setscaletype(scaletype scaletype) { if (null != mattacher) { mattacher.setscaletype(scaletype); } else { mpendingscaletype = scaletype; } } @override public void setzoomable(boolean zoomable) { mattacher.setzoomable(zoomable); } @override public void zoomto(float scale, float focalx, float focaly) { mattacher.zoomto(scale, focalx, focaly); } @override protected void ondetachedfromwindow() { mattacher.cleanup(); super.ondetachedfromwindow(); } }
6.scaleviewattacher 这个是最关键的
/******************************************************************************* * copyright 2011, 2012 chris banes. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. *******************************************************************************/ package com.suo.image; import android.content.context; import android.graphics.matrix; import android.graphics.matrix.scaletofit; import android.graphics.rectf; import android.graphics.drawable.drawable; import android.util.log; import android.view.gesturedetector; import android.view.motionevent; import android.view.view; import android.view.view.onclicklistener; import android.view.view.onlongclicklistener; import android.view.viewtreeobserver; import android.widget.imageview; import android.widget.imageview.scaletype; import java.lang.ref.weakreference; public class scaleviewattacher implements iscaleview, view.ontouchlistener, versionedgesturedetector.ongesturelistener, gesturedetector.ondoubletaplistener, viewtreeobserver.ongloballayoutlistener { static final string log_tag = "scaleviewattacher"; // let debug flag be dynamic, but still proguard can be used to remove from release builds static final boolean debug = log.isloggable(log_tag, log.debug); static final int edge_none = -1; static final int edge_left = 0; static final int edge_right = 1; static final int edge_both = 2; public static final float default_max_scale = 3.0f; public static final float default_mid_scale = 1.75f; public static final float default_min_scale = 1.0f; private float mminscale = default_min_scale; private float mmidscale = default_mid_scale; private float mmaxscale = default_max_scale; private boolean mallowparentinterceptonedge = true; private static void checkzoomlevels(float minzoom, float midzoom, float maxzoom) { if (minzoom >= midzoom) { throw new illegalargumentexception("minzoom should be less than midzoom"); } else if (midzoom >= maxzoom) { throw new illegalargumentexception("midzoom should be less than maxzoom"); } } /** * @return true if the imageview exists, and it's drawable existss */ private static boolean hasdrawable(imageview imageview) { return null != imageview && null != imageview.getdrawable(); } /** * @return true if the scaletype is supported. */ private static boolean issupportedscaletype(final scaletype scaletype) { if (null == scaletype) { return false; } switch (scaletype) { case matrix: throw new illegalargumentexception(scaletype.name() + " is not supported in scaleview"); default: return true; } } /** * set's the imageview's scaletype to matrix. */ private static void setimageviewscaletypematrix(imageview imageview) { if (null != imageview) { if (imageview instanceof scaleview) { /** * scaleview sets it's own scaletype to matrix, then diverts all * calls setscaletype to this.setscaletype. basically we don't * need to do anything here */ } else { imageview.setscaletype(scaletype.matrix); } } } private weakreference<imageview> mimageview; private viewtreeobserver mviewtreeobserver; // gesture detectors private gesturedetector mgesturedetector; private versionedgesturedetector mscaledragdetector; // these are set so we don't keep allocating them on the heap private final matrix mbasematrix = new matrix(); private final matrix mdrawmatrix = new matrix(); private final matrix msuppmatrix = new matrix(); private final rectf mdisplayrect = new rectf(); private final float[] mmatrixvalues = new float[9]; // listeners private onmatrixchangedlistener mmatrixchangelistener; private onscaletaplistener mscaletaplistener; private onviewtaplistener mviewtaplistener; private onlongclicklistener mlongclicklistener; private int mivtop, mivright, mivbottom, mivleft; private flingrunnable mcurrentflingrunnable; private int mscrolledge = edge_both; private boolean mzoomenabled; private scaletype mscaletype = scaletype.fit_center; private onclicklistener onclicklistener; public scaleviewattacher(imageview imageview) { mimageview = new weakreference<imageview>(imageview); imageview.setontouchlistener(this); mviewtreeobserver = imageview.getviewtreeobserver(); if (mviewtreeobserver != null) { mviewtreeobserver.addongloballayoutlistener(this); } onclicklistener = null; // make sure we using matrix scale type setimageviewscaletypematrix(imageview); if (!imageview.isineditmode()) { // create gesture detectors... mscaledragdetector = versionedgesturedetector.newinstance(imageview.getcontext(), this); mgesturedetector = new gesturedetector(imageview.getcontext(), new gesturedetector.simpleongesturelistener() { // forward long click listener @override public void onlongpress(motionevent e) { if(null != mlongclicklistener) { mlongclicklistener.onlongclick(mimageview.get()); } }}); mgesturedetector.setondoubletaplistener(this); // finally, update the ui so that we're zoomable setzoomable(true); } } @override public final boolean canzoom() { return mzoomenabled; } /** * clean-up the resources attached to this object. this needs to be called * when the imageview is no longer used. a good example is from * {@link android.view.view#ondetachedfromwindow()} or from {@link android.app.activity#ondestroy()}. * this is automatically called if you are using {@link scaleview.co.senab.scaleview.scaleview}. */ @suppresswarnings("deprecation") public final void cleanup() { if (null != mimageview) { android.view.viewtreeobserver obs = mimageview.get().getviewtreeobserver(); if (obs != null) { obs.removeglobalonlayoutlistener(this); } } mviewtreeobserver = null; // clear listeners too mmatrixchangelistener = null; mscaletaplistener = null; mviewtaplistener = null; // finally, clear imageview mimageview = null; } @override public final rectf getdisplayrect() { checkmatrixbounds(); return getdisplayrect(getdisplaymatrix()); } public final imageview getimageview() { imageview imageview = null; if (null != mimageview) { imageview = mimageview.get(); } // if we don't have an imageview, call cleanup() if (null == imageview) { cleanup(); // throw new illegalstateexception( // "imageview no longer exists. you should not use this scaleviewattacher any more."); } return imageview; } @override public float getminscale() { return mminscale; } @override public float getmidscale() { return mmidscale; } @override public float getmaxscale() { return mmaxscale; } @override public final float getscale() { return getvalue(msuppmatrix, matrix.mscale_x); } @override public final scaletype getscaletype() { return mscaletype; } public final boolean ondoubletap(motionevent ev) { try { float scale = getscale(); float x = ev.getx(); float y = ev.gety(); if (scale < mmidscale) { zoomto(mmidscale, x, y); } else if (scale >= mmidscale && scale < mmaxscale) { zoomto(mmaxscale, x, y); } else { zoomto(mminscale, x, y); } } catch (arrayindexoutofboundsexception e) { // can sometimes happen when getx() and gety() is called } return true; } public final boolean ondoubletapevent(motionevent e) { // wait for the confirmed ondoubletap() instead return false; } public final void ondrag(float dx, float dy) { if (debug) { log.d(log_tag, string.format("ondrag: dx: %.2f. dy: %.2f", dx, dy)); } imageview imageview = getimageview(); if (null != imageview && hasdrawable(imageview)) { msuppmatrix.posttranslate(dx, dy); checkanddisplaymatrix(); /** * here we decide whether to let the imageview's parent to start * taking over the touch event. * * first we check whether this function is enabled. we never want the * parent to take over if we're scaling. we then check the edge we're * on, and the direction of the scroll (i.e. if we're pulling against * the edge, aka 'overscrolling', let the parent take over). */ if (mallowparentinterceptonedge && !mscaledragdetector.isscaling()) { if (mscrolledge == edge_both || (mscrolledge == edge_left && dx >= 1f) || (mscrolledge == edge_right && dx <= -1f)) { android.view.viewparent vparent = imageview.getparent(); if (vparent != null) { vparent.requestdisallowintercepttouchevent(false); } } } } } @override public final void onfling(float startx, float starty, float velocityx, float velocityy) { if (debug) { log.d(log_tag, "onfling. sx: " + startx + " sy: " + starty + " vx: " + velocityx + " vy: " + velocityy); } imageview imageview = getimageview(); if (hasdrawable(imageview)) { mcurrentflingrunnable = new flingrunnable(imageview.getcontext()); mcurrentflingrunnable.fling(imageview.getwidth(), imageview.getheight(), (int) velocityx, (int) velocityy); imageview.post(mcurrentflingrunnable); } } @override public final void ongloballayout() { imageview imageview = getimageview(); if (null != imageview && mzoomenabled) { final int top = imageview.gettop(); final int right = imageview.getright(); final int bottom = imageview.getbottom(); final int left = imageview.getleft(); /** * we need to check whether the imageview's bounds have changed. * this would be easier if we targeted api 11+ as we could just use * view.onlayoutchangelistener. instead we have to replicate the * work, keeping track of the imageview's bounds and then checking * if the values change. */ if (top != mivtop || bottom != mivbottom || left != mivleft || right != mivright) { // update our base matrix, as the bounds have changed updatebasematrix(imageview.getdrawable()); // update values as something has changed mivtop = top; mivright = right; mivbottom = bottom; mivleft = left; } } } public final void setonclicklinstener(onclicklistener listener){ onclicklistener = listener; } public final void onscale(float scalefactor, float focusx, float focusy) { if (debug) { log.d(log_tag, string.format("onscale: scale: %.2f. fx: %.2f. fy: %.2f", scalefactor, focusx, focusy)); } if (hasdrawable(getimageview()) && (getscale() < mmaxscale || scalefactor < 1f)) { msuppmatrix.postscale(scalefactor, scalefactor, focusx, focusy); checkanddisplaymatrix(); } } public final boolean onsingletapconfirmed(motionevent e) { imageview imageview = getimageview(); if (null != imageview) { if (null != mscaletaplistener) { final rectf displayrect = getdisplayrect(); if (null != displayrect) { final float x = e.getx(), y = e.gety(); // check to see if the user tapped on the scale if (displayrect.contains(x, y)) { float xresult = (x - displayrect.left) / displayrect.width(); float yresult = (y - displayrect.top) / displayrect.height(); mscaletaplistener.onscaletap(imageview, xresult, yresult); return true; } } } if (null != mviewtaplistener) { mviewtaplistener.onviewtap(imageview, e.getx(), e.gety()); } } return false; } private float lastposx, lastposy; private long firclick = 0; @override public final boolean ontouch(view v, motionevent ev) { boolean handled = false; if (mzoomenabled) { switch (ev.getaction()) { case motionevent.action_down: // first, disable the parent from intercepting the touch // event android.view.viewparent vparent = v.getparent(); if (vparent != null) { vparent.requestdisallowintercepttouchevent(true); } lastposx = ev.getx(); lastposy = ev.gety(); // if we're flinging, and the user presses down, cancel // fling cancelfling(); break; case motionevent.action_cancel: case motionevent.action_up: // if the user has zoomed less than min scale, zoom back // to min scale if (getscale() < mminscale) { rectf rect = getdisplayrect(); if (null != rect) { v.post(new animatedzoomrunnable(getscale(), mminscale, rect.centerx(), rect.centery())); handled = true; } } if(ev.getx() == lastposx && ev.gety() == lastposy){ long time = system.currenttimemillis(); if(time - firclick > 500){ firclick = system.currenttimemillis(); if(onclicklistener != null){ onclicklistener.onclick(getimageview()); } } } break; } // check to see if the user double tapped if (null != mgesturedetector && mgesturedetector.ontouchevent(ev)) { handled = true; } // finally, try the scale/drag detector if (null != mscaledragdetector && mscaledragdetector.ontouchevent(ev)) { handled = true; } } return handled; } @override public void setallowparentinterceptonedge(boolean allow) { mallowparentinterceptonedge = allow; } @override public void setminscale(float minscale) { checkzoomlevels(minscale, mmidscale, mmaxscale); mminscale = minscale; } @override public void setmidscale(float midscale) { checkzoomlevels(mminscale, midscale, mmaxscale); mmidscale = midscale; } @override public void setmaxscale(float maxscale) { checkzoomlevels(mminscale, mmidscale, maxscale); mmaxscale = maxscale; } @override public final void setonlongclicklistener(onlongclicklistener listener) { mlongclicklistener = listener; } @override public final void setonmatrixchangelistener(onmatrixchangedlistener listener) { mmatrixchangelistener = listener; } @override public final void setonscaletaplistener(onscaletaplistener listener) { mscaletaplistener = listener; } @override public final void setonviewtaplistener(onviewtaplistener listener) { mviewtaplistener = listener; } @override public final void setscaletype(scaletype scaletype) { if (issupportedscaletype(scaletype) && scaletype != mscaletype) { // mscaletype = scaletype; // finally update update(); } } @override public final void setzoomable(boolean zoomable) { mzoomenabled = zoomable; update(); } public final void update() { imageview imageview = getimageview(); if (null != imageview) { if (mzoomenabled) { // make sure we using matrix scale type setimageviewscaletypematrix(imageview); // update the base matrix using the current drawable updatebasematrix(imageview.getdrawable()); } else { // reset the matrix... resetmatrix(); } } } @override public final void zoomto(float scale, float focalx, float focaly) { imageview imageview = getimageview(); if (null != imageview) { imageview.post(new animatedzoomrunnable(getscale(), scale, focalx, focaly)); } } protected matrix getdisplaymatrix() { mdrawmatrix.set(mbasematrix); mdrawmatrix.postconcat(msuppmatrix); return mdrawmatrix; } private void cancelfling() { if (null != mcurrentflingrunnable) { mcurrentflingrunnable.cancelfling(); mcurrentflingrunnable = null; } } /** * helper method that simply checks the matrix, and then displays the result */ private void checkanddisplaymatrix() { checkmatrixbounds(); setimageviewmatrix(getdisplaymatrix()); } private void checkimageviewscaletype() { imageview imageview = getimageview(); /** * scaleview's getscaletype() will just divert to this.getscaletype() so * only call if we're not attached to a scaleview. */ if (null != imageview && !(imageview instanceof scaleview)) { if (imageview.getscaletype() != scaletype.matrix) { throw new illegalstateexception( "the imageview's scaletype has been changed since attaching a scaleviewattacher"); } } } private void checkmatrixbounds() { final imageview imageview = getimageview(); if (null == imageview) { return; } final rectf rect = getdisplayrect(getdisplaymatrix()); if (null == rect) { return; } final float height = rect.height(), width = rect.width(); float deltax = 0, deltay = 0; final int viewheight = imageview.getheight(); if (height <= viewheight) { switch (mscaletype) { case fit_start: deltay = -rect.top; break; case fit_end: deltay = viewheight - height - rect.top; break; default: deltay = (viewheight - height) / 2 - rect.top; break; } } else if (rect.top > 0) { deltay = -rect.top; } else if (rect.bottom < viewheight) { deltay = viewheight - rect.bottom; } final int viewwidth = imageview.getwidth(); if (width <= viewwidth) { switch (mscaletype) { case fit_start: deltax = -rect.left; break; case fit_end: deltax = viewwidth - width - rect.left; break; default: deltax = (viewwidth - width) / 2 - rect.left; break; } mscrolledge = edge_both; } else if (rect.left > 0) { mscrolledge = edge_left; deltax = -rect.left; } else if (rect.right < viewwidth) { deltax = viewwidth - rect.right; mscrolledge = edge_right; } else { mscrolledge = edge_none; } // finally actually translate the matrix msuppmatrix.posttranslate(deltax, deltay); } /** * helper method that maps the supplied matrix to the current drawable * * @param matrix - matrix to map drawable against * @return rectf - displayed rectangle */ private rectf getdisplayrect(matrix matrix) { imageview imageview = getimageview(); if (null != imageview) { drawable d = imageview.getdrawable(); if (null != d) { mdisplayrect.set(0, 0, d.getintrinsicwidth(), d.getintrinsicheight()); matrix.maprect(mdisplayrect); return mdisplayrect; } } return null; } /** * helper method that 'unpacks' a matrix and returns the required value * * @param matrix - matrix to unpack * @param whichvalue - which value from matrix.m* to return * @return float - returned value */ private float getvalue(matrix matrix, int whichvalue) { matrix.getvalues(mmatrixvalues); return mmatrixvalues[whichvalue]; } /** * resets the matrix back to fit_center, and then displays it.s */ private void resetmatrix() { msuppmatrix.reset(); setimageviewmatrix(getdisplaymatrix()); checkmatrixbounds(); } private void setimageviewmatrix(matrix matrix) { imageview imageview = getimageview(); if (null != imageview) { checkimageviewscaletype(); imageview.setimagematrix(matrix); // call matrixchangedlistener if needed if (null != mmatrixchangelistener) { rectf displayrect = getdisplayrect(matrix); if (null != displayrect) { mmatrixchangelistener.onmatrixchanged(displayrect); } } } } /** * calculate matrix for fit_center * * @param d - drawable being displayed */ private void updatebasematrix(drawable d) { imageview imageview = getimageview(); if (null == imageview || null == d) { return; } final float viewwidth = imageview.getwidth(); final float viewheight = imageview.getheight(); final int drawablewidth = d.getintrinsicwidth(); final int drawableheight = d.getintrinsicheight(); mbasematrix.reset(); final float widthscale = viewwidth / drawablewidth; final float heightscale = viewheight / drawableheight; if (mscaletype == scaletype.center) { mbasematrix.posttranslate((viewwidth - drawablewidth) / 2f, (viewheight - drawableheight) / 2f); } else if (mscaletype == scaletype.center_crop) { float scale = math.max(widthscale, heightscale); mbasematrix.postscale(scale, scale); mbasematrix.posttranslate((viewwidth - drawablewidth * scale) / 2f, (viewheight - drawableheight * scale) / 2f); } else if (mscaletype == scaletype.center_inside) { float scale = math.min(1.0f, math.min(widthscale, heightscale)); mbasematrix.postscale(scale, scale); mbasematrix.posttranslate((viewwidth - drawablewidth * scale) / 2f, (viewheight - drawableheight * scale) / 2f); } else { rectf mtempsrc = new rectf(0, 0, drawablewidth, drawableheight); rectf mtempdst = new rectf(0, 0, viewwidth, viewheight); switch (mscaletype) { case fit_center: mbasematrix.setrecttorect(mtempsrc, mtempdst, scaletofit.center); break; case fit_start: mbasematrix.setrecttorect(mtempsrc, mtempdst, scaletofit.start); break; case fit_end: mbasematrix.setrecttorect(mtempsrc, mtempdst, scaletofit.end); break; case fit_xy: mbasematrix.setrecttorect(mtempsrc, mtempdst, scaletofit.fill); break; default: break; } } resetmatrix(); } /** * interface definition for a callback to be invoked when the internal * matrix has changed for this view. * * @author chris banes */ public static interface onmatrixchangedlistener { /** * callback for when the matrix displaying the drawable has changed. * this could be because the view's bounds have changed, or the user has * zoomed. * * @param rect - rectangle displaying the drawable's new bounds. */ void onmatrixchanged(rectf rect); } /** * interface definition for a callback to be invoked when the scale is * tapped with a single tap. * * @author chris banes */ public static interface onscaletaplistener { /** * a callback to receive where the user taps on a scale. you will only * receive a callback if the user taps on the actual scale, tapping on * 'whitespace' will be ignored. * * @param view - view the user tapped. * @param x - where the user tapped from the of the drawable, as * percentage of the drawable width. * @param y - where the user tapped from the top of the drawable, as * percentage of the drawable height. */ void onscaletap(view view, float x, float y); } /** * interface definition for a callback to be invoked when the imageview is * tapped with a single tap. * * @author chris banes */ public static interface onviewtaplistener { /** * a callback to receive where the user taps on a imageview. you will * receive a callback if the user taps anywhere on the view, tapping on * 'whitespace' will not be ignored. * * @param view - view the user tapped. * @param x - where the user tapped from the left of the view. * @param y - where the user tapped from the top of the view. */ void onviewtap(view view, float x, float y); } private class animatedzoomrunnable implements runnable { // these are 'postscale' values, means they're compounded each iteration static final float animation_scale_per_iteration_in = 1.07f; static final float animation_scale_per_iteration_out = 0.93f; private final float mfocalx, mfocaly; private final float mtargetzoom; private final float mdeltascale; public animatedzoomrunnable(final float currentzoom, final float targetzoom, final float focalx, final float focaly) { mtargetzoom = targetzoom; mfocalx = focalx; mfocaly = focaly; if (currentzoom < targetzoom) { mdeltascale = animation_scale_per_iteration_in; } else { mdeltascale = animation_scale_per_iteration_out; } } public void run() { imageview imageview = getimageview(); if (null != imageview) { msuppmatrix.postscale(mdeltascale, mdeltascale, mfocalx, mfocaly); checkanddisplaymatrix(); final float currentscale = getscale(); if ((mdeltascale > 1f && currentscale < mtargetzoom) || (mdeltascale < 1f && mtargetzoom < currentscale)) { // we haven't hit our target scale yet, so post ourselves // again compat.postonanimation(imageview, this); } else { // we've scaled past our target zoom, so calculate the // necessary scale so we're back at target zoom final float delta = mtargetzoom / currentscale; msuppmatrix.postscale(delta, delta, mfocalx, mfocaly); checkanddisplaymatrix(); } } } } private class flingrunnable implements runnable { private final scrollerproxy mscroller; private int mcurrentx, mcurrenty; public flingrunnable(context context) { mscroller = scrollerproxy.getscroller(context); } public void cancelfling() { if (debug) { log.d(log_tag, "cancel fling"); } mscroller.forcefinished(true); } public void fling(int viewwidth, int viewheight, int velocityx, int velocityy) { final rectf rect = getdisplayrect(); if (null == rect) { return; } final int startx = math.round(-rect.left); final int minx, maxx, miny, maxy; if (viewwidth < rect.width()) { minx = 0; maxx = math.round(rect.width() - viewwidth); } else { minx = maxx = startx; } final int starty = math.round(-rect.top); if (viewheight < rect.height()) { miny = 0; maxy = math.round(rect.height() - viewheight); } else { miny = maxy = starty; } mcurrentx = startx; mcurrenty = starty; if (debug) { log.d(log_tag, "fling. startx:" + startx + " starty:" + starty + " maxx:" + maxx + " maxy:" + maxy); } // if we actually can move, fling the scroller if (startx != maxx || starty != maxy) { mscroller.fling(startx, starty, velocityx, velocityy, minx, maxx, miny, maxy, 0, 0); } } @override public void run() { imageview imageview = getimageview(); if (null != imageview && mscroller.computescrolloffset()) { final int newx = mscroller.getcurrx(); final int newy = mscroller.getcurry(); if (debug) { log.d(log_tag, "fling run(). currentx:" + mcurrentx + " currenty:" + mcurrenty + " newx:" + newx + " newy:" + newy); } msuppmatrix.posttranslate(mcurrentx - newx, mcurrenty - newy); setimageviewmatrix(getdisplaymatrix()); mcurrentx = newx; mcurrenty = newy; // post on animation compat.postonanimation(imageview, this); } } } }
7.scrollerproxy
/******************************************************************************* * copyright 2011, 2012 chris banes. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. *******************************************************************************/ package com.suo.image; import android.annotation.targetapi; import android.content.context; import android.os.build.version; import android.os.build.version_codes; import android.widget.overscroller; import android.widget.scroller; public abstract class scrollerproxy { public static scrollerproxy getscroller(context context) { if (version.sdk_int < version_codes.gingerbread) { return new pregingerscroller(context); } else { return new gingerscroller(context); } } public abstract boolean computescrolloffset(); public abstract void fling(int startx, int starty, int velocityx, int velocityy, int minx, int maxx, int miny, int maxy, int overx, int overy); public abstract void forcefinished(boolean finished); public abstract int getcurrx(); public abstract int getcurry(); @targetapi(9) private static class gingerscroller extends scrollerproxy { private overscroller mscroller; public gingerscroller(context context) { mscroller = new overscroller(context); } @override public boolean computescrolloffset() { return mscroller.computescrolloffset(); } @override public void fling(int startx, int starty, int velocityx, int velocityy, int minx, int maxx, int miny, int maxy, int overx, int overy) { mscroller.fling(startx, starty, velocityx, velocityy, minx, maxx, miny, maxy, overx, overy); } @override public void forcefinished(boolean finished) { mscroller.forcefinished(finished); } @override public int getcurrx() { return mscroller.getcurrx(); } @override public int getcurry() { return mscroller.getcurry(); } } private static class pregingerscroller extends scrollerproxy { private scroller mscroller; public pregingerscroller(context context) { mscroller = new scroller(context); } @override public boolean computescrolloffset() { return mscroller.computescrolloffset(); } @override public void fling(int startx, int starty, int velocityx, int velocityy, int minx, int maxx, int miny, int maxy, int overx, int overy) { mscroller.fling(startx, starty, velocityx, velocityy, minx, maxx, miny, maxy); } @override public void forcefinished(boolean finished) { mscroller.forcefinished(finished); } @override public int getcurrx() { return mscroller.getcurrx(); } @override public int getcurry() { return mscroller.getcurry(); } } }
8.sdk16
/******************************************************************************* * copyright 2011, 2012 chris banes. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. *******************************************************************************/ package com.suo.image; import android.annotation.targetapi; import android.view.view; @targetapi(16) public class sdk16 { public static void postonanimation(view view, runnable r) { view.postonanimation(r); } }
9.versionedgesturedetector
package com.suo.image; /******************************************************************************* * copyright 2011, 2012 chris banes. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. *******************************************************************************/ import android.annotation.targetapi; import android.content.context; import android.os.build; import android.view.motionevent; import android.view.scalegesturedetector; import android.view.scalegesturedetector.onscalegesturelistener; import android.view.velocitytracker; import android.view.viewconfiguration; public abstract class versionedgesturedetector { static final string log_tag = "versionedgesturedetector"; ongesturelistener mlistener; public static versionedgesturedetector newinstance(context context, ongesturelistener listener) { final int sdkversion = build.version.sdk_int; versionedgesturedetector detector = null; if (sdkversion < build.version_codes.eclair) { detector = new cupcakedetector(context); } else if (sdkversion < build.version_codes.froyo) { detector = new eclairdetector(context); } else { detector = new froyodetector(context); } detector.mlistener = listener; return detector; } public abstract boolean ontouchevent(motionevent ev); public abstract boolean isscaling(); public static interface ongesturelistener { public void ondrag(float dx, float dy); public void onfling(float startx, float starty, float velocityx, float velocityy); public void onscale(float scalefactor, float focusx, float focusy); } private static class cupcakedetector extends versionedgesturedetector { float mlasttouchx; float mlasttouchy; final float mtouchslop; final float mminimumvelocity; public cupcakedetector(context context) { final viewconfiguration configuration = viewconfiguration.get(context); mminimumvelocity = configuration.getscaledminimumflingvelocity(); mtouchslop = configuration.getscaledtouchslop(); } private velocitytracker mvelocitytracker; private boolean misdragging; float getactivex(motionevent ev) { return ev.getx(); } float getactivey(motionevent ev) { return ev.gety(); } public boolean isscaling() { return false; } @override public boolean ontouchevent(motionevent ev) { boolean result = true; switch (ev.getaction()) { case motionevent.action_down: { mvelocitytracker = velocitytracker.obtain(); if (mvelocitytracker != null) { mvelocitytracker.addmovement(ev); } mlasttouchx = getactivex(ev); mlasttouchy = getactivey(ev); misdragging = false; break; } case motionevent.action_move: { final float x = getactivex(ev); final float y = getactivey(ev); final float dx = x - mlasttouchx, dy = y - mlasttouchy; if (!misdragging) { // use pythagoras to see if drag length is larger than // touch slop misdragging = math.sqrt((dx * dx) + (dy * dy)) >= mtouchslop; } if (misdragging) { mlistener.ondrag(dx, dy); mlasttouchx = x; mlasttouchy = y; if (null != mvelocitytracker) { mvelocitytracker.addmovement(ev); } } break; } case motionevent.action_cancel: { // recycle velocity tracker if (null != mvelocitytracker) { mvelocitytracker.recycle(); mvelocitytracker = null; } break; } case motionevent.action_up: { if (misdragging) { if (null != mvelocitytracker) { mlasttouchx = getactivex(ev); mlasttouchy = getactivey(ev); // compute velocity within the last 1000ms mvelocitytracker.addmovement(ev); mvelocitytracker.computecurrentvelocity(1000); final float vx = mvelocitytracker.getxvelocity(), vy = mvelocitytracker.getyvelocity(); // if the velocity is greater than minvelocity, call // listener if (math.max(math.abs(vx), math.abs(vy)) >= mminimumvelocity) { mlistener.onfling(mlasttouchx, mlasttouchy, -vx, -vy); } } } // recycle velocity tracker if (null != mvelocitytracker) { mvelocitytracker.recycle(); mvelocitytracker = null; } break; } } return result; } } @targetapi(5) private static class eclairdetector extends cupcakedetector { private static final int invalid_pointer_id = -1; private int mactivepointerid = invalid_pointer_id; private int mactivepointerindex = 0; public eclairdetector(context context) { super(context); } @override float getactivex(motionevent ev) { try { return ev.getx(mactivepointerindex); } catch (exception e) { return ev.getx(); } } @override float getactivey(motionevent ev) { try { return ev.gety(mactivepointerindex); } catch (exception e) { return ev.gety(); } } @override public boolean ontouchevent(motionevent ev) { final int action = ev.getaction(); switch (action & motionevent.action_mask) { case motionevent.action_down: mactivepointerid = ev.getpointerid(0); break; case motionevent.action_cancel: case motionevent.action_up: mactivepointerid = invalid_pointer_id; break; case motionevent.action_pointer_up: final int pointerindex = (ev.getaction() & motionevent.action_pointer_index_mask) >> motionevent.action_pointer_index_shift; final int pointerid = ev.getpointerid(pointerindex); if (pointerid == mactivepointerid) { // this was our active pointer going up. choose a new // active pointer and adjust accordingly. final int newpointerindex = pointerindex == 0 ? 1 : 0; mactivepointerid = ev.getpointerid(newpointerindex); mlasttouchx = ev.getx(newpointerindex); mlasttouchy = ev.gety(newpointerindex); } break; } mactivepointerindex = ev.findpointerindex(mactivepointerid != invalid_pointer_id ? mactivepointerid : 0); return super.ontouchevent(ev); } } @targetapi(8) private static class froyodetector extends eclairdetector { private final scalegesturedetector mdetector; // needs to be an inner class so that we don't hit // verifyerror's on api 4. private final onscalegesturelistener mscalelistener = new onscalegesturelistener() { @override public boolean onscale(scalegesturedetector detector) { mlistener.onscale(detector.getscalefactor(), detector.getfocusx(), detector.getfocusy()); return true; } @override public boolean onscalebegin(scalegesturedetector detector) { return true; } @override public void onscaleend(scalegesturedetector detector) { // no-op } }; public froyodetector(context context) { super(context); mdetector = new scalegesturedetector(context, mscalelistener); } @override public boolean isscaling() { return mdetector.isinprogress(); } @override public boolean ontouchevent(motionevent ev) { mdetector.ontouchevent(ev); return super.ontouchevent(ev); } } }
10.mainactivity
package com.suo.myimage; import android.os.bundle; import android.app.activity; import android.view.menu; public class mainactivity extends activity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); } @override public boolean oncreateoptionsmenu(menu menu) { // inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.activity_main, menu); return true; } }
activity_main.xml
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".mainactivity" > <textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerhorizontal="true" android:layout_centervertical="true" android:text="@string/hello_world" /> <com.suo.image.scaleview android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/a"/> </relativelayout>
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!